@@ -36,8 +36,6 @@ class MarginLoanConditions:
3636 interest_period : datetime .timedelta
3737 #: The minimum interest to charge.
3838 min_interest : Decimal
39- # Minimum threshold for the value of the collateral relative to the loan amount + outstanding interests.
40- margin_requirement : Decimal
4139
4240
4341class MarginLoan (base .Loan ):
@@ -73,10 +71,17 @@ class MarginLoans(base.LendingStrategy):
7371 This strategy will use the accounts assets as collateral for the loans.
7472
7573 :param quote_symbol: The symbol to use to normalize balances.
74+ :param margin_requirement: Minimum threshold for the value of the collateral relative to the total position.
7675 :param default_conditions: The default margin loan conditions.
7776 """
78- def __init__ (self , quote_symbol : str , default_conditions : Optional [MarginLoanConditions ] = None ):
77+ def __init__ (
78+ self , quote_symbol : str , margin_requirement : Decimal ,
79+ default_conditions : Optional [MarginLoanConditions ] = None
80+ ):
81+ assert margin_requirement > 0 , "Margin requirement must be greater than zero"
82+
7983 self ._quote_symbol = quote_symbol
84+ self ._margin_requirement = margin_requirement
8085 self ._conditions : Dict [str , MarginLoanConditions ] = {}
8186 self ._default_conditions = default_conditions
8287 self ._loan_mgr : Optional [loan_mgr .LoanManager ] = None
@@ -118,56 +123,47 @@ def margin_level(self) -> Decimal:
118123 """
119124 assert self ._exchange_ctx , "Not yet connected with the exchange"
120125 acc_balances = self ._exchange_ctx .account_balances
121- return self ._calculate_margin_level (
126+ return self .calculate_margin_level (
122127 acc_balances .balances , acc_balances .holds , acc_balances .borrowed
123128 )
124129
125- def _calculate_margin_level (
130+ def calculate_margin_level (
126131 self , updated_balances : ValueMapDict , updated_holds : ValueMapDict , updated_borrowed : ValueMapDict
127132 ) -> Decimal :
128133 assert self ._exchange_ctx and self ._loan_mgr , "Not yet connected with the exchange"
129134
130- # Calculate used margin.
131- margin_requirements = ValueMap (
132- {symbol : self .get_conditions (symbol ).margin_requirement for symbol in updated_borrowed }
133- )
134- used_margin_by_symbol = margin_requirements * updated_borrowed
135- used_margin = self ._exchange_ctx .prices .convert_value_map (used_margin_by_symbol , self ._quote_symbol )
136- if used_margin == Decimal (0 ):
137- return Decimal (0 )
135+ # If we haven't borrowed anything yet, the margin level is infinite.
136+ if all (v == Decimal (0 ) for v in updated_borrowed .values ()):
137+ # used_margin = 0, margin_level = Infinity
138+ return Decimal ("Infinity" )
138139
139140 # Calculate outstanding interest.
140- interest_by_symbol = ValueMap ()
141+ outstanding_interest = ValueMap ()
141142 for loan in self ._loan_mgr .get_loans (is_open = True ):
142- interest_by_symbol += loan .outstanding_interest
143- interest = self ._exchange_ctx .prices .convert_value_map (interest_by_symbol , self ._quote_symbol )
144-
145- # Calculate equity.
146- equity = Decimal (0 )
147- for symbol , balance in updated_balances .items ():
148- borrowed = updated_borrowed .get (symbol , Decimal (0 ))
149- net = balance - borrowed
150- if net <= Decimal (0 ):
151- continue
152-
153- if symbol != self ._quote_symbol :
154- net = self ._exchange_ctx .prices .convert (net , symbol , self ._quote_symbol )
143+ outstanding_interest += loan .outstanding_interest
144+ outstanding_interest = self ._exchange_ctx .prices .convert_value_map (outstanding_interest , self ._quote_symbol )
155145
156- equity += net
157-
158- return equity / (used_margin + interest ) * Decimal (100 )
159-
160- def _check_margin_level (
161- self , updated_balances : ValueMapDict , updated_holds : ValueMapDict , updated_borrowed : ValueMapDict
162- ):
163- margin_level = self ._calculate_margin_level (updated_balances , updated_holds , updated_borrowed )
164- if margin_level > Decimal (0 ) and margin_level < Decimal (100 ):
165- raise errors .NotEnoughBalance (f"Margin level too low { margin_level } " )
146+ # Calculate margin level.
147+ borrowed = self ._exchange_ctx .prices .convert_value_map (updated_borrowed , self ._quote_symbol )
148+ total_position_size = self ._exchange_ctx .prices .convert_value_map (updated_balances , self ._quote_symbol )
149+ total_position_size -= outstanding_interest
150+ equity = total_position_size - borrowed
151+ used_margin = Decimal (sum (total_position_size .values ())) * self ._margin_requirement
152+ margin_level = Decimal (sum (equity .values ())) / used_margin * Decimal (100 )
153+ return margin_level
166154
167155
168156class CheckMarginLevel (account_balances .UpdateRule ):
169157 def __init__ (self , margin_loans : MarginLoans ):
170158 self ._margin_loans = margin_loans
159+ self ._threshold = Decimal (100 )
171160
172- def check (self , updated_balances : ValueMapDict , updated_holds : ValueMapDict , updated_borrowed : ValueMapDict ):
173- self ._margin_loans ._check_margin_level (updated_balances , updated_holds , updated_borrowed )
161+ def check (
162+ self , updated_balances : ValueMap , updated_holds : ValueMap , updated_borrowed : ValueMap ,
163+ delta_balances : ValueMap , delta_holds : ValueMap , delta_borrowed : ValueMap
164+ ):
165+ # If we're increasing any borrowed amount we need to check the margin level.
166+ if any (v > 0 for v in delta_borrowed .values ()):
167+ margin_level = self ._margin_loans .calculate_margin_level (updated_balances , updated_holds , updated_borrowed )
168+ if margin_level < self ._threshold :
169+ raise errors .NotEnoughBalance (f"Margin level too low { margin_level } " )
0 commit comments