diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 811b7d1f8..508bb41a6 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -133,7 +133,7 @@ class Order(_DECL_BASE): order_update_date = Column(DateTime, nullable=True) 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): 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 # Margin trading properties - leverage: Optional[float] = 1.0 - borrowed: float = 0.0 borrowed_currency: str = None collateral_currency: str = None interest_rate: float = 0.0 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 - 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 def open_date_utc(self): return self.open_date.replace(tzinfo=timezone.utc) @@ -306,6 +308,20 @@ class LocalTrade(): def close_date_utc(self): 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]: return { 'trade_id': self.id, @@ -448,7 +464,7 @@ class LocalTrade(): Determines if the trade is an opening (long buy or short sell) trade :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) 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 :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) def update(self, order: Dict) -> None: @@ -466,9 +482,14 @@ class LocalTrade(): :return: None """ 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 if 'is_short' in order and order['side'] == 'sell': self.is_short = order['is_short'] + # Ignore open and cancelled orders if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None: return @@ -477,8 +498,17 @@ class LocalTrade(): if order_type in ('market', 'limit') and self.is_opening_trade(order['side']): # 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.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() if self.is_open: payment = "SELL" if self.is_short else "BUY" diff --git a/tests/conftest.py b/tests/conftest.py index a78dd2bc2..362fb8b33 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2132,6 +2132,40 @@ def limit_exit_short_order(limit_exit_short_order_open): order['status'] = 'closed' 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 def interest_rate(): diff --git a/tests/test_persistence.py b/tests/test_persistence.py index f4494e967..829e3f6e7 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -659,12 +659,13 @@ def test_migrate_new(mocker, default_conf, fee, caplog): order_date DATETIME, order_filled_date DATETIME, order_update_date DATETIME, + leverage FLOAT, PRIMARY KEY (id), CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_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(""" 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, @@ -912,6 +913,14 @@ def test_to_json(default_conf, fee): 'strategy': None, 'timeframe': None, '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 @@ -977,6 +986,14 @@ def test_to_json(default_conf, fee): 'strategy': None, 'timeframe': None, '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_total_closed_profit', 'total_open_trades_stakes', - 'get_sold_trades_without_assigned_fees', + 'get_closed_trades_without_assigned_fees', 'get_open_trades_without_assigned_fees', 'get_open_order_trades', 'get_trades', diff --git a/tests/test_persistence_margin.py b/tests/test_persistence_margin.py index d31bde590..bbca52e50 100644 --- a/tests/test_persistence_margin.py +++ b/tests/test_persistence_margin.py @@ -58,19 +58,22 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int fee_open=fee.return_value, fee_close=fee.return_value, interest_rate=interest_rate.return_value, - borrowed=90.99181073, - exchange='binance', - is_short=True + # borrowed=90.99181073, + exchange='binance' ) #assert trade.open_order_id is None assert trade.close_profit 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.update(limit_short_order) #assert trade.open_order_id is None assert trade.open_rate == 0.00001173 assert trade.close_profit 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, " r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001173, open_since=.*\).", caplog) @@ -89,7 +92,18 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int # @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( # id=1, # pair='ETH/BTC', @@ -99,11 +113,15 @@ def test_update_with_binance(limit_short_order, limit_exit_short_order, fee, int # is_open=True, # fee_open=fee.return_value, # fee_close=fee.return_value, -# open_date=arrow.utcnow().datetime, -# exchange='binance', +# open_date=ten_minutes_ago, +# exchange='kraken', +# interest_rate=interest_rate.return_value # ) # trade.open_order_id = 'something' # 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_rate == 0.00004099 # 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, " # r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).", # caplog) -# # TODO-mg: market short -# # TODO-mg: market leveraged long # @pytest.mark.usefixtures("init_persistence")