diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/README.md b/README.md index 8d9322b..6ad9788 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,12 @@ Inofficial IBKR Extension for MoneyMoney and EUR Accounts. ## Release Notes -### 0.2 (not released) +### 0.2 - Support Statements - Quote XML Chars - Ignore 'ADJ' activities in Statements +- Fix (some) foreign currencies issues ### 0.1 diff --git a/ibkr.lua b/ibkr.lua index ff34f13..a83c26e 100644 --- a/ibkr.lua +++ b/ibkr.lua @@ -1,25 +1,25 @@ WebBanking { - version = 0.2, - country = "de", - description = "Include your IBKR stock portfolio in MoneyMoney.", - services = {"IBKR"} + version = 0.2, + country = "de", + description = "Include your IBKR stock portfolio in MoneyMoney.", + services = {"IBKR"} } local parseargs = function(s) - local arg = {} - string.gsub(s, "([%-%w]+)=([\"'])(.-)%2", function(w, _, value) - value = string.gsub(value, """, "\""); - value = string.gsub(value, "'", "'"); - value = string.gsub(value, ">", ">"); - value = string.gsub(value, "<", "<"); - value = string.gsub(value, "&", "&"); - arg[w] = value - end) - return arg + local arg = {} + string.gsub(s, "([%-%w]+)=([\"'])(.-)%2", function(w, _, value) + value = string.gsub(value, """, "\""); + value = string.gsub(value, "'", "'"); + value = string.gsub(value, ">", ">"); + value = string.gsub(value, "<", "<"); + value = string.gsub(value, "&", "&"); + arg[w] = value + end) + return arg end local parseBlock = function(content, k) - return string.match(content, "^.+<" .. k .. ">(.+).+$") + return string.match(content, "^.+<" .. k .. ">(.+).+$") end local connection = Connection() @@ -28,137 +28,139 @@ local query local code function SupportsBank(protocol, bankCode) - return protocol == ProtocolWebBanking and bankCode == "IBKR" + return protocol == ProtocolWebBanking and bankCode == "IBKR" end function InitializeSession(protocol, bankCode, username, customer, password) - token = password - query = username - connection = Connection() - local content, charset, mimeType = connection:get( - "https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.SendRequest?t=" .. token .. "&q=" .. - query .. "&v=3") - local status = string.match(content, "^.+(.+).+$") - if status == "Success" then - code = string.match(content, "^.+(.+).+$") - print("8:" .. code) - else - return content - end + token = password + query = username + connection = Connection() + local content, charset, mimeType = connection:get( + "https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.SendRequest?t=" .. token .. "&q=" .. + query .. "&v=3") + local status = string.match(content, "^.+(.+).+$") + if status == "Success" then + code = string.match(content, "^.+(.+).+$") + print("8:" .. code) + else + return content + end end function ListAccounts(knownAccounts) - local account = { - name = "IBKR", - accountNumber = 1, - currency = "EUR", - portfolio = true, - type = "AccountTypePortfolio" - } - local account2 = { - name = "IBKR Cash", - accountNumber = 2, - currency = "EUR", - type = "AccountTypeOther" - } - - return {account, account2} + local account = { + name = "IBKR", + accountNumber = 1, + currency = "EUR", + portfolio = true, + type = "AccountTypePortfolio" + } + local account2 = { + name = "IBKR Cash", + accountNumber = 2, + currency = "EUR", + type = "AccountTypeOther" + } + + return {account, account2} end local statementContent function stringToTimestamp(str) - local datePattern = '(%d%d%d%d)(%d%d)(%d%d)' - local year, month, day = str:match(datePattern) - if year and month and day then - local timestamp = os.time{day=day,month=month,year=year} - return timestamp - end + local datePattern = '(%d%d%d%d)(%d%d)(%d%d)' + local year, month, day = str:match(datePattern) + if year and month and day then + local timestamp = os.time{day=day,month=month,year=year} + return timestamp + end end function RefreshAccount(account, since) - print("RefreshAccount " .. JSON():set(account):json()) - - if statementContent == nil then - local ec - repeat - statementContent, charset, mimeType = connection:get( - "https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.GetStatement?t=" .. token .. - "&q=" .. code .. "&v=3") - local ec=parseBlock(statementContent, 'ErrorCode') - if ec=="1019" then - MM.sleep(1) - end - - until ec ~="1019" - - end - if account.accountNumber == "1" then - local positions = parseBlock(statementContent, 'OpenPositions') - local securities = {} - for p in positions:gmatch("") do - print(p) - local pos = parseargs(p) - securities[#securities + 1] = { - name = pos.description, - isin = pos.isin, - securityNumber = pos.isin, - market = pos.listingExchange, - quantity = pos.position * pos.multiplier, - originalCurrencyAmount = pos.positionValue, - currencyOfOriginalAmount = pos.currency, - price = pos.markPrice, - currencyOfPrice = pos.currency, - purchasePrice = pos.costBasisPrice, - currencyOfPurchasePrice = pos.currency, - exchangeRate = 1 / pos.fxRateToBase, - userdata = {{key="_profit",value=string.format("%.02f", pos.fifoPnlUnrealized*pos.fxRateToBase) .. " EUR / " .. string.format("%.05f", 100/pos.costBasisMoney*pos.positionValue-100) .. " %"}} - --userdata = {{key="_profit",value=string.format("%.02f", pos.fifoPnlUnrealized) .. " USD / " .. string.format("%.05f", 100/pos.costBasisMoney*pos.positionValue-100) .. " %"}} - - } - - end - -- Return balance and array of transactions. - return { - securities = securities - } - elseif account.accountNumber == "2" then - local summary = parseBlock(statementContent, 'EquitySummaryInBase') - local cash = 0 - for p in summary:gmatch("") do - print(p) - local pos = parseargs(p) - cash = pos.cash - end - -- array of transactions. - local summary = parseBlock(statementContent, 'StmtFunds') - local transactions = {} - for p in summary:gmatch("") do - --print(p) - local sm = parseargs(p) - print(#transactions,sm.transactionID,sm.reportDate,sm.settleDate,sm.description,sm.activityDescription,sm.amount,sm.activityCode) - if sm.activityCode ~= 'ADJ' then - transactions[#transactions + 1] = { - name=sm.description, - amount=sm.amount, - currency="EUR", - bookingDate=stringToTimestamp(sm.reportDate), - valueDate=stringToTimestamp(sm.settleDate), - transactionCode=sm.transactionID, - purpose=sm.activityDescription, - bookingText=sm.activityCode - } - end - end - -- Return balance and array of transactions. - --print(JSON():set(transactions):json()) - return { - balance = cash, - transactions = transactions - } - end + print("RefreshAccount " .. JSON():set(account):json()) + + if statementContent == nil then + local ec + repeat + statementContent, charset, mimeType = connection:get( + "https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.GetStatement?t=" .. token .. + "&q=" .. code .. "&v=3") + local ec=parseBlock(statementContent, 'ErrorCode') + if ec=="1019" then + MM.sleep(1) + end + + until ec ~="1019" + + end + if account.accountNumber == "1" then + local positions = parseBlock(statementContent, 'OpenPositions') + local securities = {} + for p in positions:gmatch("") do + print(p) + local pos = parseargs(p) + securities[#securities + 1] = { + name = pos.description, + isin = pos.isin, + securityNumber = pos.isin, + market = pos.listingExchange, + quantity = pos.position * pos.multiplier, + originalCurrencyAmount = pos.positionValue, + currencyOfOriginalAmount = pos.currency, + price = pos.markPrice, + currencyOfPrice = pos.currency, + purchasePrice = pos.costBasisPrice, + currencyOfPurchasePrice = pos.currency, + exchangeRate = 1 / pos.fxRateToBase, + userdata = {{key="_profit",value=string.format("%.02f", pos.fifoPnlUnrealized*pos.fxRateToBase) .. " EUR / " .. string.format("%.05f", 100/pos.costBasisMoney*pos.positionValue-100) .. " %"}} + --userdata = {{key="_profit",value=string.format("%.02f", pos.fifoPnlUnrealized) .. " USD / " .. string.format("%.05f", 100/pos.costBasisMoney*pos.positionValue-100) .. " %"}} + + } + + end + -- Return balance and array of transactions. + return { + securities = securities + } + elseif account.accountNumber == "2" then + local summary = parseBlock(statementContent, 'EquitySummaryInBase') + local cash = 0 + for p in summary:gmatch("") do + print(p) + local pos = parseargs(p) + cash = pos.cash + end + -- array of transactions. + local summary = parseBlock(statementContent, 'StmtFunds') + local transactions = {} + for p in summary:gmatch("") do + --print(p) + local sm = parseargs(p) + print(#transactions,sm.transactionID,sm.reportDate,sm.settleDate,sm.description,sm.activityDescription,sm.amount,sm.activityCode) + if sm.activityCode ~= 'ADJ' then + transactions[#transactions + 1] = { + name=sm.description, + amount=sm.amount, + currency="EUR", + bookingDate=stringToTimestamp(sm.reportDate), + valueDate=stringToTimestamp(sm.settleDate), + transactionCode=sm.transactionID, + purpose=sm.activityDescription, + bookingText=sm.activityCode + } + end + end + -- Return balance and array of transactions. + --print(JSON():set(transactions):json()) + return { + balance = cash, + transactions = transactions + } + end end function EndSession() - -- Logout. + -- Logout. end + +-- SIGNATURE: MCsCFHF+25SfP/5FOEXYuH4H1XCoVDF7AhNF3D9StNKYUYIheUUOaFSh8dDr