From cc467b65464c295401afb17e190c5bac97d83bd5 Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Mon, 29 Jan 2024 21:42:50 +0300 Subject: [PATCH] fix ruff format and warnings --- investments/data_providers/cache.py | 2 +- investments/data_providers/cbr.py | 2 +- investments/deposit.py | 12 +++ investments/ibdds/ibdds.py | 9 +- investments/ibtax/ibtax.py | 12 +-- investments/ibtax/report_presenter.py | 63 ++++++++++---- investments/money.py | 2 +- investments/report_parsers/ib.py | 94 +++++++++++--------- investments/report_parsers/open_fr.py | 120 ++++++++++++++------------ investments/trades_fifo.py | 40 +++++---- investments/withdraw.py | 9 ++ 11 files changed, 225 insertions(+), 140 deletions(-) create mode 100644 investments/deposit.py create mode 100644 investments/withdraw.py diff --git a/investments/data_providers/cache.py b/investments/data_providers/cache.py index 58c1448..c183047 100644 --- a/investments/data_providers/cache.py +++ b/investments/data_providers/cache.py @@ -28,7 +28,7 @@ def get(self) -> Optional[pandas.DataFrame]: if (datetime.datetime.utcnow() - datetime.datetime.utcfromtimestamp(mtime)) > self._ttl: return None - return pandas.read_pickle(self._cache_file) # noqa:S301 + return pandas.read_pickle(self._cache_file) def put(self, df: pandas.DataFrame): if self._cache_file is not None: diff --git a/investments/data_providers/cbr.py b/investments/data_providers/cbr.py index d1153ba..9e6a327 100644 --- a/investments/data_providers/cbr.py +++ b/investments/data_providers/cbr.py @@ -7,7 +7,7 @@ import datetime import logging -import xml.etree.ElementTree as ET # noqa:N817 +import xml.etree.ElementTree as ET from typing import Dict, List, Optional, Tuple import pandas # type: ignore diff --git a/investments/deposit.py b/investments/deposit.py new file mode 100644 index 0000000..c31a78b --- /dev/null +++ b/investments/deposit.py @@ -0,0 +1,12 @@ +import datetime +from typing import NamedTuple + +from investments.money import Money + + +class Deposit(NamedTuple): + date: datetime.date + amount: Money + + # def __str__(self): + # return f'{self.ticker}, {self.date} ({self.amount} tax:{self.tax})' diff --git a/investments/ibdds/ibdds.py b/investments/ibdds/ibdds.py index 3c8f105..6fa3752 100644 --- a/investments/ibdds/ibdds.py +++ b/investments/ibdds/ibdds.py @@ -24,9 +24,12 @@ class InteractiveBrokersCashReportParser(InteractiveBrokersReportParser): def parse_csv(self, *, activity_csvs: List[str], trade_confirmation_csvs: List[str]): assert len(activity_csvs) == 1 with open(activity_csvs[0], newline='') as activity_fh: - self._real_parse_activity_csv(csv.reader(activity_fh, delimiter=','), { - 'Cash Report': self._parse_cash_report, - }) + self._real_parse_activity_csv( + csv.reader(activity_fh, delimiter=','), + { + 'Cash Report': self._parse_cash_report, + }, + ) def parse_reports(activity_report_filepath: str) -> InteractiveBrokersCashReportParser: diff --git a/investments/ibtax/ibtax.py b/investments/ibtax/ibtax.py index 9a23dbd..45244a7 100644 --- a/investments/ibtax/ibtax.py +++ b/investments/ibtax/ibtax.py @@ -11,7 +11,7 @@ from investments.data_providers import cbr from investments.dividend import Dividend from investments.fees import Fee -from investments.ibtax.report_presenter import NativeReportPresenter, ReportPresenter # noqa: I001 +from investments.ibtax.report_presenter import NativeReportPresenter, ReportPresenter from investments.interests import Interest from investments.money import Money from investments.report_parsers.ib import InteractiveBrokersReportParser @@ -89,10 +89,7 @@ def prepare_dividends_report(dividends: List[Dividend], cbr_client_usd: cbr.Exch def prepare_fees_report(fees: List[Fee], cbr_client_usd: cbr.ExchangeRatesRUB, verbose: bool) -> pandas.DataFrame: operation_date_column = 'date' - df_data = [ - (i + 1, pandas.to_datetime(x.date), x.amount, x.description, x.date.year) - for i, x in enumerate(fees) - ] + df_data = [(i + 1, pandas.to_datetime(x.date), x.amount, x.description, x.date.year) for i, x in enumerate(fees)] df = pandas.DataFrame(df_data, columns=['N', operation_date_column, 'amount', 'description', 'tax_year']) df['rate'] = df.apply(lambda x: cbr_client_usd.get_rate(x['amount'].currency, x[operation_date_column]), axis=1) df['amount_rub'] = df.apply(lambda x: cbr_client_usd.convert_to_rub(x['amount'], x[operation_date_column]), axis=1) @@ -108,10 +105,7 @@ def prepare_fees_report(fees: List[Fee], cbr_client_usd: cbr.ExchangeRatesRUB, v def prepare_interests_report(interests: List[Interest], cbr_client_usd: cbr.ExchangeRatesRUB) -> pandas.DataFrame: operation_date_column = 'date' - df_data = [ - (i + 1, pandas.to_datetime(x.date), x.amount, x.description, x.date.year) - for i, x in enumerate(interests) - ] + df_data = [(i + 1, pandas.to_datetime(x.date), x.amount, x.description, x.date.year) for i, x in enumerate(interests)] df = pandas.DataFrame(df_data, columns=['N', operation_date_column, 'amount', 'description', 'tax_year']) df['rate'] = df.apply(lambda x: cbr_client_usd.get_rate(x['amount'].currency, x[operation_date_column]), axis=1) df['amount_rub'] = df.apply(lambda x: cbr_client_usd.convert_to_rub(x['amount'], x[operation_date_column]), axis=1) diff --git a/investments/ibtax/report_presenter.py b/investments/ibtax/report_presenter.py index 592e04f..14fb89d 100644 --- a/investments/ibtax/report_presenter.py +++ b/investments/ibtax/report_presenter.py @@ -36,9 +36,15 @@ def is_print_mode(self) -> bool: return self._display_mode == DisplayMode.PRINT @abstractmethod - def prepare_report(self, trades: Optional[pandas.DataFrame], dividends: Optional[pandas.DataFrame], - fees: Optional[pandas.DataFrame], interests: Optional[pandas.DataFrame], - portfolio: List[PortfolioElement], filter_years: List[int]): # noqa: WPS319 + def prepare_report( + self, + trades: Optional[pandas.DataFrame], + dividends: Optional[pandas.DataFrame], + fees: Optional[pandas.DataFrame], + interests: Optional[pandas.DataFrame], + portfolio: List[PortfolioElement], + filter_years: List[int], + ): pass def present(self): @@ -101,15 +107,21 @@ def _append_table(self, tabulate_data: Union[list, pandas.DataFrame], headers='k class NativeReportPresenter(ReportPresenter): - def prepare_report(self, trades: Optional[pandas.DataFrame], dividends: Optional[pandas.DataFrame], - fees: Optional[pandas.DataFrame], interests: Optional[pandas.DataFrame], - portfolio: List[PortfolioElement], filter_years: List[int]): # noqa: WPS318,WPS319 + def prepare_report( + self, + trades: Optional[pandas.DataFrame], + dividends: Optional[pandas.DataFrame], + fees: Optional[pandas.DataFrame], + interests: Optional[pandas.DataFrame], + portfolio: List[PortfolioElement], + filter_years: List[int], + ): years = set() for report in (trades, dividends, fees, interests): if report is not None: years |= set(report['tax_year'].unique()) - for year in years: # noqa: WPS426 + for year in years: if filter_years and (year not in filter_years): continue @@ -188,10 +200,24 @@ def _append_trades_report(self, trades: pandas.DataFrame, year: int): trades_presenter = trades_by_year.copy(deep=True).set_index(['N', 'ticker', 'trade_date']) trades_presenter['ticker_name'] = trades_presenter.apply(lambda x: str(x.name[1]), axis=1) - trades_presenter = trades_presenter[[ - 'ticker_name', 'date', 'settle_date', 'quantity', 'price', 'fee_per_piece', 'price_rub', - 'fee_per_piece_rub', 'fee', 'total', 'total_rub', 'settle_rate', 'fee_rate', 'profit_rub', - ]] + trades_presenter = trades_presenter[ + [ + 'ticker_name', + 'date', + 'settle_date', + 'quantity', + 'price', + 'fee_per_piece', + 'price_rub', + 'fee_per_piece_rub', + 'fee', + 'total', + 'total_rub', + 'settle_rate', + 'fee_rate', + 'profit_rub', + ] + ] if not self._verbose: apply_round_for_dataframe(trades_presenter, {'price', 'total', 'total_rub', 'profit_rub'}, 2) @@ -204,10 +230,17 @@ def _append_trades_report(self, trades: pandas.DataFrame, year: int): self._start_new_page() self._append_header('TRADES RESULTS BEFORE TAXES') - trades_summary_presenter = trades_by_year.copy(deep=True).groupby(lambda idx: ( - trades_by_year.loc[idx, 'ticker'].kind, - 'expenses' if trades_by_year.loc[idx, 'quantity'] > 0 else 'income', - ))['total_rub'].sum().reset_index() + trades_summary_presenter = ( + trades_by_year.copy(deep=True) + .groupby( + lambda idx: ( + trades_by_year.loc[idx, 'ticker'].kind, + 'expenses' if trades_by_year.loc[idx, 'quantity'] > 0 else 'income', + ) + )['total_rub'] + .sum() + .reset_index() + ) trades_summary_presenter = trades_summary_presenter['index'].apply(pandas.Series).join(trades_summary_presenter).pivot(index=0, columns=1, values='total_rub') trades_summary_presenter.index.name = '' trades_summary_presenter.columns.name = '' diff --git a/investments/money.py b/investments/money.py index 4403c54..8363f74 100644 --- a/investments/money.py +++ b/investments/money.py @@ -17,7 +17,7 @@ def currency(self) -> Currency: def amount(self) -> Decimal: return self._amount - def round(self, digits=0) -> 'Money': # noqa: WPS125 + def round(self, digits=0) -> 'Money': return Money(round(self._amount, digits), self._currency) def __repr__(self): diff --git a/investments/report_parsers/ib.py b/investments/report_parsers/ib.py index b5c5e1e..474602e 100644 --- a/investments/report_parsers/ib.py +++ b/investments/report_parsers/ib.py @@ -12,6 +12,7 @@ from investments.money import Money from investments.ticker import Ticker, TickerKind from investments.trade import Trade +from investments.deposit import Deposit def _parse_datetime(strval: str) -> datetime.datetime: @@ -53,7 +54,7 @@ def parse_header(self, fields: List[str]): def parse(self, row: List[str]) -> Dict[str, str]: error_msg = f'expect {len(self._fields)} rows {self._fields}, but got {len(row)} rows ({row})' assert len(row) == len(self._fields), error_msg - return dict(zip(self._fields, row)) + return dict(zip(self._fields, row, strict=True)) class TickersStorage: @@ -151,12 +152,12 @@ def __init__(self) -> None: self._fees: List[Fee] = [] self._interests: List[Interest] = [] self._cash: List[Cash] = [] - self._deposits_and_withdrawals: List[Tuple[datetime.date, Money]] = [] + self._deposits: List[Deposit] = [] self._tickers = TickersStorage() self._settle_dates = SettleDatesStorage() def __repr__(self): - return f'IbParser(trades={len(self.trades)}, dividends={len(self.dividends)}, fees={len(self.fees)}, interests={len(self.interests)})' # noqa: WPS221 + return f'IbParser(trades={len(self.trades)}, dividends={len(self.dividends)}, fees={len(self.fees)}, interests={len(self.interests)})' @property def trades(self) -> List[Trade]: @@ -167,8 +168,8 @@ def dividends(self) -> List[Dividend]: return self._dividends @property - def deposits_and_withdrawals(self) -> List[Tuple[datetime.date, Money]]: - return self._deposits_and_withdrawals + def deposits(self) -> List[Deposit]: + return self._deposits @property def fees(self) -> List[Fee]: @@ -186,9 +187,12 @@ def parse_csv(self, *, activity_csvs: List[str], trade_confirmation_csvs: List[s # 1. parse tickers info for ac_fname in activity_csvs: with open(ac_fname, newline='') as ac_fh: - self._real_parse_activity_csv(csv.reader(ac_fh, delimiter=','), { - 'Financial Instrument Information': self._parse_instrument_information, - }) + self._real_parse_activity_csv( + csv.reader(ac_fh, delimiter=','), + { + 'Financial Instrument Information': self._parse_instrument_information, + }, + ) # 2. parse settle_date from trade confirmation for tc_fname in trade_confirmation_csvs: @@ -198,27 +202,30 @@ def parse_csv(self, *, activity_csvs: List[str], trade_confirmation_csvs: List[s # 3. parse everything else from activity (trades, dividends, ...) for activity_fname in activity_csvs: with open(activity_fname, newline='') as activity_fh: - self._real_parse_activity_csv(csv.reader(activity_fh, delimiter=','), { - 'Trades': self._parse_trades, - 'Dividends': self._parse_dividends, - 'Withholding Tax': self._parse_withholding_tax, - 'Deposits & Withdrawals': self._parse_deposits, - # 'Account Information', 'Cash Report', 'Change in Dividend Accruals', 'Change in NAV', - # 'Codes', - 'Fees': self._parse_fees, - # 'Interest Accruals', - 'Interest': self._parse_interests, - # 'Mark-to-Market Performance Summary', - # 'Net Asset Value', 'Notes/Legal Notes', 'Open Positions', 'Realized & Unrealized Performance Summary', - # 'Statement', '\ufeffStatement', 'Total P/L for Statement Period', 'Transaction Fees', - 'Cash Report': self._parse_cash_report, - }) + self._real_parse_activity_csv( + csv.reader(activity_fh, delimiter=','), + { + 'Trades': self._parse_trades, + 'Dividends': self._parse_dividends, + 'Withholding Tax': self._parse_withholding_tax, + 'Deposits & Withdrawals': self._parse_deposits, + # 'Account Information', 'Cash Report', 'Change in Dividend Accruals', 'Change in NAV', + # 'Codes', + 'Fees': self._parse_fees, + # 'Interest Accruals', + 'Interest': self._parse_interests, + # 'Mark-to-Market Performance Summary', + # 'Net Asset Value', 'Notes/Legal Notes', 'Open Positions', 'Realized & Unrealized Performance Summary', + # 'Statement', '\ufeffStatement', 'Total P/L for Statement Period', 'Transaction Fees', + 'Cash Report': self._parse_cash_report, + }, + ) # 4. sort self._trades.sort(key=lambda x: x.trade_date) self._dividends.sort(key=lambda x: x.date) self._interests.sort(key=lambda x: x.date) - self._deposits_and_withdrawals.sort(key=lambda x: x[0]) + self._deposits.sort(key=lambda x: x.date) self._fees.sort(key=lambda x: x.date) def _parse_trade_confirmation_csv(self, csv_reader: Iterator[List[str]]): @@ -284,14 +291,16 @@ def _parse_trades(self, f: Dict[str, str]): settle_date = self._settle_dates.get_date(ticker.symbol, dt) assert settle_date is not None - self._trades.append(Trade( - ticker=ticker, - trade_date=dt, - settle_date=settle_date, - quantity=_parse_trade_quantity(f['Quantity']) * quantity_multiplier, - price=Money(f['T. Price'], currency), - fee=Money(f['Comm/Fee'], currency), - )) + self._trades.append( + Trade( + ticker=ticker, + trade_date=dt, + settle_date=settle_date, + quantity=_parse_trade_quantity(f['Quantity']) * quantity_multiplier, + price=Money(f['T. Price'], currency), + fee=Money(f['Comm/Fee'], currency), + ) + ) def _parse_withholding_tax(self, f: Dict[str, str]): div_symbol, div_type = _parse_dividend_description(f['Description']) @@ -304,7 +313,7 @@ def _parse_withholding_tax(self, f: Dict[str, str]): for i, v in enumerate(self._dividends): # difference in reports for the same past year, but generated in different time # read more at https://github.com/cdump/investments/issues/17 - cash_choice_hack = (v.dtype == 'Cash Dividend' and div_type == 'Choice Dividend') + cash_choice_hack = v.dtype == 'Cash Dividend' and div_type == 'Choice Dividend' if v.ticker == ticker and v.date == date and (v.dtype == div_type or cash_choice_hack): assert v.amount.currency == tax_amount.currency @@ -341,19 +350,22 @@ def _parse_dividends(self, f: Dict[str, str]): return assert amount.amount > 0, f'unsupported dividend with non positive amount: {f}' - self._dividends.append(Dividend( - dtype=div_type, - ticker=ticker, - date=date, - amount=amount, - tax=Money(0, amount.currency), - )) + self._dividends.append( + Dividend( + dtype=div_type, + ticker=ticker, + date=date, + amount=amount, + tax=Money(0, amount.currency), + ) + ) def _parse_deposits(self, f: Dict[str, str]): currency = Currency.parse(f['Currency']) date = _parse_date(f['Settle Date']) amount = Money(f['Amount'], currency) - self._deposits_and_withdrawals.append((date, amount)) + if amount.amount > 0: # Withdrawals not supported yet + self._deposits.append(Deposit(date=date, amount=amount)) def _parse_fees(self, f: Dict[str, str]): currency = Currency.parse(f['Currency']) diff --git a/investments/report_parsers/open_fr.py b/investments/report_parsers/open_fr.py index 6bff55a..509e170 100644 --- a/investments/report_parsers/open_fr.py +++ b/investments/report_parsers/open_fr.py @@ -1,6 +1,6 @@ import datetime import re -import xml.etree.ElementTree as ET # noqa:N817 +import xml.etree.ElementTree as ET from typing import List, Optional from investments.currency import Currency @@ -123,14 +123,16 @@ def _parse_cb_convertation(self, f): assert float(f['quantity']) == qnty ticker = self._tickers.get(name=f['security_name']) dt = _parse_datetime(f['operation_date']) - self._trades.append(Trade( - ticker=ticker, - trade_date=dt, - settle_date=dt, - quantity=qnty, - price=Money(0, Currency.RUB), - fee=Money(0, Currency.RUB), - )) + self._trades.append( + Trade( + ticker=ticker, + trade_date=dt, + settle_date=dt, + quantity=qnty, + price=Money(0, Currency.RUB), + fee=Money(0, Currency.RUB), + ) + ) def _parse_money_payment(self, f, bonds_redemption): comment = f['comment'] @@ -145,26 +147,30 @@ def _parse_money_payment(self, f, bonds_redemption): 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), - )) + 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[^,]+), (?P\w+), количество (?P\d+)\) налог не удерживается$', comment) if m is not None: isin, quantity_buyout = m.group('isin'), int(m.group('quantity')) - self._trades.append(Trade( - ticker=self._tickers.get(isin=isin), - trade_date=dt, - settle_date=dt, - quantity=-1 * quantity_buyout, - price=money_total / quantity_buyout, - fee=Money(0, currency), - )) + self._trades.append( + Trade( + ticker=self._tickers.get(isin=isin), + trade_date=dt, + settle_date=dt, + quantity=-1 * quantity_buyout, + price=money_total / quantity_buyout, + fee=Money(0, currency), + ) + ) return m = re.match(r'^Выплата дохода клиент (\w+) \((?PНКД \d+|Погашение) (?P.*?)\) налог (к удержанию 0.00 рублей|не удерживается)$', comment) @@ -172,28 +178,32 @@ def _parse_money_payment(self, f, bonds_redemption): ticker = self._tickers.get(name=m.group('name')) if m.group('type').startswith('НКД'): # WARNING: do not use for tax calculation! - for (price, quantity_coupons) in ((Money(0, currency), 1), (money_total, -1)): - self._trades.append(Trade( - ticker=ticker, - trade_date=dt, - settle_date=dt, - quantity=quantity_coupons, - price=price, - fee=Money(0, currency), - )) + for price, quantity_coupons in ((Money(0, currency), 1), (money_total, -1)): + self._trades.append( + Trade( + ticker=ticker, + trade_date=dt, + settle_date=dt, + quantity=quantity_coupons, + price=price, + fee=Money(0, currency), + ) + ) return if m.group('type') == 'Погашение': key = (ticker, dt) quantity_redemption = bonds_redemption[key] - self._trades.append(Trade( - ticker=ticker, - trade_date=dt, - settle_date=dt, - quantity=quantity_redemption, - price=-1 * money_total / int(quantity_redemption), - fee=Money(0, currency), - )) + self._trades.append( + Trade( + ticker=ticker, + trade_date=dt, + settle_date=dt, + quantity=quantity_redemption, + price=-1 * money_total / int(quantity_redemption), + fee=Money(0, currency), + ) + ) del bonds_redemption[key] return @@ -226,10 +236,12 @@ def _parse_non_trade_operations(self, xml_tree: ET.ElementTree): comment = f['comment'] if any(comment.startswith(p) for p in ('Поставлены на торги средства клиента', 'Перевод денежных средств с клиента')): - self._deposits_and_withdrawals.append(( - _parse_datetime(f['operation_date']), - Money(f['amount'], Currency.parse(f['currency_code'])), - )) + self._deposits_and_withdrawals.append( + ( + _parse_datetime(f['operation_date']), + Money(f['amount'], Currency.parse(f['currency_code'])), + ) + ) continue if comment.startswith('Выплата дохода клиент'): @@ -277,11 +289,13 @@ def _parse_trades(self, xml_tree: ET.ElementTree): 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, - trade_date=_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'])), - )) + self._trades.append( + Trade( + ticker=ticker, + trade_date=_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'])), + ) + ) diff --git a/investments/trades_fifo.py b/investments/trades_fifo.py index 00ae323..1aef2d0 100644 --- a/investments/trades_fifo.py +++ b/investments/trades_fifo.py @@ -53,7 +53,6 @@ def analyze_trades(self, trades: Iterable[Trade]): quantity = trade.quantity while quantity != 0: - matched_trade, q = active_trades.match(quantity, trade.ticker) if matched_trade is None: assert q == 0 @@ -63,8 +62,13 @@ def analyze_trades(self, trades: Iterable[Trade]): total_cost = compute_total_cost(q, matched_trade.price, matched_trade.fee_per_piece) finished_trade = FinishedTrade( - finished_trade_id, trade.ticker, matched_trade.trade_date, matched_trade.settle_date, q, - matched_trade.price, matched_trade.fee_per_piece, + finished_trade_id, + trade.ticker, + matched_trade.trade_date, + matched_trade.settle_date, + q, + matched_trade.price, + matched_trade.fee_per_piece, ) self._finished_trades.append(finished_trade) @@ -80,15 +84,17 @@ def analyze_trades(self, trades: Iterable[Trade]): if total_profit is not None: q = trade.quantity - quantity - self._finished_trades.append(FinishedTrade( - finished_trade_id, - trade.ticker, - trade.trade_date, - trade.settle_date, - q, - trade.price, - trade.fee_per_piece, - )) + self._finished_trades.append( + FinishedTrade( + finished_trade_id, + trade.ticker, + trade.trade_date, + trade.settle_date, + q, + trade.price, + trade.fee_per_piece, + ) + ) finished_trade_id += 1 if quantity != 0: @@ -127,10 +133,12 @@ def put(self, quantity: int, trade: Trade): if self._portfolio[trade.ticker]: assert self.sign(quantity) == self.sign(self._portfolio[trade.ticker][0]['quantity']) - self._portfolio[trade.ticker].append({ - 'trade': trade, - 'quantity': quantity, - }) + self._portfolio[trade.ticker].append( + { + 'trade': trade, + 'quantity': quantity, + } + ) def match(self, quantity: int, ticker: Ticker) -> Tuple[Optional[Trade], int]: """ diff --git a/investments/withdraw.py b/investments/withdraw.py new file mode 100644 index 0000000..c1bb659 --- /dev/null +++ b/investments/withdraw.py @@ -0,0 +1,9 @@ +import datetime +from typing import NamedTuple + +from investments.money import Money + + +class Withdraw(NamedTuple): + date: datetime.date + amount: Money