Set leverage and borowed to computed properties

This commit is contained in:
Sam Germain 2021-06-26 20:36:19 -06:00
parent c24ec89dc4
commit d07fe1586c
4 changed files with 138 additions and 41 deletions

View File

@ -133,7 +133,7 @@ class Order(_DECL_BASE):
order_update_date = Column(DateTime, nullable=True) order_update_date = Column(DateTime, nullable=True)
leverage = Column(Float, nullable=True, default=1.0) leverage = Column(Float, nullable=True, default=1.0)
is_short = Column(Boolean, nullable=False, default=False) is_short = Column(Boolean, nullable=True, default=False)
def __repr__(self): def __repr__(self):
return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, ' return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, '
@ -264,40 +264,42 @@ class LocalTrade():
timeframe: Optional[int] = None timeframe: Optional[int] = None
# Margin trading properties # Margin trading properties
leverage: Optional[float] = 1.0
borrowed: float = 0.0
borrowed_currency: str = None borrowed_currency: str = None
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 |
__borrowed: float = 0.0 # * You probably want to use self.borrowed instead |
__is_short: bool = False # * You probably want to use self.is_short instead V
@property
def leverage(self) -> float:
return self.__leverage or 1.0
@property
def borrowed(self) -> float:
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):
self.__is_short = val
@leverage.setter
def leverage(self, lev):
self.__leverage = lev
self.__borrowed = self.amount * (lev-1)
self.amount = self.amount * lev
@borrowed.setter
def borrowed(self, bor):
self.__leverage = self.amount / (self.amount - self.borrowed)
self.__borrowed = bor
# End of margin trading properties # End of margin trading properties
def __init__(self, **kwargs):
lev = kwargs.get('leverage')
bor = kwargs.get('borrowed')
amount = kwargs.get('amount')
if lev and bor:
# TODO: should I raise an error?
raise OperationalException('Cannot pass both borrowed and leverage to Trade')
elif lev:
self.amount = amount * lev
self.borrowed = amount * (lev-1)
elif bor:
self.lev = (bor + amount)/amount
for key in kwargs:
setattr(self, key, kwargs[key])
if not self.is_short:
self.is_short = False
self.recalc_open_trade_value()
def __repr__(self):
open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed'
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
@property @property
def open_date_utc(self): def open_date_utc(self):
return self.open_date.replace(tzinfo=timezone.utc) return self.open_date.replace(tzinfo=timezone.utc)
@ -306,6 +308,20 @@ class LocalTrade():
def close_date_utc(self): def close_date_utc(self):
return self.close_date.replace(tzinfo=timezone.utc) return self.close_date.replace(tzinfo=timezone.utc)
def __init__(self, **kwargs):
if kwargs.get('leverage') and kwargs.get('borrowed'):
# TODO-mg: should I raise an error?
raise OperationalException('Cannot pass both borrowed and leverage to Trade')
for key in kwargs:
setattr(self, key, kwargs[key])
self.recalc_open_trade_value()
def __repr__(self):
open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed'
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
def to_json(self) -> Dict[str, Any]: def to_json(self) -> Dict[str, Any]:
return { return {
'trade_id': self.id, 'trade_id': self.id,
@ -448,7 +464,7 @@ 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 is_short = self.is_short or False
return (side == 'buy' and not is_short) or (side == 'sell' and 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:
@ -456,7 +472,7 @@ class LocalTrade():
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 is_short = self.is_short or False
return (side == 'sell' and not is_short) or (side == 'buy' and 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:
@ -466,9 +482,14 @@ class LocalTrade():
:return: None :return: None
""" """
order_type = order['type'] order_type = order['type']
# if ('leverage' in order and 'borrowed' in order):
# raise OperationalException('Cannot update a trade with both borrowed and leverage')
# TODO: I don't like this, but it might be the only way # 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':
self.is_short = order['is_short'] self.is_short = order['is_short']
# Ignore open and cancelled orders # Ignore open and cancelled orders
if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None: if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None:
return return
@ -477,8 +498,17 @@ 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:
self.borrowed = order['borrowed']
elif 'leverage' in order:
self.leverage = order['leverage']
self.recalc_open_trade_value() self.recalc_open_trade_value()
if self.is_open: if self.is_open:
payment = "SELL" if self.is_short else "BUY" payment = "SELL" if self.is_short else "BUY"

View File

@ -2132,6 +2132,40 @@ def limit_exit_short_order(limit_exit_short_order_open):
order['status'] = 'closed' order['status'] = 'closed'
return order return order
@pytest.fixture(scope='function')
def market_short_order():
return {
'id': 'mocked_market_buy',
'type': 'market',
'side': 'buy',
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004173,
'amount': 91.99181073,
'filled': 91.99181073,
'remaining': 0.0,
'status': 'closed',
'is_short': True,
'leverage': 3
}
@pytest.fixture
def market_exit_short_order():
return {
'id': 'mocked_limit_sell',
'type': 'market',
'side': 'sell',
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004099,
'amount': 91.99181073,
'filled': 91.99181073,
'remaining': 0.0,
'status': 'closed'
}
@pytest.fixture @pytest.fixture
def interest_rate(): def interest_rate():

View File

@ -659,12 +659,13 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
order_date DATETIME, order_date DATETIME,
order_filled_date DATETIME, order_filled_date DATETIME,
order_update_date DATETIME, order_update_date DATETIME,
leverage FLOAT,
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id), CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id),
FOREIGN KEY(ft_trade_id) REFERENCES trades (id) FOREIGN KEY(ft_trade_id) REFERENCES trades (id)
) )
""")) """))
# TODO-mg @xmatthias: Had to add field leverage to this table, check that this is correct
connection.execute(text(""" connection.execute(text("""
insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
symbol, order_type, side, price, amount, filled, remaining, cost, order_date, symbol, order_type, side, price, amount, filled, remaining, cost, order_date,
@ -912,6 +913,14 @@ def test_to_json(default_conf, fee):
'strategy': None, 'strategy': None,
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': None,
'borrowed': None,
'borrowed_currency': None,
'collateral_currency': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
} }
# Simulate dry_run entries # Simulate dry_run entries
@ -977,6 +986,14 @@ def test_to_json(default_conf, fee):
'strategy': None, 'strategy': None,
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': None,
'borrowed': None,
'borrowed_currency': None,
'collateral_currency': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
} }
@ -1315,7 +1332,7 @@ def test_Trade_object_idem():
'get_overall_performance', 'get_overall_performance',
'get_total_closed_profit', 'get_total_closed_profit',
'total_open_trades_stakes', 'total_open_trades_stakes',
'get_sold_trades_without_assigned_fees', 'get_closed_trades_without_assigned_fees',
'get_open_trades_without_assigned_fees', 'get_open_trades_without_assigned_fees',
'get_open_order_trades', 'get_open_order_trades',
'get_trades', 'get_trades',

View File

@ -58,19 +58,22 @@ 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,
interest_rate=interest_rate.return_value, interest_rate=interest_rate.return_value,
borrowed=90.99181073, # borrowed=90.99181073,
exchange='binance', exchange='binance'
is_short=True
) )
#assert trade.open_order_id is None #assert trade.open_order_id is None
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 trade.borrowed is None
assert trade.is_short is None
#trade.open_order_id = 'something' #trade.open_order_id = 'something'
trade.update(limit_short_order) trade.update(limit_short_order)
#assert trade.open_order_id is None #assert trade.open_order_id is None
assert trade.open_rate == 0.00001173 assert trade.open_rate == 0.00001173
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 trade.borrowed == 90.99181073
assert trade.is_short is True
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, " assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001173, open_since=.*\).", r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001173, open_since=.*\).",
caplog) caplog)
@ -89,7 +92,18 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int
# @pytest.mark.usefixtures("init_persistence") # @pytest.mark.usefixtures("init_persistence")
# def test_update_market_order(market_buy_order, market_sell_order, fee, caplog): # def test_update_market_order(
# market_buy_order,
# market_sell_order,
# fee,
# interest_rate,
# ten_minutes_ago,
# caplog
# ):
# """Test Kraken and leverage arguments as well as update market order
# """
# trade = Trade( # trade = Trade(
# id=1, # id=1,
# pair='ETH/BTC', # pair='ETH/BTC',
@ -99,11 +113,15 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int
# is_open=True, # is_open=True,
# fee_open=fee.return_value, # fee_open=fee.return_value,
# fee_close=fee.return_value, # fee_close=fee.return_value,
# open_date=arrow.utcnow().datetime, # open_date=ten_minutes_ago,
# exchange='binance', # 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.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.00004099
# assert trade.close_profit is None # assert trade.close_profit is None
@ -122,8 +140,6 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int
# 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, "
# r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).", # r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).",
# caplog) # caplog)
# # TODO-mg: market short
# # TODO-mg: market leveraged long
# @pytest.mark.usefixtures("init_persistence") # @pytest.mark.usefixtures("init_persistence")