From 7166a474ae9b58259a25c356d8e16f01213b63d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 19:54:16 +0100 Subject: [PATCH 01/59] Add min_rate - always update min/max rates --- freqtrade/persistence.py | 24 ++++++++++++++---------- freqtrade/tests/test_persistence.py | 1 + 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 10aff72ec..f6cdc815f 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -83,7 +83,7 @@ def check_migrate(engine) -> None: logger.debug(f'trying {table_back_name}') # Check for latest column - if not has_column(cols, 'stoploss_last_update'): + if not has_column(cols, 'min_rate'): logger.info(f'Running database migration - backup available as {table_back_name}') fee_open = get_column_def(cols, 'fee_open', 'fee') @@ -95,6 +95,7 @@ def check_migrate(engine) -> None: stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') + min_rate = get_column_def(cols, 'min_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') @@ -113,7 +114,7 @@ def check_migrate(engine) -> None: 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, stoploss_order_id, stoploss_last_update, - max_rate, sell_reason, strategy, + max_rate, min_rate, sell_reason, strategy, ticker_interval ) select id, lower(exchange), @@ -130,7 +131,7 @@ def check_migrate(engine) -> None: stake_amount, amount, open_date, close_date, open_order_id, {stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss, {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, - {max_rate} max_rate, {sell_reason} sell_reason, + {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {strategy} strategy, {ticker_interval} ticker_interval from {table_back_name} """) @@ -191,6 +192,8 @@ class Trade(_DECL_BASE): stoploss_last_update = Column(DateTime, nullable=True) # absolute value of the highest reached price max_rate = Column(Float, nullable=True, default=0.0) + # Lowest price reached + min_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) @@ -201,6 +204,14 @@ class Trade(_DECL_BASE): 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 adjust_high_low(self, current_price): + """ + Adjust the max_rate and min_rate. + """ + logger.info("Adjusting high/low") + self.max_rate = max(current_price, self.max_rate) + self.min_rate = min(current_price, self.min_rate) + def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): """this adjusts the stop loss to it's most recently observed setting""" @@ -210,13 +221,6 @@ class Trade(_DECL_BASE): new_loss = float(current_price * (1 - abs(stoploss))) - # keeping track of the highest observed rate for this trade - if self.max_rate is None: - self.max_rate = current_price - else: - if current_price > self.max_rate: - self.max_rate = current_price - # no stop loss assigned yet if not self.stop_loss: logger.debug("assigning new stop loss") diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index a9519e693..7e809de0d 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -510,6 +510,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.pair == "ETC/BTC" assert trade.exchange == "binance" assert trade.max_rate == 0.0 + assert trade.min_rate == 0.0 assert trade.stop_loss == 0.0 assert trade.initial_stop_loss == 0.0 assert trade.sell_reason is None From 738ed932217daa6c8fc13114e710cb8ea0211506 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 19:54:34 +0100 Subject: [PATCH 02/59] call new function --- freqtrade/strategy/interface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 41dcb8c57..d65c12895 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -254,6 +254,8 @@ class IStrategy(ABC): current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) + trade.adjust_high_low(current_rate) + stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, force_stoploss=force_stoploss) From 68a9b14eca297386b8494f44c31439ef27994099 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 20:04:39 +0100 Subject: [PATCH 03/59] Min-rate should not default to 0 --- freqtrade/persistence.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index f6cdc815f..ad85d5efd 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -95,7 +95,7 @@ def check_migrate(engine) -> None: stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') - min_rate = get_column_def(cols, 'min_rate', '0.0') + min_rate = get_column_def(cols, 'min_rate', 'null') 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') @@ -193,7 +193,7 @@ class Trade(_DECL_BASE): # absolute value of the highest reached price max_rate = Column(Float, nullable=True, default=0.0) # Lowest price reached - min_rate = Column(Float, nullable=True, default=0.0) + min_rate = Column(Float, nullable=True) sell_reason = Column(String, nullable=True) strategy = Column(String, nullable=True) ticker_interval = Column(Integer, nullable=True) @@ -204,13 +204,13 @@ class Trade(_DECL_BASE): 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 adjust_high_low(self, current_price): + def adjust_high_low(self, current_price: float): """ Adjust the max_rate and min_rate. """ - logger.info("Adjusting high/low") - self.max_rate = max(current_price, self.max_rate) - self.min_rate = min(current_price, self.min_rate) + logger.debug("Adjusting min/max rates") + self.max_rate = max(current_price, self.max_rate or 0.0) + self.min_rate = min(current_price, self.min_rate or 10000000.0) def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): """this adjusts the stop loss to it's most recently observed setting""" From 01733c94fa01d628fcf357fe09ca5d77d716badc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 20:04:55 +0100 Subject: [PATCH 04/59] Split up tests for adjust_stoploss and adjust_highlow --- freqtrade/tests/test_persistence.py | 87 +++++++++++++++++++---------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 7e809de0d..c4d07b3f9 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -510,7 +510,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.pair == "ETC/BTC" assert trade.exchange == "binance" assert trade.max_rate == 0.0 - assert trade.min_rate == 0.0 + assert trade.min_rate is None assert trade.stop_loss == 0.0 assert trade.initial_stop_loss == 0.0 assert trade.sell_reason is None @@ -586,7 +586,48 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): caplog.record_tuples) -def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): +def test_adjust_stop_loss(fee): + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='bittrex', + open_rate=1, + max_rate=1, + ) + + trade.adjust_stop_loss(trade.open_rate, 0.05, True) + assert trade.stop_loss == 0.95 + assert trade.initial_stop_loss == 0.95 + + # Get percent of profit with a lower rate + trade.adjust_stop_loss(0.96, 0.05) + assert trade.stop_loss == 0.95 + assert trade.initial_stop_loss == 0.95 + + # Get percent of profit with a custom rate (Higher than open rate) + trade.adjust_stop_loss(1.3, -0.1) + assert round(trade.stop_loss, 8) == 1.17 + assert trade.initial_stop_loss == 0.95 + + # current rate lower again ... should not change + trade.adjust_stop_loss(1.2, 0.1) + assert round(trade.stop_loss, 8) == 1.17 + assert trade.initial_stop_loss == 0.95 + + # current rate higher... should raise stoploss + trade.adjust_stop_loss(1.4, 0.1) + assert round(trade.stop_loss, 8) == 1.26 + assert trade.initial_stop_loss == 0.95 + + # Initial is true but stop_loss set - so doesn't do anything + trade.adjust_stop_loss(1.7, 0.1, True) + assert round(trade.stop_loss, 8) == 1.26 + assert trade.initial_stop_loss == 0.95 + + +def test_adjust_high_low(fee): trade = Trade( pair='ETH/BTC', stake_amount=0.001, @@ -596,40 +637,24 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): open_rate=1, ) - trade.adjust_stop_loss(trade.open_rate, 0.05, True) - assert trade.stop_loss == 0.95 + trade.adjust_high_low(trade.open_rate) assert trade.max_rate == 1 - assert trade.initial_stop_loss == 0.95 + assert trade.min_rate == 1 - # Get percent of profit with a lowre rate - trade.adjust_stop_loss(0.96, 0.05) - assert trade.stop_loss == 0.95 + # check min adjusted, max remained + trade.adjust_high_low(0.96) assert trade.max_rate == 1 - assert trade.initial_stop_loss == 0.95 + assert trade.min_rate == 0.96 - # Get percent of profit with a custom rate (Higher than open rate) - trade.adjust_stop_loss(1.3, -0.1) - assert round(trade.stop_loss, 8) == 1.17 - assert trade.max_rate == 1.3 - assert trade.initial_stop_loss == 0.95 + # check max adjusted, min remains + trade.adjust_high_low(1.05) + assert trade.max_rate == 1.05 + assert trade.min_rate == 0.96 - # current rate lower again ... should not change - trade.adjust_stop_loss(1.2, 0.1) - assert round(trade.stop_loss, 8) == 1.17 - assert trade.max_rate == 1.3 - assert trade.initial_stop_loss == 0.95 - - # current rate higher... should raise stoploss - trade.adjust_stop_loss(1.4, 0.1) - assert round(trade.stop_loss, 8) == 1.26 - assert trade.max_rate == 1.4 - assert trade.initial_stop_loss == 0.95 - - # Initial is true but stop_loss set - so doesn't do anything - trade.adjust_stop_loss(1.7, 0.1, True) - assert round(trade.stop_loss, 8) == 1.26 - assert trade.max_rate == 1.4 - assert trade.initial_stop_loss == 0.95 + # current rate "in the middle" - no adjustment + trade.adjust_high_low(1.03) + assert trade.max_rate == 1.05 + assert trade.min_rate == 0.96 def test_get_open(default_conf, fee): From fc360608b7f6dfd7d8f1206d95ed48b6d040f5b4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 20:06:15 +0100 Subject: [PATCH 05/59] Rename function to adjust_min_max --- freqtrade/persistence.py | 2 +- freqtrade/strategy/interface.py | 2 +- freqtrade/tests/test_persistence.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ad85d5efd..ebafe7355 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -204,7 +204,7 @@ class Trade(_DECL_BASE): 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 adjust_high_low(self, current_price: float): + def adjust_min_max_rates(self, current_price: float): """ Adjust the max_rate and min_rate. """ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d65c12895..85d4d8c13 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -254,7 +254,7 @@ class IStrategy(ABC): current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) - trade.adjust_high_low(current_rate) + trade.adjust_min_max_rates(current_rate) stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index c4d07b3f9..042237ce7 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -627,7 +627,7 @@ def test_adjust_stop_loss(fee): assert trade.initial_stop_loss == 0.95 -def test_adjust_high_low(fee): +def test_adjust_min_max_rates(fee): trade = Trade( pair='ETH/BTC', stake_amount=0.001, @@ -637,22 +637,22 @@ def test_adjust_high_low(fee): open_rate=1, ) - trade.adjust_high_low(trade.open_rate) + trade.adjust_min_max_rates(trade.open_rate) assert trade.max_rate == 1 assert trade.min_rate == 1 # check min adjusted, max remained - trade.adjust_high_low(0.96) + trade.adjust_min_max_rates(0.96) assert trade.max_rate == 1 assert trade.min_rate == 0.96 # check max adjusted, min remains - trade.adjust_high_low(1.05) + trade.adjust_min_max_rates(1.05) assert trade.max_rate == 1.05 assert trade.min_rate == 0.96 # current rate "in the middle" - no adjustment - trade.adjust_high_low(1.03) + trade.adjust_min_max_rates(1.03) assert trade.max_rate == 1.05 assert trade.min_rate == 0.96 From 2d4a2fd10bd4517dd653a4a2754f056440009b22 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 13:12:04 +0100 Subject: [PATCH 06/59] Use oppen_rate instead of artificial defaults --- freqtrade/persistence.py | 4 ++-- freqtrade/strategy/interface.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ebafe7355..b807e10e1 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -209,8 +209,8 @@ class Trade(_DECL_BASE): Adjust the max_rate and min_rate. """ logger.debug("Adjusting min/max rates") - self.max_rate = max(current_price, self.max_rate or 0.0) - self.min_rate = min(current_price, self.min_rate or 10000000.0) + self.max_rate = max(current_price, self.max_rate or self.open_rate) + self.min_rate = min(current_price, self.min_rate or self.open_rate) def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): """this adjusts the stop loss to it's most recently observed setting""" diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 85d4d8c13..b844c1e58 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -301,8 +301,9 @@ class IStrategy(ABC): # evaluate if the stoploss was hit if stoploss is not on exchange if ((self.stoploss is not None) and - (trade.stop_loss >= current_rate) and - (not self.order_types.get('stoploss_on_exchange'))): + (trade.stop_loss >= current_rate) and + (not self.order_types.get('stoploss_on_exchange'))): + selltype = SellType.STOP_LOSS # If Trailing stop (and max-rate did move above open rate) if trailing_stop and trade.open_rate != trade.max_rate: From 7b99daebd766d986287143ddca0d5b62cac49968 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 13:18:29 +0100 Subject: [PATCH 07/59] Update docstring for adjust_stoploss --- freqtrade/persistence.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index b807e10e1..a1ac65c04 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -213,7 +213,13 @@ class Trade(_DECL_BASE): self.min_rate = min(current_price, self.min_rate or self.open_rate) def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): - """this adjusts the stop loss to it's most recently observed setting""" + """ + This adjusts the stop loss to it's most recently observed setting + :param current_price: Current rate the asset is traded + :param stoploss: Stoploss as factor (sample -0.05 -> -5% below current price). + :param initial: Called to initiate stop_loss. + Skips everything if self.stop_loss is already set. + """ if initial and not (self.stop_loss is None or self.stop_loss == 0): # Don't modify if called with initial and nothing to do From a77d51351342913611fb7cc7b703d098c0e15683 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 13:27:32 +0100 Subject: [PATCH 08/59] Fix backteest detail numbering ... --- .../tests/optimize/test_backtest_detail.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index e8514e76f..d6295b778 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -14,10 +14,10 @@ from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataf from freqtrade.tests.conftest import patch_exchange -# Test 0 Minus 8% Close +# Test 1 Minus 8% Close # Test with Stop-loss at 1% # TC1: Stop-Loss Triggered 1% loss -tc0 = BTContainer(data=[ +tc1 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -30,10 +30,10 @@ tc0 = BTContainer(data=[ ) -# Test 1 Minus 4% Low, minus 1% close +# Test 2 Minus 4% Low, minus 1% close # Test with Stop-Loss at 3% # TC2: Stop-Loss Triggered 3% Loss -tc1 = BTContainer(data=[ +tc2 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -53,7 +53,7 @@ tc1 = BTContainer(data=[ # Test with Stop-Loss at 2% # TC3: Trade-A: Stop-Loss Triggered 2% Loss # Trade-B: Stop-Loss Triggered 2% Loss -tc2 = BTContainer(data=[ +tc3 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -71,7 +71,7 @@ tc2 = BTContainer(data=[ # Candle Data for test 3 – Candle drops 3% Closed 15% up # Test with Stop-loss at 2% ROI 6% # TC4: Stop-Loss Triggered 2% Loss -tc3 = BTContainer(data=[ +tc4 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -83,10 +83,10 @@ tc3 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] ) -# Test 4 / Drops 0.5% Closes +20% +# Test 5 / Drops 0.5% Closes +20% # Set stop-loss at 1% ROI 3% # TC5: ROI triggers 3% Gain -tc4 = BTContainer(data=[ +tc5 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4980, 4987, 6172, 1, 0], [1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -102,7 +102,7 @@ tc4 = BTContainer(data=[ # Candle Data for test 6 # Set stop-loss at 2% ROI at 5% # TC6: Stop-Loss triggers 2% Loss -tc5 = BTContainer(data=[ +tc6 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -118,7 +118,7 @@ tc5 = BTContainer(data=[ # Candle Data for test 7 # Set stop-loss at 2% ROI at 3% # TC7: ROI Triggers 3% Gain -tc6 = BTContainer(data=[ +tc7 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], @@ -131,13 +131,13 @@ tc6 = BTContainer(data=[ ) TESTS = [ - tc0, tc1, tc2, tc3, tc4, tc5, tc6, + tc7, ] From a830bee9c7a84f63f1b99346db27f2564d649f55 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 15:28:04 +0100 Subject: [PATCH 09/59] Enable trailing_stop for BTContainer tests --- freqtrade/tests/optimize/__init__.py | 1 + freqtrade/tests/optimize/test_backtest_detail.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/optimize/__init__.py b/freqtrade/tests/optimize/__init__.py index 129a09f40..075938a61 100644 --- a/freqtrade/tests/optimize/__init__.py +++ b/freqtrade/tests/optimize/__init__.py @@ -28,6 +28,7 @@ class BTContainer(NamedTuple): roi: float trades: List[BTrade] profit_perc: float + trailing_stop: bool = False def _get_frame_time_from_offset(offset): diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index d6295b778..b4ca4ee26 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -148,8 +148,9 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: """ default_conf["stoploss"] = data.stop_loss default_conf["minimal_roi"] = {"0": data.roi} - default_conf['ticker_interval'] = tests_ticker_interval - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0)) + default_conf["ticker_interval"] = tests_ticker_interval + default_conf["trailing_stop"] = data.trailing_stop + mocker.patch("freqtrade.exchange.Exchange.get_fee", MagicMock(return_value=0.0)) patch_exchange(mocker) frame = _build_backtest_dataframe(data.data) backtesting = Backtesting(default_conf) @@ -157,7 +158,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: backtesting.advise_sell = lambda a, m: frame caplog.set_level(logging.DEBUG) - pair = 'UNITTEST/BTC' + pair = "UNITTEST/BTC" # Dummy data as we mock the analyze functions data_processed = {pair: DataFrame()} min_date, max_date = get_timeframe({pair: frame}) From f0e5113a7ffe919f632ec66817f7eaaf9096c48c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 15:39:05 +0100 Subject: [PATCH 10/59] Use Magicmock instead of lambda for mocking --- freqtrade/tests/test_freqtradebot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index fc7c48663..a5de283a5 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2259,9 +2259,8 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market } freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.stop_loss_reached = \ - lambda current_rate, trade, current_time, force_stoploss, current_profit: SellCheckTuple( - sell_flag=False, sell_type=SellType.NONE) + freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( + sell_flag=False, sell_type=SellType.NONE)) freqtrade.create_trade() trade = Trade.query.first() From 8c7e8255bb3f68c91925b68ea1229785155feb5d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:01:34 +0100 Subject: [PATCH 11/59] Add detailed test for trailing stop --- .../tests/optimize/test_backtest_detail.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index b4ca4ee26..f33f56efc 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -130,6 +130,22 @@ tc7 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] ) + +# Test 8 - trailing_stop should raise so candle 3 causes a stoploss. +# Candle Data for test 8 +# Set stop-loss at 10%, ROI at 10% (should not apply) +# TC8: Trailing stoploss - stoploss should be adjusted to 94.5 after candle 1 +tc8 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5000, 6172, 0, 0], + [2, 5000, 5250, 4750, 4850, 6172, 0, 0], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.10, roi=0.10, profit_perc=-0.055, trailing_stop=True, + trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] +) + TESTS = [ tc1, tc2, @@ -138,6 +154,7 @@ TESTS = [ tc5, tc6, tc7, + tc8, ] From 05ab1c2e0a833bfcbaac15447bb2c5bdecedf110 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:02:13 +0100 Subject: [PATCH 12/59] Fix some comments --- freqtrade/strategy/interface.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index b844c1e58..6e2f35cc8 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -247,6 +247,9 @@ class IStrategy(ABC): """ 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. + :param low: Only used during backtesting to simulate stoploss + :param high: Only used during backtesting, to simulate ROI + :param force_stoploss: Externally provided stoploss :return: True if trade should be sold, False otherwise """ @@ -263,7 +266,7 @@ class IStrategy(ABC): if stoplossflag.sell_flag: return stoplossflag - # Set current rate to low for backtesting sell + # Set current rate to high for backtesting sell current_rate = high or rate current_profit = trade.calc_profit_percent(current_rate) experimental = self.config.get('experimental', {}) From a7b60f6780e2230909400fd8437311c07a018ae2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:03:44 +0100 Subject: [PATCH 13/59] update trailing_stop with high in case of backtesting --- freqtrade/strategy/interface.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 6e2f35cc8..a086139c7 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -257,11 +257,11 @@ class IStrategy(ABC): current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) - trade.adjust_min_max_rates(current_rate) + trade.adjust_min_max_rates(high or current_rate) stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, - force_stoploss=force_stoploss) + force_stoploss=force_stoploss, high=high) if stoplossflag.sell_flag: return stoplossflag @@ -291,7 +291,7 @@ class IStrategy(ABC): 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, force_stoploss: float) -> SellCheckTuple: + current_profit: float, force_stoploss: float, high) -> SellCheckTuple: """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not @@ -322,6 +322,7 @@ class IStrategy(ABC): return SellCheckTuple(sell_flag=True, sell_type=selltype) # update the stop loss afterwards, after all by definition it's supposed to be hanging + # TODO: Maybe this needs to be moved to the start of this function. check #1575 for details if trailing_stop: # check if we have a special stop loss for positive condition @@ -342,7 +343,7 @@ class IStrategy(ABC): # we update trailing stoploss only if offset is reached. tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) if not (tsl_only_offset and current_profit < sl_offset): - trade.adjust_stop_loss(current_rate, stop_loss_value) + trade.adjust_stop_loss(high or current_rate, stop_loss_value) return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) From 8afce7e65105c07364991b4ab48ee718e10530eb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:26:38 +0100 Subject: [PATCH 14/59] Add testcase for Testcase 2 --- .../tests/optimize/test_backtest_detail.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index f33f56efc..b98369533 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -49,7 +49,6 @@ tc2 = BTContainer(data=[ # Test 3 Candle drops 4%, Recovers 1%. # Entry Criteria Met # Candle drops 20% -# Candle Data for test 3 # Test with Stop-Loss at 2% # TC3: Trade-A: Stop-Loss Triggered 2% Loss # Trade-B: Stop-Loss Triggered 2% Loss @@ -99,7 +98,6 @@ tc5 = BTContainer(data=[ ) # Test 6 / Drops 3% / Recovers 6% Positive / Closes 1% positve -# Candle Data for test 6 # Set stop-loss at 2% ROI at 5% # TC6: Stop-Loss triggers 2% Loss tc6 = BTContainer(data=[ @@ -115,7 +113,6 @@ tc6 = BTContainer(data=[ ) # Test 7 - 6% Positive / 1% Negative / Close 1% Positve -# Candle Data for test 7 # Set stop-loss at 2% ROI at 3% # TC7: ROI Triggers 3% Gain tc7 = BTContainer(data=[ @@ -132,11 +129,10 @@ tc7 = BTContainer(data=[ # Test 8 - trailing_stop should raise so candle 3 causes a stoploss. -# Candle Data for test 8 # Set stop-loss at 10%, ROI at 10% (should not apply) -# TC8: Trailing stoploss - stoploss should be adjusted to 94.5 after candle 1 +# TC8: Trailing stoploss - stoploss should be adjusted candle 2 tc8 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5050, 4950, 5000, 6172, 0, 0], [2, 5000, 5250, 4750, 4850, 6172, 0, 0], @@ -146,6 +142,22 @@ tc8 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] ) + +# Test 9 - trailing_stop should raise - high and low in same candle. +# Candle Data for test 9 +# Set stop-loss at 10%, ROI at 10% (should not apply) +# TC9: Trailing stoploss - stoploss should be adjusted candle 2 +tc9 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5000, 6172, 0, 0], + [2, 5000, 5050, 4950, 5000, 6172, 0, 0], + [3, 5000, 5200, 4550, 4850, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.10, roi=0.10, profit_perc=-0.064, trailing_stop=True, + trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] +) + TESTS = [ tc1, tc2, @@ -155,6 +167,7 @@ TESTS = [ tc6, tc7, tc8, + tc9, ] From 7307084dfd5c0ec955278e285d464203d71a13bb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 16:21:58 +0100 Subject: [PATCH 15/59] Move stoploss-adjustment to the top --- freqtrade/strategy/interface.py | 47 ++++++++++++++-------------- freqtrade/tests/test_freqtradebot.py | 9 ++---- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a086139c7..542b6bab0 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -302,6 +302,29 @@ class IStrategy(ABC): trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss else self.stoploss, initial=True) + # update the stop loss afterwards, after all by definition it's supposed to be hanging + # TODO: Maybe this needs to be moved to the start of this function. check #1575 for details + if trailing_stop: + + # check if we have a special stop loss for positive condition + # and if profit is positive + stop_loss_value = force_stoploss if force_stoploss else self.stoploss + + sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 + + if 'trailing_stop_positive' in self.config and current_profit > sl_offset: + + # Ignore mypy error check in configuration that this is a float + stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore + logger.debug(f"using positive stop loss: {stop_loss_value} " + f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%") + + # if trailing_only_offset_is_reached is true, + # we update trailing stoploss only if offset is reached. + tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) + if not (tsl_only_offset and current_profit < sl_offset): + trade.adjust_stop_loss(high or current_rate, stop_loss_value) + # evaluate if the stoploss was hit if stoploss is not on exchange if ((self.stoploss is not None) and (trade.stop_loss >= current_rate) and @@ -321,30 +344,6 @@ class IStrategy(ABC): logger.debug('Stop loss hit.') return SellCheckTuple(sell_flag=True, sell_type=selltype) - # update the stop loss afterwards, after all by definition it's supposed to be hanging - # TODO: Maybe this needs to be moved to the start of this function. check #1575 for details - if trailing_stop: - - # check if we have a special stop loss for positive condition - # and if profit is positive - stop_loss_value = force_stoploss if force_stoploss else self.stoploss - - sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 - - if 'trailing_stop_positive' in self.config and current_profit > sl_offset: - - # Ignore mypy error check in configuration that this is a float - stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore - logger.debug(f"using positive stop loss mode: {stop_loss_value} " - f"with offset {sl_offset:.4g} " - f"since we have profit {current_profit:.4f}%") - - # if trailing_only_offset_is_reached is true, - # we update trailing stoploss only if offset is reached. - tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) - if not (tsl_only_offset and current_profit < sl_offset): - trade.adjust_stop_loss(high or current_rate, stop_loss_value) - 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 a5de283a5..561df7c05 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2406,8 +2406,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets })) # stop-loss not reached, adjusted stoploss assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss mode: 0.01 with offset 0 ' - f'since we have profit 0.2666%', + assert log_has(f'using positive stop loss: 0.01 offset: 0 profit: 0.2666%', caplog.record_tuples) assert log_has(f'adjusted stop loss', caplog.record_tuples) assert trade.stop_loss == 0.0000138501 @@ -2466,8 +2465,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, })) # stop-loss not reached, adjusted stoploss assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss mode: 0.01 with offset 0.011 ' - f'since we have profit 0.2666%', + assert log_has(f'using positive stop loss: 0.01 offset: 0.011 profit: 0.2666%', caplog.record_tuples) assert log_has(f'adjusted stop loss', caplog.record_tuples) assert trade.stop_loss == 0.0000138501 @@ -2546,8 +2544,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, })) assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss mode: 0.05 with offset 0.055 ' - f'since we have profit 0.1218%', + assert log_has(f'using positive stop loss: 0.05 offset: 0.055 profit: 0.1218%', caplog.record_tuples) assert log_has(f'adjusted stop loss', caplog.record_tuples) assert trade.stop_loss == 0.0000117705 From b1fe8c532549d1c0552390e68d59f105d83a7649 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 16:23:32 +0100 Subject: [PATCH 16/59] Simplify stoploss_reached --- freqtrade/strategy/interface.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 542b6bab0..a9925b654 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -299,18 +299,16 @@ class IStrategy(ABC): """ trailing_stop = self.config.get('trailing_stop', False) - trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss - else self.stoploss, initial=True) + stop_loss_value = force_stoploss if force_stoploss else self.stoploss + + trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) - # update the stop loss afterwards, after all by definition it's supposed to be hanging - # TODO: Maybe this needs to be moved to the start of this function. check #1575 for details if trailing_stop: - # check if we have a special stop loss for positive condition # and if profit is positive - stop_loss_value = force_stoploss if force_stoploss else self.stoploss sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 + tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) if 'trailing_stop_positive' in self.config and current_profit > sl_offset: @@ -321,7 +319,6 @@ class IStrategy(ABC): # if trailing_only_offset_is_reached is true, # we update trailing stoploss only if offset is reached. - tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) if not (tsl_only_offset and current_profit < sl_offset): trade.adjust_stop_loss(high or current_rate, stop_loss_value) From c404e9ffd06e94d320b95ba2387d011cd5eab70d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 16:48:17 +0100 Subject: [PATCH 17/59] Simplify trailing_stop logic --- freqtrade/strategy/interface.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a9925b654..eb9ed825e 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -301,6 +301,7 @@ class IStrategy(ABC): trailing_stop = self.config.get('trailing_stop', False) stop_loss_value = force_stoploss if force_stoploss else self.stoploss + # Initiate stoploss with open_rate. Does nothing if stoploss is already set. trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) if trailing_stop: @@ -310,16 +311,15 @@ class IStrategy(ABC): sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) - if 'trailing_stop_positive' in self.config and current_profit > sl_offset: - - # Ignore mypy error check in configuration that this is a float - stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore - logger.debug(f"using positive stop loss: {stop_loss_value} " - f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%") - - # if trailing_only_offset_is_reached is true, - # we update trailing stoploss only if offset is reached. + # Don't update stoploss if trailing_only_offset_is_reached is true. if not (tsl_only_offset and current_profit < sl_offset): + + if 'trailing_stop_positive' in self.config and current_profit > sl_offset: + # Ignore mypy error check in configuration that this is a float + stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore + logger.debug(f"using positive stop loss: {stop_loss_value} " + f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%") + trade.adjust_stop_loss(high or current_rate, stop_loss_value) # evaluate if the stoploss was hit if stoploss is not on exchange From 9a632d9b7ca09bd0c85ad58f05e2583dc2951804 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 16:51:36 +0100 Subject: [PATCH 18/59] Formatting --- freqtrade/strategy/interface.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index eb9ed825e..fcb27d7bd 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -290,8 +290,9 @@ class IStrategy(ABC): 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, force_stoploss: float, high) -> SellCheckTuple: + def stop_loss_reached(self, current_rate: float, trade: Trade, + current_time: datetime, current_profit: float, + force_stoploss: float, high: float = None) -> SellCheckTuple: """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not @@ -305,15 +306,14 @@ class IStrategy(ABC): trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) if trailing_stop: - # check if we have a special stop loss for positive condition - # and if profit is positive + # trailing stoploss handling sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) # Don't update stoploss if trailing_only_offset_is_reached is true. if not (tsl_only_offset and current_profit < sl_offset): - + # Specific handling for trailing_stop_positive if 'trailing_stop_positive' in self.config and current_profit > sl_offset: # Ignore mypy error check in configuration that this is a float stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore From 6045f07a9c4fafe7d730c0d3e3d57b3e761933e5 Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 27 Mar 2019 21:12:57 +0100 Subject: [PATCH 19/59] telegram message concatenation refactored --- freqtrade/rpc/telegram.py | 30 ++++++++++++++---------- freqtrade/tests/rpc/test_rpc_telegram.py | 6 +++++ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2c419e417..1e1436631 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -193,21 +193,25 @@ class Telegram(RPC): for result in results: result['date'] = result['date'].humanize() - messages = [ - "*Trade ID:* `{trade_id}`\n" - "*Current Pair:* {pair}\n" - "*Open Since:* `{date}`\n" - "*Amount:* `{amount}`\n" - "*Open Rate:* `{open_rate:.8f}`\n" - "*Close Rate:* `{close_rate}`\n" - "*Current Rate:* `{current_rate:.8f}`\n" - "*Close Profit:* `{close_profit}`\n" - "*Current Profit:* `{current_profit:.2f}%`\n" - "*Open Order:* `{open_order}`".format(**result) - for result in results - ] + messages = [] + for r in results: + lines = [ + "*Trade ID:* `{trade_id}`", + "*Current Pair:* {pair}", + "*Open Since:* `{date}`", + "*Amount:* `{amount}`", + "*Open Rate:* `{open_rate:.8f}`", + "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", + "*Current Rate:* `{current_rate:.8f}`", + "*Close Profit:* `{close_profit}`", + "*Current Profit:* `{current_profit:.2f}%`", + "*Open Order:* `{open_order}`" + ] + messages.append("\n".join(filter(None,lines)).format(**r)) + for msg in messages: self._send_msg(msg, bot=bot) + except RPCException as e: self._send_msg(str(e), bot=bot) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index dd49b0000..39973d5db 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -267,6 +267,12 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No # Trigger status while we have a fulfilled order for the open trade telegram._status(bot=MagicMock(), update=update) + # close_rate should not be included in the message as the trade is not closed + # and no line should be empty + lines = msg_mock.call_args_list[0][0][0].split('\n') + assert '' not in lines + assert 'Close Rate' not in ''.join(lines) + assert msg_mock.call_count == 1 assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] From 1678a039aee6cd06981152205b833bd0202ab512 Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 27 Mar 2019 21:32:56 +0100 Subject: [PATCH 20/59] removing close profit is trade is open --- freqtrade/rpc/telegram.py | 2 +- freqtrade/tests/rpc/test_rpc_telegram.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 1e1436631..0870e660b 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -203,7 +203,7 @@ class Telegram(RPC): "*Open Rate:* `{open_rate:.8f}`", "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", "*Current Rate:* `{current_rate:.8f}`", - "*Close Profit:* `{close_profit}`", + "*Close Profit:* `{close_profit}`" if r['close_profit'] else "", "*Current Profit:* `{current_profit:.2f}%`", "*Open Order:* `{open_order}`" ] diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 39973d5db..d1a068100 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -272,6 +272,7 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No lines = msg_mock.call_args_list[0][0][0].split('\n') assert '' not in lines assert 'Close Rate' not in ''.join(lines) + assert 'Close Profit' not in ''.join(lines) assert msg_mock.call_count == 1 assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] From 0ca3a38ba6b6de63574c23ea8355863c858d0da9 Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 27 Mar 2019 21:39:17 +0100 Subject: [PATCH 21/59] moved date to top and show open order only if it is not none --- freqtrade/rpc/telegram.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0870e660b..2ba431b07 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -196,18 +196,17 @@ class Telegram(RPC): messages = [] for r in results: lines = [ - "*Trade ID:* `{trade_id}`", + "*Trade ID:* `{trade_id}` (since `{date}`)", "*Current Pair:* {pair}", - "*Open Since:* `{date}`", "*Amount:* `{amount}`", "*Open Rate:* `{open_rate:.8f}`", "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", "*Current Rate:* `{current_rate:.8f}`", "*Close Profit:* `{close_profit}`" if r['close_profit'] else "", "*Current Profit:* `{current_profit:.2f}%`", - "*Open Order:* `{open_order}`" + "*Open Order:* `{open_order}`" if r['open_order'] else "", ] - messages.append("\n".join(filter(None,lines)).format(**r)) + messages.append("\n".join(filter(None ,lines)).format(**r)) for msg in messages: self._send_msg(msg, bot=bot) From 941921dd0f9f741d81da890941d9a3f6294ba81c Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 27 Mar 2019 22:00:46 +0100 Subject: [PATCH 22/59] initial SL and SL added to RPC --- freqtrade/rpc/rpc.py | 2 ++ freqtrade/tests/rpc/test_rpc.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index a5601a502..3272059a9 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -110,6 +110,8 @@ class RPC(object): amount=round(trade.amount, 8), close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), + initial_stoploss=trade.initial_stop_loss, + stoploss=trade.stop_loss, open_order='({} {} rem={:.8f})'.format( order['type'], order['side'], order['remaining'] ) if order else None, diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index e6f7ea41e..f9862c9ca 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -58,6 +58,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: 'amount': 90.99181074, 'close_profit': None, 'current_profit': -0.59, + 'initial_stoploss': 0.0, + 'stoploss': 0.0, 'open_order': '(limit buy rem=0.00000000)' } == results[0] @@ -78,6 +80,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: 'amount': 90.99181074, 'close_profit': None, 'current_profit': ANY, + 'initial_stoploss': 0.0, + 'stoploss': 0.0, 'open_order': '(limit buy rem=0.00000000)' } == results[0] From 0e5b0ebda6fa26aab71a877a6c62616110a46d7d Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 28 Mar 2019 12:09:07 +0100 Subject: [PATCH 23/59] adding SL and SL percentage to telegram msg --- freqtrade/rpc/rpc.py | 8 ++++++-- freqtrade/rpc/telegram.py | 3 ++- freqtrade/tests/rpc/test_rpc_telegram.py | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3272059a9..553e66d85 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -97,9 +97,13 @@ class RPC(object): current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN + current_profit = trade.calc_profit_percent(current_rate) fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%' if trade.close_profit else None) + sl_percentage = round(((trade.stop_loss - current_rate) / current_rate) * 100, 2) + txt_sl_percentage = f'{sl_percentage}%' + results.append(dict( trade_id=trade.id, pair=trade.pair, @@ -110,8 +114,8 @@ class RPC(object): amount=round(trade.amount, 8), close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), - initial_stoploss=trade.initial_stop_loss, - stoploss=trade.stop_loss, + stop_loss=trade.stop_loss, + stop_loss_percentage=txt_sl_percentage, open_order='({} {} rem={:.8f})'.format( order['type'], order['side'], order['remaining'] ) if order else None, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2ba431b07..8bec8e6cd 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -204,9 +204,10 @@ class Telegram(RPC): "*Current Rate:* `{current_rate:.8f}`", "*Close Profit:* `{close_profit}`" if r['close_profit'] else "", "*Current Profit:* `{current_profit:.2f}%`", + "*Stoploss:* `{stop_loss:.8f}` ({stop_loss_percentage})", "*Open Order:* `{open_order}`" if r['open_order'] else "", ] - messages.append("\n".join(filter(None ,lines)).format(**r)) + messages.append("\n".join(filter(None, lines)).format(**r)) for msg in messages: self._send_msg(msg, bot=bot) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index d1a068100..edd099dc4 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -201,6 +201,8 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: 'amount': 90.99181074, 'close_profit': None, 'current_profit': -0.59, + 'stop_loss': 1.099e-05, + 'stop_loss_percentage': '-2%', 'open_order': '(limit buy rem=0.00000000)' }]), _status_table=status_table, From daeb172ba1d481f0c46dda83576c6b5032c34c27 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 28 Mar 2019 12:38:05 +0000 Subject: [PATCH 24/59] Update ccxt from 1.18.406 to 1.18.407 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2bf329bd8..0fff9dacf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.406 +ccxt==1.18.407 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From e11eb4775e875533c21f21a7c2a8d63eaf506b55 Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 28 Mar 2019 16:21:49 +0100 Subject: [PATCH 25/59] stoploss precentage in telegram msg removed --- freqtrade/rpc/rpc.py | 3 --- freqtrade/rpc/telegram.py | 2 +- freqtrade/tests/rpc/test_rpc.py | 6 ++---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 553e66d85..20bfe41ba 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -101,8 +101,6 @@ class RPC(object): current_profit = trade.calc_profit_percent(current_rate) fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%' if trade.close_profit else None) - sl_percentage = round(((trade.stop_loss - current_rate) / current_rate) * 100, 2) - txt_sl_percentage = f'{sl_percentage}%' results.append(dict( trade_id=trade.id, @@ -115,7 +113,6 @@ class RPC(object): close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), stop_loss=trade.stop_loss, - stop_loss_percentage=txt_sl_percentage, open_order='({} {} rem={:.8f})'.format( order['type'], order['side'], order['remaining'] ) if order else None, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 8bec8e6cd..d3f3521aa 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -204,7 +204,7 @@ class Telegram(RPC): "*Current Rate:* `{current_rate:.8f}`", "*Close Profit:* `{close_profit}`" if r['close_profit'] else "", "*Current Profit:* `{current_profit:.2f}%`", - "*Stoploss:* `{stop_loss:.8f}` ({stop_loss_percentage})", + "*Stoploss:* `{stop_loss:.8f}`", "*Open Order:* `{open_order}`" if r['open_order'] else "", ] messages.append("\n".join(filter(None, lines)).format(**r)) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index f9862c9ca..529bf31f3 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -58,8 +58,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: 'amount': 90.99181074, 'close_profit': None, 'current_profit': -0.59, - 'initial_stoploss': 0.0, - 'stoploss': 0.0, + 'stop_loss': 0.0, 'open_order': '(limit buy rem=0.00000000)' } == results[0] @@ -80,8 +79,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: 'amount': 90.99181074, 'close_profit': None, 'current_profit': ANY, - 'initial_stoploss': 0.0, - 'stoploss': 0.0, + 'stop_loss': 0.0, 'open_order': '(limit buy rem=0.00000000)' } == results[0] From 2f3f5f19cdc82971525990918c08fb5d17a173f8 Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 28 Mar 2019 16:26:59 +0100 Subject: [PATCH 26/59] sl percentage removed form rpc test --- freqtrade/rpc/rpc.py | 2 -- freqtrade/tests/rpc/test_rpc_telegram.py | 1 - 2 files changed, 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 20bfe41ba..55f9a302c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -97,11 +97,9 @@ class RPC(object): current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN - current_profit = trade.calc_profit_percent(current_rate) fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%' if trade.close_profit else None) - results.append(dict( trade_id=trade.id, pair=trade.pair, diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index edd099dc4..b66e29143 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -202,7 +202,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: 'close_profit': None, 'current_profit': -0.59, 'stop_loss': 1.099e-05, - 'stop_loss_percentage': '-2%', 'open_order': '(limit buy rem=0.00000000)' }]), _status_table=status_table, From 50fc63251ecf33edc8afebfe38cc2ce8d479b6a2 Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 28 Mar 2019 21:18:26 +0100 Subject: [PATCH 27/59] added SL pct to DB --- freqtrade/persistence.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index a1ac65c04..0b46768c1 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -83,7 +83,7 @@ def check_migrate(engine) -> None: logger.debug(f'trying {table_back_name}') # Check for latest column - if not has_column(cols, 'min_rate'): + if not has_column(cols, 'stop_loss_pct'): logger.info(f'Running database migration - backup available as {table_back_name}') fee_open = get_column_def(cols, 'fee_open', 'fee') @@ -91,7 +91,9 @@ def check_migrate(engine) -> None: open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null') close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null') stop_loss = get_column_def(cols, 'stop_loss', '0.0') + stop_loss_pct = get_column_def(cols, 'stop_loss_pct', '0.0') initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') + initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', '0.0') stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') @@ -113,7 +115,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, stoploss_order_id, stoploss_last_update, + stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, + stoploss_order_id, stoploss_last_update, max_rate, min_rate, sell_reason, strategy, ticker_interval ) @@ -129,7 +132,9 @@ def check_migrate(engine) -> None: open_rate, {open_rate_requested} open_rate_requested, close_rate, {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, + {stop_loss} stop_loss, {stop_loss_pct} stop_loss_pct, + {initial_stop_loss} initial_stop_loss, + {initial_stop_loss_pct} initial_stop_loss_pct, {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {strategy} strategy, {ticker_interval} ticker_interval @@ -184,8 +189,12 @@ class Trade(_DECL_BASE): open_order_id = Column(String) # absolute value of the stop loss stop_loss = Column(Float, nullable=True, default=0.0) + # percentage value of the stop loss + stop_loss_pct = Column(Float, nullable=True, default=0.0) # absolute value of the initial stop loss initial_stop_loss = Column(Float, nullable=True, default=0.0) + # percentage value of the initial stop loss + initial_stop_loss_pct = Column(Float, nullable=True, default=0.0) # stoploss order id which is on exchange stoploss_order_id = Column(String, nullable=True, index=True) # last update time of the stoploss order on exchange @@ -231,13 +240,16 @@ class Trade(_DECL_BASE): if not self.stop_loss: logger.debug("assigning new stop loss") self.stop_loss = new_loss + self.stop_loss_pct = stoploss self.initial_stop_loss = new_loss + self.initial_stop_loss_pct = stoploss self.stoploss_last_update = datetime.utcnow() # evaluate if the stop loss needs to be updated else: if new_loss > self.stop_loss: # stop losses only walk up, never down! self.stop_loss = new_loss + self.stop_loss_pct = stoploss self.stoploss_last_update = datetime.utcnow() logger.debug("adjusted stop loss") else: From f2599ffe9029acea5a0a08137376c6e894a8fa2b Mon Sep 17 00:00:00 2001 From: Misagh Date: Fri, 29 Mar 2019 08:08:29 +0100 Subject: [PATCH 28/59] pct default to None --- freqtrade/persistence.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 0b46768c1..5be61393e 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -91,9 +91,9 @@ def check_migrate(engine) -> None: open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null') close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null') stop_loss = get_column_def(cols, 'stop_loss', '0.0') - stop_loss_pct = get_column_def(cols, 'stop_loss_pct', '0.0') + stop_loss_pct = get_column_def(cols, 'stop_loss_pct', 'null') initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') - initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', '0.0') + initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', 'null') stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') @@ -190,11 +190,11 @@ class Trade(_DECL_BASE): # absolute value of the stop loss stop_loss = Column(Float, nullable=True, default=0.0) # percentage value of the stop loss - stop_loss_pct = Column(Float, nullable=True, default=0.0) + stop_loss_pct = Column(Float, nullable=True) # absolute value of the initial stop loss initial_stop_loss = Column(Float, nullable=True, default=0.0) # percentage value of the initial stop loss - initial_stop_loss_pct = Column(Float, nullable=True, default=0.0) + initial_stop_loss_pct = Column(Float, nullable=True) # stoploss order id which is on exchange stoploss_order_id = Column(String, nullable=True, index=True) # last update time of the stoploss order on exchange From 82b344db1bd93dcda60c48cbf65c8b2936f4d935 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 29 Mar 2019 12:38:05 +0000 Subject: [PATCH 29/59] Update ccxt from 1.18.407 to 1.18.412 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0fff9dacf..22d3ae485 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.407 +ccxt==1.18.412 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From bb5a310aec09fd9c2f961f32a78fa86bb97bb411 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Mar 2019 20:12:44 +0100 Subject: [PATCH 30/59] Add --logfile argument --- freqtrade/arguments.py | 7 +++++++ freqtrade/configuration.py | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 604386426..8d7dac4bc 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -71,6 +71,13 @@ class Arguments(object): dest='loglevel', default=0, ) + self.parser.add_argument( + '--logfile', + help='Log to the file specified', + dest='logfile', + type=str, + metavar='FILE' + ) self.parser.add_argument( '--version', action='version', diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index ba7a0e200..adaf7a59b 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -4,7 +4,9 @@ This module contains the configuration class import json import logging import os +import sys from argparse import Namespace +from logging.handlers import RotatingFileHandler from typing import Any, Dict, Optional import ccxt @@ -12,8 +14,8 @@ from jsonschema import Draft4Validator, validate from jsonschema.exceptions import ValidationError, best_match from freqtrade import OperationalException, constants -from freqtrade.state import RunMode from freqtrade.misc import deep_merge_dicts +from freqtrade.state import RunMode logger = logging.getLogger(__name__) @@ -116,9 +118,22 @@ class Configuration(object): config.update({'verbosity': self.args.loglevel}) else: config.update({'verbosity': 0}) + + # Log to stdout, not stderr + log_handlers = [logging.StreamHandler(sys.stdout)] + if 'logfile' in self.args and self.args.logfile: + config.update({'logfile': self.args.logfile}) + + # Allow setting this as either configuration or argument + if 'logfile' in config: + log_handlers.append(RotatingFileHandler(config['logfile'], + maxBytes=1024 * 1024, # 1Mb + backupCount=10)) + logging.basicConfig( level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=log_handlers ) set_loggers(config['verbosity']) logger.info('Verbosity set to %s', config['verbosity']) From d4ffdaffc2a19d4a5f54a0b1cee6d00a8534e258 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Mar 2019 20:16:41 +0100 Subject: [PATCH 31/59] Correctly add types --- freqtrade/configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index adaf7a59b..fdd71f2f5 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -7,7 +7,7 @@ import os import sys from argparse import Namespace from logging.handlers import RotatingFileHandler -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional import ccxt from jsonschema import Draft4Validator, validate @@ -120,7 +120,7 @@ class Configuration(object): config.update({'verbosity': 0}) # Log to stdout, not stderr - log_handlers = [logging.StreamHandler(sys.stdout)] + log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stdout)] if 'logfile' in self.args and self.args.logfile: config.update({'logfile': self.args.logfile}) From e5008fbf9347d5d8234a70afc2f65c1049b695ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Mar 2019 20:16:52 +0100 Subject: [PATCH 32/59] Add test for logfile attribute --- freqtrade/tests/test_configuration.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 21547d205..45e539c2f 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -5,6 +5,7 @@ import logging from argparse import Namespace from copy import deepcopy from unittest.mock import MagicMock +from pathlib import Path import pytest from jsonschema import Draft4Validator, ValidationError, validate @@ -547,6 +548,23 @@ def test_set_loggers() -> None: assert logging.getLogger('telegram').level is logging.INFO +def test_set_logfile(default_conf, mocker): + mocker.patch('freqtrade.configuration.open', + mocker.mock_open(read_data=json.dumps(default_conf))) + + arglist = [ + '--logfile', 'test_file.log', + ] + args = Arguments(arglist, '').get_parsed_arg() + configuration = Configuration(args) + validated_conf = configuration.load_config() + + assert validated_conf['logfile'] == "test_file.log" + f = Path("test_file.log") + assert f.is_file() + f.unlink() + + def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None: default_conf['forcebuy_enable'] = True mocker.patch('freqtrade.configuration.open', mocker.mock_open( From 12066411db802e942bd990cb3c461280ed4adb51 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Mar 2019 20:19:40 +0100 Subject: [PATCH 33/59] Update docs with logfile methods --- README.md | 8 +++++--- docs/bot-usage.md | 7 ++++--- docs/configuration.md | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ade62ce94..8f7578561 100644 --- a/README.md +++ b/README.md @@ -68,9 +68,9 @@ For any other type of installation please refer to [Installation doc](https://ww ### Bot commands ``` -usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] - [--strategy-path PATH] [--dynamic-whitelist [INT]] - [--db-url PATH] +usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH] + [-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]] + [--db-url PATH] [--sd-notify] {backtesting,edge,hyperopt} ... Free, open source crypto trading bot @@ -84,6 +84,7 @@ positional arguments: optional arguments: -h, --help show this help message and exit -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified --version show program's version number and exit -c PATH, --config PATH Specify configuration file (default: None). Multiple @@ -100,6 +101,7 @@ optional arguments: --db-url PATH Override trades database URL, this is useful if dry_run is enabled or in custom deployments (default: None). + --sd-notify Notify systemd service manager. ``` ### Telegram RPC commands diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 35e4a776d..5ec390d5c 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -6,9 +6,9 @@ This page explains the different parameters of the bot and how to run it. ## Bot commands ``` -usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] - [--strategy-path PATH] [--dynamic-whitelist [INT]] - [--db-url PATH] +usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH] + [-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]] + [--db-url PATH] [--sd-notify] {backtesting,edge,hyperopt} ... Free, open source crypto trading bot @@ -22,6 +22,7 @@ positional arguments: optional arguments: -h, --help show this help message and exit -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified --version show program's version number and exit -c PATH, --config PATH Specify configuration file (default: None). Multiple diff --git a/docs/configuration.md b/docs/configuration.md index 11b941220..75843ef4a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -70,6 +70,7 @@ Mandatory Parameters are marked as **Required**. | `strategy` | DefaultStrategy | Defines Strategy class to use. | `strategy_path` | null | Adds an additional strategy lookup path (must be a folder). | `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second. +| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file. | `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. ### Parameters in the strategy From 44142706c3cb73cb51b5dd821daf505269b802c8 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 30 Mar 2019 12:38:03 +0000 Subject: [PATCH 34/59] Update ccxt from 1.18.412 to 1.18.415 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 22d3ae485..b2dee3542 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.412 +ccxt==1.18.415 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 40c0b4ef2e4315df1d8fc5baa0937c66c929e147 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Mar 2019 13:47:09 +0100 Subject: [PATCH 35/59] Autopatch coinmarketcap --- freqtrade/tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index c0f8e49b7..6478706a4 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -95,7 +95,6 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: :param config: Config to pass to the bot :return: None """ - patch_coinmarketcap(mocker, {'price_usd': 12345.0}) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) patch_exchange(mocker, None) @@ -105,7 +104,8 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: return FreqtradeBot(config) -def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None: +@pytest.fixture(autouse=True) +def patch_coinmarketcap(mocker) -> None: """ Mocker to coinmarketcap to speed up tests :param mocker: mocker to patch coinmarketcap class From e98c0621d33988c354bf2400052f1daf1ee7ac2a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Mar 2019 13:47:30 +0100 Subject: [PATCH 36/59] We don't need to call patch_coinmarketcap each time. --- freqtrade/tests/rpc/test_fiat_convert.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/freqtrade/tests/rpc/test_fiat_convert.py b/freqtrade/tests/rpc/test_fiat_convert.py index fbc942432..66870efcc 100644 --- a/freqtrade/tests/rpc/test_fiat_convert.py +++ b/freqtrade/tests/rpc/test_fiat_convert.py @@ -8,7 +8,7 @@ import pytest from requests.exceptions import RequestException from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter -from freqtrade.tests.conftest import log_has, patch_coinmarketcap +from freqtrade.tests.conftest import log_has def test_pair_convertion_object(): @@ -40,7 +40,6 @@ def test_pair_convertion_object(): def test_fiat_convert_is_supported(mocker): - patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() assert fiat_convert._is_supported_fiat(fiat='USD') is True assert fiat_convert._is_supported_fiat(fiat='usd') is True @@ -49,7 +48,6 @@ def test_fiat_convert_is_supported(mocker): def test_fiat_convert_add_pair(mocker): - patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() @@ -72,8 +70,6 @@ def test_fiat_convert_add_pair(mocker): def test_fiat_convert_find_price(mocker): - patch_coinmarketcap(mocker) - fiat_convert = CryptoToFiatConverter() with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'): @@ -93,15 +89,12 @@ def test_fiat_convert_find_price(mocker): def test_fiat_convert_unsupported_crypto(mocker, caplog): mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[]) - patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0 assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples) def test_fiat_convert_get_price(mocker): - patch_coinmarketcap(mocker) - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=28000.0) @@ -134,21 +127,18 @@ def test_fiat_convert_get_price(mocker): def test_fiat_convert_same_currencies(mocker): - patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0 def test_fiat_convert_two_FIAT(mocker): - patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0 def test_loadcryptomap(mocker): - patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() assert len(fiat_convert._cryptomap) == 2 @@ -174,7 +164,6 @@ def test_fiat_init_network_exception(mocker): def test_fiat_convert_without_network(mocker): # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap - patch_coinmarketcap(mocker) fiat_convert = CryptoToFiatConverter() @@ -205,7 +194,6 @@ def test_fiat_invalid_response(mocker, caplog): def test_convert_amount(mocker): - patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0) fiat_convert = CryptoToFiatConverter() From 87a296f728487c9bcef763bd98a46e760b07d954 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Mar 2019 13:47:47 +0100 Subject: [PATCH 37/59] No need to call patch_coinmarketcap each tim --- freqtrade/tests/rpc/test_rpc.py | 21 +-------------- freqtrade/tests/rpc/test_rpc_telegram.py | 34 +----------------------- 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index df89c03d4..627768ec2 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -14,7 +14,7 @@ from freqtrade.persistence import Trade from freqtrade.rpc import RPC, RPCException from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from freqtrade.state import State -from freqtrade.tests.conftest import patch_coinmarketcap, patch_exchange +from freqtrade.tests.conftest import patch_exchange from freqtrade.tests.test_freqtradebot import patch_get_signal @@ -28,7 +28,6 @@ def prec_satoshi(a, b) -> float: # Unit tests def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: - patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -86,7 +85,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -122,7 +120,6 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: def test_rpc_daily_profit(default_conf, update, ticker, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -175,7 +172,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, 'freqtrade.rpc.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), ) - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -333,7 +329,6 @@ def test_rpc_balance_handle(default_conf, mocker): 'freqtrade.rpc.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), ) - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -363,7 +358,6 @@ def test_rpc_balance_handle(default_conf, mocker): def test_rpc_start(mocker, default_conf) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -386,7 +380,6 @@ def test_rpc_start(mocker, default_conf) -> None: def test_rpc_stop(mocker, default_conf) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -410,7 +403,6 @@ def test_rpc_stop(mocker, default_conf) -> None: def test_rpc_stopbuy(mocker, default_conf) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -430,7 +422,6 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -531,7 +522,6 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: def test_performance_handle(default_conf, ticker, limit_buy_order, fee, limit_sell_order, markets, mocker) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -567,7 +557,6 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -595,7 +584,6 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None: default_conf['forcebuy_enable'] = True - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) buy_mm = MagicMock(return_value={'id': limit_buy_order['id']}) @@ -644,7 +632,6 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order def test_rpcforcebuy_stopped(mocker, default_conf) -> None: default_conf['forcebuy_enable'] = True default_conf['initial_state'] = 'stopped' - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -657,7 +644,6 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None: def test_rpcforcebuy_disabled(mocker, default_conf) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -670,7 +656,6 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None: def test_rpc_whitelist(mocker, default_conf) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -682,7 +667,6 @@ def test_rpc_whitelist(mocker, default_conf) -> None: def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) default_conf['pairlist'] = {'method': 'VolumePairList', 'config': {'number_assets': 4} @@ -699,7 +683,6 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: def test_rpc_blacklist(mocker, default_conf) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -719,7 +702,6 @@ def test_rpc_blacklist(mocker, default_conf) -> None: def test_rpc_edge_disabled(mocker, default_conf) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = FreqtradeBot(default_conf) @@ -729,7 +711,6 @@ def test_rpc_edge_disabled(mocker, default_conf) -> None: def test_rpc_edge_enabled(mocker, edge_conf) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 03a6442e5..fb2d71d4f 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -20,8 +20,7 @@ from freqtrade.rpc import RPCMessageType from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.state import State from freqtrade.strategy.interface import SellType -from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, - patch_coinmarketcap, patch_exchange) +from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange) from freqtrade.tests.test_freqtradebot import patch_get_signal @@ -90,7 +89,6 @@ def test_cleanup(default_conf, mocker) -> None: def test_authorized_only(default_conf, mocker, caplog) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker, None) chat = Chat(0, 0) @@ -118,7 +116,6 @@ def test_authorized_only(default_conf, mocker, caplog) -> None: def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker, None) chat = Chat(0xdeadbeef, 0) update = Update(randint(1, 100)) @@ -145,7 +142,6 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: def test_authorized_only_exception(default_conf, mocker, caplog) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) update = Update(randint(1, 100)) @@ -178,7 +174,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: default_conf['telegram']['enabled'] = False default_conf['telegram']['chat_id'] = 123 - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -227,7 +222,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -280,7 +274,6 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -332,7 +325,6 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, limit_sell_order, markets, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_exchange(mocker) mocker.patch( 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', @@ -403,7 +395,6 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -439,7 +430,6 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_exchange(mocker) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( @@ -544,7 +534,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None: 'last': 0.1, } - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=mock_balance) mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker) @@ -631,7 +620,6 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None: def test_stop_handle(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -651,7 +639,6 @@ def test_stop_handle(default_conf, update, mocker) -> None: def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -671,7 +658,6 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: def test_stopbuy_handle(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -691,7 +677,6 @@ def test_stopbuy_handle(default_conf, update, mocker) -> None: def test_reload_conf_handle(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -712,7 +697,6 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None: def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, markets, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) @@ -762,7 +746,6 @@ def test_forcesell_handle(default_conf, update, ticker, fee, def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_down, markets, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -816,7 +799,6 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_exchange(mocker) mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) @@ -862,7 +844,6 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) msg_mock = MagicMock() @@ -902,7 +883,6 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: def test_forcebuy_handle(default_conf, update, markets, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) @@ -939,7 +919,6 @@ def test_forcebuy_handle(default_conf, update, markets, mocker) -> None: def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> None: - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) @@ -962,7 +941,6 @@ def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> Non def test_performance_handle(default_conf, update, ticker, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -1002,7 +980,6 @@ def test_performance_handle(default_conf, update, ticker, fee, def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None: - patch_coinmarketcap(mocker) patch_exchange(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -1043,7 +1020,6 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non def test_whitelist_static(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -1061,7 +1037,6 @@ def test_whitelist_static(default_conf, update, mocker) -> None: def test_whitelist_dynamic(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -1083,7 +1058,6 @@ def test_whitelist_dynamic(default_conf, update, mocker) -> None: def test_blacklist_static(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -1108,7 +1082,6 @@ def test_blacklist_static(default_conf, update, mocker) -> None: def test_edge_disabled(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -1126,7 +1099,6 @@ def test_edge_disabled(default_conf, update, mocker) -> None: def test_edge_enabled(edge_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( return_value={ @@ -1150,7 +1122,6 @@ def test_edge_enabled(edge_conf, update, mocker) -> None: def test_help_handle(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -1167,7 +1138,6 @@ def test_help_handle(default_conf, update, mocker) -> None: def test_version_handle(default_conf, update, mocker) -> None: - patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -1394,7 +1364,6 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: def test__send_msg(default_conf, mocker) -> None: - patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) bot = MagicMock() freqtradebot = get_patched_freqtradebot(mocker, default_conf) @@ -1406,7 +1375,6 @@ def test__send_msg(default_conf, mocker) -> None: def test__send_msg_network_error(default_conf, mocker, caplog) -> None: - patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) bot = MagicMock() bot.send_message = MagicMock(side_effect=NetworkError('Oh snap')) From 1a61bf7bff0003b7632ef20609aff3e83e99bb87 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Mar 2019 13:48:30 +0100 Subject: [PATCH 38/59] sort imports --- freqtrade/tests/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 6478706a4..cba754199 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -4,7 +4,6 @@ import logging import re from datetime import datetime from functools import reduce -from typing import Dict, Optional from unittest.mock import MagicMock, PropertyMock import arrow @@ -13,8 +12,8 @@ from telegram import Chat, Message, Update from freqtrade import constants from freqtrade.data.converter import parse_ticker_dataframe -from freqtrade.exchange import Exchange from freqtrade.edge import Edge, PairInfo +from freqtrade.exchange import Exchange from freqtrade.freqtradebot import FreqtradeBot from freqtrade.resolvers import ExchangeResolver From 9b38c04579d22bc371a6ff417a21397ae4de05ac Mon Sep 17 00:00:00 2001 From: Misagh Date: Sun, 31 Mar 2019 13:15:35 +0200 Subject: [PATCH 39/59] negating SL pct and adding tests --- freqtrade/persistence.py | 6 +++--- freqtrade/tests/test_persistence.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 5be61393e..5a18a922a 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -240,16 +240,16 @@ class Trade(_DECL_BASE): if not self.stop_loss: logger.debug("assigning new stop loss") self.stop_loss = new_loss - self.stop_loss_pct = stoploss + self.stop_loss_pct = -1 * abs(stoploss) self.initial_stop_loss = new_loss - self.initial_stop_loss_pct = stoploss + self.initial_stop_loss_pct = -1 * abs(stoploss) self.stoploss_last_update = datetime.utcnow() # evaluate if the stop loss needs to be updated else: if new_loss > self.stop_loss: # stop losses only walk up, never down! self.stop_loss = new_loss - self.stop_loss_pct = stoploss + self.stop_loss_pct = -1 * abs(stoploss) self.stoploss_last_update = datetime.utcnow() logger.debug("adjusted stop loss") else: diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 042237ce7..f57a466e3 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -599,32 +599,42 @@ def test_adjust_stop_loss(fee): trade.adjust_stop_loss(trade.open_rate, 0.05, True) assert trade.stop_loss == 0.95 + assert trade.stop_loss_pct == -0.05 assert trade.initial_stop_loss == 0.95 + assert trade.initial_stop_loss_pct == -0.05 # Get percent of profit with a lower rate trade.adjust_stop_loss(0.96, 0.05) assert trade.stop_loss == 0.95 + assert trade.stop_loss_pct == -0.05 assert trade.initial_stop_loss == 0.95 + assert trade.initial_stop_loss_pct == -0.05 # Get percent of profit with a custom rate (Higher than open rate) trade.adjust_stop_loss(1.3, -0.1) assert round(trade.stop_loss, 8) == 1.17 + assert trade.stop_loss_pct == -0.1 assert trade.initial_stop_loss == 0.95 + assert trade.initial_stop_loss_pct == -0.05 # current rate lower again ... should not change trade.adjust_stop_loss(1.2, 0.1) assert round(trade.stop_loss, 8) == 1.17 assert trade.initial_stop_loss == 0.95 + assert trade.initial_stop_loss_pct == -0.05 # current rate higher... should raise stoploss trade.adjust_stop_loss(1.4, 0.1) assert round(trade.stop_loss, 8) == 1.26 assert trade.initial_stop_loss == 0.95 + assert trade.initial_stop_loss_pct == -0.05 # Initial is true but stop_loss set - so doesn't do anything trade.adjust_stop_loss(1.7, 0.1, True) assert round(trade.stop_loss, 8) == 1.26 assert trade.initial_stop_loss == 0.95 + assert trade.initial_stop_loss_pct == -0.05 + assert trade.stop_loss_pct == -0.1 def test_adjust_min_max_rates(fee): From 707a5fca919d1561413733f3182357f7cffdd8d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 13:30:22 +0200 Subject: [PATCH 40/59] ifix typos in full_json_example --- config_full.json.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 357a8f525..58d3d3ac6 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -39,12 +39,12 @@ "buy": "limit", "sell": "limit", "stoploss": "market", - "stoploss_on_exchange": "false", + "stoploss_on_exchange": false, "stoploss_on_exchange_interval": 60 }, "order_time_in_force": { "buy": "gtc", - "sell": "gtc", + "sell": "gtc" }, "pairlist": { "method": "VolumePairList", From 93229fc54b840fd7f6643b59072270004ae607ac Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 31 Mar 2019 12:38:03 +0000 Subject: [PATCH 41/59] Update ccxt from 1.18.415 to 1.18.418 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b2dee3542..6e21e879d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.415 +ccxt==1.18.418 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From c28a0374f15e6fbd2660a1dc1a08daebf8f91d76 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 31 Mar 2019 12:38:04 +0000 Subject: [PATCH 42/59] Update pytest-mock from 1.10.2 to 1.10.3 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 56d7964c3..0592b99e8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ flake8==3.7.7 flake8-type-annotations==0.1.0 flake8-tidy-imports==2.0.0 pytest==4.3.1 -pytest-mock==1.10.2 +pytest-mock==1.10.3 pytest-asyncio==0.10.0 pytest-cov==2.6.1 coveralls==1.7.0 From 4fa736114c1321e274803a4235d08c7e684f0043 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 15:30:22 +0200 Subject: [PATCH 43/59] Don't set order_id to none here - it's used in "update_open_order". should fix bugs observed in #1371 connected to stoploss --- freqtrade/freqtradebot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6f1fb2c99..291b74913 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -472,7 +472,6 @@ class FreqtradeBot(object): stake_amount = order['cost'] amount = order['amount'] buy_limit_filled_price = order['price'] - order_id = None self.rpc.send_msg({ 'type': RPCMessageType.BUY_NOTIFICATION, From 8f4cca47e9a31d08015bc0093d252b4a202f80e5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 15:39:41 +0200 Subject: [PATCH 44/59] Refactor update_open_order into it's own function --- freqtrade/freqtradebot.py | 41 ++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 291b74913..f78a9078e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -530,24 +530,7 @@ class FreqtradeBot(object): :return: True if executed """ try: - # Get order details for actual price per unit - if trade.open_order_id: - # Update trade with order values - logger.info('Found open order for %s', trade) - order = self.exchange.get_order(trade.open_order_id, trade.pair) - # Try update amount (binance-fix) - try: - new_amount = self.get_real_amount(trade, order) - if order['amount'] != new_amount: - order['amount'] = new_amount - # Fee was applied, so set to 0 - trade.fee_open = 0 - - except OperationalException as exception: - logger.warning("Could not update trade amount: %s", exception) - - # This handles both buy and sell orders! - trade.update(order) + self.update_open_order(trade) if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open: result = self.handle_stoploss_on_exchange(trade) @@ -612,6 +595,28 @@ class FreqtradeBot(object): f"(from {order_amount} to {real_amount}) from Trades") return real_amount + def update_open_order(self, trade, action_order: dict = None): + """ + Checks trades with open orders and updates the amount if necessary + """ + # Get order details for actual price per unit + if trade.open_order_id: + # Update trade with order values + logger.info('Found open order for %s', trade) + order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair) + # Try update amount (binance-fix) + try: + new_amount = self.get_real_amount(trade, order) + if order['amount'] != new_amount: + order['amount'] = new_amount + # Fee was applied, so set to 0 + trade.fee_open = 0 + + except OperationalException as exception: + logger.warning("Could not update trade amount: %s", exception) + + trade.update(order) + def get_sell_rate(self, pair: str, refresh: bool) -> float: """ Get sell rate - either using get-ticker bid or first bid based on orderbook From f11a1b01228801dd5ea5a6877c2f819b2564d236 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 15:40:16 +0200 Subject: [PATCH 45/59] Call update_open_order inline with buy captures FOK / market orders --- freqtrade/freqtradebot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f78a9078e..74c74324f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -500,6 +500,10 @@ class FreqtradeBot(object): ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] ) + # Update fees if order is closed already. + if order_status == 'closed': + self.update_open_order(trade, order) + Trade.session.add(trade) Trade.session.flush() From 5c8fbe2c6ff44d36322f3268deabdbe29c6e8779 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 15:41:10 +0200 Subject: [PATCH 46/59] Handle exception for stoploss independently of sell order --- freqtrade/freqtradebot.py | 67 ++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 74c74324f..a06e146f9 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -691,43 +691,44 @@ class FreqtradeBot(object): """ result = False + try: + # If trade is open and the buy order is fulfilled but there is no stoploss, + # then we add a stoploss on exchange + if not trade.open_order_id and not trade.stoploss_order_id: + if self.edge: + stoploss = self.edge.stoploss(pair=trade.pair) + else: + stoploss = self.strategy.stoploss - # If trade is open and the buy order is fulfilled but there is no stoploss, - # then we add a stoploss on exchange - if not trade.open_order_id and not trade.stoploss_order_id: - if self.edge: - stoploss = self.edge.stoploss(pair=trade.pair) - else: - stoploss = self.strategy.stoploss + stop_price = trade.open_rate * (1 + stoploss) - stop_price = trade.open_rate * (1 + stoploss) + # limit price should be less than stop price. + # 0.99 is arbitrary here. + limit_price = stop_price * 0.99 - # limit price should be less than stop price. - # 0.99 is arbitrary here. - limit_price = stop_price * 0.99 - - stoploss_order_id = self.exchange.stoploss_limit( - pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price - )['id'] - trade.stoploss_order_id = str(stoploss_order_id) - trade.stoploss_last_update = datetime.now() - - # Or the trade open and there is already a stoploss on exchange. - # so we check if it is hit ... - elif trade.stoploss_order_id: - logger.debug('Handling stoploss on exchange %s ...', trade) - order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) - if order['status'] == 'closed': - trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value - trade.update(order) - self.notify_sell(trade) - result = True - elif self.config.get('trailing_stop', False): - # if trailing stoploss is enabled we check if stoploss value has changed - # in which case we cancel stoploss order and put another one with new - # value immediately - self.handle_trailing_stoploss_on_exchange(trade, order) + stoploss_order_id = self.exchange.stoploss_limit( + pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price + )['id'] + trade.stoploss_order_id = str(stoploss_order_id) + trade.stoploss_last_update = datetime.now() + # Or the trade open and there is already a stoploss on exchange. + # so we check if it is hit ... + elif trade.stoploss_order_id: + logger.debug('Handling stoploss on exchange %s ...', trade) + order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) + if order['status'] == 'closed': + trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value + trade.update(order) + self.notify_sell(trade) + result = True + elif self.config.get('trailing_stop', False): + # if trailing stoploss is enabled we check if stoploss value has changed + # in which case we cancel stoploss order and put another one with new + # value immediately + self.handle_trailing_stoploss_on_exchange(trade, order) + except DependencyException as exception: + logger.warning('Unable to create stoploss order: %s', exception) return result def handle_trailing_stoploss_on_exchange(self, trade: Trade, order): From e46dac3fbd2846d7a22d1c54ca9ff1a64368d2e8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 15:45:22 +0200 Subject: [PATCH 47/59] Test stoploss does not raise dependencyexception --- 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 5bf0bfcbb..e53d26793 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1031,6 +1031,13 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, assert trade.stoploss_order_id is None assert trade.is_open is False + mocker.patch( + 'freqtrade.exchange.Exchange.stoploss_limit', + side_effect=DependencyException() + ) + freqtrade.handle_stoploss_on_exchange(trade) + assert log_has('Unable to create stoploss order: ', caplog.record_tuples) + def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, markets, limit_buy_order, limit_sell_order) -> None: From b2ad402df4ec8b6a4e35a2abaf368b09fce1bda8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 15:51:45 +0200 Subject: [PATCH 48/59] Split tests for update-open_order --- freqtrade/tests/test_freqtradebot.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index e53d26793..38a6df695 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1306,22 +1306,32 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf, trade.open_order_id = '123' trade.open_fee = 0.001 + # Test raise of DependencyException exception + mocker.patch( + 'freqtrade.freqtradebot.FreqtradeBot.update_open_order', + side_effect=DependencyException() + ) + freqtrade.process_maybe_execute_sell(trade) + assert log_has('Unable to sell trade: ', caplog.record_tuples) + + +def test_update_open_order_exception(mocker, default_conf, + limit_buy_order, caplog) -> None: + freqtrade = get_patched_freqtradebot(mocker, default_conf) + mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) + + trade = MagicMock() + trade.open_order_id = '123' + trade.open_fee = 0.001 + # Test raise of OperationalException exception mocker.patch( 'freqtrade.freqtradebot.FreqtradeBot.get_real_amount', side_effect=OperationalException() ) - freqtrade.process_maybe_execute_sell(trade) + freqtrade.update_open_order(trade) assert log_has('Could not update trade amount: ', caplog.record_tuples) - # Test raise of DependencyException exception - mocker.patch( - 'freqtrade.freqtradebot.FreqtradeBot.get_real_amount', - side_effect=DependencyException() - ) - freqtrade.process_maybe_execute_sell(trade) - assert log_has('Unable to sell trade: ', caplog.record_tuples) - def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, markets, mocker) -> None: From 0ddafeeabf1322fa679a1c56f77ed709cd3ac4bf Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 16:05:00 +0200 Subject: [PATCH 49/59] Split test for open_orders from maybe_sell --- freqtrade/tests/test_freqtradebot.py | 39 +++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 38a6df695..a3b01c542 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1291,7 +1291,7 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo trade.is_open = True trade.open_order_id = None # Assert we call handle_trade() if trade is feasible for execution - assert freqtrade.process_maybe_execute_sell(trade) + freqtrade.update_open_order(trade) regexp = re.compile('Found open order for.*') assert filter(regexp.match, caplog.record_tuples) @@ -1315,6 +1315,43 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf, assert log_has('Unable to sell trade: ', caplog.record_tuples) +def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> None: + freqtrade = get_patched_freqtradebot(mocker, default_conf) + + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', + return_value=limit_buy_order['amount']) + + trade = Trade() + # Mock session away + Trade.session = MagicMock() + trade.open_order_id = '123' + trade.open_fee = 0.001 + freqtrade.update_open_order(trade) + # Test amount not modified by fee-logic + assert not log_has_re(r'Applying fee to .*', caplog.record_tuples) + assert trade.open_order_id is None + assert trade.amount == limit_buy_order['amount'] + + trade.open_order_id = '123' + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81) + assert trade.amount != 90.81 + # test amount modified by fee-logic + freqtrade.update_open_order(trade) + assert trade.amount == 90.81 + assert trade.open_order_id is None + + trade.is_open = True + trade.open_order_id = None + # Assert we call handle_trade() if trade is feasible for execution + freqtrade.update_open_order(trade) + + regexp = re.compile('Found open order for.*') + assert filter(regexp.match, caplog.record_tuples) + + def test_update_open_order_exception(mocker, default_conf, limit_buy_order, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From 19d3a0cbacd21b58589abc4c5100fe0b335bb69f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 19:41:17 +0200 Subject: [PATCH 50/59] Update comment --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a06e146f9..5b1e5625d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -500,7 +500,7 @@ class FreqtradeBot(object): ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] ) - # Update fees if order is closed already. + # Update fees if order is closed if order_status == 'closed': self.update_open_order(trade, order) From 7be90f71d379b235d2cbfa507b5d528bf42756fc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Mar 2019 19:56:01 +0200 Subject: [PATCH 51/59] Add test as called from execute_buy --- freqtrade/tests/test_freqtradebot.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index a3b01c542..af8fc3b09 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1352,6 +1352,26 @@ def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> Non assert filter(regexp.match, caplog.record_tuples) +def test_update_open_order_withorderdict(default_conf, trades_for_order, limit_buy_order, mocker): + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) + # get_order should not be called!! + mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError)) + patch_exchange(mocker) + Trade.session = MagicMock() + amount = sum(x['amount'] for x in trades_for_order) + freqtrade = get_patched_freqtradebot(mocker, default_conf) + trade = Trade( + pair='LTC/ETH', + amount=amount, + exchange='binance', + open_rate=0.245441, + open_order_id="123456" + ) + freqtrade.update_open_order(trade, limit_buy_order) + assert trade.amount != amount + assert trade.amount == limit_buy_order['amount'] + + def test_update_open_order_exception(mocker, default_conf, limit_buy_order, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) From 97b31352c2640909a4e05b330354599767732f1c Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 1 Apr 2019 12:38:06 +0000 Subject: [PATCH 52/59] Update ccxt from 1.18.418 to 1.18.420 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6e21e879d..bdad28181 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.418 +ccxt==1.18.420 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 061f91ba4173d5e4d329c0a5623690a2ceccb00c Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 1 Apr 2019 12:38:07 +0000 Subject: [PATCH 53/59] Update pytest from 4.3.1 to 4.4.0 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0592b99e8..69082587a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ flake8==3.7.7 flake8-type-annotations==0.1.0 flake8-tidy-imports==2.0.0 -pytest==4.3.1 +pytest==4.4.0 pytest-mock==1.10.3 pytest-asyncio==0.10.0 pytest-cov==2.6.1 From ab579587f29e3802f1c038e804214a228a4b1b26 Mon Sep 17 00:00:00 2001 From: Misagh Date: Mon, 1 Apr 2019 19:13:45 +0200 Subject: [PATCH 54/59] adding percentage to telegram status messages --- docs/telegram-usage.md | 6 ++---- freqtrade/rpc/rpc.py | 3 +++ freqtrade/rpc/telegram.py | 6 ++++-- freqtrade/tests/rpc/test_rpc.py | 6 ++++++ freqtrade/tests/rpc/test_rpc_telegram.py | 3 +++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 1ca61e54a..381a47ae9 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -65,16 +65,14 @@ Once all positions are sold, run `/stop` to completely stop the bot. For each open trade, the bot will send you the following message. -> **Trade ID:** `123` +> **Trade ID:** `123` `(since 1 days ago)` > **Current Pair:** CVC/BTC > **Open Since:** `1 days ago` > **Amount:** `26.64180098` > **Open Rate:** `0.00007489` -> **Close Rate:** `None` > **Current Rate:** `0.00007489` -> **Close Profit:** `None` > **Current Profit:** `12.95%` -> **Open Order:** `None` +> **Stoploss:** `0.00007389 (-0.02%)` ### /status table diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 79db08fd3..5308c9d51 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -111,6 +111,9 @@ class RPC(object): close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), stop_loss=trade.stop_loss, + stop_loss_pct=trade.stop_loss_pct, + initial_stop_loss=trade.initial_stop_loss, + initial_stop_loss_pct=trade.initial_stop_loss_pct, open_order='({} {} rem={:.8f})'.format( order['type'], order['side'], order['remaining'] ) if order else None, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7b36e8a1f..ca9b376ec 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -197,7 +197,7 @@ class Telegram(RPC): messages = [] for r in results: lines = [ - "*Trade ID:* `{trade_id}` (since `{date}`)", + "*Trade ID:* `{trade_id}` `(since {date})`", "*Current Pair:* {pair}", "*Amount:* `{amount}`", "*Open Rate:* `{open_rate:.8f}`", @@ -205,7 +205,9 @@ class Telegram(RPC): "*Current Rate:* `{current_rate:.8f}`", "*Close Profit:* `{close_profit}`" if r['close_profit'] else "", "*Current Profit:* `{current_profit:.2f}%`", - "*Stoploss:* `{stop_loss:.8f}`", + "*Initial Stoploss:* `{initial_stop_loss:.8f}` `({initial_stop_loss_pct}%)`" + if r['stop_loss'] != r['initial_stop_loss'] else "", + "*Stoploss:* `{stop_loss:.8f}` `({stop_loss_pct}%)`", "*Open Order:* `{open_order}`" if r['open_order'] else "", ] messages.append("\n".join(filter(None, lines)).format(**r)) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 627768ec2..b454f9cd8 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -59,6 +59,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: 'close_profit': None, 'current_profit': -0.59, 'stop_loss': 0.0, + 'initial_stop_loss': 0.0, + 'initial_stop_loss_pct': None, + 'stop_loss_pct': None, 'open_order': '(limit buy rem=0.00000000)' } == results[0] @@ -80,6 +83,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: 'close_profit': None, 'current_profit': ANY, 'stop_loss': 0.0, + 'initial_stop_loss': 0.0, + 'initial_stop_loss_pct': None, + 'stop_loss_pct': None, 'open_order': '(limit buy rem=0.00000000)' } == results[0] diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index fb2d71d4f..b6d12fe41 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -196,7 +196,10 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: 'amount': 90.99181074, 'close_profit': None, 'current_profit': -0.59, + 'initial_stop_loss': 1.098e-05, 'stop_loss': 1.099e-05, + 'initial_stop_loss_pct': -0.05, + 'stop_loss_pct': -0.01, 'open_order': '(limit buy rem=0.00000000)' }]), _status_table=status_table, From a3b0135557bf221760f2f19fe9db323e9f7b2191 Mon Sep 17 00:00:00 2001 From: Misagh Date: Mon, 1 Apr 2019 19:25:13 +0200 Subject: [PATCH 55/59] documentation added for telegram --- docs/bot-usage.md | 2 +- docs/telegram-usage.md | 68 +++++++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 5ec390d5c..55988985a 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -79,7 +79,7 @@ prevent unintended disclosure of sensitive private data when you publish example of your configuration in the project issues or in the Internet. See more details on this technique with examples in the documentation page on -[configuration](bot-configuration.md). +[configuration](configuration.md). ### How to use **--strategy**? diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 381a47ae9..4cc8eaa5c 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -65,14 +65,14 @@ Once all positions are sold, run `/stop` to completely stop the bot. For each open trade, the bot will send you the following message. -> **Trade ID:** `123` `(since 1 days ago)` -> **Current Pair:** CVC/BTC -> **Open Since:** `1 days ago` -> **Amount:** `26.64180098` -> **Open Rate:** `0.00007489` -> **Current Rate:** `0.00007489` -> **Current Profit:** `12.95%` -> **Stoploss:** `0.00007389 (-0.02%)` +> **Trade ID:** `123` `(since 1 days ago)` +> **Current Pair:** CVC/BTC +> **Open Since:** `1 days ago` +> **Amount:** `26.64180098` +> **Open Rate:** `0.00007489` +> **Current Rate:** `0.00007489` +> **Current Profit:** `12.95%` +> **Stoploss:** `0.00007389 (-0.02%)` ### /status table @@ -97,18 +97,18 @@ current max Return a summary of your profit/loss and performance. -> **ROI:** Close trades -> ∙ `0.00485701 BTC (258.45%)` -> ∙ `62.968 USD` -> **ROI:** All trades -> ∙ `0.00255280 BTC (143.43%)` -> ∙ `33.095 EUR` -> -> **Total Trade Count:** `138` -> **First Trade opened:** `3 days ago` -> **Latest Trade opened:** `2 minutes ago` -> **Avg. Duration:** `2:33:45` -> **Best Performing:** `PAY/BTC: 50.23%` +> **ROI:** Close trades +> ∙ `0.00485701 BTC (258.45%)` +> ∙ `62.968 USD` +> **ROI:** All trades +> ∙ `0.00255280 BTC (143.43%)` +> ∙ `33.095 EUR` +> +> **Total Trade Count:** `138` +> **First Trade opened:** `3 days ago` +> **Latest Trade opened:** `2 minutes ago` +> **Avg. Duration:** `2:33:45` +> **Best Performing:** `PAY/BTC: 50.23%` ### /forcesell @@ -126,26 +126,26 @@ Note that for this to work, `forcebuy_enable` needs to be set to true. Return the performance of each crypto-currency the bot has sold. > Performance: -> 1. `RCN/BTC 57.77%` -> 2. `PAY/BTC 56.91%` -> 3. `VIB/BTC 47.07%` -> 4. `SALT/BTC 30.24%` -> 5. `STORJ/BTC 27.24%` -> ... +> 1. `RCN/BTC 57.77%` +> 2. `PAY/BTC 56.91%` +> 3. `VIB/BTC 47.07%` +> 4. `SALT/BTC 30.24%` +> 5. `STORJ/BTC 27.24%` +> ... ### /balance Return the balance of all crypto-currency your have on the exchange. -> **Currency:** BTC -> **Available:** 3.05890234 -> **Balance:** 3.05890234 -> **Pending:** 0.0 +> **Currency:** BTC +> **Available:** 3.05890234 +> **Balance:** 3.05890234 +> **Pending:** 0.0 -> **Currency:** CVC -> **Available:** 86.64180098 -> **Balance:** 86.64180098 -> **Pending:** 0.0 +> **Currency:** CVC +> **Available:** 86.64180098 +> **Balance:** 86.64180098 +> **Pending:** 0.0 ### /daily From 0cfdce0d5e69a8b79c95bbac839ed37583dcd9b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Apr 2019 07:12:48 +0200 Subject: [PATCH 56/59] Update function name from update_open_order to update_trade_state --- freqtrade/freqtradebot.py | 6 +++--- freqtrade/tests/test_freqtradebot.py | 28 ++++++++++------------------ 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5b1e5625d..55ef6f611 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -502,7 +502,7 @@ class FreqtradeBot(object): # Update fees if order is closed if order_status == 'closed': - self.update_open_order(trade, order) + self.update_trade_state(trade, order) Trade.session.add(trade) Trade.session.flush() @@ -534,7 +534,7 @@ class FreqtradeBot(object): :return: True if executed """ try: - self.update_open_order(trade) + self.update_trade_state(trade) if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open: result = self.handle_stoploss_on_exchange(trade) @@ -599,7 +599,7 @@ class FreqtradeBot(object): f"(from {order_amount} to {real_amount}) from Trades") return real_amount - def update_open_order(self, trade, action_order: dict = None): + def update_trade_state(self, trade, action_order: dict = None): """ Checks trades with open orders and updates the amount if necessary """ diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index af8fc3b09..416a085ad 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1288,14 +1288,6 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo # test amount modified by fee-logic assert not freqtrade.process_maybe_execute_sell(trade) - trade.is_open = True - trade.open_order_id = None - # Assert we call handle_trade() if trade is feasible for execution - freqtrade.update_open_order(trade) - - regexp = re.compile('Found open order for.*') - assert filter(regexp.match, caplog.record_tuples) - def test_process_maybe_execute_sell_exception(mocker, default_conf, limit_buy_order, caplog) -> None: @@ -1308,14 +1300,14 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf, # Test raise of DependencyException exception mocker.patch( - 'freqtrade.freqtradebot.FreqtradeBot.update_open_order', + 'freqtrade.freqtradebot.FreqtradeBot.update_trade_state', side_effect=DependencyException() ) freqtrade.process_maybe_execute_sell(trade) assert log_has('Unable to sell trade: ', caplog.record_tuples) -def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> None: +def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) @@ -1329,7 +1321,7 @@ def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> Non Trade.session = MagicMock() trade.open_order_id = '123' trade.open_fee = 0.001 - freqtrade.update_open_order(trade) + freqtrade.update_trade_state(trade) # Test amount not modified by fee-logic assert not log_has_re(r'Applying fee to .*', caplog.record_tuples) assert trade.open_order_id is None @@ -1339,20 +1331,20 @@ def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> Non mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81) assert trade.amount != 90.81 # test amount modified by fee-logic - freqtrade.update_open_order(trade) + freqtrade.update_trade_state(trade) assert trade.amount == 90.81 assert trade.open_order_id is None trade.is_open = True trade.open_order_id = None # Assert we call handle_trade() if trade is feasible for execution - freqtrade.update_open_order(trade) + freqtrade.update_trade_state(trade) regexp = re.compile('Found open order for.*') assert filter(regexp.match, caplog.record_tuples) -def test_update_open_order_withorderdict(default_conf, trades_for_order, limit_buy_order, mocker): +def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_buy_order, mocker): mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) # get_order should not be called!! mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError)) @@ -1367,13 +1359,13 @@ def test_update_open_order_withorderdict(default_conf, trades_for_order, limit_b open_rate=0.245441, open_order_id="123456" ) - freqtrade.update_open_order(trade, limit_buy_order) + freqtrade.update_trade_state(trade, limit_buy_order) assert trade.amount != amount assert trade.amount == limit_buy_order['amount'] -def test_update_open_order_exception(mocker, default_conf, - limit_buy_order, caplog) -> None: +def test_update_trade_state_exception(mocker, default_conf, + limit_buy_order, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) @@ -1386,7 +1378,7 @@ def test_update_open_order_exception(mocker, default_conf, 'freqtrade.freqtradebot.FreqtradeBot.get_real_amount', side_effect=OperationalException() ) - freqtrade.update_open_order(trade) + freqtrade.update_trade_state(trade) assert log_has('Could not update trade amount: ', caplog.record_tuples) From b9b76977b69c3e846944517eb86d57d72023f041 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 2 Apr 2019 12:38:06 +0000 Subject: [PATCH 57/59] Update ccxt from 1.18.420 to 1.18.425 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bdad28181..83d77b693 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.420 +ccxt==1.18.425 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From a6daf0d991469e5db365a3106bd848a22fb1f5a7 Mon Sep 17 00:00:00 2001 From: Misagh Date: Tue, 2 Apr 2019 20:00:58 +0200 Subject: [PATCH 58/59] formatting pct --- freqtrade/rpc/telegram.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ca9b376ec..9d1f9f206 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -205,9 +205,11 @@ class Telegram(RPC): "*Current Rate:* `{current_rate:.8f}`", "*Close Profit:* `{close_profit}`" if r['close_profit'] else "", "*Current Profit:* `{current_profit:.2f}%`", - "*Initial Stoploss:* `{initial_stop_loss:.8f}` `({initial_stop_loss_pct}%)`" + "*Initial Stoploss:* `{initial_stop_loss:.8f}` " + + ("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "") if r['stop_loss'] != r['initial_stop_loss'] else "", - "*Stoploss:* `{stop_loss:.8f}` `({stop_loss_pct}%)`", + "*Stoploss:* `{stop_loss:.8f}` " + + ("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""), "*Open Order:* `{open_order}`" if r['open_order'] else "", ] messages.append("\n".join(filter(None, lines)).format(**r)) From 7b39a3084fc15406cd56fc17ea4eb8a539d47d13 Mon Sep 17 00:00:00 2001 From: Misagh Date: Tue, 2 Apr 2019 20:08:10 +0200 Subject: [PATCH 59/59] formatting and readability --- freqtrade/rpc/telegram.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 9d1f9f206..2d822820f 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -205,12 +205,17 @@ class Telegram(RPC): "*Current Rate:* `{current_rate:.8f}`", "*Close Profit:* `{close_profit}`" if r['close_profit'] else "", "*Current Profit:* `{current_profit:.2f}%`", + + # Adding initial stoploss only if it is different from stoploss "*Initial Stoploss:* `{initial_stop_loss:.8f}` " + ("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "") if r['stop_loss'] != r['initial_stop_loss'] else "", + + # Adding stoploss and stoploss percentage only if it is not None "*Stoploss:* `{stop_loss:.8f}` " + ("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""), - "*Open Order:* `{open_order}`" if r['open_order'] else "", + + "*Open Order:* `{open_order}`" if r['open_order'] else "" ] messages.append("\n".join(filter(None, lines)).format(**r))