From 54f52fb36664c375ab8b2c4ce64ea08c40cc7e48 Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Fri, 1 Jun 2018 20:50:40 -0700 Subject: [PATCH 01/23] Create stoploss.md --- docs/stoploss.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 docs/stoploss.md diff --git a/docs/stoploss.md b/docs/stoploss.md new file mode 100644 index 000000000..ff93fd1d8 --- /dev/null +++ b/docs/stoploss.md @@ -0,0 +1,50 @@ +# Stop Loss support + +at this stage the bot contains the following stoploss support modes: + +1. static stop loss, defined in either the strategy or configuration + +2. trailing stop loss, defined in the configuration + +3. trailing stop loss, custom positive loss, defined in configuration + +## Static Stop Loss + +This is very simple, basically you define a stop loss of x in your strategy file or alternative in the configuration, which +will overwrite the strategy definition. This will basically try to sell your asset, the second the loss exceeds the defined loss. + +## Trail Stop Loss + +The initial value for this stop loss, is defined in your strategy or configuration. Just as you would define your Stop Loss normally. +To enable this Feauture all you have to do, is to define the configuration element: + +``` +"trailing_stop" : True +``` +This will now actiave an algorithm, whihch automatically moves up your stop loss, every time the price of your asset increases. + +For example, simplified math, + +* you buy an asset at a price of 100$ +* your stop loss is defined at 2% +* which means your stop loss, gets triggered once your asset dropped below 98$ +* assuming your asset now increases in proce to 102$ +* your stop loss, will now be 2% of 102$ or 99.96$ +* now your asset drops in value to 101$, your stop loss, will still be 99.96$ + +basically what this means, is that your stop loss will be adjusted to be always be 2% of the highest observed price + +### Custom positive loss + +due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your buy turns positive, +the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you are in the +black, it will be changed to be only a 1% stop loss + +this can be configured in the main confiuration file, the following way: + +``` + "trailing_stop": { + "positive" : 0.01 + }, +``` +The 0.01 would translate to a 1% stop loss, once you hit profit. From 257e1847b121dce297f5dc540ca52e3656c74517 Mon Sep 17 00:00:00 2001 From: peterkorodi Date: Sun, 17 Jun 2018 11:00:34 +0200 Subject: [PATCH 02/23] Update stoploss.md --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index ff93fd1d8..110cfa0d1 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -40,7 +40,7 @@ due to demand, it is possible to have a default stop loss, when you are in the r the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you are in the black, it will be changed to be only a 1% stop loss -this can be configured in the main confiuration file, the following way: +this can be configured in the main configuration file, the following way: ``` "trailing_stop": { From 9ac3c559b6028bf0b382ed4a8adc8e0786f6b57c Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 26 Jun 2018 20:26:28 +0200 Subject: [PATCH 03/23] fix some stoploss documentation --- docs/stoploss.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 110cfa0d1..cf6a2bcab 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -1,11 +1,9 @@ # Stop Loss support -at this stage the bot contains the following stoploss support modes: +At this stage the bot contains the following stoploss support modes: 1. static stop loss, defined in either the strategy or configuration - 2. trailing stop loss, defined in the configuration - 3. trailing stop loss, custom positive loss, defined in configuration ## Static Stop Loss @@ -16,35 +14,37 @@ will overwrite the strategy definition. This will basically try to sell your ass ## Trail Stop Loss The initial value for this stop loss, is defined in your strategy or configuration. Just as you would define your Stop Loss normally. -To enable this Feauture all you have to do, is to define the configuration element: +To enable this Feauture all you have to do is to define the configuration element: -``` +``` json "trailing_stop" : True ``` -This will now actiave an algorithm, whihch automatically moves up your stop loss, every time the price of your asset increases. + +This will now activate an algorithm, which automatically moves your stop loss up every time the price of your asset increases. For example, simplified math, * you buy an asset at a price of 100$ * your stop loss is defined at 2% * which means your stop loss, gets triggered once your asset dropped below 98$ -* assuming your asset now increases in proce to 102$ +* assuming your asset now increases to 102$ * your stop loss, will now be 2% of 102$ or 99.96$ * now your asset drops in value to 101$, your stop loss, will still be 99.96$ -basically what this means, is that your stop loss will be adjusted to be always be 2% of the highest observed price +basically what this means is that your stop loss will be adjusted to be always be 2% of the highest observed price ### Custom positive loss -due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your buy turns positive, +Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your buy turns positive, the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you are in the black, it will be changed to be only a 1% stop loss this can be configured in the main configuration file, the following way: -``` +``` json "trailing_stop": { "positive" : 0.01 }, ``` + The 0.01 would translate to a 1% stop loss, once you hit profit. From 243c36b39bda5e126cd9ca6f805e70b15a57deab Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 26 Jun 2018 20:49:07 +0200 Subject: [PATCH 04/23] get persistence.py for stop_loss --- freqtrade/persistence.py | 49 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 20a7db4bb..76a499eed 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -151,6 +151,12 @@ class Trade(_DECL_BASE): open_date = Column(DateTime, nullable=False, default=datetime.utcnow) close_date = Column(DateTime) open_order_id = Column(String) + # absolute value of the stop loss + stop_loss = Column(Float, nullable=True, default=0.0) + # absolute value of the initial stop loss + initial_stop_loss = Column(Float, nullable=True, default=0.0) + # absolute value of the highest reached price + max_rate = Column(Float, nullable=True, default=0.0) def __repr__(self): return 'Trade(id={}, pair={}, amount={:.8f}, open_rate={:.8f}, open_since={})'.format( @@ -161,6 +167,49 @@ class Trade(_DECL_BASE): arrow.get(self.open_date).humanize() if self.is_open else 'closed' ) + def adjust_stop_loss(self, current_price, stoploss): + """ + this adjusts the stop loss to it's most recently observed + setting + :param current_price: + :param stoploss: + :return: + """ + + new_loss = Decimal(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 self.stop_loss is None or self.stop_loss == 0: + logger.debug("assigning new stop loss") + self.stop_loss = new_loss + self.initial_stop_loss = new_loss + + # 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 + logger.debug("adjusted stop loss") + else: + logger.debug("keeping current stop loss") + + logger.debug( + "{} - current price {:.8f}, bought at {:.8f} and calculated " + "stop loss is at: {:.8f} initial stop at {:.8f}. trailing stop loss saved us: {:.8f} " + "and max observed rate was {:.8f}".format( + self.pair, current_price, self.open_rate, + self.initial_stop_loss, + self.stop_loss, float(self.stop_loss) - float(self.initial_stop_loss), + self.max_rate + + )) + def update(self, order: Dict) -> None: """ Updates this entity with amount and actual open/close rates. From 5015bc9bb09d8904131e37d7d26addd47a025e3a Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 26 Jun 2018 22:41:28 +0200 Subject: [PATCH 05/23] slight update to persistence --- freqtrade/persistence.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 76a499eed..db97fc858 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -176,7 +176,7 @@ class Trade(_DECL_BASE): :return: """ - new_loss = Decimal(current_price * (1 - abs(stoploss))) + new_loss = float(current_price * (1 - abs(stoploss))) # keeping track of the highest observed rate for this trade if self.max_rate is None: @@ -200,15 +200,13 @@ class Trade(_DECL_BASE): logger.debug("keeping current stop loss") logger.debug( - "{} - current price {:.8f}, bought at {:.8f} and calculated " - "stop loss is at: {:.8f} initial stop at {:.8f}. trailing stop loss saved us: {:.8f} " - "and max observed rate was {:.8f}".format( - self.pair, current_price, self.open_rate, - self.initial_stop_loss, - self.stop_loss, float(self.stop_loss) - float(self.initial_stop_loss), - self.max_rate - - )) + f"{self.pair} - current price {current_price:.8f}, " + f"bought at {self.open_rate:.8f} and calculated " + f"stop loss is at: {self.initial_stop_loss:.8f} initial " + f"stop at {self.stop_loss:.8f}. " + f"trailing stop loss saved us: " + f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f} " + f"and max observed rate was {self.max_rate:.8f}") def update(self, order: Dict) -> None: """ From 3e167e11705b204bb2dcc504bf511bd7b2d9f6ca Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 26 Jun 2018 22:41:38 +0200 Subject: [PATCH 06/23] update sample configs --- config.json.example | 5 ++++- config_full.json.example | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/config.json.example b/config.json.example index e5bdc95b1..7b53ffa99 100644 --- a/config.json.example +++ b/config.json.example @@ -5,6 +5,9 @@ "fiat_display_currency": "USD", "ticker_interval" : "5m", "dry_run": false, + "trailing_stop": { + "positive" : 0.005 + }, "unfilledtimeout": 600, "bid_strategy": { "ask_last_balance": 0.0 @@ -32,7 +35,7 @@ "experimental": { "use_sell_signal": false, "sell_profit_only": false, - "ignore_roi_if_buy_signal": false + "sell_fullfilled_at_roi": false }, "telegram": { "enabled": true, diff --git a/config_full.json.example b/config_full.json.example index 231384acc..d7d8f7369 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -5,6 +5,7 @@ "fiat_display_currency": "USD", "dry_run": false, "ticker_interval": "5m", + "trailing_stop": true, "minimal_roi": { "40": 0.0, "30": 0.01, From da5be9fbd0ea132a9490ecca007d2e8adf568c34 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 26 Jun 2018 23:06:27 +0200 Subject: [PATCH 07/23] add stop_loss based on work from @berlinguyinca --- config.json.example | 4 +--- config_full.json.example | 3 ++- docs/stoploss.md | 6 ++---- freqtrade/analyze.py | 42 ++++++++++++++++++++++++++++++++++++---- freqtrade/constants.py | 2 ++ 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/config.json.example b/config.json.example index 7b53ffa99..f92849ec9 100644 --- a/config.json.example +++ b/config.json.example @@ -5,9 +5,7 @@ "fiat_display_currency": "USD", "ticker_interval" : "5m", "dry_run": false, - "trailing_stop": { - "positive" : 0.005 - }, + "trailing_stop": false, "unfilledtimeout": 600, "bid_strategy": { "ask_last_balance": 0.0 diff --git a/config_full.json.example b/config_full.json.example index d7d8f7369..244daabac 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -5,7 +5,8 @@ "fiat_display_currency": "USD", "dry_run": false, "ticker_interval": "5m", - "trailing_stop": true, + "trailing_stop": false, + "trailing_stop_positive": 0.005, "minimal_roi": { "40": 0.0, "30": 0.01, diff --git a/docs/stoploss.md b/docs/stoploss.md index cf6a2bcab..db4433a02 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -39,12 +39,10 @@ Due to demand, it is possible to have a default stop loss, when you are in the r the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you are in the black, it will be changed to be only a 1% stop loss -this can be configured in the main configuration file, the following way: +This can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true. ``` json - "trailing_stop": { - "positive" : 0.01 - }, + "trailing_stop_positive": 0.01, ``` The 0.01 would translate to a 1% stop loss, once you hit profit. diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 36e00dd0e..b283e3ae9 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -180,7 +180,7 @@ class Analyze(object): :return: True if trade should be sold, False otherwise """ current_profit = trade.calc_profit_percent(rate) - if self.stop_loss_reached(current_profit=current_profit): + if self.stop_loss_reached(current_rate=rate, trade=trade, current_time=date): return True experimental = self.config.get('experimental', {}) @@ -204,12 +204,46 @@ class Analyze(object): return False - def stop_loss_reached(self, current_profit: float) -> bool: - """Based on current profit of the trade and configured stoploss, decides to sell or not""" + def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime) -> bool: + """ + Based on current profit of the trade and configured (trailing) stoploss, + decides to sell or not + """ + + current_profit = trade.calc_profit_percent(current_rate) + trailing_stop = self.config.get('trailing_stop', False) + if trade.stop_loss is None: + # initially adjust the stop loss to the base value + trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss) + + # evaluate if the stoploss was hit + if self.strategy.stoploss is not None and trade.stop_loss >= current_rate: + + if trailing_stop: + logger.debug( + f"HIT STOP: current price at {current_rate:.6f}, " + f"stop loss is {trade.stop_loss:.6f}, " + f"initial stop loss was at {trade.initial_stop_loss:.6f}, " + f"trade opened at {trade.open_rate:.6f}") + logger.debug(f"trailing stop saved {trade.stop_loss - trade.initial_stop_loss:.6f}") - if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss: logger.debug('Stop loss hit.') return True + + # update the stop loss afterwards, after all by definition it's supposed to be hanging + if trailing_stop: + + # check if we have a special stop loss for positive condition + # and if profit is positive + stop_loss_value = self.strategy.stoploss + if 'trailing_stop_positive' in self.config and current_profit > 0: + + stop_loss_value = self.config.get('trailing_stop_positive') + logger.debug(f"using positive stop loss mode: {stop_loss_value} " + f"since we have profit {current_profit}") + + trade.adjust_stop_loss(current_rate, stop_loss_value) + return False def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 0f12905e3..4ba1a7094 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -61,6 +61,8 @@ CONF_SCHEMA = { 'minProperties': 1 }, 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, + 'trailing_stop': {'type': 'boolean'}, + 'trailing_stop_positive': {'type': 'number', 'minimum': 0}, 'unfilledtimeout': {'type': 'integer', 'minimum': 0}, 'bid_strategy': { 'type': 'object', From 03005bc0f14f9f7747652a13980c8f8a209ded11 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 26 Jun 2018 23:14:12 +0200 Subject: [PATCH 08/23] update documentation --- docs/configuration.md | 42 ++++++++++++++++++++++++++++++++++-------- docs/index.md | 2 ++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 5279ed06e..f24adb61e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,12 +1,15 @@ # Configure the bot + This page explains how to configure your `config.json` file. ## Table of Contents + - [Bot commands](#bot-commands) - [Backtesting commands](#backtesting-commands) - [Hyperopt commands](#hyperopt-commands) ## Setup config.json + We recommend to copy and use the `config.json.example` as a template for your bot configuration. @@ -22,6 +25,8 @@ The table below will list all configuration parameters. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. | `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file. | `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file. +| `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). +| `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached. | `unfilledtimeout` | 0 | No | How long (in minutes) the bot will wait for an unfilled order to complete, after which the order will be cancelled. | `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below. | `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). @@ -41,10 +46,10 @@ The table below will list all configuration parameters. | `strategy_path` | null | No | Adds an additional strategy lookup path (must be a folder). | `internals.process_throttle_secs` | 5 | Yes | Set the process throttle. Value in second. -The definition of each config parameters is in -[misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.py#L205). +The definition of each config parameters is in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.py#L205). ### Understand stake_amount + `stake_amount` is an amount of crypto-currency your bot will use for each trade. The minimal value is 0.0005. If there is not enough crypto-currency in the account an exception is generated. @@ -52,10 +57,12 @@ To allow the bot to trade all the avaliable `stake_currency` in your account set In this case a trade amount is calclulated as `currency_balanse / (max_open_trades - current_open_trades)`. ### Understand minimal_roi + `minimal_roi` is a JSON object where the key is a duration in minutes and the value is the minimum ROI in percent. See the example below: -``` + +``` json "minimal_roi": { "40": 0.0, # Sell after 40 minutes if the profit is not negative "30": 0.01, # Sell after 30 minutes if there is at least 1% profit @@ -69,6 +76,7 @@ value. This parameter is optional. If you use it, it will take over the `minimal_roi` value from the strategy file. ### Understand stoploss + `stoploss` is loss in percentage that should trigger a sale. For example value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional. @@ -77,82 +85,100 @@ Most of the strategy files already include the optimal `stoploss` value. This parameter is optional. If you use it, it will take over the `stoploss` value from the strategy file. +### Understand trailing stoploss + +Go to the [trailing stoploss Documentation](stoploss.md) for details on trailing stoploss. + ### Understand initial_state + `initial_state` is an optional field that defines the initial application state. Possible values are `running` or `stopped`. (default=`running`) If the value is `stopped` the bot has to be started with `/start` first. ### Understand process_throttle_secs + `process_throttle_secs` is an optional field that defines in seconds how long the bot should wait before asking the strategy if we should buy or a sell an asset. After each wait period, the strategy is asked again for every opened trade wether or not we should sell, and for all the remaining pairs (either the dynamic list of pairs or the static list of pairs) if we should buy. ### Understand ask_last_balance + `ask_last_balance` sets the bidding price. Value `0.0` will use `ask` price, `1.0` will use the `last` price and values between those interpolate between ask and last price. Using `ask` price will guarantee quick success in bid, but bot will also end up paying more then would probably have been necessary. ### What values for exchange.name? + Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency exchange markets and trading APIs. The complete up-to-date list can be found in the [CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). However, the bot was tested with only Bittrex and Binance. The bot was tested with the following exchanges: + - [Bittrex](https://bittrex.com/): "bittrex" - [Binance](https://www.binance.com/): "binance" Feel free to test other exchanges and submit your PR to improve the bot. ### What values for fiat_display_currency? + `fiat_display_currency` set the base currency to use for the conversion from coin to fiat in Telegram. The valid values are: "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD". In addition to central bank currencies, a range of cryto currencies are supported. The valid values are: "BTC", "ETH", "XRP", "LTC", "BCH", "USDT". ## Switch to dry-run mode + We recommend starting the bot in dry-run mode to see how your bot will behave and how is the performance of your strategy. In Dry-run mode the bot does not engage your money. It only runs a live simulation without creating trades. ### To switch your bot in Dry-run mode: + 1. Edit your `config.json` file 2. Switch dry-run to true and specify db_url for a persistent db + ```json "dry_run": true, "db_url": "sqlite///tradesv3.dryrun.sqlite", ``` 3. Remove your Exchange API key (change them by fake api credentials) + ```json "exchange": { "name": "bittrex", "key": "key", "secret": "secret", ... -} +} ``` Once you will be happy with your bot performance, you can switch it to production mode. ## Switch to production mode + In production mode, the bot will engage your money. Be careful a wrong strategy can lose all your money. Be aware of what you are doing when you run it in production mode. ### To switch your bot in production mode: + 1. Edit your `config.json` file 2. Switch dry-run to false and don't forget to adapt your database URL if set + ```json "dry_run": false, ``` 3. Insert your Exchange API key (change them by fake api keys) + ```json "exchange": { "name": "bittrex", @@ -160,10 +186,10 @@ you run it in production mode. "secret": "08a9dc6db3d7b53e1acebd9275677f4b0a04f1a5", ... } + ``` -If you have not your Bittrex API key yet, -[see our tutorial](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md). +If you have not your Bittrex API key yet, [see our tutorial](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md). ## Next step -Now you have configured your config.json, the next step is to -[start your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md). + +Now you have configured your config.json, the next step is to [start your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md). diff --git a/docs/index.md b/docs/index.md index afde2d5eb..fd6bf4378 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,5 @@ # freqtrade documentation + Welcome to freqtrade documentation. Please feel free to contribute to this documentation if you see it became outdated by sending us a Pull-request. Do not hesitate to reach us on @@ -6,6 +7,7 @@ Pull-request. Do not hesitate to reach us on if you do not find the answer to your questions. ## Table of Contents + - [Pre-requisite](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md) - [Setup your Bittrex account](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-bittrex-account) - [Setup your Telegram bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-telegram-bot) From a3708bc56e17912af630cd2258f2920ec9d4ec17 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 26 Jun 2018 23:40:20 +0200 Subject: [PATCH 09/23] add missing test --- freqtrade/tests/test_analyze.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/tests/test_analyze.py b/freqtrade/tests/test_analyze.py index 89dac21c6..02225acca 100644 --- a/freqtrade/tests/test_analyze.py +++ b/freqtrade/tests/test_analyze.py @@ -42,6 +42,7 @@ def test_analyze_object() -> None: assert hasattr(Analyze, 'get_signal') assert hasattr(Analyze, 'should_sell') assert hasattr(Analyze, 'min_roi_reached') + assert hasattr(Analyze, 'stop_loss_reached') def test_dataframe_correct_length(result): From 8bec505bbe23919da0035f86627d63a150ab445d Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 26 Jun 2018 23:40:36 +0200 Subject: [PATCH 10/23] add test for trailing_stoploss --- freqtrade/tests/test_freqtradebot.py | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 1cb2bfca2..3568dad1d 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1654,6 +1654,43 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, mocker) -> assert freqtrade.handle_trade(trade) is True +def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker) -> None: + """ + Test sell_profit_only feature when enabled and we have a loss + """ + patch_get_signal(mocker) + patch_RPCManager(mocker) + patch_coinmarketcap(mocker) + mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_ticker=MagicMock(return_value={ + 'bid': 0.0000172, + 'ask': 0.0000173, + 'last': 0.0000172 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + ) + + conf = deepcopy(default_conf) + conf['trailing_stop'] = True + print(limit_buy_order) + freqtrade = FreqtradeBot(conf) + freqtrade.create_trade() + + trade = Trade.query.first() + trade.update(limit_buy_order) + trade.stop_loss = 3.2 + caplog.set_level(logging.DEBUG) + assert freqtrade.handle_trade(trade) is True + assert log_has( + f'HIT STOP: current price at 0.000017, stop loss is {trade.stop_loss:.6f}, ' + f'initial stop loss was at 0.000000, trade opened at 0.000011', caplog.record_tuples) + + + def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, mocker) -> None: """ From 88b898cce4a434cd15567581f42663056fe05da0 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 27 Jun 2018 00:16:19 +0200 Subject: [PATCH 11/23] add test for moving stoploss --- freqtrade/tests/test_freqtradebot.py | 63 ++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 3568dad1d..468f5f8aa 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1661,14 +1661,14 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker) patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True) + mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=MagicMock(return_value={ - 'bid': 0.0000172, - 'ask': 0.0000173, - 'last': 0.0000172 + 'bid': 0.00000102, + 'ask': 0.00000103, + 'last': 0.00000102 }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, @@ -1682,14 +1682,61 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker) trade = Trade.query.first() trade.update(limit_buy_order) - trade.stop_loss = 3.2 caplog.set_level(logging.DEBUG) + # Sell as trailing-stop is reached + assert freqtrade.handle_trade(trade) is True + assert log_has( + f'HIT STOP: current price at 0.000001, stop loss is {trade.stop_loss:.6f}, ' + f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) + + +def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, caplog, mocker) -> None: + """ + Test sell_profit_only feature when enabled and we have a loss + """ + patch_get_signal(mocker) + patch_RPCManager(mocker) + patch_coinmarketcap(mocker) + mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_ticker=MagicMock(return_value={ + 'bid': 0.0000182, + 'ask': 0.0000183, + 'last': 0.0000182 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + ) + + conf = deepcopy(default_conf) + conf['trailing_stop'] = True + conf['trailing_stop_positive'] = 0.01 + print(limit_buy_order) + freqtrade = FreqtradeBot(conf) + freqtrade.create_trade() + + trade = Trade.query.first() + trade.update(limit_buy_order) + caplog.set_level(logging.DEBUG) + # stop-loss not reached + assert freqtrade.handle_trade(trade) is False + # Adjusted stoploss + assert log_has(f'using positive stop loss mode: 0.01 since we have profit 0.64779142', + caplog.record_tuples) + assert log_has(f'adjusted stop loss', caplog.record_tuples) + assert trade.stop_loss == 0.000018018 + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': 0.0000172, + 'ask': 0.0000173, + 'last': 0.0000172 + })) assert freqtrade.handle_trade(trade) is True assert log_has( f'HIT STOP: current price at 0.000017, stop loss is {trade.stop_loss:.6f}, ' - f'initial stop loss was at 0.000000, trade opened at 0.000011', caplog.record_tuples) - - + f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, mocker) -> None: From e9d5bceeb98b12aa35c020fea29d84bf24088ed4 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 27 Jun 2018 00:18:50 +0200 Subject: [PATCH 12/23] cleanly check if stop_loss is initialized --- freqtrade/analyze.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index b283e3ae9..b8caa45b7 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -212,7 +212,7 @@ class Analyze(object): current_profit = trade.calc_profit_percent(current_rate) trailing_stop = self.config.get('trailing_stop', False) - if trade.stop_loss is None: + if trade.stop_loss is None or trade.stop_loss == 0: # initially adjust the stop loss to the base value trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss) From a91d75b3b2acdcce30a5e7bc9745430072da00d3 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 27 Jun 2018 06:23:49 +0200 Subject: [PATCH 13/23] Add test for adjust_stop-loss --- freqtrade/tests/test_persistence.py | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 30ad239a1..6ffcbbf65 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -453,3 +453,37 @@ def test_migrate_new(mocker, default_conf, fee): assert trade.stake_amount == default_conf.get("stake_amount") assert trade.pair == "ETC/BTC" assert trade.exchange == "binance" + + +def test_adjust_stop_loss(limit_buy_order, limit_sell_order, 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, + ) + + # Get percent of profit with a custom rate (Higher than open rate) + trade.adjust_stop_loss(1, 0.05) + assert trade.stop_loss == 0.95 + assert trade.max_rate == 1 + assert trade.initial_stop_loss == 0.95 + + 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 + + # current rate lower ... 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 From c997aa9864ef0aec7b89bfb59982f7b99a609e39 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 27 Jun 2018 06:38:49 +0200 Subject: [PATCH 14/23] move initial logic to persistence --- freqtrade/analyze.py | 5 ++--- freqtrade/persistence.py | 12 ++++++++---- freqtrade/tests/test_persistence.py | 18 +++++++++++++++--- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index b8caa45b7..d74f9566a 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -212,9 +212,8 @@ class Analyze(object): current_profit = trade.calc_profit_percent(current_rate) trailing_stop = self.config.get('trailing_stop', False) - if trade.stop_loss is None or trade.stop_loss == 0: - # initially adjust the stop loss to the base value - trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss) + + trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True) # evaluate if the stoploss was hit if self.strategy.stoploss is not None and trade.stop_loss >= current_rate: diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index db97fc858..4fc83a18f 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -167,15 +167,19 @@ class Trade(_DECL_BASE): arrow.get(self.open_date).humanize() if self.is_open else 'closed' ) - def adjust_stop_loss(self, current_price, stoploss): + 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: - :param stoploss: + :param stoploss in percent: + :param initial: :return: """ + 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 + return + new_loss = float(current_price * (1 - abs(stoploss))) # keeping track of the highest observed rate for this trade diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 6ffcbbf65..45957853f 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -465,18 +465,24 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): open_rate=1, ) - # Get percent of profit with a custom rate (Higher than open rate) - trade.adjust_stop_loss(1, 0.05) + trade.adjust_stop_loss(trade.open_rate, 0.05, True) assert trade.stop_loss == 0.95 assert trade.max_rate == 1 assert trade.initial_stop_loss == 0.95 + # Get percent of profit with a lowre rate + trade.adjust_stop_loss(0.96, 0.05) + assert trade.stop_loss == 0.95 + assert trade.max_rate == 1 + 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.max_rate == 1.3 assert trade.initial_stop_loss == 0.95 - # current rate lower ... should not change + # 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 @@ -487,3 +493,9 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): 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 From 78e6c9fdf680307c262e1246d02e4d76a877c90a Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 27 Jun 2018 06:51:48 +0200 Subject: [PATCH 15/23] add tests for trailing stoploss --- freqtrade/tests/test_freqtradebot.py | 41 ++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 468f5f8aa..7b2786421 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1694,6 +1694,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, caplog, """ Test sell_profit_only feature when enabled and we have a loss """ + buy_price = limit_buy_order['price'] patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) @@ -1702,9 +1703,9 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, caplog, 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=MagicMock(return_value={ - 'bid': 0.0000182, - 'ask': 0.0000183, - 'last': 0.0000182 + 'bid': buy_price - 0.000001, + 'ask': buy_price - 0.000001, + 'last': buy_price - 0.000001 }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, @@ -1713,7 +1714,6 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, caplog, conf = deepcopy(default_conf) conf['trailing_stop'] = True conf['trailing_stop_positive'] = 0.01 - print(limit_buy_order) freqtrade = FreqtradeBot(conf) freqtrade.create_trade() @@ -1722,22 +1722,35 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, caplog, caplog.set_level(logging.DEBUG) # stop-loss not reached assert freqtrade.handle_trade(trade) is False - # Adjusted stoploss - assert log_has(f'using positive stop loss mode: 0.01 since we have profit 0.64779142', - caplog.record_tuples) - assert log_has(f'adjusted stop loss', caplog.record_tuples) - assert trade.stop_loss == 0.000018018 + + # Raise ticker above buy price mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={ - 'bid': 0.0000172, - 'ask': 0.0000173, - 'last': 0.0000172 - })) + 'bid': buy_price + 0.000003, + 'ask': buy_price + 0.000003, + 'last': buy_price + 0.000003 + })) + # stop-loss not reached, adjusted stoploss + assert freqtrade.handle_trade(trade) is False + assert log_has(f'using positive stop loss mode: 0.01 since we have profit 0.26662643', + caplog.record_tuples) + assert log_has(f'adjusted stop loss', caplog.record_tuples) + assert trade.stop_loss == 0.0000138501 + + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': buy_price + 0.000002, + 'ask': buy_price + 0.000002, + 'last': buy_price + 0.000002 + })) + # Lower price again (but still positive) assert freqtrade.handle_trade(trade) is True assert log_has( - f'HIT STOP: current price at 0.000017, stop loss is {trade.stop_loss:.6f}, ' + f'HIT STOP: current price at {buy_price + 0.000002:.6f}, ' + f'stop loss is {trade.stop_loss:.6f}, ' f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) + def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, mocker) -> None: """ From e6e868a03c3f4b6f94d89449509b8c618f53b84f Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 27 Jun 2018 06:54:29 +0200 Subject: [PATCH 16/23] remove markdown code type as it is not valid json --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index f24adb61e..984f2529b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -62,7 +62,7 @@ In this case a trade amount is calclulated as `currency_balanse / (max_open_trad in minutes and the value is the minimum ROI in percent. See the example below: -``` json +``` "minimal_roi": { "40": 0.0, # Sell after 40 minutes if the profit is not negative "30": 0.01, # Sell after 30 minutes if there is at least 1% profit From 8ecdae67e1417de14177da25ce30df8a28c7ad66 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 27 Jun 2018 06:57:41 +0200 Subject: [PATCH 17/23] add mypy ignore (and comment as to why) --- freqtrade/analyze.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index d74f9566a..bf6b89b17 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -237,7 +237,8 @@ class Analyze(object): stop_loss_value = self.strategy.stoploss if 'trailing_stop_positive' in self.config and current_profit > 0: - stop_loss_value = self.config.get('trailing_stop_positive') + # 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"since we have profit {current_profit}") From 860b270e309a022e607d789754eb3ab338475d58 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 27 Jun 2018 19:49:08 +0200 Subject: [PATCH 18/23] update db migrate script to work for more changes --- freqtrade/persistence.py | 33 +++++++++++++++++++---------- freqtrade/tests/test_persistence.py | 3 +++ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 4fc83a18f..c72974c61 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -66,6 +66,10 @@ def has_column(columns, searchname: str) -> bool: return len(list(filter(lambda x: x["name"] == searchname, columns))) == 1 +def get_column_def(columns, column: str, default: str) -> str: + return default if not has_column(columns, column) else column + + def check_migrate(engine) -> None: """ Checks if migration is necessary and migrates if necessary @@ -74,17 +78,26 @@ def check_migrate(engine) -> None: cols = inspector.get_columns('trades') - if not has_column(cols, 'fee_open'): + # Check for latest column + if not has_column(cols, 'max_rate'): + 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') + initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') + max_rate = get_column_def(cols, 'max_rate', '0.0') + # Schema migration necessary engine.execute("alter table trades rename to trades_bak") # let SQLAlchemy create the schema as required _DECL_BASE.metadata.create_all(engine) # Copy data back - following the correct schema - engine.execute("""insert into trades + engine.execute(f"""insert into trades (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) + stake_amount, amount, open_date, close_date, open_order_id, + stop_loss, initial_stop_loss, max_rate + ) select id, lower(exchange), case when instr(pair, '_') != 0 then @@ -94,9 +107,12 @@ def check_migrate(engine) -> None: end pair, is_open, fee fee_open, fee fee_close, - open_rate, null open_rate_requested, close_rate, - null close_rate_requested, close_profit, - stake_amount, amount, open_date, close_date, open_order_id + 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, + {max_rate} max_rate + from trades_bak """) @@ -104,11 +120,6 @@ def check_migrate(engine) -> None: inspector = inspect(engine) cols = inspector.get_columns('trades') - if not has_column(cols, 'open_rate_requested'): - engine.execute("alter table trades add open_rate_requested float") - if not has_column(cols, 'close_rate_requested'): - engine.execute("alter table trades add close_rate_requested float") - def cleanup() -> None: """ diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 45957853f..0cd2d089c 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -400,6 +400,9 @@ def test_migrate_old(mocker, default_conf, fee): assert trade.stake_amount == default_conf.get("stake_amount") assert trade.pair == "ETC/BTC" assert trade.exchange == "bittrex" + assert trade.max_rate == 0.0 + assert trade.stop_loss == 0.0 + assert trade.initial_stop_loss == 0.0 def test_migrate_new(mocker, default_conf, fee): From d5ad066f8d4af30340da2e12eaef61a88bf48224 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 27 Jun 2018 20:15:25 +0200 Subject: [PATCH 19/23] support multiple db transitions by keeping the backup-table dynamic --- freqtrade/persistence.py | 12 +++++++++--- freqtrade/tests/test_persistence.py | 10 +++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index c72974c61..2b226e53a 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -77,6 +77,13 @@ def check_migrate(engine) -> None: inspector = inspect(engine) cols = inspector.get_columns('trades') + tabs = inspector.get_table_names() + table_back_name = 'trades_bak' + i = 0 + while table_back_name in tabs: + i += 1 + table_back_name = f'trades_bak{i}' + logger.info(f'trying {table_back_name}') # Check for latest column if not has_column(cols, 'max_rate'): @@ -87,7 +94,7 @@ def check_migrate(engine) -> None: max_rate = get_column_def(cols, 'max_rate', '0.0') # Schema migration necessary - engine.execute("alter table trades rename to trades_bak") + engine.execute(f"alter table trades rename to {table_back_name}") # let SQLAlchemy create the schema as required _DECL_BASE.metadata.create_all(engine) @@ -112,8 +119,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, {max_rate} max_rate - - from trades_bak + from {table_back_name} """) # Reread columns - the above recreated the table! diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 0cd2d089c..7ef4d2c25 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -7,6 +7,7 @@ from sqlalchemy import create_engine from freqtrade import constants, OperationalException from freqtrade.persistence import Trade, init, clean_dry_run_db +from freqtrade.tests.conftest import log_has @pytest.fixture(scope='function') @@ -405,7 +406,7 @@ def test_migrate_old(mocker, default_conf, fee): assert trade.initial_stop_loss == 0.0 -def test_migrate_new(mocker, default_conf, fee): +def test_migrate_new(mocker, default_conf, fee, caplog): """ Test Database migration (starting with new pairformat) """ @@ -442,6 +443,9 @@ def test_migrate_new(mocker, default_conf, fee): # Create table using the old format engine.execute(create_table_old) engine.execute(insert_table_old) + + # fake previous backup + engine.execute("create table trades_bak as select * from trades") # Run init to test migration init(default_conf) @@ -456,6 +460,10 @@ def test_migrate_new(mocker, default_conf, fee): assert trade.stake_amount == default_conf.get("stake_amount") assert trade.pair == "ETC/BTC" assert trade.exchange == "binance" + assert trade.max_rate == 0.0 + assert trade.stop_loss == 0.0 + assert trade.initial_stop_loss == 0.0 + assert log_has("trying trades_bak1", caplog.record_tuples) def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): From e39d88ef65e72387f18198da269a2d7660a2d6c6 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 1 Jul 2018 19:54:26 +0200 Subject: [PATCH 20/23] Address some PR comments --- freqtrade/constants.py | 2 +- freqtrade/persistence.py | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 4ba1a7094..d80eea6f4 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -62,7 +62,7 @@ CONF_SCHEMA = { }, 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'trailing_stop': {'type': 'boolean'}, - 'trailing_stop_positive': {'type': 'number', 'minimum': 0}, + 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'unfilledtimeout': {'type': 'integer', 'minimum': 0}, 'bid_strategy': { 'type': 'object', diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 2b226e53a..ad90d3f92 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -184,14 +184,8 @@ class Trade(_DECL_BASE): arrow.get(self.open_date).humanize() if self.is_open else 'closed' ) - 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 - :param current_price: - :param stoploss in percent: - :param initial: - :return: - """ + 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""" 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 @@ -207,7 +201,7 @@ class Trade(_DECL_BASE): self.max_rate = current_price # no stop loss assigned yet - if self.stop_loss is None or self.stop_loss == 0: + if not self.stop_loss: logger.debug("assigning new stop loss") self.stop_loss = new_loss self.initial_stop_loss = new_loss From 937644a04b96adbdb26ed54c5969945e09f314c2 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 1 Jul 2018 19:55:51 +0200 Subject: [PATCH 21/23] change while-loop to enumerate - add intensified test for this scenario --- freqtrade/tests/test_persistence.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 7ef4d2c25..12ae6579f 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -446,6 +446,8 @@ def test_migrate_new(mocker, default_conf, fee, caplog): # fake previous backup engine.execute("create table trades_bak as select * from trades") + + engine.execute("create table trades_bak1 as select * from trades") # Run init to test migration init(default_conf) @@ -464,6 +466,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.stop_loss == 0.0 assert trade.initial_stop_loss == 0.0 assert log_has("trying trades_bak1", caplog.record_tuples) + assert log_has("trying trades_bak2", caplog.record_tuples) def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): From 782570e71eb6fca9c5a70cf36896d692661a14a7 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 1 Jul 2018 20:03:07 +0200 Subject: [PATCH 22/23] Address PR comment --- config.json.example | 2 +- freqtrade/persistence.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json.example b/config.json.example index 87c27090e..e8473e919 100644 --- a/config.json.example +++ b/config.json.example @@ -36,7 +36,7 @@ "experimental": { "use_sell_signal": false, "sell_profit_only": false, - "sell_fullfilled_at_roi": false + "ignore_roi_if_buy_signal": false }, "telegram": { "enabled": true, diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ad90d3f92..dfeb6145c 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -80,7 +80,7 @@ def check_migrate(engine) -> None: tabs = inspector.get_table_names() table_back_name = 'trades_bak' i = 0 - while table_back_name in tabs: + for i, table_back_name in enumerate(tabs): i += 1 table_back_name = f'trades_bak{i}' logger.info(f'trying {table_back_name}') From 3c5be55eb907878c73f0cfb26a4f21b3af2a4745 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sun, 1 Jul 2018 20:17:30 +0200 Subject: [PATCH 23/23] remove unnecessary variable --- freqtrade/persistence.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index dfeb6145c..aa33f7063 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -79,9 +79,7 @@ def check_migrate(engine) -> None: cols = inspector.get_columns('trades') tabs = inspector.get_table_names() table_back_name = 'trades_bak' - i = 0 for i, table_back_name in enumerate(tabs): - i += 1 table_back_name = f'trades_bak{i}' logger.info(f'trying {table_back_name}')