From 3d401a3e0037a27eae8eaef8a49f4c7b3ba0e245 Mon Sep 17 00:00:00 2001 From: Adam Charnock Date: Mon, 27 May 2024 14:38:07 +0100 Subject: [PATCH] currency_exchange() can now charge fees in the desination currency, #56 --- hordak/tests/utilities/test_currency.py | 23 ++++++++++++++++++- hordak/utilities/currency.py | 30 +++++++++++++++++-------- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/hordak/tests/utilities/test_currency.py b/hordak/tests/utilities/test_currency.py index 35726a86..79c3107c 100644 --- a/hordak/tests/utilities/test_currency.py +++ b/hordak/tests/utilities/test_currency.py @@ -365,7 +365,7 @@ def test_peter_selinger_tutorial_table_4_4(self): self.assertEqual(cad_cash.balance(), Balance(135, "CAD")) self.assertEqual(food.balance(), Balance(72, "CAD")) - def test_fees(self): + def test_fees_source_currency(self): cad_cash = self.account(type=Account.TYPES.asset, currencies=["CAD"]) usd_cash = self.account(type=Account.TYPES.asset, currencies=["USD"]) initial_capital = self.account(type=Account.TYPES.equity, currencies=["CAD"]) @@ -385,3 +385,24 @@ def test_fees(self): self.assertEqual(cad_cash.balance(), Balance(80, "CAD")) self.assertEqual(usd_cash.balance(), Balance(100, "USD")) self.assertEqual(banking_fees.balance(), Balance(1.50, "CAD")) + + def test_fees_destination_currency(self): + cad_cash = self.account(type=Account.TYPES.asset, currencies=["CAD"]) + usd_cash = self.account(type=Account.TYPES.asset, currencies=["USD"]) + initial_capital = self.account(type=Account.TYPES.equity, currencies=["CAD"]) + trading = self.account(type=Account.TYPES.trading, currencies=["CAD", "USD"]) + banking_fees = self.account(type=Account.TYPES.expense, currencies=["USD"]) + + initial_capital.transfer_to(cad_cash, Money(200, "CAD")) + currency_exchange( + source=cad_cash, + source_amount=Money(120, "CAD"), + destination=usd_cash, + destination_amount=Money(100, "USD"), + trading_account=trading, + fee_destination=banking_fees, + fee_amount=Money(1.50, "USD"), + ) + self.assertEqual(cad_cash.balance(), Balance(80, "CAD")) + self.assertEqual(usd_cash.balance(), Balance(100, "USD")) + self.assertEqual(banking_fees.balance(), Balance(1.50, "USD")) diff --git a/hordak/utilities/currency.py b/hordak/utilities/currency.py index aa085564..40ae7643 100644 --- a/hordak/utilities/currency.py +++ b/hordak/utilities/currency.py @@ -103,13 +103,12 @@ def currency_exchange( of currency going in and out of the transaction. You can also record any exchange fees by syphoning off funds to - ``fee_account`` of amount ``fee_amount``. Note - that the free currency must be the same as the source currency. + ``fee_account`` of amount ``fee_amount``. Examples: For example, imagine our Canadian bank has obligingly transferred 120 CAD into our US bank account. - We sent CAD 120, and received USD 100. We were also changed 1.50 CAD in fees. + We sent CAD 120, and received USD 100. We were also charged 1.50 CAD in fees. We can represent this exchange in Hordak as follows:: @@ -182,11 +181,13 @@ def currency_exchange( fee_amount = Money(0, source_amount.currency) else: # If we do have fees then make sure the fee currency matches the source currency - if fee_amount.currency != source_amount.currency: + if fee_amount.currency not in ( + source_amount.currency, + destination_amount.currency, + ): raise InvalidFeeCurrency( - "Fee amount currency ({}) must match source amount currency ({})".format( - fee_amount.currency, source_amount.currency - ) + f"Fee amount currency ({fee_amount.currency}) must match source " + f"({source_amount.currency}) or destination ({destination_amount.currency}) amount currency " ) # Checks over and done now. Let's create the transaction @@ -201,6 +202,9 @@ def currency_exchange( ), ) + # Are we charging the fee at the source or destination? + charge_fee_at_source = source_amount.currency == fee_amount.currency + # Source currency into trading account Leg.objects.create( transaction=transaction, account=source, amount=source_amount @@ -208,7 +212,9 @@ def currency_exchange( Leg.objects.create( transaction=transaction, account=trading_account, - amount=-(source_amount - fee_amount), + amount=-( + (source_amount - fee_amount) if charge_fee_at_source else source_amount + ), ) # Any fees @@ -222,7 +228,13 @@ def currency_exchange( # Destination currency out of trading account Leg.objects.create( - transaction=transaction, account=trading_account, amount=destination_amount + transaction=transaction, + account=trading_account, + amount=( + destination_amount + if charge_fee_at_source + else destination_amount + fee_amount + ), ) Leg.objects.create( transaction=transaction, account=destination, amount=-destination_amount