Пример #1
0
def test_parse_dividends_with_changed_tax():
    """Обработка возврата WHT по дивидендам в начале следующего года."""

    p = InteractiveBrokersReportParser()

    lines = """Financial Instrument Information,Header,Asset Category,Symbol,Description,Conid,Security ID,Multiplier,Type,Code
Financial Instrument Information,Data,Stocks,FREL,FIDELITY REAL ESTATE ETF,183005003,US3160928574,1,ETF,
Dividends,Header,Currency,Date,Description,Amount
Dividends,Data,USD,2020-03-25,FREL(US3160928574) Cash Dividend USD 0.282 per Share (Ordinary Dividend),54.14
Withholding Tax,Header,Currency,Date,Description,Amount,Code
Withholding Tax,Data,USD,2020-03-25,FREL(US3160928574) Cash Dividend USD 0.282 per Share - US Tax,-5.41,
Withholding Tax,Data,USD,2020-03-25,FREL(US3160928574) Cash Dividend USD 0.282 per Share - US Tax,5.41,
Withholding Tax,Data,USD,2020-03-25,FREL(US3160928574) Cash Dividend USD 0.282 per Share - US Tax,-3.07,"""

    lines = lines.split('\n')
    p._real_parse_activity_csv(
        csv.reader(lines, delimiter=','), {
            'Financial Instrument Information':
            p._parse_instrument_information,
            'Dividends': p._parse_dividends,
            'Withholding Tax': p._parse_withholding_tax,
        })

    d = p.dividends
    assert d[0].ticker.symbol == 'FREL'
    assert d[0].amount == Money(54.14, Currency.USD)
    assert d[0].tax == Money(3.07, Currency.USD)
Пример #2
0
def test_group_confirmation_reports_by_order_id():
    """
    Иногда в отчётах о подтверждении сделок появляется операция отмены исполнения и следом правильная строка
    с данными об исполнении.
    Выглядит как
    - строка с датой исполнения A
    - строка с типом TradeCancel
    - строка с датой исполнения Б
    и всё это по одной сделке.

    Чтобы решить эту проблему автоматически, мы группируем строки в отчёте о подтверждении по параметру OrderId
    и далее работаем только с последней строкой по каждому ордеру.
    """
    p = InteractiveBrokersReportParser()
    lines = """"ClientAccountID","AccountAlias","Model","CurrencyPrimary","AssetClass","Symbol","Description","Conid","SecurityID","SecurityIDType","CUSIP","ISIN","ListingExchange","UnderlyingConid","UnderlyingSymbol","UnderlyingSecurityID","UnderlyingListingExchange","Issuer","Multiplier","Strike","Expiry","Put/Call","PrincipalAdjustFactor","TransactionType","TradeID","OrderID","ExecID","BrokerageOrderID","OrderReference","VolatilityOrderLink","ClearingFirmID","OrigTradePrice","OrigTradeDate","OrigTradeID","OrderTime","Date/Time","ReportDate","SettleDate","TradeDate","Exchange","Buy/Sell","Quantity","Price","Amount","Proceeds","Commission","BrokerExecutionCommission","BrokerClearingCommission","ThirdPartyExecutionCommission","ThirdPartyClearingCommission","ThirdPartyRegulatoryCommission","OtherCommission","CommissionCurrency","Tax","Code","OrderType","LevelOfDetail","TraderID","IsAPIOrder","AllocatedTo","AccruedInterest","RFQID","SerialNumber","DeliveryType","CommodityType","Fineness","Weight"
"U3473202","","","EUR","STK","DXETd","X EURO STOXX 50 1C","59141442","LU0380865021","ISIN","","LU0380865021","IBIS","","","","","","1","","","","","ExchTrade","3567512579","1784592333","0000d349.603f512e.01.01","004ef3dd.000128a9.603f2319.0001","","","","0","","","2021-03-03,10:32:45","2021-03-03,10:32:45","2021-03-03","2021-03-05","2021-03-03","GETTEX","BUY","70","56.04","3922.8","-3922.8","-1.9614","-1.9614","0","0","0","0","0","EUR","0","O","LMT","EXECUTION","","N","","0","","","","","0.0","0.0 ()"
"U3473202","","","EUR","STK","DXETd","X EURO STOXX 50 1C","59141442","LU0380865021","ISIN","","LU0380865021","IBIS","","","","","","1","","","","","ExchTrade","3571384109","1786706570","0000d349.60409e83.01.01","004ef3dd.000128a9.6040747b.0001","","","","0","","","2021-03-04,07:15:50","2021-03-04,07:15:50","2021-03-04","2021-03-08","2021-03-04","GETTEX","BUY","100","56.11","5611","-5611","-2.8055","-2.8055","0","0","0","0","0","EUR","0","O","LMT","EXECUTION","","N","","0","","","","","0.0","0.0 ()"
"U3473202","","","EUR","STK","DXETd","X EURO STOXX 50 1C","59141442","LU0380865021","ISIN","","LU0380865021","IBIS","","","","","","1","","","","","TradeCancel","","1786706570","","","","","","56.11","2021-03-04","3571384109","","2021-03-04,07:15:50","2021-03-05","","2021-03-04","--","BUY (Ca.)","-100","56.11","-5611","5611","0","","","","","","","EUR","0","Ca","LMT","EXECUTION","","N","","0","","","","","0.0","0.0 ()"
"U3473202","","","EUR","STK","DXETd","X EURO STOXX 50 1C","59141442","LU0380865021","ISIN","","LU0380865021","IBIS","","","","","","1","","","","","ExchTrade","3571384109","1786706570","0000d349.60409e83.01.01","004ef3dd.000128a9.6040747b.0001","","","","0","","","2021-03-04,07:15:50","2021-03-04,07:15:50","2021-03-05","2021-03-09","2021-03-04","GETTEX","BUY","100","56.11","5611","-5611","0","","","","","","","EUR","0","O","LMT","EXECUTION","","N","","0","","","","","0.0","0.0 ()"
"U3473202","","","EUR","STK","DXETd","X EURO STOXX 50 1C","59141442","LU0380865021","ISIN","","LU0380865021","IBIS","","","","","","1","","","","","ExchTrade","3650141124","1831441961","0000d349.605d9a95.01.01","004ef3dd.000128a9.605d74ba.0001","","","","0","","","2021-03-26,06:37:20","2021-03-26,06:37:20","2021-03-26","2021-03-30","2021-03-26","GETTEX","BUY","15","58.38","875.7","-875.7","-1.25","-1.25","0","0","0","0","0","EUR","0","O","LMT","EXECUTION","","N","","0","","","","","0.0","0.0 ()\""""
    lines = lines.split('\n')

    p._parse_trade_confirmation_csv(csv.reader(lines, delimiter=','))

    assert len(p._settle_dates) == 3
    assert {'1784592333', '1786706570', '1831441961'
            } == {i.order_id
                  for i in p._settle_dates._settle_data.values()}
    assert p._settle_dates.get_date(
        'DXETd',
        _parse_datetime('2021-03-04,07:15:50')) == _parse_date('2021-03-09')
    assert p._settle_dates.get_date(
        'DXETd',
        _parse_datetime('2021-03-03,10:32:45')) == _parse_date('2021-03-05')
Пример #3
0
def test_parse_dividends_with_tax():
    p = InteractiveBrokersReportParser()

    # both cases described in https://github.com/cdump/investments/issues/17

    lines = """Financial Instrument Information,Header,Asset Category,Symbol,Description,Conid,Security ID,Multiplier,Type,Code
Financial Instrument Information,Data,Stocks,INTC,INTEL CORP,270639,,1,,
Financial Instrument Information,Data,Stocks,JNJ,JOHNSON & JOHNSON,8719,,1,,
Dividends,Header,Currency,Date,Description,Amount
Dividends,Data,USD,2016-06-01,INTC (US4581401001) Cash Dividend USD 0.26000000 (Ordinary Dividend),6.5
Dividends,Data,USD,2016-06-07,JNJ(US4781601046) Cash Dividend 0.80000000 USD per Share (Ordinary Dividend),8
Withholding Tax,Header,Currency,Date,Description,Amount,Code
Withholding Tax,Data,USD,2016-06-01,INTC(US4581401001) Choice Dividend  0.26000000 USD Distribution Value - US Tax,-0.65,
Withholding Tax,Data,USD,2016-06-07,JNJ(US4781601046) Cash Dividend 0.80000000 USD per Share - US Tax,-0.8,"""

    lines = lines.split('\n')
    p._real_parse_activity_csv(
        csv.reader(lines, delimiter=','), {
            'Financial Instrument Information':
            p._parse_instrument_information,
            'Dividends': p._parse_dividends,
            'Withholding Tax': p._parse_withholding_tax,
        })

    d = p.dividends
    assert d[0].ticker.symbol == 'INTC'
    assert d[0].amount == Money(6.5, Currency.USD)
    assert d[0].tax == Money(0.65, Currency.USD)

    assert d[1].ticker.symbol == 'JNJ'
    assert d[1].amount == Money(8, Currency.USD)
    assert d[1].tax == Money(0.8, Currency.USD)
Пример #4
0
def test_parse_trades_with_thousands_separator():
    """Сделки с более чем 1000 бумаг в отчёте форматируются с запятой как разделителем тысяч."""
    p = InteractiveBrokersReportParser()

    lines = """Financial Instrument Information,Header,Asset Category,Symbol,Description,Conid,Security ID,Listing Exch,Multiplier,Type,Code
Financial Instrument Information,Data,Stocks,NOK,NOKIA CORP-SPON ADR,661513,US6549022043,NYSE,1,ADR,
Trades,Header,DataDiscriminator,Asset Category,Currency,Symbol,Date/Time,Quantity,T. Price,C. Price,Proceeds,Comm/Fee,Basis,Realized P/L,MTM P/L,Code
Trades,Data,Order,Stocks,USD,NOK,"2020-04-03, 09:48:58","-3200",2.995,2.97,-958.4,-1.6,960,0,-8,O
Trades,Data,Order,Stocks,USD,NOK,"2020-04-06, 11:43:36",500,3.125,3.16,-1562.5,-2.5,1565,0,17.5,O
Trades,Data,Order,Stocks,USD,NOK,"2020-04-06, 11:44:50","5,000",3.125,3.16,-6250,-10,6260,0,70,O;P
Trades,SubTotal,,Stocks,USD,NOK,,"2,299.81",,,-8770.9,-14.1,8785,0,79.5,"""

    lines = lines.split('\n')

    p._settle_dates.put('NOK', _parse_datetime('2020-04-03, 09:48:58'),
                        _parse_date('2020-02-04'), 'a')
    p._settle_dates.put('NOK', _parse_datetime('2020-04-06, 11:43:36'),
                        _parse_date('2020-02-12'), 'b')
    p._settle_dates.put('NOK', _parse_datetime('2020-04-06, 11:44:50'),
                        _parse_date('2020-02-12'), 'c')

    p._real_parse_activity_csv(
        csv.reader(lines, delimiter=','), {
            'Financial Instrument Information':
            p._parse_instrument_information,
            'Trades': p._parse_trades,
        })

    assert len(p.trades) == 3
    assert {-3200, 500, 5000} == {trade.quantity for trade in p.trades}
Пример #5
0
def test_parse_interests():
    p = InteractiveBrokersReportParser()

    lines = """Interest,Header,Currency,Date,Description,Amount
Interest,Data,RUB,2020-03-04,RUB Credit Interest for Feb-2020,3.21
Interest,Data,Total,,,3.21
Interest,Data,Total in USD,,,0.04844211
Interest,Data,CAD,2020-03-04,CAD Credit Interest for Feb-2020,7.45
Interest,Data,Total,,,7.45
Interest,Data,Total in USD,,,6.69
Interest,Data,USD,2020-03-04,USD Credit Interest for Feb-2020,0.09
Interest,Data,Total,,,0.09
Interest,Data,Total Interest in USD,,,0.13844211"""

    lines = lines.split('\n')
    p._real_parse_activity_csv(csv.reader(lines, delimiter=','), {
        'Interest': p._parse_interests,
    })

    assert len(p.interests) == 3
    assert p.interests[0] == Interest(
        date=datetime.date(2020, 3, 4),
        amount=Money(3.21, Currency.RUB),
        description='RUB Credit Interest for Feb-2020')
    assert p.interests[2] == Interest(
        date=datetime.date(2020, 3, 4),
        amount=Money(0.09, Currency.USD),
        description='USD Credit Interest for Feb-2020')
    assert p.interests[1] == Interest(
        date=datetime.date(2020, 3, 4),
        amount=Money(7.45, Currency.CAD),
        description='CAD Credit Interest for Feb-2020')
Пример #6
0
def test_parse_dividends():
    p = InteractiveBrokersReportParser()

    lines = """Financial Instrument Information,Header,Asset Category,Symbol,Description,Conid,Security ID,Multiplier,Type,Code
Financial Instrument Information,Data,Stocks,JNJ,JOHNSON & JOHNSON,8719,,1,,
Financial Instrument Information,Data,Stocks,INTC,INTEL CORP,270639,,1,,
Financial Instrument Information,Data,Stocks,BND,BLABLABLA,270666,,1,,
Financial Instrument Information,Data,Stocks,GXC,SPDR S&P CHINA ETF,45540754,78463X400,1,ETF,
Dividends,Header,Currency,Date,Description,Amount
Dividends,Data,USD,2016-06-01,INTC (US4581401001) Cash Dividend USD 0.26000000 (Ordinary Dividend),6.5
Dividends,Data,USD,2016-06-07,JNJ(US4781601046) Cash Dividend 0.80000000 USD per Share (Ordinary Dividend),8
Dividends,Data,USD,2019-07-01,GXC(US78463X4007) Cash Dividend 0.80726400 USD per Share (Ordinary Dividend),1.61
Dividends,Data,USD,2019-07-01,GXC(US78463X4007) Payment in Lieu of Dividend (Ordinary Dividend),2.42
Dividends,Data,USD,2019-08-01,BND(US9219378356) Choice Dividend  0.17220900 USD Distribution Value - US Tax,1.66
Dividends,Data,USD,2019-08-02,BND(US9219378356) Cash Dividend USD 0.193413 per Share (Ordinary Dividend),3.87
Dividends,Data,USD,2019-08-02,BND(US9219378356) Cash Dividend USD 0.193413 per Share - Reversal (Ordinary Dividend),-3.87
Dividends,Data,Total,,,777.11"""

    lines = lines.split('\n')
    p._real_parse_activity_csv(
        csv.reader(lines, delimiter=','), {
            'Financial Instrument Information':
            p._parse_instrument_information,
            'Dividends': p._parse_dividends,
            'Withholding Tax': p._parse_withholding_tax,
        })

    d = p.dividends
    assert d[0].amount == Money(6.5, Currency.USD)
    assert d[1].amount == Money(8, Currency.USD)
    assert d[2].amount == Money(1.61, Currency.USD)
    assert d[3].amount == Money(2.42, Currency.USD)
Пример #7
0
def test_parse_fees():
    p = InteractiveBrokersReportParser()

    lines = """Fees,Header,Subtitle,Currency,Date,Description,Amount
Fees,Data,Other Fees,USD,2020-02-05,E*****42:GLOBAL SNAPSHOT FOR JAN 2020,-0.03
Fees,Data,Other Fees,USD,2020-02-05,E*****42:GLOBAL SNAPSHOT FOR JAN 2020,0.03
Fees,Data,Other Fees,USD,2020-06-03,E*****42:US CONSOLIDATED SNAPSHOT FOR MAY 2020,-0.01
Fees,Data,Other Fees,USD,2020-06-03,E*****42:US CONSOLIDATED SNAPSHOT FOR MAY 2020,0.01
Fees,Data,Other Fees,USD,2020-07-02,Balance of Monthly Minimum Fee for Jun 2020,-7.64
Fees,Data,Other Fees,USD,2020-09-03,Balance of Monthly Minimum Fee for Aug 2020,-10
Fees,Data,Total,,,,-17.64
Fees,Notes,"Market data is provided by Global Financial Information Services (GmbH)"""

    lines = lines.split('\n')
    p._real_parse_activity_csv(csv.reader(lines, delimiter=','), {
        'Fees': p._parse_fees,
    })

    assert len(p.fees) == 6
    assert p.fees[1] == Fee(
        date=datetime.date(2020, 2, 5),
        amount=Money(0.03, Currency.USD),
        description='Other Fees - E*****42:GLOBAL SNAPSHOT FOR JAN 2020')
    assert p.fees[5] == Fee(
        date=datetime.date(2020, 9, 3),
        amount=Money(-10., Currency.USD),
        description='Other Fees - Balance of Monthly Minimum Fee for Aug 2020')
Пример #8
0
def test_parse_trades_with_fees():
    p = InteractiveBrokersReportParser()

    lines = """Financial Instrument Information,Header,Asset Category,Symbol,Description,Conid,Security ID,Listing Exch,Multiplier,Type,Code
Financial Instrument Information,Data,Stocks,VT,VANGUARD TOT WORLD STK ETF,52197301,US9220427424,ARCA,1,ETF,
Trades,Header,DataDiscriminator,Asset Category,Currency,Symbol,Date/Time,Quantity,T. Price,C. Price,Proceeds,Comm/Fee,Basis,Realized P/L,MTM P/L,Code
Trades,Data,Order,Stocks,USD,VT,"2020-01-31, 09:30:00",10,80.62,79.73,-806.2,-1,807.2,0,-8.9,O
Trades,Data,Order,Stocks,USD,VT,"2020-02-10, 09:38:00",-10,81.82,82.25,818.2,-1.01812674,-807.2,9.981873,-4.3,C
Trades,SubTotal,,Stocks,USD,VT,,0,,,12,-2.01812674,0,9.981873,-13.2,"""

    lines = lines.split('\n')
    p._settle_dates.put('VT', _parse_datetime('2020-01-31, 09:30:00'),
                        _parse_date('2020-02-04'), '')
    p._settle_dates.put('VT', _parse_datetime('2020-02-10, 09:38:00'),
                        _parse_date('2020-02-12'), '')

    p._real_parse_activity_csv(
        csv.reader(lines, delimiter=','), {
            'Financial Instrument Information':
            p._parse_instrument_information,
            'Trades': p._parse_trades,
        })

    assert len(p.trades) == 2

    # buy trade
    assert p.trades[0].ticker.symbol == 'VT'
    assert p.trades[0].trade_date == _parse_datetime('2020-01-31, 09:30:00')
    assert p.trades[0].settle_date == _parse_date('2020-02-04')
    assert p.trades[0].quantity == 10
    assert p.trades[0].price.amount == Decimal('80.62')
    assert p.trades[0].price.currency == Currency.USD
    assert p.trades[0].fee.amount == Decimal('-1')
    assert p.trades[0].fee.currency == Currency.USD
    assert p.trades[0].fee_per_piece.currency == Currency.USD
    assert p.trades[0].fee_per_piece.amount == Decimal('-0.1')

    # sell trade
    assert p.trades[1].ticker.symbol == 'VT'
    assert p.trades[1].trade_date == _parse_datetime('2020-02-10, 09:38:00')
    assert p.trades[1].settle_date == _parse_date('2020-02-12')
    assert p.trades[1].quantity == -10
    assert p.trades[1].price.amount == Decimal('81.82')
    assert p.trades[1].price.currency == Currency.USD
    assert p.trades[1].fee.amount == Decimal('-1.01812674')
    assert p.trades[1].fee.currency == Currency.USD
    assert p.trades[1].fee_per_piece.amount == Decimal('-0.101812674')
    assert p.trades[1].fee_per_piece.currency == Currency.USD
Пример #9
0
def test_parse_ticker_description_changed():
    p = InteractiveBrokersReportParser()

    lines = """Financial Instrument Information,Header,Asset Category,Symbol,Description,Conid,Security ID,Multiplier,Type,Code
Financial Instrument Information,Data,Stocks,VNQ,VANGUARD REIT ETF,31230302,922908553,1,ETF,
Financial Instrument Information,Data,Stocks,VNQ,VANGUARD REAL ESTATE ETF,31230302,US9229085538,1,ETF,"""

    lines = lines.split('\n')
    p._real_parse_activity_csv(csv.reader(lines, delimiter=','), {
        'Financial Instrument Information':
        p._parse_instrument_information,
    })

    a = p._tickers.get_ticker('VANGUARD REIT ETF', TickerKind.Stock)
    assert a.symbol == 'VNQ'

    b = p._tickers.get_ticker('VANGUARD REAL ESTATE ETF', TickerKind.Stock)
    assert b.symbol == 'VNQ'
Пример #10
0
def test_parse_cash():
    p = InteractiveBrokersReportParser()

    lines = """Cash Report,Header,Currency Summary,Currency,Total,Securities,Futures,Month to Date,Year to Date,
Cash Report,Data,Starting Cash,Base Currency Summary,0,0,0,,,
Cash Report,Data,Commissions,Base Currency Summary,-82.64370531,-82.64370531,0,-8.62621762,-82.64370531,
Cash Report,Data,Deposits,Base Currency Summary,65663.765,65663.765,0,1625.96,65663.765,
Cash Report,Data,Dividends,Base Currency Summary,1045.45,1045.45,0,523.67,1045.45,
Cash Report,Data,Broker Interest Paid and Received,Base Currency Summary,0.13844211,0.13844211,0,0,0.13844211,
Cash Report,Data,Net Trades (Sales),Base Currency Summary,74217.07255104,74217.07255104,0,3923.70991107,74217.07255106,
Cash Report,Data,Net Trades (Purchase),Base Currency Summary,-140090.704656633,-140090.704656633,0,-7874.79454332,-140090.70465664,
Cash Report,Data,Other Fees,Base Currency Summary,-23.2,-23.2,0,-0.6,-23.2,
Cash Report,Data,Withholding Tax,Base Currency Summary,-103.55,-103.55,0,-51.39,-103.55,
Cash Report,Data,Cash FX Translation Gain/Loss,Base Currency Summary,-156.23378547,-156.23378547,0,,,
Cash Report,Data,Ending Cash,Base Currency Summary,470.093845757,470.093845757,0,,,
Cash Report,Data,Ending Settled Cash,Base Currency Summary,470.093845757,470.093845757,0,,,
Cash Report,Data,Starting Cash,RUB,0,0,0,,,
Cash Report,Data,Deposits,RUB,4495000,4495000,0,120000,4495000,
Cash Report,Data,Broker Interest Paid and Received,RUB,3.21,3.21,0,0,3.21,
Cash Report,Data,Net Trades (Purchase),RUB,-4495003.210000001,-4495003.210000001,0,-295619.97975,-4495003.21,
Cash Report,Data,Ending Cash,RUB,0,0,0,,,
Cash Report,Data,Ending Settled Cash,RUB,0,0,0,,,
Cash Report,Data,Starting Cash,USD,0,0,0,,,
Cash Report,Data,Commissions,USD,-82.64370531,-82.64370531,0,-8.62621762,-82.64370531,
Cash Report,Data,Dividends,USD,1045.45,1045.45,0,523.67,1045.45,
Cash Report,Data,Broker Interest Paid and Received,USD,0.09,0.09,0,0,0.09,
Cash Report,Data,Net Trades (Sales),USD,74217.07255104,74217.07255104,0,3923.70991107,74217.07255106,
Cash Report,Data,Net Trades (Purchase),USD,-74583.125,-74583.125,0,-3932.96,-74583.125,
Cash Report,Data,Other Fees,USD,-23.2,-23.2,0,-0.6,-23.2,
Cash Report,Data,Withholding Tax,USD,-103.55,-103.55,0,-51.39,-103.55,
Cash Report,Data,Ending Cash,USD,470.093845757,470.093845757,0,,,
Cash Report,Data,Ending Settled Cash,USD,470.093845757,470.093845757,0,,,"""

    lines = lines.split('\n')
    p._real_parse_activity_csv(csv.reader(lines, delimiter=','), {
        'Cash Report': p._parse_cash_report,
    })

    assert len(p.cash) == 16
    assert p.cash[1] == Cash(amount=Money(4495000, Currency.RUB),
                             description='Deposits')
    assert p.cash[15] == Cash(amount=Money(470.093845757, Currency.USD),
                              description='Ending Settled Cash')
Пример #11
0
def parse_reports(activity_reports_dir: str, confirmation_reports_dir: str) -> InteractiveBrokersReportParser:
    parser_object = InteractiveBrokersReportParser()

    activity_reports = csvs_in_dir(activity_reports_dir)
    confirmation_reports = csvs_in_dir(confirmation_reports_dir)

    for apath in activity_reports:
        logging.info('Activity report %s', apath)
    for cpath in confirmation_reports:
        logging.info('Confirmation report %s', cpath)

    logging.info('start reports parse')
    parser_object.parse_csv(
        activity_csvs=activity_reports,
        trade_confirmation_csvs=confirmation_reports,
    )
    logging.info(f'end reports parse {parser_object}')

    return parser_object
Пример #12
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--activity-reports-dir', type=str, required=True, help='directory with InteractiveBrokers .csv activity reports')
    parser.add_argument('--confirmation-reports-dir', type=str, required=True, help='directory with InteractiveBrokers .csv confirmation reports')
    parser.add_argument('--cache-dir', type=str, default='.', help='directory for caching (CBR RUB exchange rates)')
    parser.add_argument('--years', type=lambda x: [int(v.strip()) for v in x.split(',')], default=[], help='comma separated years for final report, omit for all')
    args = parser.parse_args()

    if os.path.abspath(args.activity_reports_dir) == os.path.abspath(args.confirmation_reports_dir):
        print('--activity-reports-dir and --confirmation-reports-dir MUST be different directories')
        return

    p = InteractiveBrokersReportParser()

    activity_reports = csvs_in_dir(args.activity_reports_dir)
    confirmation_reports = csvs_in_dir(args.confirmation_reports_dir)

    for apath in activity_reports:
        print(f'[*] Activity report {apath}')
    for cpath in confirmation_reports:
        print(f'[*] Confirmation report {cpath}')

    print('========' * 8)
    print('')

    p.parse_csv(
        activity_csvs=activity_reports,
        trade_confirmation_csvs=confirmation_reports,
    )

    trades = p.trades()
    dividends = p.dividends()

    if not trades:
        print('no trades found')
        return

    first_year = min(trades[0].datetime.year, dividends[0].date.year) if dividends else trades[0].datetime.year
    cbrates_df = ExchangeRatesRUB(year_from=first_year, cache_dir=args.cache_dir).dataframe()

    dividends_report = prepare_dividends_report(dividends, cbrates_df) if dividends else None

    portfolio, finished_trades = analyze_trades_fifo(trades)
    if finished_trades:
        finished_trades_df = pandas.DataFrame(finished_trades, columns=finished_trades[0]._fields)  # noqa: WPS437
        trades_report = prepare_trades_report(finished_trades_df, cbrates_df)
    else:
        trades_report = None

    show_report(trades_report, dividends_report, portfolio, args.years)