Skip to content

Commit

Permalink
fix ruff format and warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
cdump committed Jan 29, 2024
1 parent 25fbf90 commit cc467b6
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 140 deletions.
2 changes: 1 addition & 1 deletion investments/data_providers/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion investments/data_providers/cbr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions investments/deposit.py
Original file line number Diff line number Diff line change
@@ -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})'
9 changes: 6 additions & 3 deletions investments/ibdds/ibdds.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 3 additions & 9 deletions investments/ibtax/ibtax.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
63 changes: 48 additions & 15 deletions investments/ibtax/report_presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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 = ''
Expand Down
2 changes: 1 addition & 1 deletion investments/money.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
94 changes: 53 additions & 41 deletions investments/report_parsers/ib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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]:
Expand All @@ -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]:
Expand All @@ -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:
Expand All @@ -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]]):
Expand Down Expand Up @@ -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'])
Expand All @@ -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
Expand Down Expand Up @@ -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'])
Expand Down
Loading

0 comments on commit cc467b6

Please sign in to comment.