Skip to content

Commit 8786e6a

Browse files
authored
Merge branch 'main' into per_wallet_application
2 parents d903e91 + 244ccff commit 8786e6a

File tree

1 file changed

+130
-133
lines changed

1 file changed

+130
-133
lines changed

tests/test_accounting_method.py

Lines changed: 130 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,18 @@ class SeekLotResult:
3636

3737

3838
@dataclass(frozen=True, eq=True)
39-
class InTransactionSpec:
39+
class InTransactionDescriptor:
4040
spot_price: int
4141
amount: int
4242

43+
@dataclass(frozen=True, eq=True)
44+
class _Test:
45+
description: str
46+
lot_selection_method: AbstractAccountingMethod
47+
in_transactions: List[InTransactionDescriptor]
48+
amounts_to_match: List[int]
49+
want: List[SeekLotResult]
50+
4351

4452
class TestAccountingMethod(unittest.TestCase):
4553
_configuration: Configuration
@@ -51,10 +59,10 @@ def setUpClass(cls) -> None:
5159
def setUp(self) -> None:
5260
self.maxDiff = None # pylint: disable=invalid-name
5361

54-
def _initialize_acquired_lots(self, in_transaction_spec_list: List[InTransactionSpec]) -> List[InTransaction]:
62+
def _initialize_acquired_lots(self, in_transaction_descriptors: List[InTransactionDescriptor]) -> List[InTransaction]:
5563
date = datetime.strptime("2021-01-01", "%Y-%m-%d")
5664
in_transactions: List[InTransaction] = []
57-
for i, in_transaction_spec in enumerate(in_transaction_spec_list):
65+
for i, in_transaction_descriptor in enumerate(in_transaction_descriptors):
5866
in_transactions.append(
5967
InTransaction(
6068
self._configuration,
@@ -63,49 +71,46 @@ def _initialize_acquired_lots(self, in_transaction_spec_list: List[InTransaction
6371
"Coinbase",
6472
"Bob",
6573
"Buy",
66-
RP2Decimal(in_transaction_spec.spot_price),
67-
RP2Decimal(in_transaction_spec.amount),
74+
RP2Decimal(in_transaction_descriptor.spot_price),
75+
RP2Decimal(in_transaction_descriptor.amount),
6876
row=1 + i,
6977
)
7078
)
7179
date += timedelta(days=1)
7280
return in_transactions
7381

7482
# This function adds all acquired lots at first and then does amount pairings.
75-
def _test_fixed_lot_candidates(
76-
self, lot_selection_method: AbstractAccountingMethod, in_transactions: List[InTransaction], amounts_to_match: List[int], want: List[SeekLotResult]
77-
) -> None:
83+
def _run_test_fixed_lot_candidates(self, lot_selection_method: AbstractAccountingMethod, test: _Test) -> None:
84+
print(f"\nDescription: {test.description:}")
85+
in_transactions = self._initialize_acquired_lots(test.in_transactions)
7886
acquired_lot_candidates = lot_selection_method.create_lot_candidates(in_transactions, {})
7987
acquired_lot_candidates.set_to_index(len(in_transactions) - 1)
80-
print(in_transactions)
8188
i = 0
82-
for int_amount in amounts_to_match:
89+
for int_amount in test.amounts_to_match:
8390
amount = RP2Decimal(int_amount)
8491
while True:
8592
result = lot_selection_method.seek_non_exhausted_acquired_lot(acquired_lot_candidates, amount)
8693
if result is None:
8794
break
8895
if result.amount >= amount:
8996
acquired_lot_candidates.set_partial_amount(result.acquired_lot, result.amount - amount)
90-
print(i, want[i], amount, result)
91-
self.assertEqual(result.amount, RP2Decimal(want[i].amount))
92-
self.assertEqual(result.acquired_lot.row, want[i].row)
97+
self.assertEqual(result.amount, RP2Decimal(test.want[i].amount))
98+
self.assertEqual(result.acquired_lot.row, test.want[i].row)
9399
i += 1
94100
break
95101
acquired_lot_candidates.clear_partial_amount(result.acquired_lot)
96102
amount -= result.amount
97-
print(i, want[i], amount, result)
98-
self.assertEqual(result.amount, RP2Decimal(want[i].amount))
99-
self.assertEqual(result.acquired_lot.row, want[i].row)
103+
self.assertEqual(result.amount, RP2Decimal(test.want[i].amount))
104+
self.assertEqual(result.acquired_lot.row, test.want[i].row)
100105
i += 1
101106

102107
# This function grows lot_candidates dynamically: it adds an acquired lot, does an amount pairing and repeats.
103-
def _test_dynamic_lot_candidates(
104-
self, lot_selection_method: AbstractAccountingMethod, in_transactions: List[InTransaction], amounts_to_match: List[int], want: List[SeekLotResult]
105-
) -> None:
108+
def _run_test_dynamic_lot_candidates(self, lot_selection_method: AbstractAccountingMethod, test: _Test) -> None:
109+
print(f"\nDescription: {test.description:}")
110+
in_transactions = self._initialize_acquired_lots(test.in_transactions)
106111
acquired_lot_candidates = lot_selection_method.create_lot_candidates([], {})
107112
i = 0
108-
for int_amount in amounts_to_match:
113+
for int_amount in test.amounts_to_match:
109114
amount = RP2Decimal(int_amount)
110115
while True:
111116
if i < len(in_transactions):
@@ -116,125 +121,117 @@ def _test_dynamic_lot_candidates(
116121
break
117122
if result.amount >= amount:
118123
acquired_lot_candidates.set_partial_amount(result.acquired_lot, result.amount - amount)
119-
print(i, want[i], amount, result)
120-
self.assertEqual(result.amount, RP2Decimal(want[i].amount))
121-
self.assertEqual(result.acquired_lot.row, want[i].row)
124+
self.assertEqual(result.amount, RP2Decimal(test.want[i].amount))
125+
self.assertEqual(result.acquired_lot.row, test.want[i].row)
122126
i += 1
123127
break
124128
acquired_lot_candidates.clear_partial_amount(result.acquired_lot)
125129
amount -= result.amount
126-
print(i, want[i], amount, result)
127-
self.assertEqual(result.amount, RP2Decimal(want[i].amount))
128-
self.assertEqual(result.acquired_lot.row, want[i].row)
130+
self.assertEqual(result.amount, RP2Decimal(test.want[i].amount))
131+
self.assertEqual(result.acquired_lot.row, test.want[i].row)
129132
i += 1
130133

131-
def test_lot_candidates_with_fifo(self) -> None:
132-
lot_selection_method = AccountingMethodFIFO()
133-
134-
# Simple test.
135-
self._test_fixed_lot_candidates(
136-
lot_selection_method=lot_selection_method,
137-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(10, 10), InTransactionSpec(11, 20), InTransactionSpec(12, 30)]),
138-
amounts_to_match=[6, 4, 2, 18, 3],
139-
want=[SeekLotResult(10, 1), SeekLotResult(4, 1), SeekLotResult(20, 2), SeekLotResult(18, 2), SeekLotResult(30, 3)],
140-
)
141-
142-
# Test with requested amount greater than acquired lot.
143-
self._test_fixed_lot_candidates(
144-
lot_selection_method=lot_selection_method,
145-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(10, 10), InTransactionSpec(11, 20), InTransactionSpec(12, 30)]),
146-
amounts_to_match=[15, 10, 5],
147-
want=[SeekLotResult(10, 1), SeekLotResult(20, 2), SeekLotResult(15, 2), SeekLotResult(5, 2)],
148-
)
149-
150-
# Test with dynamic lot candidates
151-
self._test_dynamic_lot_candidates(
152-
lot_selection_method=lot_selection_method,
153-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(10, 10), InTransactionSpec(11, 20), InTransactionSpec(12, 30)]),
154-
amounts_to_match=[6, 4, 2, 18, 3],
155-
want=[SeekLotResult(10, 1), SeekLotResult(4, 1), SeekLotResult(20, 2), SeekLotResult(18, 2), SeekLotResult(30, 3)],
156-
)
157-
158-
def test_lot_candidates_with_lifo(self) -> None:
159-
lot_selection_method = AccountingMethodLIFO()
160-
161-
# Simple test.
162-
self._test_fixed_lot_candidates(
163-
lot_selection_method=lot_selection_method,
164-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(10, 10), InTransactionSpec(11, 20), InTransactionSpec(12, 30)]),
165-
amounts_to_match=[7, 23, 19, 1, 9],
166-
want=[SeekLotResult(30, 3), SeekLotResult(23, 3), SeekLotResult(20, 2), SeekLotResult(1, 2), SeekLotResult(10, 1)],
167-
)
168-
169-
# Test with requested amount greater than acquired lot.
170-
self._test_fixed_lot_candidates(
171-
lot_selection_method=lot_selection_method,
172-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(10, 10), InTransactionSpec(11, 20), InTransactionSpec(12, 30)]),
173-
amounts_to_match=[55, 5],
174-
want=[SeekLotResult(30, 3), SeekLotResult(20, 2), SeekLotResult(10, 1), SeekLotResult(5, 1)],
175-
)
176-
177-
# Test with dynamic lot candidates
178-
self._test_dynamic_lot_candidates(
179-
lot_selection_method=lot_selection_method,
180-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(10, 10), InTransactionSpec(11, 20), InTransactionSpec(12, 30)]),
181-
amounts_to_match=[4, 15, 27, 14],
182-
want=[SeekLotResult(10, 1), SeekLotResult(20, 2), SeekLotResult(30, 3), SeekLotResult(3, 3), SeekLotResult(5, 2), SeekLotResult(6, 1)],
183-
)
184-
185-
def test_fixed_lot_candidates_with_hifo(self) -> None:
186-
lot_selection_method = AccountingMethodHIFO()
187-
188-
# Simple test.
189-
self._test_fixed_lot_candidates(
190-
lot_selection_method=lot_selection_method,
191-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(10, 10), InTransactionSpec(12, 20), InTransactionSpec(11, 30)]),
192-
amounts_to_match=[15, 5, 20, 10, 7],
193-
want=[SeekLotResult(20, 2), SeekLotResult(5, 2), SeekLotResult(30, 3), SeekLotResult(10, 3), SeekLotResult(10, 1)],
194-
)
195-
196-
# Test with requested amount greater than acquired lot.
197-
self._test_fixed_lot_candidates(
198-
lot_selection_method=lot_selection_method,
199-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(10, 10), InTransactionSpec(12, 20), InTransactionSpec(11, 30)]),
200-
amounts_to_match=[15, 5, 35, 5],
201-
want=[SeekLotResult(20, 2), SeekLotResult(5, 2), SeekLotResult(30, 3), SeekLotResult(10, 1), SeekLotResult(5, 1)],
202-
)
203-
204-
# Test with dynamic lot candidates
205-
self._test_dynamic_lot_candidates(
206-
lot_selection_method=lot_selection_method,
207-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(10, 10), InTransactionSpec(12, 20), InTransactionSpec(11, 30)]),
208-
amounts_to_match=[4, 16, 40],
209-
want=[SeekLotResult(10, 1), SeekLotResult(20, 2), SeekLotResult(4, 2), SeekLotResult(30, 3), SeekLotResult(6, 1)],
210-
)
211-
212-
def test_fixed_lot_candidates_with_lofo(self) -> None:
213-
lot_selection_method = AccountingMethodLOFO()
214-
215-
# Simple test.
216-
self._test_fixed_lot_candidates(
217-
lot_selection_method=lot_selection_method,
218-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(12, 10), InTransactionSpec(10, 20), InTransactionSpec(11, 30)]),
219-
amounts_to_match=[15, 5, 20, 10, 7],
220-
want=[SeekLotResult(20, 2), SeekLotResult(5, 2), SeekLotResult(30, 3), SeekLotResult(10, 3), SeekLotResult(10, 1)],
221-
)
222-
223-
# Test with requested amount greater than acquired lot.
224-
self._test_fixed_lot_candidates(
225-
lot_selection_method=lot_selection_method,
226-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(12, 10), InTransactionSpec(10, 20), InTransactionSpec(11, 30)]),
227-
amounts_to_match=[15, 5, 35, 5],
228-
want=[SeekLotResult(20, 2), SeekLotResult(5, 2), SeekLotResult(30, 3), SeekLotResult(10, 1), SeekLotResult(5, 1)],
229-
)
230-
231-
# Test with dynamic lot candidates
232-
self._test_dynamic_lot_candidates(
233-
lot_selection_method=lot_selection_method,
234-
in_transactions=self._initialize_acquired_lots([InTransactionSpec(12, 10), InTransactionSpec(10, 20), InTransactionSpec(11, 30)]),
235-
amounts_to_match=[4, 16, 40],
236-
want=[SeekLotResult(10, 1), SeekLotResult(20, 2), SeekLotResult(4, 2), SeekLotResult(30, 3), SeekLotResult(6, 1)],
237-
)
134+
def test_with_fixed_lot_candidates(self) -> None:
135+
# Go-style, table-based tests. The want field contains the expected results.
136+
tests: List[_Test] = [
137+
_Test(
138+
description="Simple test (FIFO)",
139+
lot_selection_method=AccountingMethodFIFO(),
140+
in_transactions=[InTransactionDescriptor(10, 10), InTransactionDescriptor(11, 20), InTransactionDescriptor(12, 30)],
141+
amounts_to_match=[6, 4, 2, 18, 3],
142+
want=[SeekLotResult(10, 1), SeekLotResult(4, 1), SeekLotResult(20, 2), SeekLotResult(18, 2), SeekLotResult(30, 3)],
143+
),
144+
_Test(
145+
description="Requested amount greater than acquired lot (FIFO)",
146+
lot_selection_method=AccountingMethodFIFO(),
147+
in_transactions=[InTransactionDescriptor(10, 10), InTransactionDescriptor(11, 20), InTransactionDescriptor(12, 30)],
148+
amounts_to_match=[15, 10, 5],
149+
want=[SeekLotResult(10, 1), SeekLotResult(20, 2), SeekLotResult(15, 2), SeekLotResult(5, 2)],
150+
),
151+
_Test(
152+
description="Simple test (LIFO)",
153+
lot_selection_method=AccountingMethodLIFO(),
154+
in_transactions=[InTransactionDescriptor(10, 10), InTransactionDescriptor(11, 20), InTransactionDescriptor(12, 30)],
155+
amounts_to_match=[7, 23, 19, 1, 9],
156+
want=[SeekLotResult(30, 3), SeekLotResult(23, 3), SeekLotResult(20, 2), SeekLotResult(1, 2), SeekLotResult(10, 1)],
157+
),
158+
_Test(
159+
description="Requested amount greater than acquired lot (LIFO)",
160+
lot_selection_method=AccountingMethodLIFO(),
161+
in_transactions=[InTransactionDescriptor(10, 10), InTransactionDescriptor(11, 20), InTransactionDescriptor(12, 30)],
162+
amounts_to_match=[55, 5],
163+
want=[SeekLotResult(30, 3), SeekLotResult(20, 2), SeekLotResult(10, 1), SeekLotResult(5, 1)],
164+
),
165+
_Test(
166+
description="Simple test (HIFO)",
167+
lot_selection_method=AccountingMethodHIFO(),
168+
in_transactions=[InTransactionDescriptor(10, 10), InTransactionDescriptor(12, 20), InTransactionDescriptor(11, 30)],
169+
amounts_to_match=[15, 5, 20, 10, 7],
170+
want=[SeekLotResult(20, 2), SeekLotResult(5, 2), SeekLotResult(30, 3), SeekLotResult(10, 3), SeekLotResult(10, 1)],
171+
),
172+
_Test(
173+
description="Requested amount greater than acquired lot (HIFO)",
174+
lot_selection_method=AccountingMethodHIFO(),
175+
in_transactions=[InTransactionDescriptor(10, 10), InTransactionDescriptor(12, 20), InTransactionDescriptor(11, 30)],
176+
amounts_to_match=[15, 5, 35, 5],
177+
want=[SeekLotResult(20, 2), SeekLotResult(5, 2), SeekLotResult(30, 3), SeekLotResult(10, 1), SeekLotResult(5, 1)],
178+
),
179+
_Test(
180+
description="Simple test (LOFO)",
181+
lot_selection_method=AccountingMethodLOFO(),
182+
in_transactions=[InTransactionDescriptor(12, 10), InTransactionDescriptor(10, 20), InTransactionDescriptor(11, 30)],
183+
amounts_to_match=[15, 5, 20, 10, 7],
184+
want=[SeekLotResult(20, 2), SeekLotResult(5, 2), SeekLotResult(30, 3), SeekLotResult(10, 3), SeekLotResult(10, 1)],
185+
),
186+
_Test(
187+
description="Requested amount greater than acquired lot (LOFO)",
188+
lot_selection_method=AccountingMethodLOFO(),
189+
in_transactions=[InTransactionDescriptor(12, 10), InTransactionDescriptor(10, 20), InTransactionDescriptor(11, 30)],
190+
amounts_to_match=[15, 5, 35, 5],
191+
want=[SeekLotResult(20, 2), SeekLotResult(5, 2), SeekLotResult(30, 3), SeekLotResult(10, 1), SeekLotResult(5, 1)],
192+
)
193+
194+
]
195+
for test in tests:
196+
with self.subTest(name=f"{test.description}"):
197+
self._run_test_fixed_lot_candidates(lot_selection_method=test.lot_selection_method, test=test)
198+
199+
200+
def test_with_dynamic_lot_candidates(self) -> None:
201+
# Go-style, table-based tests. The want field contains the expected results.
202+
tests: List[_Test] = [
203+
_Test(
204+
description="Dynamic test (FIFO)",
205+
lot_selection_method=AccountingMethodFIFO(),
206+
in_transactions=[InTransactionDescriptor(10, 10), InTransactionDescriptor(11, 20), InTransactionDescriptor(12, 30)],
207+
amounts_to_match=[6, 4, 2, 18, 3],
208+
want=[SeekLotResult(10, 1), SeekLotResult(4, 1), SeekLotResult(20, 2), SeekLotResult(18, 2), SeekLotResult(30, 3)],
209+
),
210+
_Test(
211+
description="Dynamic test (LIFO)",
212+
lot_selection_method=AccountingMethodLIFO(),
213+
in_transactions=[InTransactionDescriptor(10, 10), InTransactionDescriptor(11, 20), InTransactionDescriptor(12, 30)],
214+
amounts_to_match=[4, 15, 27, 14],
215+
want=[SeekLotResult(10, 1), SeekLotResult(20, 2), SeekLotResult(30, 3), SeekLotResult(3, 3), SeekLotResult(5, 2), SeekLotResult(6, 1)],
216+
),
217+
_Test(
218+
description="Dynamic test (HIFO)",
219+
lot_selection_method=AccountingMethodHIFO(),
220+
in_transactions=[InTransactionDescriptor(10, 10), InTransactionDescriptor(12, 20), InTransactionDescriptor(11, 30)],
221+
amounts_to_match=[4, 16, 40],
222+
want=[SeekLotResult(10, 1), SeekLotResult(20, 2), SeekLotResult(4, 2), SeekLotResult(30, 3), SeekLotResult(6, 1)],
223+
),
224+
_Test(
225+
description="Dynamic test (LOFO)",
226+
lot_selection_method=AccountingMethodLOFO(),
227+
in_transactions=[InTransactionDescriptor(12, 10), InTransactionDescriptor(10, 20), InTransactionDescriptor(11, 30)],
228+
amounts_to_match=[4, 16, 40],
229+
want=[SeekLotResult(10, 1), SeekLotResult(20, 2), SeekLotResult(4, 2), SeekLotResult(30, 3), SeekLotResult(6, 1)],
230+
)
231+
]
232+
for test in tests:
233+
with self.subTest(name=f"{test.description}"):
234+
self._run_test_dynamic_lot_candidates(lot_selection_method=test.lot_selection_method, test=test)
238235

239236

240237
if __name__ == "__main__":

0 commit comments

Comments
 (0)