From 6eb947ae09416acac11c2c4b79f342d24986993b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 27 Apr 2021 20:25:36 +0200 Subject: [PATCH] Move static Trade functions to right class --- freqtrade/persistence/models.py | 198 +++++++++++++++++--------------- tests/test_persistence.py | 33 +++++- 2 files changed, 132 insertions(+), 99 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index e7fd488c7..afd51366a 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -567,23 +567,6 @@ class LocalTrade(): else: return None - @staticmethod - def get_trades(trade_filter=None) -> Query: - """ - Helper function to query Trades using filters. - :param trade_filter: Optional filter to apply to trades - Can be either a Filter object, or a List of filters - e.g. `(trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True),])` - e.g. `(trade_filter=Trade.id == trade_id)` - :return: unsorted query object - """ - if trade_filter is not None: - if not isinstance(trade_filter, list): - trade_filter = [trade_filter] - return Trade.query.filter(*trade_filter) - else: - return Trade.query - @staticmethod def get_trades_proxy(*, pair: str = None, is_open: bool = None, open_date: datetime = None, close_date: datetime = None, @@ -636,83 +619,7 @@ class LocalTrade(): """ Query trades from persistence layer """ - return Trade.get_trades(Trade.is_open.is_(True)).all() - - @staticmethod - def get_open_order_trades(): - """ - Returns all open trades - """ - return Trade.get_trades(Trade.open_order_id.isnot(None)).all() - - @staticmethod - def get_open_trades_without_assigned_fees(): - """ - Returns all open trades which don't have open fees set correctly - """ - return Trade.get_trades([Trade.fee_open_currency.is_(None), - Trade.orders.any(), - Trade.is_open.is_(True), - ]).all() - - @staticmethod - def get_sold_trades_without_assigned_fees(): - """ - Returns all closed trades which don't have fees set correctly - """ - return Trade.get_trades([Trade.fee_close_currency.is_(None), - Trade.orders.any(), - Trade.is_open.is_(False), - ]).all() - - @staticmethod - def total_open_trades_stakes() -> float: - """ - Calculates total invested amount in open trades - in stake currency - """ - if Trade.use_db: - total_open_stake_amount = Trade.query.with_entities( - func.sum(Trade.stake_amount)).filter(Trade.is_open.is_(True)).scalar() - else: - total_open_stake_amount = sum( - t.stake_amount for t in Trade.get_trades_proxy(is_open=True)) - return total_open_stake_amount or 0 - - @staticmethod - def get_overall_performance() -> List[Dict[str, Any]]: - """ - Returns List of dicts containing all Trades, including profit and trade count - """ - pair_rates = Trade.query.with_entities( - Trade.pair, - func.sum(Trade.close_profit).label('profit_sum'), - func.count(Trade.pair).label('count') - ).filter(Trade.is_open.is_(False))\ - .group_by(Trade.pair) \ - .order_by(desc('profit_sum')) \ - .all() - return [ - { - 'pair': pair, - 'profit': rate, - 'count': count - } - for pair, rate, count in pair_rates - ] - - @staticmethod - def get_best_pair(): - """ - Get best pair with closed trade. - :returns: Tuple containing (pair, profit_sum) - """ - best_pair = Trade.query.with_entities( - Trade.pair, func.sum(Trade.close_profit).label('profit_sum') - ).filter(Trade.is_open.is_(False)) \ - .group_by(Trade.pair) \ - .order_by(desc('profit_sum')).first() - return best_pair + return Trade.get_trades_proxy(is_open=True) @staticmethod def stoploss_reinitialization(desired_stoploss): @@ -810,7 +717,7 @@ class Trade(_DECL_BASE, LocalTrade): open_date: datetime = None, close_date: datetime = None, ) -> List['LocalTrade']: """ - Helper function to query Trades. + Helper function to query Trades.j Returns a List of trades, filtered on the parameters given. In live mode, converts the filter to a database query and returns all rows In Backtest mode, uses filters on Trade.trades to get the result. @@ -835,6 +742,107 @@ class Trade(_DECL_BASE, LocalTrade): close_date=close_date ) + @staticmethod + def get_trades(trade_filter=None) -> Query: + """ + Helper function to query Trades using filters. + NOTE: Not supported in Backtesting. + :param trade_filter: Optional filter to apply to trades + Can be either a Filter object, or a List of filters + e.g. `(trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True),])` + e.g. `(trade_filter=Trade.id == trade_id)` + :return: unsorted query object + """ + if not Trade.use_db: + raise NotImplementedError('`Trade.get_trades()` not supported in backtesting mode.') + if trade_filter is not None: + if not isinstance(trade_filter, list): + trade_filter = [trade_filter] + return Trade.query.filter(*trade_filter) + else: + return Trade.query + + @staticmethod + def get_open_order_trades(): + """ + Returns all open trades + NOTE: Not supported in Backtesting. + """ + return Trade.get_trades(Trade.open_order_id.isnot(None)).all() + + @staticmethod + def get_open_trades_without_assigned_fees(): + """ + Returns all open trades which don't have open fees set correctly + NOTE: Not supported in Backtesting. + """ + return Trade.get_trades([Trade.fee_open_currency.is_(None), + Trade.orders.any(), + Trade.is_open.is_(True), + ]).all() + + @staticmethod + def get_sold_trades_without_assigned_fees(): + """ + Returns all closed trades which don't have fees set correctly + NOTE: Not supported in Backtesting. + """ + return Trade.get_trades([Trade.fee_close_currency.is_(None), + Trade.orders.any(), + Trade.is_open.is_(False), + ]).all() + + @staticmethod + def total_open_trades_stakes() -> float: + """ + Calculates total invested amount in open trades + in stake currency + """ + if Trade.use_db: + total_open_stake_amount = Trade.query.with_entities( + func.sum(Trade.stake_amount)).filter(Trade.is_open.is_(True)).scalar() + else: + total_open_stake_amount = sum( + t.stake_amount for t in LocalTrade.get_trades_proxy(is_open=True)) + return total_open_stake_amount or 0 + + @staticmethod + def get_overall_performance() -> List[Dict[str, Any]]: + """ + Returns List of dicts containing all Trades, including profit and trade count + NOTE: Not supported in Backtesting. + """ + pair_rates = Trade.query.with_entities( + Trade.pair, + func.sum(Trade.close_profit).label('profit_sum'), + func.count(Trade.pair).label('count') + ).filter(Trade.is_open.is_(False))\ + .group_by(Trade.pair) \ + .order_by(desc('profit_sum')) \ + .all() + return [ + { + 'pair': pair, + 'profit': rate, + 'count': count + } + for pair, rate, count in pair_rates + ] + + @staticmethod + def get_best_pair(): + """ + Get best pair with closed trade. + NOTE: Not supported in Backtesting. + :returns: Tuple containing (pair, profit_sum) + """ + best_pair = Trade.query.with_entities( + Trade.pair, func.sum(Trade.close_profit).label('profit_sum') + ).filter(Trade.is_open.is_(False)) \ + .group_by(Trade.pair) \ + .order_by(desc('profit_sum')).first() + return best_pair + class PairLock(_DECL_BASE): """ diff --git a/tests/test_persistence.py b/tests/test_persistence.py index dad0e275e..8470e12c2 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -774,11 +774,16 @@ def test_adjust_min_max_rates(fee): @pytest.mark.usefixtures("init_persistence") -def test_get_open(fee): +@pytest.mark.parametrize('use_db', [True, False]) +def test_get_open(fee, use_db): + Trade.use_db = use_db + Trade.reset_trades() - create_mock_trades(fee) + create_mock_trades(fee, use_db) assert len(Trade.get_open_trades()) == 4 + Trade.use_db = True + @pytest.mark.usefixtures("init_persistence") def test_to_json(default_conf, fee): @@ -1083,6 +1088,13 @@ def test_get_trades_proxy(fee, use_db): Trade.use_db = True +def test_get_trades_backtest(): + Trade.use_db = False + with pytest.raises(NotImplementedError, match=r"`Trade.get_trades\(\)` not .*"): + Trade.get_trades([]) + Trade.use_db = True + + @pytest.mark.usefixtures("init_persistence") def test_get_overall_performance(fee): @@ -1216,11 +1228,24 @@ def test_Trade_object_idem(): trade = vars(Trade) localtrade = vars(LocalTrade) + excludes = ( + 'delete', + 'session', + 'query', + 'open_date', + 'get_best_pair', + 'get_overall_performance', + 'total_open_trades_stakes', + 'get_sold_trades_without_assigned_fees', + 'get_open_trades_without_assigned_fees', + 'get_open_order_trades', + 'get_trades', + ) + # Parent (LocalTrade) should have the same attributes for item in trade: # Exclude private attributes and open_date (as it's not assigned a default) - if (not item.startswith('_') - and item not in ('delete', 'session', 'query', 'open_date')): + if (not item.startswith('_') and item not in excludes): assert item in localtrade # Fails if only a column is added without corresponding parent field