From 6bb7167b56d525f9c68ad1bbc0b154d4fe2a695f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Jul 2018 19:22:34 +0200 Subject: [PATCH 01/16] Add sellType enum --- freqtrade/strategy/interface.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index fb8bcd31d..811f3232e 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -27,6 +27,16 @@ class SignalType(Enum): SELL = "sell" +class SellType(Enum): + """ + Enum to distinguish between sell reasons + """ + ROI = "roi" + STOP_LOSS = "stop_loss" + TRAILING_STOP_LOSS = "trailing_stop_loss" + SELL_SIGNAL = "sell_signal" + + class IStrategy(ABC): """ Interface for freqtrade strategies From f991109b0a35df2423b35cf27b55a5020d503b71 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Jul 2018 19:57:01 +0200 Subject: [PATCH 02/16] Add sell-reason to sell-tree --- freqtrade/freqtradebot.py | 10 +++++++--- freqtrade/persistence.py | 6 ++++-- freqtrade/rpc/rpc.py | 3 ++- freqtrade/strategy/interface.py | 32 ++++++++++++++++++-------------- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 35c0a1705..65dab15dd 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -20,6 +20,7 @@ from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.state import State +from freqtrade.strategy.interface import SellType from freqtrade.strategy.resolver import IStrategy, StrategyResolver logger = logging.getLogger(__name__) @@ -505,8 +506,9 @@ class FreqtradeBot(object): (buy, sell) = self.strategy.get_signal(self.exchange, trade.pair, self.strategy.ticker_interval) - if self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell): - self.execute_sell(trade, current_rate) + should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell) + if should_sell[0]: + self.execute_sell(trade, current_rate, should_sell[1]) return True logger.info('Found no sell signals for whitelisted currencies. Trying again..') return False @@ -607,17 +609,19 @@ class FreqtradeBot(object): # TODO: figure out how to handle partially complete sell orders return False - def execute_sell(self, trade: Trade, limit: float) -> None: + def execute_sell(self, trade: Trade, limit: float, sellreason: SellType) -> None: """ Executes a limit sell for the given trade and limit :param trade: Trade instance :param limit: limit rate for the sell order + :param sellrason: Reaseon the sell was triggered :return: None """ # Execute sell and update trade record order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id'] trade.open_order_id = order_id trade.close_rate_requested = limit + trade.sell_reason = sellreason.value profit_trade = trade.calc_profit(rate=limit) current_rate = self.exchange.get_ticker(trade.pair)['bid'] diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 0e0b22e82..086e8c2ea 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -88,6 +88,7 @@ def check_migrate(engine) -> None: stop_loss = get_column_def(cols, 'stop_loss', '0.0') initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') max_rate = get_column_def(cols, 'max_rate', '0.0') + sell_reason = get_column_def(cols, 'sell_reason', 'null') # Schema migration necessary engine.execute(f"alter table trades rename to {table_back_name}") @@ -99,7 +100,7 @@ def check_migrate(engine) -> None: (id, exchange, pair, is_open, fee_open, fee_close, open_rate, open_rate_requested, close_rate, close_rate_requested, close_profit, stake_amount, amount, open_date, close_date, open_order_id, - stop_loss, initial_stop_loss, max_rate + stop_loss, initial_stop_loss, max_rate, sell_reason ) select id, lower(exchange), case @@ -114,7 +115,7 @@ def check_migrate(engine) -> None: {close_rate_requested} close_rate_requested, close_profit, stake_amount, amount, open_date, close_date, open_order_id, {stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss, - {max_rate} max_rate + {max_rate} max_rate, {sell_reason} sell_reason from {table_back_name} """) @@ -170,6 +171,7 @@ class Trade(_DECL_BASE): initial_stop_loss = Column(Float, nullable=True, default=0.0) # absolute value of the highest reached price max_rate = Column(Float, nullable=True, default=0.0) + sell_reason = Column(String, nullable=True) def __repr__(self): open_since = arrow.get(self.open_date).humanize() if self.is_open else 'closed' diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 9411e983b..96556df78 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -13,6 +13,7 @@ import sqlalchemy as sql from numpy import mean, nan_to_num from pandas import DataFrame +from freqtrade.analyze import SellType from freqtrade.misc import shorten_date from freqtrade.persistence import Trade from freqtrade.state import State @@ -344,7 +345,7 @@ class RPC(object): # Get current rate and execute sell current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] - self._freqtrade.execute_sell(trade, current_rate) + self._freqtrade.execute_sell(trade, current_rate, SellType.FORCE_SELL) # ---- EOF def _exec_forcesell ---- if self._freqtrade.state != State.RUNNING: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 811f3232e..c5d58dd46 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -6,7 +6,7 @@ import logging from abc import ABC, abstractmethod from datetime import datetime from enum import Enum -from typing import Dict, List, Tuple +from typing import Dict, List, Tuple, Optional import arrow from pandas import DataFrame @@ -35,6 +35,7 @@ class SellType(Enum): STOP_LOSS = "stop_loss" TRAILING_STOP_LOSS = "trailing_stop_loss" SELL_SIGNAL = "sell_signal" + FORCE_SELL = "force_sell" class IStrategy(ABC): @@ -147,40 +148,42 @@ class IStrategy(ABC): ) return buy, sell - def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, sell: bool) -> bool: + def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, + sell: bool) -> Tuple[bool, Optional[SellType]]: """ This function evaluate if on the condition required to trigger a sell has been reached if the threshold is reached and updates the trade record. :return: True if trade should be sold, False otherwise """ current_profit = trade.calc_profit_percent(rate) - if self.stop_loss_reached(current_rate=rate, trade=trade, current_time=date, - current_profit=current_profit): - return True + stoplossflag = self.stop_loss_reached(current_rate=rate, trade=trade, current_time=date, + current_profit=current_profit) + if stoplossflag[0]: + return (True, stoplossflag[1]) experimental = self.config.get('experimental', {}) if buy and experimental.get('ignore_roi_if_buy_signal', False): logger.debug('Buy signal still active - not selling.') - return False + return (False, None) # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) if self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date): logger.debug('Required profit reached. Selling..') - return True + return (True, SellType.ROI) if experimental.get('sell_profit_only', False): logger.debug('Checking if trade is profitable..') if trade.calc_profit(rate=rate) <= 0: - return False + return (False, None) if sell and not buy and experimental.get('use_sell_signal', False): logger.debug('Sell signal received. Selling..') - return True + return (True, SellType.SELL_SIGNAL) - return False + return (False, None) def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, - current_profit: float) -> bool: + current_profit: float) -> Tuple[bool, Optional[SellType]]: """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not @@ -192,8 +195,9 @@ class IStrategy(ABC): # evaluate if the stoploss was hit if self.stoploss is not None and trade.stop_loss >= current_rate: - + selltype = SellType.STOP_LOSS if trailing_stop: + selltype = SellType.TRAILING_STOP_LOSS logger.debug( f"HIT STOP: current price at {current_rate:.6f}, " f"stop loss is {trade.stop_loss:.6f}, " @@ -202,7 +206,7 @@ class IStrategy(ABC): logger.debug(f"trailing stop saved {trade.stop_loss - trade.initial_stop_loss:.6f}") logger.debug('Stop loss hit.') - return True + return (True, selltype) # update the stop loss afterwards, after all by definition it's supposed to be hanging if trailing_stop: @@ -219,7 +223,7 @@ class IStrategy(ABC): trade.adjust_stop_loss(current_rate, stop_loss_value) - return False + return (False, None) def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: """ From 49a7c7f08e4e0f6d4cb522dd9538430d6aaabb76 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Jul 2018 19:57:20 +0200 Subject: [PATCH 03/16] fix tests --- freqtrade/tests/test_freqtradebot.py | 11 +++++++---- freqtrade/tests/test_persistence.py | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index b7ae96048..d37b2aaf6 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -16,6 +16,7 @@ import requests from freqtrade import (DependencyException, OperationalException, TemporaryError, constants) +from freqtrade.analyze import SellType from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType @@ -1369,7 +1370,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc get_ticker=ticker_sell_up ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid']) + freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sellreason=SellType.ROI) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -1421,7 +1422,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, get_ticker=ticker_sell_down ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid']) + freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], + sellreason=SellType.STOP_LOSS) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -1474,7 +1476,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, ) freqtrade.config = {} - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid']) + freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sellreason=SellType.ROI) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -1524,7 +1526,8 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, ) freqtrade.config = {} - freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid']) + freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], + sellreason=SellType.STOP_LOSS) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index b24f2dd6c..d2a736f73 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -465,6 +465,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.max_rate == 0.0 assert trade.stop_loss == 0.0 assert trade.initial_stop_loss == 0.0 + assert trade.sell_reason is None assert log_has("trying trades_bak1", caplog.record_tuples) assert log_has("trying trades_bak2", caplog.record_tuples) From 0147b1631aaa78e25037f7e6378325aaec56a0b4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Jul 2018 19:59:30 +0200 Subject: [PATCH 04/16] remove optional from selltype --- freqtrade/strategy/interface.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index c5d58dd46..cb8b8873d 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -36,6 +36,7 @@ class SellType(Enum): TRAILING_STOP_LOSS = "trailing_stop_loss" SELL_SIGNAL = "sell_signal" FORCE_SELL = "force_sell" + NONE = "" class IStrategy(ABC): @@ -149,7 +150,7 @@ class IStrategy(ABC): return buy, sell def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, - sell: bool) -> Tuple[bool, Optional[SellType]]: + sell: bool) -> Tuple[bool, SellType]: """ This function evaluate if on the condition required to trigger a sell has been reached if the threshold is reached and updates the trade record. @@ -165,7 +166,7 @@ class IStrategy(ABC): if buy and experimental.get('ignore_roi_if_buy_signal', False): logger.debug('Buy signal still active - not selling.') - return (False, None) + return (False, SellType.NONE) # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) if self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date): @@ -175,15 +176,15 @@ class IStrategy(ABC): if experimental.get('sell_profit_only', False): logger.debug('Checking if trade is profitable..') if trade.calc_profit(rate=rate) <= 0: - return (False, None) + return (False, SellType.NONE) if sell and not buy and experimental.get('use_sell_signal', False): logger.debug('Sell signal received. Selling..') return (True, SellType.SELL_SIGNAL) - return (False, None) + return (False, SellType.NONE) def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, - current_profit: float) -> Tuple[bool, Optional[SellType]]: + current_profit: float) -> Tuple[bool, SellType]: """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not @@ -223,7 +224,7 @@ class IStrategy(ABC): trade.adjust_stop_loss(current_rate, stop_loss_value) - return (False, None) + return (False, SellType.NONE) def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: """ From cbffd3650b85f93ecc7fca1613102285e3e4b7dd Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Jul 2018 20:03:40 +0200 Subject: [PATCH 05/16] add sell_reason to backtesting --- freqtrade/optimize/backtesting.py | 13 +++++++++---- freqtrade/tests/optimize/test_backtesting.py | 5 ++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 9c124f35b..da73b7648 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -20,6 +20,7 @@ from freqtrade.configuration import Configuration from freqtrade.exchange import Exchange from freqtrade.misc import file_dump_json from freqtrade.persistence import Trade +from freqtrade.strategy.interface import SellType from freqtrade.strategy.resolver import IStrategy, StrategyResolver logger = logging.getLogger(__name__) @@ -40,6 +41,7 @@ class BacktestResult(NamedTuple): open_at_end: bool open_rate: float close_rate: float + sell_reason: SellType class Backtesting(object): @@ -151,8 +153,9 @@ class Backtesting(object): trade_count_lock[sell_row.date] = trade_count_lock.get(sell_row.date, 0) + 1 buy_signal = sell_row.buy - if self.strategy.should_sell(trade, sell_row.open, sell_row.date, buy_signal, - sell_row.sell): + sell = self.strategy.should_sell(trade, sell_row.open, sell_row.date, buy_signal, + sell_row.sell) + if sell[0]: return BacktestResult(pair=pair, profit_percent=trade.calc_profit_percent(rate=sell_row.open), @@ -164,7 +167,8 @@ class Backtesting(object): close_index=sell_row.Index, open_at_end=False, open_rate=buy_row.open, - close_rate=sell_row.open + close_rate=sell_row.open, + sell_reason=sell[1] ) if partial_ticker: # no sell condition found - trade stil open at end of backtest period @@ -179,7 +183,8 @@ class Backtesting(object): close_index=sell_row.Index, open_at_end=True, open_rate=buy_row.open, - close_rate=sell_row.open + close_rate=sell_row.open, + sell_reason=SellType.FORCE_SELL ) logger.debug('Force_selling still open trade %s with %s perc - %s', btr.pair, btr.profit_percent, btr.profit_abs) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 6e4f91891..6d1f7391d 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -17,6 +17,7 @@ from freqtrade.arguments import Arguments, TimeRange from freqtrade.optimize.backtesting import (Backtesting, setup_configuration, start) from freqtrade.tests.conftest import log_has, patch_exchange +from freqtrade.strategy.interface import SellType from freqtrade.strategy.default_strategy import DefaultStrategy @@ -511,7 +512,9 @@ def test_backtest(default_conf, fee, mocker) -> None: 'trade_duration': [240, 50], 'open_at_end': [False, False], 'open_rate': [0.104445, 0.10302485], - 'close_rate': [0.105, 0.10359999]}) + 'close_rate': [0.105, 0.10359999], + 'sell_reason': [SellType.ROI, SellType.ROI] + }) pd.testing.assert_frame_equal(results, expected) data_pair = data_processed[pair] for _, t in results.iterrows(): From 838b0e7b76c3d02ccf4c82528216645706fad19f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Jul 2018 20:12:27 +0200 Subject: [PATCH 06/16] Remove unused import --- freqtrade/strategy/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index cb8b8873d..dde1df614 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -6,7 +6,7 @@ import logging from abc import ABC, abstractmethod from datetime import datetime from enum import Enum -from typing import Dict, List, Tuple, Optional +from typing import Dict, List, Tuple import arrow from pandas import DataFrame From 8c0b19f80c67040181045af52c48e0a1259e7f15 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Jul 2018 20:20:44 +0200 Subject: [PATCH 07/16] Check sell-reason for sell-reason-specific tests --- freqtrade/tests/test_freqtradebot.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index d37b2aaf6..7ac421bbb 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1580,6 +1580,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, trade.update(limit_buy_order) patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True + assert trade.sell_reason == SellType.SELL_SIGNAL.value def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, @@ -1615,6 +1616,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, trade.update(limit_buy_order) patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True + assert trade.sell_reason == SellType.SELL_SIGNAL.value def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, markets, mocker) -> None: @@ -1687,6 +1689,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke trade.update(limit_buy_order) patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True + assert trade.sell_reason == SellType.SELL_SIGNAL.value def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, mocker) -> None: @@ -1727,6 +1730,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, m # Test if buy-signal is absent (should sell due to roi = true) patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True + assert trade.sell_reason == SellType.ROI.value def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker) -> None: @@ -1764,6 +1768,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker) assert log_has( f'HIT STOP: current price at 0.000001, stop loss is {trade.stop_loss:.6f}, ' f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) + assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, caplog, mocker) -> None: @@ -1825,6 +1830,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, caplog, f'HIT STOP: current price at {buy_price + 0.000002:.6f}, ' f'stop loss is {trade.stop_loss:.6f}, ' f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) + assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, @@ -1867,6 +1873,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, # Test if buy-signal is absent patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True + assert trade.sell_reason == SellType.STOP_LOSS.value def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, caplog, mocker): From 2a6162901449e063e93b828f2b3afb400c5a97bf Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Jul 2018 20:32:56 +0200 Subject: [PATCH 08/16] Export sell_reason from backtest --- freqtrade/optimize/backtesting.py | 2 +- freqtrade/tests/optimize/test_backtesting.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index da73b7648..46b2aac19 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -124,7 +124,7 @@ class Backtesting(object): records = [(t.pair, t.profit_percent, t.open_time.timestamp(), t.close_time.timestamp(), t.open_index - 1, t.trade_duration, - t.open_rate, t.close_rate, t.open_at_end) + t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value) for index, t in results.iterrows()] if records: diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 6d1f7391d..c71930782 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -660,7 +660,9 @@ def test_backtest_record(default_conf, fee, mocker): "open_index": [1, 119, 153, 185], "close_index": [118, 151, 184, 199], "trade_duration": [123, 34, 31, 14], - "open_at_end": [False, False, False, True] + "open_at_end": [False, False, False, True], + "sell_reason": [SellType.ROI, SellType.STOP_LOSS, + SellType.ROI, SellType.FORCE_SELL] }) backtesting._store_backtest_result("backtest-result.json", results) assert len(results) == 4 @@ -673,7 +675,7 @@ def test_backtest_record(default_conf, fee, mocker): # Below follows just a typecheck of the schema/type of trade-records oix = None for (pair, profit, date_buy, date_sell, buy_index, dur, - openr, closer, open_at_end) in records: + openr, closer, open_at_end, sell_reason) in records: assert pair == 'UNITTEST/BTC' assert isinstance(profit, float) # FIX: buy/sell should be converted to ints @@ -682,6 +684,7 @@ def test_backtest_record(default_conf, fee, mocker): assert isinstance(openr, float) assert isinstance(closer, float) assert isinstance(open_at_end, bool) + assert isinstance(sell_reason, str) isinstance(buy_index, pd._libs.tslib.Timestamp) if oix: assert buy_index > oix From 4059871c286275afe97dc4d0931d90a828379cc2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Jul 2018 20:38:14 +0200 Subject: [PATCH 09/16] Add get_strategy_name --- freqtrade/strategy/interface.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index dde1df614..5d1cecdb3 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -80,6 +80,13 @@ class IStrategy(ABC): :param dataframe: DataFrame :return: DataFrame with sell column """ + return self.strategy.populate_sell_trend(dataframe=dataframe) + + def get_strategy_name(self) -> str: + """ + Returns strategy class name + """ + return self.strategy.__class__.__name__ def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame: """ From 426c25f63105befaf9fef999188dc6c936a90c71 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Jul 2018 20:38:57 +0200 Subject: [PATCH 10/16] record ticker_interval and strategyname --- freqtrade/freqtradebot.py | 5 +++-- freqtrade/persistence.py | 10 ++++++++-- freqtrade/tests/test_persistence.py | 2 ++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 65dab15dd..8c71119ae 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -54,7 +54,6 @@ class FreqtradeBot(object): self.rpc: RPCManager = RPCManager(self) self.persistence = None self.exchange = Exchange(self.config) - self._init_modules() def _init_modules(self) -> None: @@ -393,7 +392,9 @@ class FreqtradeBot(object): open_rate_requested=buy_limit, open_date=datetime.utcnow(), exchange=self.exchange.id, - open_order_id=order_id + open_order_id=order_id, + strategy=self.analyze.get_strategy_name(), + ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.analyze.get_ticker_interval()] ) Trade.session.add(trade) Trade.session.flush() diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 086e8c2ea..7544ca8db 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -89,6 +89,8 @@ def check_migrate(engine) -> None: initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') max_rate = get_column_def(cols, 'max_rate', '0.0') sell_reason = get_column_def(cols, 'sell_reason', 'null') + strategy = get_column_def(cols, 'strategy', 'null') + ticker_interval = get_column_def(cols, 'ticker_interval', 'null') # Schema migration necessary engine.execute(f"alter table trades rename to {table_back_name}") @@ -100,7 +102,8 @@ def check_migrate(engine) -> None: (id, exchange, pair, is_open, fee_open, fee_close, open_rate, open_rate_requested, close_rate, close_rate_requested, close_profit, stake_amount, amount, open_date, close_date, open_order_id, - stop_loss, initial_stop_loss, max_rate, sell_reason + stop_loss, initial_stop_loss, max_rate, sell_reason, strategy, + ticker_interval ) select id, lower(exchange), case @@ -115,7 +118,8 @@ def check_migrate(engine) -> None: {close_rate_requested} close_rate_requested, close_profit, stake_amount, amount, open_date, close_date, open_order_id, {stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss, - {max_rate} max_rate, {sell_reason} sell_reason + {max_rate} max_rate, {sell_reason} sell_reason, {strategy} strategy, + {ticker_interval} ticker_interval from {table_back_name} """) @@ -172,6 +176,8 @@ class Trade(_DECL_BASE): # absolute value of the highest reached price max_rate = Column(Float, nullable=True, default=0.0) sell_reason = Column(String, nullable=True) + strategy = Column(String, nullable=True) + ticker_interval = Column(Integer, nullable=True) def __repr__(self): open_since = arrow.get(self.open_date).humanize() if self.is_open else 'closed' diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index d2a736f73..bb6747739 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -466,6 +466,8 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.stop_loss == 0.0 assert trade.initial_stop_loss == 0.0 assert trade.sell_reason is None + assert trade.strategy is None + assert trade.ticker_interval is None assert log_has("trying trades_bak1", caplog.record_tuples) assert log_has("trying trades_bak2", caplog.record_tuples) From 506aa0e3d355873b8e0a2e3d19a70f0eb34414e3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Jul 2018 21:19:43 +0200 Subject: [PATCH 11/16] Add print_sales table and test --- freqtrade/optimize/backtesting.py | 34 ++++++++++++++++---- freqtrade/tests/optimize/test_backtesting.py | 29 +++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 46b2aac19..3f867df22 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -120,6 +120,16 @@ class Backtesting(object): ]) return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe") + def _generate_text_table_sell_reason(self, data: Dict[str, Dict], results: DataFrame) -> str: + """ + Generate small table outlining Backtest results + """ + tabular_data = [] + headers = ['Sell Reason', 'Count'] + for reason, count in results['sell_reason'].value_counts().iteritems(): + tabular_data.append([reason.value, count]) + return tabulate(tabular_data, headers=headers, tablefmt="pipe") + def _store_backtest_result(self, recordfilename: Optional[str], results: DataFrame) -> None: records = [(t.pair, t.profit_percent, t.open_time.timestamp(), @@ -319,20 +329,32 @@ class Backtesting(object): self._store_backtest_result(self.config.get('exportfilename'), results) logger.info( - '\n================================================= ' - 'BACKTESTING REPORT' - ' ==================================================\n' + '\n' + '=' * 49 + + ' BACKTESTING REPORT ' + + '=' * 50 + '\n' '%s', self._generate_text_table( data, results ) ) + # logger.info( + # results[['sell_reason']].groupby('sell_reason').count() + # ) logger.info( - '\n=============================================== ' - 'LEFT OPEN TRADES REPORT' - ' ===============================================\n' + '\n' + '=' * 4 + + ' SELL READON STATS ' + + '=' * 4 + '\n' + '%s \n', + self._generate_text_table_sell_reason(data, results) + + ) + + logger.info( + '\n' + '=' * 47 + + ' LEFT OPEN TRADES REPORT ' + + '=' * 47 + '\n' '%s', self._generate_text_table( data, diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index c71930782..d3fc233ce 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -404,6 +404,35 @@ def test_generate_text_table(default_conf, mocker): assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str +def test_generate_text_table_sell_reason(default_conf, mocker): + """ + Test Backtesting.generate_text_table_sell_reason() method + """ + patch_exchange(mocker) + backtesting = Backtesting(default_conf) + + results = pd.DataFrame( + { + 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'], + 'profit_percent': [0.1, 0.2, 0.3], + 'profit_abs': [0.2, 0.4, 0.5], + 'trade_duration': [10, 30, 10], + 'profit': [2, 0, 0], + 'loss': [0, 0, 1], + 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] + } + ) + + result_str = ( + '| Sell Reason | Count |\n' + '|:--------------|--------:|\n' + '| roi | 2 |\n' + '| stop_loss | 1 |' + ) + assert backtesting._generate_text_table_sell_reason( + data={'ETH/BTC': {}}, results=results) == result_str + + def test_backtesting_start(default_conf, mocker, caplog) -> None: """ Test Backtesting.start() method From ad98c62329457ba441dcaaccefa975456d8e4e38 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Jul 2018 21:37:54 +0200 Subject: [PATCH 12/16] update backtest anlaysis cheatsheet --- docs/backtesting.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 172969ae2..0a7675848 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -83,7 +83,7 @@ with filename.open() as file: data = json.load(file) columns = ["pair", "profit", "opents", "closets", "index", "duration", - "open_rate", "close_rate", "open_at_end"] + "open_rate", "close_rate", "open_at_end", "sell_reason"] df = pd.DataFrame(data, columns=columns) df['opents'] = pd.to_datetime(df['opents'], @@ -98,6 +98,8 @@ df['closets'] = pd.to_datetime(df['closets'], ) ``` +If you have some ideas for interresting / helpfull backtest data analysis, feel free to submit a PR so the community can benefit from it. + #### Exporting trades to file specifying a custom filename ```bash From a452864b419733292e321e0311989e01fc4e057b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Jul 2018 22:21:52 +0200 Subject: [PATCH 13/16] Use namedtuple for sell_return --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/optimize/backtesting.py | 6 +++--- freqtrade/rpc/rpc.py | 2 +- freqtrade/strategy/interface.py | 32 +++++++++++++++++----------- freqtrade/tests/test_freqtradebot.py | 4 +++- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8c71119ae..566b0670f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -508,8 +508,8 @@ class FreqtradeBot(object): trade.pair, self.strategy.ticker_interval) should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell) - if should_sell[0]: - self.execute_sell(trade, current_rate, should_sell[1]) + if should_sell.sell_flag: + self.execute_sell(trade, current_rate, should_sell.sell_type) return True logger.info('Found no sell signals for whitelisted currencies. Trying again..') return False diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3f867df22..391e05b83 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -164,8 +164,8 @@ class Backtesting(object): buy_signal = sell_row.buy sell = self.strategy.should_sell(trade, sell_row.open, sell_row.date, buy_signal, - sell_row.sell) - if sell[0]: + sell_row.sell) + if sell.sell_flag: return BacktestResult(pair=pair, profit_percent=trade.calc_profit_percent(rate=sell_row.open), @@ -178,7 +178,7 @@ class Backtesting(object): open_at_end=False, open_rate=buy_row.open, close_rate=sell_row.open, - sell_reason=sell[1] + sell_reason=sell.sell_type ) if partial_ticker: # no sell condition found - trade stil open at end of backtest period diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 96556df78..27ec7ea7a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -13,10 +13,10 @@ import sqlalchemy as sql from numpy import mean, nan_to_num from pandas import DataFrame -from freqtrade.analyze import SellType from freqtrade.misc import shorten_date from freqtrade.persistence import Trade from freqtrade.state import State +from freqtrade.strategy.interface import SellType logger = logging.getLogger(__name__) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 5d1cecdb3..7b8d91010 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -6,7 +6,7 @@ import logging from abc import ABC, abstractmethod from datetime import datetime from enum import Enum -from typing import Dict, List, Tuple +from typing import Dict, List, NamedTuple, Tuple import arrow from pandas import DataFrame @@ -39,6 +39,14 @@ class SellType(Enum): NONE = "" +class SellCheckTuple(NamedTuple): + """ + NamedTuple for Sell type + reason + """ + sell_flag: bool + sell_type: SellType + + class IStrategy(ABC): """ Interface for freqtrade strategies @@ -157,7 +165,7 @@ class IStrategy(ABC): return buy, sell def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, - sell: bool) -> Tuple[bool, SellType]: + sell: bool) -> SellCheckTuple: """ This function evaluate if on the condition required to trigger a sell has been reached if the threshold is reached and updates the trade record. @@ -166,32 +174,32 @@ class IStrategy(ABC): current_profit = trade.calc_profit_percent(rate) stoplossflag = self.stop_loss_reached(current_rate=rate, trade=trade, current_time=date, current_profit=current_profit) - if stoplossflag[0]: - return (True, stoplossflag[1]) + if stoplossflag.sell_flag: + return stoplossflag experimental = self.config.get('experimental', {}) if buy and experimental.get('ignore_roi_if_buy_signal', False): logger.debug('Buy signal still active - not selling.') - return (False, SellType.NONE) + return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) if self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date): logger.debug('Required profit reached. Selling..') - return (True, SellType.ROI) + return SellCheckTuple(sell_flag=True, sell_type=SellType.ROI) if experimental.get('sell_profit_only', False): logger.debug('Checking if trade is profitable..') if trade.calc_profit(rate=rate) <= 0: - return (False, SellType.NONE) + return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) if sell and not buy and experimental.get('use_sell_signal', False): logger.debug('Sell signal received. Selling..') - return (True, SellType.SELL_SIGNAL) + return SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL) - return (False, SellType.NONE) + return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, - current_profit: float) -> Tuple[bool, SellType]: + current_profit: float) -> SellCheckTuple: """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not @@ -214,7 +222,7 @@ class IStrategy(ABC): logger.debug(f"trailing stop saved {trade.stop_loss - trade.initial_stop_loss:.6f}") logger.debug('Stop loss hit.') - return (True, selltype) + return SellCheckTuple(sell_flag=True, sell_type=selltype) # update the stop loss afterwards, after all by definition it's supposed to be hanging if trailing_stop: @@ -231,7 +239,7 @@ class IStrategy(ABC): trade.adjust_stop_loss(current_rate, stop_loss_value) - return (False, SellType.NONE) + return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: """ diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 7ac421bbb..65cd99689 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -16,7 +16,7 @@ import requests from freqtrade import (DependencyException, OperationalException, TemporaryError, constants) -from freqtrade.analyze import SellType +from freqtrade.strategy.interface.IStrategy import SellType, SellCheckTuple from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType @@ -1625,6 +1625,8 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market """ patch_RPCManager(mocker) patch_coinmarketcap(mocker) + mocker.patch('freqtrade.freqtradebot.strategy.interface.stop_loss_reached', + return_value=SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), From 760c79c5e95bb3eeec23c366d986f97a8a78f08a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Jul 2018 20:19:32 +0200 Subject: [PATCH 14/16] Use `.center()` to output trades header line --- freqtrade/optimize/backtesting.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 391e05b83..73899f9a2 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -343,19 +343,17 @@ class Backtesting(object): # ) logger.info( - '\n' + '=' * 4 + - ' SELL READON STATS ' + - '=' * 4 + '\n' - '%s \n', + '\n' + + ' SELL READON STATS '.center(119, '=') + + '\n%s \n', self._generate_text_table_sell_reason(data, results) ) logger.info( - '\n' + '=' * 47 + - ' LEFT OPEN TRADES REPORT ' + - '=' * 47 + '\n' - '%s', + '\n' + + ' LEFT OPEN TRADES REPORT '.center(119, '=') + + '\n%s', self._generate_text_table( data, results.loc[results.open_at_end] From 4fb9823cfb6e09ed16ca97f40b76b2647abd3c7f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Jul 2018 19:41:42 +0200 Subject: [PATCH 15/16] fix rebase problem --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/optimize/backtesting.py | 2 +- freqtrade/strategy/interface.py | 3 +-- freqtrade/tests/test_freqtradebot.py | 7 +++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 566b0670f..f6bdcdf52 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -393,8 +393,8 @@ class FreqtradeBot(object): open_date=datetime.utcnow(), exchange=self.exchange.id, open_order_id=order_id, - strategy=self.analyze.get_strategy_name(), - ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.analyze.get_ticker_interval()] + strategy=self.strategy.get_strategy_name(), + ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] ) Trade.session.add(trade) Trade.session.flush() diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 73899f9a2..db8e923b2 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -164,7 +164,7 @@ class Backtesting(object): buy_signal = sell_row.buy sell = self.strategy.should_sell(trade, sell_row.open, sell_row.date, buy_signal, - sell_row.sell) + sell_row.sell) if sell.sell_flag: return BacktestResult(pair=pair, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7b8d91010..9120d3e04 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -88,13 +88,12 @@ class IStrategy(ABC): :param dataframe: DataFrame :return: DataFrame with sell column """ - return self.strategy.populate_sell_trend(dataframe=dataframe) def get_strategy_name(self) -> str: """ Returns strategy class name """ - return self.strategy.__class__.__name__ + return self.__class__.__name__ def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame: """ diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 65cd99689..223e7318d 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -16,11 +16,11 @@ import requests from freqtrade import (DependencyException, OperationalException, TemporaryError, constants) -from freqtrade.strategy.interface.IStrategy import SellType, SellCheckTuple from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType from freqtrade.state import State +from freqtrade.strategy.interface import SellType, SellCheckTuple from freqtrade.tests.conftest import log_has, patch_coinmarketcap, patch_exchange @@ -1625,8 +1625,6 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market """ patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.strategy.interface.stop_loss_reached', - return_value=SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), @@ -1647,7 +1645,8 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market freqtrade = FreqtradeBot(conf) patch_get_signal(freqtrade) freqtrade.strategy.stop_loss_reached = \ - lambda current_rate, trade, current_time, current_profit: False + lambda current_rate, trade, current_time, current_profit: SellCheckTuple( + sell_flag=False, sell_type=SellType.NONE) freqtrade.create_trade() trade = Trade.query.first() From 0775a371fe6e6f56bace96e1ce4406fee5dccad4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 23 Jul 2018 00:54:20 +0100 Subject: [PATCH 16/16] rename sellreason to sell_Reason, fix typos --- docs/backtesting.md | 2 +- freqtrade/freqtradebot.py | 6 +++--- freqtrade/tests/test_freqtradebot.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 0a7675848..255359a6a 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -98,7 +98,7 @@ df['closets'] = pd.to_datetime(df['closets'], ) ``` -If you have some ideas for interresting / helpfull backtest data analysis, feel free to submit a PR so the community can benefit from it. +If you have some ideas for interesting / helpful backtest data analysis, feel free to submit a PR so the community can benefit from it. #### Exporting trades to file specifying a custom filename diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f6bdcdf52..c0263c6fb 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -610,19 +610,19 @@ class FreqtradeBot(object): # TODO: figure out how to handle partially complete sell orders return False - def execute_sell(self, trade: Trade, limit: float, sellreason: SellType) -> None: + def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> None: """ Executes a limit sell for the given trade and limit :param trade: Trade instance :param limit: limit rate for the sell order - :param sellrason: Reaseon the sell was triggered + :param sellreason: Reason the sell was triggered :return: None """ # Execute sell and update trade record order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id'] trade.open_order_id = order_id trade.close_rate_requested = limit - trade.sell_reason = sellreason.value + trade.sell_reason = sell_reason.value profit_trade = trade.calc_profit(rate=limit) current_rate = self.exchange.get_ticker(trade.pair)['bid'] diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 223e7318d..277193464 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1370,7 +1370,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc get_ticker=ticker_sell_up ) - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sellreason=SellType.ROI) + freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -1423,7 +1423,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, ) freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], - sellreason=SellType.STOP_LOSS) + sell_reason=SellType.STOP_LOSS) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -1476,7 +1476,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, ) freqtrade.config = {} - freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sellreason=SellType.ROI) + freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -1527,7 +1527,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, freqtrade.config = {} freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], - sellreason=SellType.STOP_LOSS) + sell_reason=SellType.STOP_LOSS) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0]