Coverage for hledger_lots/avg.py: 86%
59 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 22:41 -0300
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 22:41 -0300
1import subprocess
2from dataclasses import dataclass
3from datetime import date, datetime
4from decimal import Decimal
5from typing import List, Optional
7from hledger_lots.hl import adjust_txn
9from . import checks
10from .lib import AdjustedTxn, CostMethodError, adjust_commodity, get_xirr
13@dataclass
14class AvgCost:
15 date: str
16 total_qtty: float = 0
17 total_amount: float = 0
18 avg_cost: float = 0
21def check_sell(sell: AdjustedTxn, avg_cost: float, check: bool):
22 if not check:
23 return
25 decimals_price = Decimal(str(sell.price)).as_tuple().exponent
26 decimals_avg = Decimal(str(avg_cost)).as_tuple().exponent
27 if type(decimals_price) == int and type(decimals_avg) == int:
28 decimals = min(abs(decimals_price), abs(decimals_avg))
29 else:
30 raise ValueError("Not a decimal")
32 if abs(sell.price - avg_cost) > 10 ** (-decimals):
33 raise CostMethodError(sell, avg_cost, sell.base_cur)
34 pass
37def get_avg_cost(
38 txns: List[AdjustedTxn], check: bool, until: Optional[date] = None
39) -> List[AvgCost]:
40 if until:
41 included_txns = [
42 txn
43 for txn in txns
44 if datetime.strptime(txn.date, "%Y-%m-%d").date() <= until
45 ]
46 else:
47 included_txns = txns
49 checks.check_base_currency(included_txns)
51 total_qtty = 0
52 total_amount = 0
53 avg_cost = 0
55 avg_costs: List[AvgCost] = []
57 for txn in included_txns:
58 total_qtty += txn.qtty
60 if txn.qtty >= 0:
61 total_amount += txn.qtty * txn.price
62 else:
63 check_sell(txn, avg_cost, check)
64 total_amount += txn.qtty * avg_cost
66 avg_cost = total_amount / total_qtty if total_qtty != 0 else 0
67 avg_costs.append(AvgCost(txn.date, total_qtty, total_amount, avg_cost))
69 return avg_costs
72def avg_sell(
73 txns: List[AdjustedTxn],
74 date: str,
75 qtty: float,
76 cur: str,
77 cash_account: str,
78 revenue_account: str,
79 comm_account: str,
80 value: float,
81 check: bool,
82):
83 adj_comm = adjust_commodity(cur)
84 checks.check_short_sell_current(txns, qtty)
85 checks.check_base_currency(txns)
86 checks.check_available(txns, comm_account, qtty)
88 sell_date = datetime.strptime(date, "%Y-%m-%d").date()
89 avg_cost = get_avg_cost(txns, check)
90 cost = avg_cost[-1].avg_cost
92 base_curr = txns[0].base_cur
93 price = value / qtty
94 xirr = get_xirr(price, sell_date, txns) or 0 * 100
96 txn_hl = f"""{date} Sold {cur} ; cost_method:avg_cost
97 ; commodity:{cur}, qtty:{qtty:,.2f}, price:{price:,.2f}
98 ; xirr:{xirr:.2f}% annual percent rate 30/360US
99 {cash_account} {value:.2f} {base_curr}
100 {comm_account} {qtty * -1} {adj_comm} @ {cost} {base_curr}
101 {revenue_account}"""
103 comm = ["hledger", "-f-", "print", "--explicit"]
104 txn_proc = subprocess.run(comm, input=txn_hl.encode(), capture_output=True)
106 txn_print: str = txn_proc.stdout.decode("utf8")
107 return txn_print