Exemplo n.º 1
0
def test_trades_precision() -> List[FinishedTrade]:
    ticker = Ticker(symbol='VT', kind=TickerKind.Stock)
    test_case = [
        Trade(
            ticker=ticker,
            trade_date=datetime.datetime(2020, 1, 31, 9, 30),  # 63,0359₽
            settle_date=datetime.date(2020, 2, 4),  # 63,9091₽
            quantity=10,
            price=Money('80.62', Currency.USD),
            fee=Money('-1', Currency.USD)),
        Trade(
            ticker=ticker,
            trade_date=datetime.datetime(2020, 2, 10, 9, 38),  # 63,4720₽
            settle_date=datetime.date(2020, 2, 12),  # 63,9490₽
            quantity=-10,
            price=Money('81.82', Currency.USD),
            fee=Money('-1.01812674', Currency.USD))
    ]

    finished_trades = TradesAnalyzer(test_case).finished_trades

    buy_trade: FinishedTrade = finished_trades[0]
    assert buy_trade.price.amount == Decimal('80.62')
    assert buy_trade.fee_per_piece.amount == Decimal('-0.1')

    sell_trade: FinishedTrade = finished_trades[1]
    assert sell_trade.price.amount == Decimal('81.82')
    assert sell_trade.fee_per_piece.amount == Decimal('-0.101812674')

    return finished_trades
Exemplo n.º 2
0
def test_analyze_portfolio_different_kinds():
    ticker_stock = Ticker(symbol='TEST', kind=TickerKind.Stock)
    ticker_option = Ticker(symbol='TEST', kind=TickerKind.Option)
    dt = datetime.datetime.now()

    request_trades = [
        Trade(ticker=ticker_stock,
              trade_date=dt,
              settle_date=dt.date(),
              quantity=-3,
              price=Money(4.2, Currency.USD),
              fee=Money(1, Currency.USD)),
        Trade(ticker=ticker_stock,
              trade_date=dt,
              settle_date=dt.date(),
              quantity=8,
              price=Money(4.2, Currency.USD),
              fee=Money(1, Currency.USD)),
        Trade(ticker=ticker_option,
              trade_date=dt,
              settle_date=dt.date(),
              quantity=10,
              price=Money(4.2, Currency.USD),
              fee=Money(1, Currency.USD)),
    ]

    res = TradesAnalyzer(request_trades).final_portfolio

    assert len(res) == 2
    assert res[0].quantity == 5
    assert res[0].ticker == ticker_stock
    assert res[1].quantity == 10
    assert res[1].ticker == ticker_option
Exemplo n.º 3
0
def test_tradesfifo_ticker_different_kinds():
    dt = datetime.datetime.now()
    tfifo = _TradesFIFO()

    ticker_stock = Ticker(symbol='TEST', kind=TickerKind.Stock)
    ticker_option = Ticker(symbol='TEST', kind=TickerKind.Option)

    tfifo.put(
        3,
        Trade(
            ticker=ticker_stock,
            datetime=dt,
            settle_date=dt.date(),
            quantity=4,
            price=Money(4.2, Currency.USD),
            fee=Money(1, Currency.USD),
        ))
    unmatched = tfifo.unmatched()
    assert len(unmatched) == 1
    assert unmatched[0]['ticker'] == ticker_stock
    assert unmatched[0]['quantity'] == 3

    tfifo.put(
        2,
        Trade(
            ticker=ticker_stock,
            datetime=dt,
            settle_date=dt.date(),
            quantity=4,
            price=Money(4.2, Currency.USD),
            fee=Money(1, Currency.USD),
        ))
    unmatched = tfifo.unmatched()
    assert len(unmatched) == 1
    assert unmatched[0]['ticker'] == ticker_stock
    assert unmatched[0]['quantity'] == 5

    tfifo.put(
        1,
        Trade(
            ticker=ticker_option,
            datetime=dt,
            settle_date=dt.date(),
            quantity=3,
            price=Money(4.2, Currency.USD),
            fee=Money(1, Currency.USD),
        ))
    unmatched = tfifo.unmatched()
    assert len(unmatched) == 2
    unmatched_q = {x['ticker']: x['quantity'] for x in unmatched}
    assert ticker_stock in unmatched_q
    assert ticker_option in unmatched_q
    assert unmatched_q[ticker_stock] == 5
    assert unmatched_q[ticker_option] == 1
Exemplo n.º 4
0
def test_trades_fees_simple():
    request_trades = []
    ticker = Ticker(symbol='VT', kind=TickerKind.Stock)

    # buy 10
    request_trades.append(
        Trade(
            ticker=ticker,
            trade_date=datetime.datetime(year=2020, month=1,
                                         day=31),  # 63,0359₽
            settle_date=datetime.datetime(year=2020, month=2,
                                          day=4),  # 63,9091₽
            quantity=10,
            price=Money(80.62, Currency.USD),
            fee=Money(-1, Currency.USD),
        ))
    # sell 10
    request_trades.append(
        Trade(
            ticker=ticker,
            trade_date=datetime.datetime(year=2020, month=2,
                                         day=10),  # 63,4720₽
            settle_date=datetime.datetime(year=2020, month=2,
                                          day=12),  # 63,9490₽
            quantity=-10,
            price=Money(81.82, Currency.USD),
            fee=Money(Decimal('-1.01812674'), Currency.USD),
        ))

    finished_trades = TradesAnalyzer(request_trades).finished_trades

    assert len(finished_trades) == 2

    buy_trade: FinishedTrade = finished_trades[0]
    assert buy_trade.trade_date == datetime.datetime(year=2020,
                                                     month=1,
                                                     day=31)
    assert buy_trade.settle_date == datetime.datetime(year=2020,
                                                      month=2,
                                                      day=4)
    assert buy_trade.quantity == 10
    assert buy_trade.price.amount == Decimal('80.62')
    assert buy_trade.fee_per_piece.amount == Decimal('-0.1')

    sell_trade: FinishedTrade = finished_trades[1]
    assert sell_trade.trade_date == datetime.datetime(year=2020,
                                                      month=2,
                                                      day=10)
    assert sell_trade.settle_date == datetime.datetime(year=2020,
                                                       month=2,
                                                       day=12)
    assert sell_trade.quantity == -10
    assert sell_trade.price.amount == Decimal('81.82')
    assert sell_trade.fee_per_piece.amount == Decimal('-0.101812674')
Exemplo n.º 5
0
    def _parse_trades(self, f: Dict[str, str]):
        ticker_kind = _parse_tickerkind(f['Asset Category'])
        if ticker_kind == TickerKind.Forex:
            logging.warning(
                f'Skipping FOREX trade (not supported yet), your final report may be incorrect! {f}'
            )
            return

        ticker = self._tickers.get_ticker(f['Symbol'], ticker_kind)
        quantity_multiplier = self._tickers.get_multiplier(ticker)
        currency = Currency.parse(f['Currency'])

        dt = _parse_datetime(f['Date/Time'])

        settle_date = self._settle_dates.get((ticker.symbol, dt))
        assert settle_date is not None

        self._trades.append(
            Trade(
                ticker=ticker,
                datetime=dt,
                settle_date=settle_date,
                quantity=int(f['Quantity']) * quantity_multiplier,
                price=Money(f['T. Price'], currency),
                fee=Money(f['Comm/Fee'], currency),
            ))
Exemplo n.º 6
0
    def _parse_trades(self, xml_tree: ET.ElementTree):
        for rec in xml_tree.findall('spot_main_deals_conclusion/item'):
            f = rec.attrib
            qnty = -1 * float(f['sell_qnty']) if 'sell_qnty' in f else float(f['buy_qnty'])
            assert float(int(qnty)) == qnty

            ticker = self._tickers.get(
                grn=f['security_grn_code'] if 'security_grn_code' in f else None,
                name=f['security_name'],
            )
            if ticker.kind == TickerKind.Bond:
                price = Money(f['volume_currency'], Currency.parse(f['price_currency_code'])) / abs(int(qnty))
            else:
                price = Money(f['price'], Currency.parse(f['price_currency_code']))

            expected_volume = abs(int(qnty)) * price
            actual_volume = Money(f['volume_currency'], Currency.parse(f['price_currency_code']))
            assert expected_volume == actual_volume, f'expected_volume({expected_volume} = {qnty} * {price}) != ({actual_volume}) for {f}'

            self._trades.append(Trade(
                ticker=ticker,
                datetime=_parse_datetime(f['conclusion_time']),
                settle_date=_parse_datetime(f['execution_date']),
                quantity=int(qnty),
                price=price,
                fee=Money(f['broker_commission'], Currency.parse(f['broker_commission_currency_code'])),
            ))
Exemplo n.º 7
0
def test_analyze_trades_without_fees(trades, expect_trades):
    request_trades = []
    for date, ticker, qty, price in trades:
        dt = datetime.datetime.strptime(date, '%Y-%m-%d')
        request_trades.append(
            Trade(
                ticker=Ticker(symbol=ticker, kind=TickerKind.Stock),
                trade_date=dt,
                settle_date=dt.date(),
                quantity=qty,
                price=Money(price, Currency.USD),
                fee=Money(0, Currency.USD),
            ))

    finished_trades = TradesAnalyzer(request_trades).finished_trades

    assert len(finished_trades) == len(
        expect_trades
    ), f'expect {len(expect_trades)} finished trades but got {len(finished_trades)}'
    for trade, expected in zip(finished_trades, expect_trades):
        assert expected[
            0] == trade.N, f'expect trade N={expected[0]} but got {trade.N}'
        assert expected[
            1] == trade.ticker.symbol, f'expect trade ticker={expected[1]} but got {trade.ticker.symbol}'
        assert expected[
            2] == trade.quantity, f'expect trade quantity={expected[2]} but got {trade.quantity}'
Exemplo n.º 8
0
 def _parse_cb_convertation(self, f):
     # WARNING: lost price information, do not use for tax calculation!
     qnty = float(f['quantity'])
     assert float(int(qnty)) == qnty
     ticker = self._tickers.get(name=f['security_name'])
     dt = _parse_datetime(f['operation_date'])
     self._trades.append(Trade(
         ticker=ticker,
         datetime=dt,
         settle_date=dt,
         quantity=int(qnty),
         price=Money(0, Currency.RUB),  # TODO: other currencies
         fee=Money(0, Currency.RUB),
     ))
Exemplo n.º 9
0
def test_analyze_trades_fifo():
    dt = datetime.datetime.now()

    testcases = [
        {
            'trades': [
                ('2018-01-01', 'TEST', 100, 4.2),
                ('2018-01-04', 'TEST', 50, 17.5),
                ('2018-01-07', 'TEST', -100, 50.3),
            ],
            'expect_portfolio': [('TEST', 50)],
        },
        {
            'trades': [
                ('2018-01-01', 'TEST', 100, 4.2),
                ('2018-01-04', 'TEST', 50, 17.5),
                ('2018-01-07', 'TEST', -130, 50.3),
            ],
            'expect_portfolio': [('TEST', 20)],
        },
        {
            'trades': [
                ('2018-01-01', 'TEST', -100, 4.2),
                ('2018-01-04', 'TEST', 30, 17.5),
            ],
            'expect_portfolio': [('TEST', -70)],
        },
    ]

    for tc in testcases:
        trades = []
        for t in tc['trades']:
            dt = datetime.datetime.strptime(t[0], '%Y-%m-%d')
            trades.append(
                Trade(
                    ticker=Ticker(symbol=t[1], kind=TickerKind.Stock),
                    datetime=dt,
                    settle_date=dt.date(),
                    quantity=t[2],
                    price=Money(t[3], Currency.USD),
                    fee=Money(1, Currency.USD),
                ))

        portfolio, finished_trades = analyze_trades_fifo(trades)
        print(portfolio)
        assert len(portfolio) == len(tc['expect_portfolio'])
        assert len(portfolio) == 1  # FIXME
        assert portfolio[0]['ticker'].symbol == tc['expect_portfolio'][0][0]
        assert portfolio[0]['quantity'] == tc['expect_portfolio'][0][1]
Exemplo n.º 10
0
def test_analyze_portfolio(trades: list, expect_portfolio: dict):
    request_trades = []
    for date, ticker, qty in trades:
        dt = datetime.datetime.strptime(date, '%Y-%m-%d')
        request_trades.append(
            Trade(
                ticker=Ticker(symbol=ticker, kind=TickerKind.Stock),
                trade_date=dt,
                settle_date=dt.date(),
                quantity=qty,
                price=Money(1, Currency.USD),
                fee=Money(-1, Currency.USD),
            ))

    resp_portfolio = TradesAnalyzer(request_trades).final_portfolio

    assert expect_portfolio == [(str(i.ticker), i.quantity)
                                for i in resp_portfolio]
Exemplo n.º 11
0
    def _parse_money_payment(self, f, bonds_redemption):
        comment = f['comment']
        currency = Currency.parse(f['currency_code'])
        money_total = Money(f['amount'], currency)
        dt = _parse_datetime(f['operation_date'])

        m1 = re.match(r'^Выплата дохода клиент (\w+) дивиденды (?P<name>.*?) налог к удержанию (?P<tax>[0-9.]+) рублей$', comment)
        m2 = re.match(r'^Выплата дохода клиент (\w+) дивиденды (?P<name>.*?) налог 0.00 рублей удержан эмитентом$', comment)
        if m1 is not None or m2 is not None:
            if m1 is not None:
                name, tax = m1.group('name'), m1.group('tax')
            elif m2 is not None:
                name, tax = m2.group('name'), '0'
            self._dividends.append(Dividend(
                dtype='',
                ticker=self._tickers.get_by_dividend_name(name),
                date=dt.date(),
                amount=money_total,
                tax=Money(tax, currency),
            ))
            return

        m = re.match(r'^Выплата дохода клиент (\w+) \(Выкуп (?P<name>[^,]+), (?P<isin>\w+), количество (?P<quantity>\d+)\) налог не удерживается$', comment)
        if m is not None:
            isin, quantity = m.group('isin'), int(m.group('quantity'))
            self._trades.append(Trade(
                ticker=self._tickers.get(isin=isin),
                datetime=dt,
                settle_date=dt,
                quantity=-1 * quantity,
                price=money_total / quantity,
                fee=Money(0, currency),
            ))
            return

        m = re.match(r'^Выплата дохода клиент (\w+) \((?P<type>НКД \d+|Погашение) (?P<name>.*?)\) налог (к удержанию 0.00 рублей|не удерживается)$', comment)
        if m is not None:
            ticker = self._tickers.get(name=m.group('name'))
            if m.group('type').startswith('НКД'):
                # WARNING: do not use for tax calculation!
                for (price, quantity) in ((Money(0, currency), 1), (money_total, -1)):
                    self._trades.append(Trade(
                        ticker=ticker,
                        datetime=dt,
                        settle_date=dt,
                        quantity=quantity,
                        price=price,
                        fee=Money(0, currency),
                    ))
                return

            if m.group('type') == 'Погашение':
                key = (ticker, dt)
                qnty = bonds_redemption[key]
                self._trades.append(Trade(
                    ticker=ticker,
                    datetime=dt,
                    settle_date=dt,
                    quantity=qnty,
                    price=-1 * money_total / int(qnty),
                    fee=Money(0, currency),
                ))
                del bonds_redemption[key]
                return

            raise Exception(f'Unknown type {m.group("type")}')

        raise Exception(f'unsupported description {f}')