added exception checks to LocalTrade.leverage and LocalTrade.borrowed
This commit is contained in:
parent
c68a0f05d8
commit
6f6deae376
10
docs/leverage.md
Normal file
10
docs/leverage.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
An instance of a `Trade`/`LocalTrade` object is given either a value for `leverage` or a value for `borrowed`, but not both, on instantiation/update with a short/long.
|
||||||
|
|
||||||
|
- If given a value for `leverage`, then the `amount` value of the `Trade`/`Local` object is multiplied by the `leverage` value to obtain the new value for `amount`. The borrowed value is also calculated from the `amount` and `leverage` value
|
||||||
|
- If given a value for `borrowed`, then the `leverage` value is calculated from `borrowed` and `amount`
|
||||||
|
|
||||||
|
For shorts, the currency which pays the interest fee for the `borrowed` currency is purchased at the same time of the closing trade (This means that the amount purchased in short closing trades is greater than the amount sold in short opening trades).
|
||||||
|
|
||||||
|
For longs, the currency which pays the interest fee for the `borrowed` will already be owned by the user and does not need to be purchased
|
||||||
|
|
||||||
|
The interest fee is paid following the closing trade, or simultaneously depending on the exchange
|
@ -268,9 +268,9 @@ class LocalTrade():
|
|||||||
collateral_currency: str = None
|
collateral_currency: str = None
|
||||||
interest_rate: float = 0.0
|
interest_rate: float = 0.0
|
||||||
liquidation_price: float = None
|
liquidation_price: float = None
|
||||||
|
is_short: bool = False
|
||||||
__leverage: float = 1.0 # * You probably want to use self.leverage instead |
|
__leverage: float = 1.0 # * You probably want to use self.leverage instead |
|
||||||
__borrowed: float = 0.0 # * You probably want to use self.borrowed instead |
|
__borrowed: float = 0.0 # * You probably want to use self.borrowed instead V
|
||||||
__is_short: bool = False # * You probably want to use self.is_short instead V
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def leverage(self) -> float:
|
def leverage(self) -> float:
|
||||||
@ -280,24 +280,22 @@ class LocalTrade():
|
|||||||
def borrowed(self) -> float:
|
def borrowed(self) -> float:
|
||||||
return self.__borrowed or 0.0
|
return self.__borrowed or 0.0
|
||||||
|
|
||||||
@property
|
|
||||||
def is_short(self) -> bool:
|
|
||||||
return self.__is_short or False
|
|
||||||
|
|
||||||
@is_short.setter
|
|
||||||
def is_short(self, val: bool):
|
|
||||||
self.__is_short = val
|
|
||||||
|
|
||||||
@leverage.setter
|
@leverage.setter
|
||||||
def leverage(self, lev: float):
|
def leverage(self, lev: float):
|
||||||
|
if self.is_short is None or self.amount is None:
|
||||||
|
raise OperationalException(
|
||||||
|
'LocalTrade.amount and LocalTrade.is_short must be assigned before LocalTrade.leverage')
|
||||||
self.__leverage = lev
|
self.__leverage = lev
|
||||||
self.__borrowed = self.amount * (lev-1)
|
self.__borrowed = self.amount * (lev-1)
|
||||||
self.amount = self.amount * lev
|
self.amount = self.amount * lev
|
||||||
|
|
||||||
@borrowed.setter
|
@borrowed.setter
|
||||||
def borrowed(self, bor: float):
|
def borrowed(self, bor: float):
|
||||||
self.__leverage = self.amount / (self.amount - self.borrowed)
|
if not self.amount:
|
||||||
|
raise OperationalException(
|
||||||
|
'LocalTrade.amount must be assigned before LocalTrade.borrowed')
|
||||||
self.__borrowed = bor
|
self.__borrowed = bor
|
||||||
|
self.__leverage = self.amount / (self.amount - self.borrowed)
|
||||||
# End of margin trading properties
|
# End of margin trading properties
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -314,6 +312,8 @@ class LocalTrade():
|
|||||||
raise OperationalException('Cannot pass both borrowed and leverage to Trade')
|
raise OperationalException('Cannot pass both borrowed and leverage to Trade')
|
||||||
for key in kwargs:
|
for key in kwargs:
|
||||||
setattr(self, key, kwargs[key])
|
setattr(self, key, kwargs[key])
|
||||||
|
if not self.is_short:
|
||||||
|
self.is_short = False
|
||||||
self.recalc_open_trade_value()
|
self.recalc_open_trade_value()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -464,16 +464,14 @@ class LocalTrade():
|
|||||||
Determines if the trade is an opening (long buy or short sell) trade
|
Determines if the trade is an opening (long buy or short sell) trade
|
||||||
:param side (string): the side (buy/sell) that order happens on
|
:param side (string): the side (buy/sell) that order happens on
|
||||||
"""
|
"""
|
||||||
is_short = self.is_short or False
|
return (side == 'buy' and not self.is_short) or (side == 'sell' and self.is_short)
|
||||||
return (side == 'buy' and not is_short) or (side == 'sell' and is_short)
|
|
||||||
|
|
||||||
def is_closing_trade(self, side) -> bool:
|
def is_closing_trade(self, side) -> bool:
|
||||||
"""
|
"""
|
||||||
Determines if the trade is an closing (long sell or short buy) trade
|
Determines if the trade is an closing (long sell or short buy) trade
|
||||||
:param side (string): the side (buy/sell) that order happens on
|
:param side (string): the side (buy/sell) that order happens on
|
||||||
"""
|
"""
|
||||||
is_short = self.is_short or False
|
return (side == 'sell' and not self.is_short) or (side == 'buy' and self.is_short)
|
||||||
return (side == 'sell' and not is_short) or (side == 'buy' and is_short)
|
|
||||||
|
|
||||||
def update(self, order: Dict) -> None:
|
def update(self, order: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
@ -483,11 +481,13 @@ class LocalTrade():
|
|||||||
"""
|
"""
|
||||||
order_type = order['type']
|
order_type = order['type']
|
||||||
|
|
||||||
# if ('leverage' in order and 'borrowed' in order):
|
if ('leverage' in order and 'borrowed' in order):
|
||||||
# raise OperationalException('Cannot update a trade with both borrowed and leverage')
|
raise OperationalException(
|
||||||
|
'Pass only one of Leverage or Borrowed to the order in update trade')
|
||||||
|
|
||||||
# TODO: I don't like this, but it might be the only way
|
|
||||||
if 'is_short' in order and order['side'] == 'sell':
|
if 'is_short' in order and order['side'] == 'sell':
|
||||||
|
# Only set's is_short on opening trades, ignores non-shorts
|
||||||
|
# TODO-mg: I don't like this, but it might be the only way
|
||||||
self.is_short = order['is_short']
|
self.is_short = order['is_short']
|
||||||
|
|
||||||
# Ignore open and cancelled orders
|
# Ignore open and cancelled orders
|
||||||
@ -499,9 +499,6 @@ class LocalTrade():
|
|||||||
if order_type in ('market', 'limit') and self.is_opening_trade(order['side']):
|
if order_type in ('market', 'limit') and self.is_opening_trade(order['side']):
|
||||||
# Update open rate and actual amount
|
# Update open rate and actual amount
|
||||||
|
|
||||||
# self.is_short = safe_value_fallback(order, 'is_short', default_value=False)
|
|
||||||
# self.borrowed = safe_value_fallback(order, 'is_short', default_value=False)
|
|
||||||
|
|
||||||
self.open_rate = float(safe_value_fallback(order, 'average', 'price'))
|
self.open_rate = float(safe_value_fallback(order, 'average', 'price'))
|
||||||
self.amount = float(safe_value_fallback(order, 'filled', 'amount'))
|
self.amount = float(safe_value_fallback(order, 'filled', 'amount'))
|
||||||
if 'borrowed' in order:
|
if 'borrowed' in order:
|
||||||
@ -654,7 +651,8 @@ class LocalTrade():
|
|||||||
if self.is_short:
|
if self.is_short:
|
||||||
amount = Decimal(self.amount) + interest
|
amount = Decimal(self.amount) + interest
|
||||||
else:
|
else:
|
||||||
amount = Decimal(self.amount) - interest
|
# The interest does not need to be purchased on longs because the user already owns that currency in your wallet
|
||||||
|
amount = Decimal(self.amount)
|
||||||
|
|
||||||
close_trade = Decimal(amount) * Decimal(rate or self.close_rate) # type: ignore
|
close_trade = Decimal(amount) * Decimal(rate or self.close_rate) # type: ignore
|
||||||
fees = close_trade * Decimal(fee or self.fee_close)
|
fees = close_trade * Decimal(fee or self.fee_close)
|
||||||
@ -662,7 +660,7 @@ class LocalTrade():
|
|||||||
if (self.is_short):
|
if (self.is_short):
|
||||||
return float(close_trade + fees)
|
return float(close_trade + fees)
|
||||||
else:
|
else:
|
||||||
return float(close_trade - fees)
|
return float(close_trade - fees - interest)
|
||||||
|
|
||||||
def calc_profit(self, rate: Optional[float] = None,
|
def calc_profit(self, rate: Optional[float] = None,
|
||||||
fee: Optional[float] = None) -> float:
|
fee: Optional[float] = None) -> float:
|
||||||
|
@ -2132,6 +2132,7 @@ def limit_exit_short_order(limit_exit_short_order_open):
|
|||||||
order['status'] = 'closed'
|
order['status'] = 'closed'
|
||||||
return order
|
return order
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def market_short_order():
|
def market_short_order():
|
||||||
return {
|
return {
|
||||||
@ -2162,11 +2163,12 @@ def market_exit_short_order():
|
|||||||
'amount': 91.99181073,
|
'amount': 91.99181073,
|
||||||
'filled': 91.99181073,
|
'filled': 91.99181073,
|
||||||
'remaining': 0.0,
|
'remaining': 0.0,
|
||||||
'status': 'closed'
|
'status': 'closed',
|
||||||
|
'leverage': 3,
|
||||||
|
'interest_rate': 0.0005
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def interest_rate():
|
def interest_rate():
|
||||||
return MagicMock(return_value=0.0005)
|
return MagicMock(return_value=0.0005)
|
||||||
|
@ -101,8 +101,14 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int
|
|||||||
# caplog
|
# caplog
|
||||||
# ):
|
# ):
|
||||||
# """Test Kraken and leverage arguments as well as update market order
|
# """Test Kraken and leverage arguments as well as update market order
|
||||||
|
# fee: 0.25%
|
||||||
|
# interest_rate: 0.05% per 4 hrs
|
||||||
|
# open_rate: 0.00004173
|
||||||
|
# close_rate: 0.00004099
|
||||||
|
# amount: 91.99181073 * leverage(3) = 275.97543219
|
||||||
|
# borrowed: 183.98362146
|
||||||
|
# time: 10 minutes(rounds to min of 4hrs)
|
||||||
|
# interest
|
||||||
# """
|
# """
|
||||||
# trade = Trade(
|
# trade = Trade(
|
||||||
# id=1,
|
# id=1,
|
||||||
@ -114,27 +120,25 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int
|
|||||||
# fee_open=fee.return_value,
|
# fee_open=fee.return_value,
|
||||||
# fee_close=fee.return_value,
|
# fee_close=fee.return_value,
|
||||||
# open_date=ten_minutes_ago,
|
# open_date=ten_minutes_ago,
|
||||||
# exchange='kraken',
|
# exchange='kraken'
|
||||||
# interest_rate=interest_rate.return_value
|
|
||||||
# )
|
# )
|
||||||
# trade.open_order_id = 'something'
|
# trade.open_order_id = 'something'
|
||||||
# trade.update(market_buy_order)
|
# trade.update(market_buy_order)
|
||||||
# assert trade.leverage is 3
|
# assert trade.leverage is 3
|
||||||
# assert trade.is_short is true
|
# assert trade.is_short is True
|
||||||
# assert trade.leverage is 3
|
|
||||||
# assert trade.open_order_id is None
|
# assert trade.open_order_id is None
|
||||||
# assert trade.open_rate == 0.00004099
|
# assert trade.open_rate == 0.00004173
|
||||||
# assert trade.close_profit is None
|
# assert trade.close_profit is None
|
||||||
# assert trade.close_date is None
|
# assert trade.close_date is None
|
||||||
# assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, "
|
# assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, "
|
||||||
# r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).",
|
# r"pair=ETH/BTC, amount=275.97543219, open_rate=0.00004173, open_since=.*\).",
|
||||||
# caplog)
|
# caplog)
|
||||||
# caplog.clear()
|
# caplog.clear()
|
||||||
# trade.is_open = True
|
# trade.is_open = True
|
||||||
# trade.open_order_id = 'something'
|
# trade.open_order_id = 'something'
|
||||||
# trade.update(market_sell_order)
|
# trade.update(market_sell_order)
|
||||||
# assert trade.open_order_id is None
|
# assert trade.open_order_id is None
|
||||||
# assert trade.close_rate == 0.00004173
|
# assert trade.close_rate == 0.00004099
|
||||||
# assert trade.close_profit == 0.01297561
|
# assert trade.close_profit == 0.01297561
|
||||||
# assert trade.close_date is not None
|
# assert trade.close_date is not None
|
||||||
# assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
|
# assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
|
||||||
|
Loading…
Reference in New Issue
Block a user