Merge branch 'develop' into pr/hroff-1902/3478

This commit is contained in:
Matthias 2020-06-18 19:54:46 +02:00
commit 45ffb26910
83 changed files with 789 additions and 278 deletions

View File

@ -82,7 +82,8 @@ positional arguments:
new-hyperopt Create new hyperopt new-hyperopt Create new hyperopt
new-strategy Create new strategy new-strategy Create new strategy
download-data Download backtesting data. download-data Download backtesting data.
convert-data Convert candle (OHLCV) data from one format to another. convert-data Convert candle (OHLCV) data from one format to
another.
convert-trade-data Convert trade data from one format to another. convert-trade-data Convert trade data from one format to another.
backtesting Backtesting module. backtesting Backtesting module.
edge Edge module. edge Edge module.
@ -94,7 +95,7 @@ positional arguments:
list-markets Print markets on exchange. list-markets Print markets on exchange.
list-pairs Print pairs on exchange. list-pairs Print pairs on exchange.
list-strategies Print available strategies. list-strategies Print available strategies.
list-timeframes Print available ticker intervals (timeframes) for the exchange. list-timeframes Print available timeframes for the exchange.
show-trades Show trades. show-trades Show trades.
test-pairlist Test your pairlist configuration. test-pairlist Test your pairlist configuration.
plot-dataframe Plot candles with indicators. plot-dataframe Plot candles with indicators.

View File

@ -4,7 +4,7 @@
"stake_amount": 0.05, "stake_amount": 0.05,
"tradable_balance_ratio": 0.99, "tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"ticker_interval": "5m", "timeframe": "5m",
"dry_run": false, "dry_run": false,
"cancel_open_orders_on_exit": false, "cancel_open_orders_on_exit": false,
"trailing_stop": false, "trailing_stop": false,

View File

@ -4,7 +4,7 @@
"stake_amount": 0.05, "stake_amount": 0.05,
"tradable_balance_ratio": 0.99, "tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"ticker_interval": "5m", "timeframe": "5m",
"dry_run": true, "dry_run": true,
"cancel_open_orders_on_exit": false, "cancel_open_orders_on_exit": false,
"trailing_stop": false, "trailing_stop": false,

View File

@ -9,7 +9,7 @@
"last_stake_amount_min_ratio": 0.5, "last_stake_amount_min_ratio": 0.5,
"dry_run": false, "dry_run": false,
"cancel_open_orders_on_exit": false, "cancel_open_orders_on_exit": false,
"ticker_interval": "5m", "timeframe": "5m",
"trailing_stop": false, "trailing_stop": false,
"trailing_stop_positive": 0.005, "trailing_stop_positive": 0.005,
"trailing_stop_positive_offset": 0.0051, "trailing_stop_positive_offset": 0.0051,

View File

@ -4,7 +4,7 @@
"stake_amount": 10, "stake_amount": 10,
"tradable_balance_ratio": 0.99, "tradable_balance_ratio": 0.99,
"fiat_display_currency": "EUR", "fiat_display_currency": "EUR",
"ticker_interval": "5m", "timeframe": "5m",
"dry_run": true, "dry_run": true,
"cancel_open_orders_on_exit": false, "cancel_open_orders_on_exit": false,
"trailing_stop": false, "trailing_stop": false,

View File

@ -4,6 +4,54 @@ This page explains some advanced tasks and configuration options that can be per
If you do not know what things mentioned here mean, you probably do not need it. If you do not know what things mentioned here mean, you probably do not need it.
## Running multiple instances of Freqtrade
This section will show you how to run multiple bots at the same time, on the same machine.
### Things to consider
* Use different database files.
* Use different Telegram bots (requires multiple different configuration files; applies only when Telegram is enabled).
* Use different ports (applies only when Freqtrade REST API webserver is enabled).
### Different database files
In order to keep track of your trades, profits, etc., freqtrade is using a SQLite database where it stores various types of information such as the trades you performed in the past and the current position(s) you are holding at any time. This allows you to keep track of your profits, but most importantly, keep track of ongoing activity if the bot process would be restarted or would be terminated unexpectedly.
Freqtrade will, by default, use separate database files for dry-run and live bots (this assumes no database-url is given in either configuration nor via command line argument).
For live trading mode, the default database will be `tradesv3.sqlite` and for dry-run it will be `tradesv3.dryrun.sqlite`.
The optional argument to the trade command used to specify the path of these files is `--db-url`, which requires a valid SQLAlchemy url.
So when you are starting a bot with only the config and strategy arguments in dry-run mode, the following 2 commands would have the same outcome.
``` bash
freqtrade trade -c MyConfig.json -s MyStrategy
# is equivalent to
freqtrade trade -c MyConfig.json -s MyStrategy --db-url sqlite:///tradesv3.dryrun.sqlite
```
It means that if you are running the trade command in two different terminals, for example to test your strategy both for trades in USDT and in another instance for trades in BTC, you will have to run them with different databases.
If you specify the URL of a database which does not exist, freqtrade will create one with the name you specified. So to test your custom strategy with BTC and USDT stake currencies, you could use the following commands (in 2 separate terminals):
``` bash
# Terminal 1:
freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite:///user_data/tradesBTC.dryrun.sqlite
# Terminal 2:
freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite:///user_data/tradesUSDT.dryrun.sqlite
```
Conversely, if you wish to do the same thing in production mode, you will also have to create at least one new database (in addition to the default one) and specify the path to the "live" databases, for example:
``` bash
# Terminal 1:
freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite:///user_data/tradesBTC.live.sqlite
# Terminal 2:
freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite:///user_data/tradesUSDT.live.sqlite
```
For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the [SQL Cheatsheet](sql_cheatsheet.md).
## Configure the bot running as a systemd service ## Configure the bot running as a systemd service
Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup. Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.

View File

@ -12,7 +12,7 @@ real data. This is what we call
[backtesting](https://en.wikipedia.org/wiki/Backtesting). [backtesting](https://en.wikipedia.org/wiki/Backtesting).
Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHCLV) data from `user_data/data/<exchange>` by default. Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHCLV) data from `user_data/data/<exchange>` by default.
If no data is available for the exchange / pair / timeframe (ticker interval) combination, backtesting will ask you to download them first using `freqtrade download-data`. If no data is available for the exchange / pair / timeframe combination, backtesting will ask you to download them first using `freqtrade download-data`.
For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation. For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation.
The result of backtesting will confirm if your bot has better odds of making a profit than a loss. The result of backtesting will confirm if your bot has better odds of making a profit than a loss.
@ -35,7 +35,7 @@ freqtrade backtesting
#### With 1 min candle (OHLCV) data #### With 1 min candle (OHLCV) data
```bash ```bash
freqtrade backtesting --ticker-interval 1m freqtrade backtesting --timeframe 1m
``` ```
#### Using a different on-disk historical candle (OHLCV) data source #### Using a different on-disk historical candle (OHLCV) data source
@ -58,7 +58,7 @@ Where `-s SampleStrategy` refers to the class name within the strategy file `sam
#### Comparing multiple Strategies #### Comparing multiple Strategies
```bash ```bash
freqtrade backtesting --strategy-list SampleStrategy1 AwesomeStrategy --ticker-interval 5m freqtrade backtesting --strategy-list SampleStrategy1 AwesomeStrategy --timeframe 5m
``` ```
Where `SampleStrategy1` and `AwesomeStrategy` refer to class names of strategies. Where `SampleStrategy1` and `AwesomeStrategy` refer to class names of strategies.
@ -228,13 +228,13 @@ You can then load the trades to perform further analysis as shown in our [data a
To compare multiple strategies, a list of Strategies can be provided to backtesting. To compare multiple strategies, a list of Strategies can be provided to backtesting.
This is limited to 1 timeframe (ticker interval) value per run. However, data is only loaded once from disk so if you have multiple This is limited to 1 timeframe value per run. However, data is only loaded once from disk so if you have multiple
strategies you'd like to compare, this will give a nice runtime boost. strategies you'd like to compare, this will give a nice runtime boost.
All listed Strategies need to be in the same directory. All listed Strategies need to be in the same directory.
``` bash ``` bash
freqtrade backtesting --timerange 20180401-20180410 --ticker-interval 5m --strategy-list Strategy001 Strategy002 --export trades freqtrade backtesting --timerange 20180401-20180410 --timeframe 5m --strategy-list Strategy001 Strategy002 --export trades
``` ```
This will save the results to `user_data/backtest_results/backtest-result-<strategy>.json`, injecting the strategy-name into the target filename. This will save the results to `user_data/backtest_results/backtest-result-<strategy>.json`, injecting the strategy-name into the target filename.

View File

@ -9,22 +9,35 @@ This page explains the different parameters of the bot and how to run it.
``` ```
usage: freqtrade [-h] [-V] usage: freqtrade [-h] [-V]
{trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit} {trade,create-userdir,new-config,new-hyperopt,new-strategy,download-data,convert-data,convert-trade-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,plot-dataframe,plot-profit}
... ...
Free, open source crypto trading bot Free, open source crypto trading bot
positional arguments: positional arguments:
{trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit} {trade,create-userdir,new-config,new-hyperopt,new-strategy,download-data,convert-data,convert-trade-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,plot-dataframe,plot-profit}
trade Trade module. trade Trade module.
create-userdir Create user-data directory.
new-config Create new config
new-hyperopt Create new hyperopt
new-strategy Create new strategy
download-data Download backtesting data.
convert-data Convert candle (OHLCV) data from one format to
another.
convert-trade-data Convert trade data from one format to another.
backtesting Backtesting module. backtesting Backtesting module.
edge Edge module. edge Edge module.
hyperopt Hyperopt module. hyperopt Hyperopt module.
create-userdir Create user-data directory. hyperopt-list List Hyperopt results
hyperopt-show Show details of Hyperopt results
list-exchanges Print available exchanges. list-exchanges Print available exchanges.
list-timeframes Print available ticker intervals (timeframes) for the list-hyperopts Print available hyperopt classes.
exchange. list-markets Print markets on exchange.
download-data Download backtesting data. list-pairs Print pairs on exchange.
list-strategies Print available strategies.
list-timeframes Print available timeframes for the exchange.
show-trades Show trades.
test-pairlist Test your pairlist configuration.
plot-dataframe Plot candles with indicators. plot-dataframe Plot candles with indicators.
plot-profit Generate plot showing profits. plot-profit Generate plot showing profits.
@ -72,7 +85,6 @@ Strategy arguments:
Specify strategy class name which will be used by the Specify strategy class name which will be used by the
bot. bot.
--strategy-path PATH Specify additional strategy lookup path. --strategy-path PATH Specify additional strategy lookup path.
.
``` ```
@ -197,7 +209,7 @@ Backtesting also uses the config specified via `-c/--config`.
``` ```
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME] [-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-i TICKER_INTERVAL] [--strategy-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE] [--max-open-trades INT] [--timerange TIMERANGE] [--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT] [--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--eps] [--dmmp] [--eps] [--dmmp]
@ -206,7 +218,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`, Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`). `1d`).
--timerange TIMERANGE --timerange TIMERANGE
@ -280,7 +292,7 @@ to find optimal parameter values for your strategy.
``` ```
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TICKER_INTERVAL] [--timerange TIMERANGE] [-i TIMEFRAME] [--timerange TIMERANGE]
[--max-open-trades INT] [--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT] [--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--hyperopt NAME] [--hyperopt-path PATH] [--eps] [--hyperopt NAME] [--hyperopt-path PATH] [--eps]
@ -292,7 +304,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`, Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`). `1d`).
--timerange TIMERANGE --timerange TIMERANGE
@ -323,7 +335,7 @@ optional arguments:
--print-all Print all results, not only the best ones. --print-all Print all results, not only the best ones.
--no-color Disable colorization of hyperopt results. May be --no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file. useful if you are redirecting output to a file.
--print-json Print best results in JSON format. --print-json Print output in JSON format.
-j JOBS, --job-workers JOBS -j JOBS, --job-workers JOBS
The number of concurrently running jobs for The number of concurrently running jobs for
hyperoptimization (hyperopt worker processes). If -1 hyperoptimization (hyperopt worker processes). If -1
@ -341,11 +353,11 @@ optional arguments:
class (IHyperOptLoss). Different functions can class (IHyperOptLoss). Different functions can
generate completely different results, since the generate completely different results, since the
target for optimization is different. Built-in target for optimization is different. Built-in
Hyperopt-loss-functions are: Hyperopt-loss-functions are: DefaultHyperOptLoss,
DefaultHyperOptLoss, OnlyProfitHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss,
SharpeHyperOptLoss, SharpeHyperOptLossDaily, SharpeHyperOptLossDaily, SortinoHyperOptLoss,
SortinoHyperOptLoss, SortinoHyperOptLossDaily. SortinoHyperOptLossDaily.(default:
(default: `DefaultHyperOptLoss`). `DefaultHyperOptLoss`).
Common arguments: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
@ -378,13 +390,13 @@ To know your trade expectancy and winrate against historical data, you can use E
``` ```
usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TICKER_INTERVAL] [--timerange TIMERANGE] [-i TIMEFRAME] [--timerange TIMERANGE]
[--max-open-trades INT] [--stake-amount STAKE_AMOUNT] [--max-open-trades INT] [--stake-amount STAKE_AMOUNT]
[--fee FLOAT] [--stoplosses STOPLOSS_RANGE] [--fee FLOAT] [--stoplosses STOPLOSS_RANGE]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`, Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`). `1d`).
--timerange TIMERANGE --timerange TIMERANGE

View File

@ -47,7 +47,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **Datatype:** Boolean | `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **Datatype:** Float (as ratio) | `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **Datatype:** Float (as ratio)
| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive Float as ratio. | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive Float as ratio.
| `ticker_interval` | The timeframe (ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String | `timeframe` | The timeframe (former ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
@ -126,7 +126,7 @@ The following parameters can be set in either configuration file or strategy.
Values set in the configuration file always overwrite values set in the strategy. Values set in the configuration file always overwrite values set in the strategy.
* `minimal_roi` * `minimal_roi`
* `ticker_interval` * `timeframe`
* `stoploss` * `stoploss`
* `trailing_stop` * `trailing_stop`
* `trailing_stop_positive` * `trailing_stop_positive`
@ -272,7 +272,7 @@ the static list of pairs) if we should buy.
### Understand order_types ### Understand order_types
The `order_types` configuration parameter maps actions (`buy`, `sell`, `stoploss`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds. The `order_types` configuration parameter maps actions (`buy`, `sell`, `stoploss`, `emergencysell`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds.
This allows to buy using limit orders, sell using This allows to buy using limit orders, sell using
limit-orders, and create stoplosses using using market orders. It also allows to set the limit-orders, and create stoplosses using using market orders. It also allows to set the
@ -288,8 +288,12 @@ If this is configured, the following 4 values (`buy`, `sell`, `stoploss` and
`emergencysell` is an optional value, which defaults to `market` and is used when creating stoploss on exchange orders fails. `emergencysell` is an optional value, which defaults to `market` and is used when creating stoploss on exchange orders fails.
The below is the default which is used if this is not configured in either strategy or configuration file. The below is the default which is used if this is not configured in either strategy or configuration file.
Since `stoploss_on_exchange` uses limit orders, the exchange needs 2 prices, the stoploss_price and the Limit price. Not all Exchanges support `stoploss_on_exchange`. If an exchange supports both limit and market stoploss orders, then the value of `stoploss` will be used to determine the stoploss type.
`stoploss` defines the stop-price - and limit should be slightly below this. This defaults to 0.99 / 1% (configurable via `stoploss_on_exchange_limit_ratio`).
If `stoploss_on_exchange` uses limit orders, the exchange needs 2 prices, the stoploss_price and the Limit price.
`stoploss` defines the stop-price - and limit should be slightly below this.
This defaults to 0.99 / 1% (configurable via `stoploss_on_exchange_limit_ratio`).
Calculation example: we bought the asset at 100$. Calculation example: we bought the asset at 100$.
Stop-price is 95$, then limit would be `95 * 0.99 = 94.05$` - so the stoploss will happen between 95$ and 94.05$. Stop-price is 95$, then limit would be `95 * 0.99 = 94.05$` - so the stoploss will happen between 95$ and 94.05$.
@ -331,7 +335,7 @@ Configuration:
refer to [the stoploss documentation](stoploss.md). refer to [the stoploss documentation](stoploss.md).
!!! Note !!! Note
If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new order. If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order.
!!! Warning "Using market orders" !!! Warning "Using market orders"
Please read the section [Market order pricing](#market-order-pricing) section when using market orders. Please read the section [Market order pricing](#market-order-pricing) section when using market orders.

View File

@ -109,7 +109,7 @@ The following command will convert all candle (OHLCV) data available in `~/.freq
It'll also remove original json data files (`--erase` parameter). It'll also remove original json data files (`--erase` parameter).
``` bash ``` bash
freqtrade convert-data --format-from json --format-to jsongz --data-dir ~/.freqtrade/data/binance -t 5m 15m --erase freqtrade convert-data --format-from json --format-to jsongz --datadir ~/.freqtrade/data/binance -t 5m 15m --erase
``` ```
#### Subcommand convert-trade data #### Subcommand convert-trade data
@ -155,7 +155,7 @@ The following command will convert all available trade-data in `~/.freqtrade/dat
It'll also remove original jsongz data files (`--erase` parameter). It'll also remove original jsongz data files (`--erase` parameter).
``` bash ``` bash
freqtrade convert-trade-data --format-from jsongz --format-to json --data-dir ~/.freqtrade/data/kraken --erase freqtrade convert-trade-data --format-from jsongz --format-to json --datadir ~/.freqtrade/data/kraken --erase
``` ```
### Pairs file ### Pairs file

View File

@ -155,7 +155,7 @@ Edge module has following configuration options:
| `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate. <br>This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio. <br>*Defaults to `0.60`.* <br> **Datatype:** Float | `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate. <br>This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio. <br>*Defaults to `0.60`.* <br> **Datatype:** Float
| `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number. <br>Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return. <br>*Defaults to `0.20`.* <br> **Datatype:** Float | `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number. <br>Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return. <br>*Defaults to `0.20`.* <br> **Datatype:** Float
| `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. <br>Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. <br>*Defaults to `10` (it is highly recommended not to decrease this number).* <br> **Datatype:** Integer | `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. <br>Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. <br>*Defaults to `10` (it is highly recommended not to decrease this number).* <br> **Datatype:** Integer
| `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.<br>**NOTICE:** While configuring this value, you should take into consideration your timeframe (ticker interval). As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).<br>*Defaults to `1440` (one day).* <br> **Datatype:** Integer | `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.<br>**NOTICE:** While configuring this value, you should take into consideration your timeframe. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).<br>*Defaults to `1440` (one day).* <br> **Datatype:** Integer
| `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br>*Defaults to `false`.* <br> **Datatype:** Boolean | `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br>*Defaults to `false`.* <br> **Datatype:** Boolean
## Running Edge independently ## Running Edge independently

View File

@ -73,6 +73,11 @@ print(res)
## FTX ## FTX
!!! Tip "Stoploss on Exchange"
FTX supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide.
### Using subaccounts ### Using subaccounts
To use subaccounts with FTX, you need to edit the configuration and add the following: To use subaccounts with FTX, you need to edit the configuration and add the following:

View File

@ -45,6 +45,10 @@ the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-c
You can use the `/forcesell all` command from Telegram. You can use the `/forcesell all` command from Telegram.
### I want to run multiple bots on the same machine
Please look at the [advanced setup documentation Page](advanced-setup.md#running-multiple-instances-of-freqtrade).
### I'm getting the "RESTRICTED_MARKET" message in the log ### I'm getting the "RESTRICTED_MARKET" message in the log
Currently known to happen for US Bittrex users. Currently known to happen for US Bittrex users.

View File

@ -124,9 +124,9 @@ To avoid naming collisions in the search-space, please prefix all sell-spaces wi
#### Using timeframe as a part of the Strategy #### Using timeframe as a part of the Strategy
The Strategy class exposes the timeframe (ticker interval) value as the `self.ticker_interval` attribute. The Strategy class exposes the timeframe value as the `self.timeframe` attribute.
The same value is available as class-attribute `HyperoptName.ticker_interval`. The same value is available as class-attribute `HyperoptName.timeframe`.
In the case of the linked sample-value this would be `SampleHyperOpt.ticker_interval`. In the case of the linked sample-value this would be `SampleHyperOpt.timeframe`.
## Solving a Mystery ## Solving a Mystery
@ -403,7 +403,7 @@ As stated in the comment, you can also use it as the value of the `minimal_roi`
#### Default ROI Search Space #### Default ROI Search Space
If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the ticker_interval used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 5 digits after the decimal point): If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the timeframe used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 5 digits after the decimal point):
| # step | 1m | | 5m | | 1h | | 1d | | | # step | 1m | | 5m | | 1h | | 1d | |
| ------ | ------ | ----------------- | -------- | ----------- | ---------- | ----------------- | ------------ | ----------------- | | ------ | ------ | ----------------- | -------- | ----------- | ---------- | ----------------- | ------------ | ----------------- |
@ -412,7 +412,7 @@ If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace f
| 3 | 4...20 | 0.00387...0.01547 | 20...100 | 0.01...0.04 | 240...1200 | 0.02294...0.09177 | 5760...28800 | 0.04059...0.16237 | | 3 | 4...20 | 0.00387...0.01547 | 20...100 | 0.01...0.04 | 240...1200 | 0.02294...0.09177 | 5760...28800 | 0.04059...0.16237 |
| 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 | | 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 |
These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe (ticker interval) used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used. These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used.
If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default. If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default.

View File

@ -31,7 +31,7 @@ usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--plot-limit INT] [--db-url PATH] [--plot-limit INT] [--db-url PATH]
[--trade-source {DB,file}] [--export EXPORT] [--trade-source {DB,file}] [--export EXPORT]
[--export-filename PATH] [--export-filename PATH]
[--timerange TIMERANGE] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [-i TIMEFRAME]
[--no-trades] [--no-trades]
optional arguments: optional arguments:
@ -65,7 +65,7 @@ optional arguments:
_today.json` _today.json`
--timerange TIMERANGE --timerange TIMERANGE
Specify what timerange of data to use. Specify what timerange of data to use.
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`, Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`). `1d`).
--no-trades Skip using trades from backtesting file and DB. --no-trades Skip using trades from backtesting file and DB.
@ -227,7 +227,7 @@ usage: freqtrade plot-profit [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-p PAIRS [PAIRS ...]] [-d PATH] [--userdir PATH] [-p PAIRS [PAIRS ...]]
[--timerange TIMERANGE] [--export EXPORT] [--timerange TIMERANGE] [--export EXPORT]
[--export-filename PATH] [--db-url PATH] [--export-filename PATH] [--db-url PATH]
[--trade-source {DB,file}] [-i TICKER_INTERVAL] [--trade-source {DB,file}] [-i TIMEFRAME]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@ -250,7 +250,7 @@ optional arguments:
--trade-source {DB,file} --trade-source {DB,file}
Specify the source for trades (Can be DB or file Specify the source for trades (Can be DB or file
(backtest file)) Default: file (backtest file)) Default: file
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`, Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`). `1d`).
@ -261,9 +261,10 @@ Common arguments:
details. details.
-V, --version show program's version number and exit -V, --version show program's version number and exit
-c PATH, --config PATH -c PATH, --config PATH
Specify configuration file (default: `config.json`). Specify configuration file (default:
Multiple --config options may be used. Can be set to `userdir/config.json` or `config.json` whichever
`-` to read config from stdin. exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH -d PATH, --datadir PATH
Path to directory with historical backtesting data. Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH --userdir PATH, --user-data-dir PATH

View File

@ -1,2 +1,2 @@
mkdocs-material==5.2.3 mkdocs-material==5.3.0
mdx_truly_sane_lists==1.2 mdx_truly_sane_lists==1.2

View File

@ -70,7 +70,7 @@ CREATE TABLE trades
min_rate FLOAT, min_rate FLOAT,
sell_reason VARCHAR, sell_reason VARCHAR,
strategy VARCHAR, strategy VARCHAR,
ticker_interval INTEGER, timeframe INTEGER,
PRIMARY KEY (id), PRIMARY KEY (id),
CHECK (is_open IN (0, 1)) CHECK (is_open IN (0, 1))
); );

View File

@ -27,7 +27,7 @@ So this parameter will tell the bot how often it should update the stoploss orde
This same logic will reapply a stoploss order on the exchange should you cancel it accidentally. This same logic will reapply a stoploss order on the exchange should you cancel it accidentally.
!!! Note !!! Note
Stoploss on exchange is only supported for Binance (stop-loss-limit) and Kraken (stop-loss-market) as of now. Stoploss on exchange is only supported for Binance (stop-loss-limit), Kraken (stop-loss-market) and FTX (stop limit and stop-market) as of now.
## Static Stop Loss ## Static Stop Loss

View File

@ -142,7 +142,7 @@ By letting the bot know how much history is needed, backtest trades can start at
Let's try to backtest 1 month (January 2019) of 5m candles using an example strategy with EMA100, as above. Let's try to backtest 1 month (January 2019) of 5m candles using an example strategy with EMA100, as above.
``` bash ``` bash
freqtrade backtesting --timerange 20190101-20190201 --ticker-interval 5m freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m
``` ```
Assuming `startup_candle_count` is set to 100, backtesting knows it needs 100 candles to generate valid buy signals. It will load data from `20190101 - (100 * 5m)` - which is ~2019-12-31 15:30:00. Assuming `startup_candle_count` is set to 100, backtesting knows it needs 100 candles to generate valid buy signals. It will load data from `20190101 - (100 * 5m)` - which is ~2019-12-31 15:30:00.
@ -248,7 +248,7 @@ minimal_roi = {
While technically not completely disabled, this would sell once the trade reaches 10000% Profit. While technically not completely disabled, this would sell once the trade reaches 10000% Profit.
To use times based on candle duration (ticker_interval or timeframe), the following snippet can be handy. To use times based on candle duration (timeframe), the following snippet can be handy.
This will allow you to change the ticket_interval for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...) This will allow you to change the ticket_interval for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...)
``` python ``` python
@ -256,12 +256,12 @@ from freqtrade.exchange import timeframe_to_minutes
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
ticker_interval = "1d" timeframe = "1d"
ticker_interval_mins = timeframe_to_minutes(ticker_interval) timeframe_mins = timeframe_to_minutes(timeframe)
minimal_roi = { minimal_roi = {
"0": 0.05, # 5% for the first 3 candles "0": 0.05, # 5% for the first 3 candles
str(ticker_interval_mins * 3)): 0.02, # 2% after 3 candles str(timeframe_mins * 3)): 0.02, # 2% after 3 candles
str(ticker_interval_mins * 6)): 0.01, # 1% After 6 candles str(timeframe_mins * 6)): 0.01, # 1% After 6 candles
} }
``` ```
@ -290,7 +290,7 @@ Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported
Please note that the same buy/sell signals may work well with one timeframe, but not with the others. Please note that the same buy/sell signals may work well with one timeframe, but not with the others.
This setting is accessible within the strategy methods as the `self.ticker_interval` attribute. This setting is accessible within the strategy methods as the `self.timeframe` attribute.
### Metadata dict ### Metadata dict
@ -400,7 +400,7 @@ This is where calling `self.dp.current_whitelist()` comes in handy.
class SampleStrategy(IStrategy): class SampleStrategy(IStrategy):
# strategy init stuff... # strategy init stuff...
ticker_interval = '5m' timeframe = '5m'
# more strategy init stuff.. # more strategy init stuff..

View File

@ -18,7 +18,7 @@ config = Configuration.from_files([])
# config = Configuration.from_files(["config.json"]) # config = Configuration.from_files(["config.json"])
# Define some constants # Define some constants
config["ticker_interval"] = "5m" config["timeframe"] = "5m"
# Name of the strategy class # Name of the strategy class
config["strategy"] = "SampleStrategy" config["strategy"] = "SampleStrategy"
# Location of the data # Location of the data
@ -33,7 +33,7 @@ pair = "BTC_USDT"
from freqtrade.data.history import load_pair_history from freqtrade.data.history import load_pair_history
candles = load_pair_history(datadir=data_location, candles = load_pair_history(datadir=data_location,
timeframe=config["ticker_interval"], timeframe=config["timeframe"],
pair=pair) pair=pair)
# Confirm success # Confirm success

View File

@ -62,7 +62,7 @@ $ freqtrade new-config --config config_binance.json
? Please insert your stake currency: BTC ? Please insert your stake currency: BTC
? Please insert your stake amount: 0.05 ? Please insert your stake amount: 0.05
? Please insert max_open_trades (Integer or 'unlimited'): 3 ? Please insert max_open_trades (Integer or 'unlimited'): 3
? Please insert your timeframe (ticker interval): 5m ? Please insert your desired timeframe (e.g. 5m): 5m
? Please insert your display Currency (for reporting): USD ? Please insert your display Currency (for reporting): USD
? Select exchange binance ? Select exchange binance
? Do you want to enable Telegram? No ? Do you want to enable Telegram? No

View File

@ -15,7 +15,7 @@ ARGS_STRATEGY = ["strategy", "strategy_path"]
ARGS_TRADE = ["db_url", "sd_notify", "dry_run"] ARGS_TRADE = ["db_url", "sd_notify", "dry_run"]
ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange", ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange",
"max_open_trades", "stake_amount", "fee"] "max_open_trades", "stake_amount", "fee"]
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
@ -59,10 +59,10 @@ ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchang
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
"db_url", "trade_source", "export", "exportfilename", "db_url", "trade_source", "export", "exportfilename",
"timerange", "ticker_interval", "no_trades"] "timerange", "timeframe", "no_trades"]
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
"trade_source", "ticker_interval"] "trade_source", "timeframe"]
ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"] ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"]
@ -318,7 +318,7 @@ class Arguments:
# Add list-timeframes subcommand # Add list-timeframes subcommand
list_timeframes_cmd = subparsers.add_parser( list_timeframes_cmd = subparsers.add_parser(
'list-timeframes', 'list-timeframes',
help='Print available ticker intervals (timeframes) for the exchange.', help='Print available timeframes for the exchange.',
parents=[_common_parser], parents=[_common_parser],
) )
list_timeframes_cmd.set_defaults(func=start_list_timeframes) list_timeframes_cmd.set_defaults(func=start_list_timeframes)

View File

@ -75,8 +75,8 @@ def ask_user_config() -> Dict[str, Any]:
}, },
{ {
"type": "text", "type": "text",
"name": "ticker_interval", "name": "timeframe",
"message": "Please insert your timeframe (ticker interval):", "message": "Please insert your desired timeframe (e.g. 5m):",
"default": "5m", "default": "5m",
}, },
{ {

View File

@ -110,8 +110,8 @@ AVAILABLE_CLI_OPTIONS = {
action='store_true', action='store_true',
), ),
# Optimize common # Optimize common
"ticker_interval": Arg( "timeframe": Arg(
'-i', '--ticker-interval', '-i', '--timeframe', '--ticker-interval',
help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).', help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).',
), ),
"timerange": Arg( "timerange": Arg(

View File

@ -102,8 +102,8 @@ def start_list_timeframes(args: Dict[str, Any]) -> None:
Print ticker intervals (timeframes) available on Exchange Print ticker intervals (timeframes) available on Exchange
""" """
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
# Do not use ticker_interval set in the config # Do not use timeframe set in the config
config['ticker_interval'] = None config['timeframe'] = None
# Init exchange # Init exchange
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)

View File

@ -25,7 +25,6 @@ def start_test_pairlist(args: Dict[str, Any]) -> None:
results = {} results = {}
for curr in quote_currencies: for curr in quote_currencies:
config['stake_currency'] = curr config['stake_currency'] = curr
# Do not use ticker_interval set in the config
pairlists = PairListManager(exchange, config) pairlists = PairListManager(exchange, config)
pairlists.refresh_pairlist() pairlists.refresh_pairlist()
results[curr] = pairlists.whitelist results[curr] = pairlists.whitelist

View File

@ -204,9 +204,9 @@ class Configuration:
def _process_optimize_options(self, config: Dict[str, Any]) -> None: def _process_optimize_options(self, config: Dict[str, Any]) -> None:
# This will override the strategy configuration # This will override the strategy configuration
self._args_to_config(config, argname='ticker_interval', self._args_to_config(config, argname='timeframe',
logstring='Parameter -i/--ticker-interval detected ... ' logstring='Parameter -i/--timeframe detected ... '
'Using ticker_interval: {} ...') 'Using timeframe: {} ...')
self._args_to_config(config, argname='position_stacking', self._args_to_config(config, argname='position_stacking',
logstring='Parameter --enable-position-stacking detected ...') logstring='Parameter --enable-position-stacking detected ...')
@ -242,8 +242,8 @@ class Configuration:
self._args_to_config(config, argname='strategy_list', self._args_to_config(config, argname='strategy_list',
logstring='Using strategy list of {} strategies', logfun=len) logstring='Using strategy list of {} strategies', logfun=len)
self._args_to_config(config, argname='ticker_interval', self._args_to_config(config, argname='timeframe',
logstring='Overriding ticker interval with Command line argument') logstring='Overriding timeframe with Command line argument')
self._args_to_config(config, argname='export', self._args_to_config(config, argname='export',
logstring='Parameter --export detected: {} ...') logstring='Parameter --export detected: {} ...')

View File

@ -67,3 +67,14 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
"'tradable_balance_ratio' and remove 'capital_available_percentage' " "'tradable_balance_ratio' and remove 'capital_available_percentage' "
"from the edge configuration." "from the edge configuration."
) )
if 'ticker_interval' in config:
logger.warning(
"DEPRECATED: "
"Please use 'timeframe' instead of 'ticker_interval."
)
if 'timeframe' in config:
raise OperationalException(
"Both 'timeframe' and 'ticker_interval' detected."
"Please remove 'ticker_interval' from your configuration to continue operating."
)
config['timeframe'] = config['ticker_interval']

View File

@ -71,7 +71,7 @@ CONF_SCHEMA = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'max_open_trades': {'type': ['integer', 'number'], 'minimum': -1}, 'max_open_trades': {'type': ['integer', 'number'], 'minimum': -1},
'ticker_interval': {'type': 'string'}, 'timeframe': {'type': 'string'},
'stake_currency': {'type': 'string'}, 'stake_currency': {'type': 'string'},
'stake_amount': { 'stake_amount': {
'type': ['number', 'string'], 'type': ['number', 'string'],
@ -303,6 +303,7 @@ CONF_SCHEMA = {
SCHEMA_TRADE_REQUIRED = [ SCHEMA_TRADE_REQUIRED = [
'exchange', 'exchange',
'timeframe',
'max_open_trades', 'max_open_trades',
'stake_currency', 'stake_currency',
'stake_amount', 'stake_amount',

View File

@ -103,7 +103,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame:
"open_rate", "close_rate", "amount", "duration", "sell_reason", "open_rate", "close_rate", "amount", "duration", "sell_reason",
"fee_open", "fee_close", "open_rate_requested", "close_rate_requested", "fee_open", "fee_close", "open_rate_requested", "close_rate_requested",
"stake_amount", "max_rate", "min_rate", "id", "exchange", "stake_amount", "max_rate", "min_rate", "id", "exchange",
"stop_loss", "initial_stop_loss", "strategy", "ticker_interval"] "stop_loss", "initial_stop_loss", "strategy", "timeframe"]
trades = pd.DataFrame([(t.pair, trades = pd.DataFrame([(t.pair,
t.open_date.replace(tzinfo=timezone.utc), t.open_date.replace(tzinfo=timezone.utc),
@ -121,7 +121,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame:
t.min_rate, t.min_rate,
t.id, t.exchange, t.id, t.exchange,
t.stop_loss, t.initial_stop_loss, t.stop_loss, t.initial_stop_loss,
t.strategy, t.ticker_interval t.strategy, t.timeframe
) )
for t in Trade.get_trades().all()], for t in Trade.get_trades().all()],
columns=columns) columns=columns)

View File

@ -236,12 +236,12 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to:
from freqtrade.data.history.idatahandler import get_datahandler from freqtrade.data.history.idatahandler import get_datahandler
src = get_datahandler(config['datadir'], convert_from) src = get_datahandler(config['datadir'], convert_from)
trg = get_datahandler(config['datadir'], convert_to) trg = get_datahandler(config['datadir'], convert_to)
timeframes = config.get('timeframes', [config.get('ticker_interval')]) timeframes = config.get('timeframes', [config.get('timeframe')])
logger.info(f"Converting candle (OHLCV) for timeframe {timeframes}") logger.info(f"Converting candle (OHLCV) for timeframe {timeframes}")
if 'pairs' not in config: if 'pairs' not in config:
config['pairs'] = [] config['pairs'] = []
# Check timeframes or fall back to ticker_interval. # Check timeframes or fall back to timeframe.
for timeframe in timeframes: for timeframe in timeframes:
config['pairs'].extend(src.ohlcv_get_pairs(config['datadir'], config['pairs'].extend(src.ohlcv_get_pairs(config['datadir'],
timeframe)) timeframe))

View File

@ -55,7 +55,7 @@ class DataProvider:
Use False only for read-only operations (where the dataframe is not modified) Use False only for read-only operations (where the dataframe is not modified)
""" """
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
return self._exchange.klines((pair, timeframe or self._config['ticker_interval']), return self._exchange.klines((pair, timeframe or self._config['timeframe']),
copy=copy) copy=copy)
else: else:
return DataFrame() return DataFrame()
@ -67,7 +67,7 @@ class DataProvider:
:param timeframe: timeframe to get data for :param timeframe: timeframe to get data for
""" """
return load_pair_history(pair=pair, return load_pair_history(pair=pair,
timeframe=timeframe or self._config['ticker_interval'], timeframe=timeframe or self._config['timeframe'],
datadir=self._config['datadir'] datadir=self._config['datadir']
) )

View File

@ -98,14 +98,14 @@ class Edge:
datadir=self.config['datadir'], datadir=self.config['datadir'],
pairs=pairs, pairs=pairs,
exchange=self.exchange, exchange=self.exchange,
timeframe=self.strategy.ticker_interval, timeframe=self.strategy.timeframe,
timerange=self._timerange, timerange=self._timerange,
) )
data = load_data( data = load_data(
datadir=self.config['datadir'], datadir=self.config['datadir'],
pairs=pairs, pairs=pairs,
timeframe=self.strategy.ticker_interval, timeframe=self.strategy.timeframe,
timerange=self._timerange, timerange=self._timerange,
startup_candles=self.strategy.startup_candle_count, startup_candles=self.strategy.startup_candle_count,
data_format=self.config.get('dataformat_ohlcv', 'json'), data_format=self.config.get('dataformat_ohlcv', 'json'),

View File

@ -79,7 +79,7 @@ class Exchange:
if config['dry_run']: if config['dry_run']:
logger.info('Instance is running with dry_run enabled') logger.info('Instance is running with dry_run enabled')
logger.info(f"Using CCXT {ccxt.__version__}")
exchange_config = config['exchange'] exchange_config = config['exchange']
# Deep merge ft_has with default ft_has options # Deep merge ft_has with default ft_has options
@ -115,7 +115,7 @@ class Exchange:
if validate: if validate:
# Check if timeframe is available # Check if timeframe is available
self.validate_timeframes(config.get('ticker_interval')) self.validate_timeframes(config.get('timeframe'))
# Initial markets load # Initial markets load
self._load_markets() self._load_markets()
@ -285,6 +285,8 @@ class Exchange:
logger.debug("Performing scheduled market reload..") logger.debug("Performing scheduled market reload..")
try: try:
self._api.load_markets(reload=True) self._api.load_markets(reload=True)
# Also reload async markets to avoid issues with newly listed pairs
self._load_async_markets(reload=True)
self._last_markets_refresh = arrow.utcnow().timestamp self._last_markets_refresh = arrow.utcnow().timestamp
except ccxt.BaseError: except ccxt.BaseError:
logger.exception("Could not reload markets.") logger.exception("Could not reload markets.")
@ -959,6 +961,9 @@ class Exchange:
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
# Assign method to get_stoploss_order to allow easy overriding in other classes
cancel_stoploss_order = cancel_order
def is_cancel_order_result_suitable(self, corder) -> bool: def is_cancel_order_result_suitable(self, corder) -> bool:
if not isinstance(corder, dict): if not isinstance(corder, dict):
return False return False
@ -1011,6 +1016,9 @@ class Exchange:
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
# Assign method to get_stoploss_order to allow easy overriding in other classes
get_stoploss_order = get_order
@retrier @retrier
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict: def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
""" """

View File

@ -2,7 +2,12 @@
import logging import logging
from typing import Dict from typing import Dict
import ccxt
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -10,5 +15,104 @@ logger = logging.getLogger(__name__)
class Ftx(Exchange): class Ftx(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"stoploss_on_exchange": True,
"ohlcv_candle_limit": 1500, "ohlcv_candle_limit": 1500,
} }
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
"""
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
"""
return order['type'] == 'stop' and stop_loss > float(order['price'])
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
"""
Creates a stoploss order.
depending on order_types.stoploss configuration, uses 'market' or limit order.
Limit orders are defined by having orderPrice set, otherwise a market order is used.
"""
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
limit_rate = stop_price * limit_price_pct
ordertype = "stop"
stop_price = self.price_to_precision(pair, stop_price)
if self._config['dry_run']:
dry_order = self.dry_run_order(
pair, ordertype, "sell", amount, stop_price)
return dry_order
try:
params = self._params.copy()
if order_types.get('stoploss', 'market') == 'limit':
# set orderPrice to place limit order, otherwise it's a market order
params['orderPrice'] = limit_rate
amount = self.amount_to_precision(pair, amount)
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
amount=amount, price=stop_price, params=params)
logger.info('stoploss order added for %s. '
'stop price: %s.', pair, stop_price)
return order
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create {ordertype} sell order on market {pair}. '
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
f'Message: {e}') from e
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
f'Could not create {ordertype} sell order on market {pair}. '
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
f'Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def get_stoploss_order(self, order_id: str, pair: str) -> Dict:
if self._config['dry_run']:
try:
order = self._dry_run_open_orders[order_id]
return order
except KeyError as e:
# Gracefully handle errors with dry-run orders.
raise InvalidOrderException(
f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e
try:
orders = self._api.fetch_orders(pair, None, params={'type': 'stop'})
order = [order for order in orders if order['id'] == order_id]
if len(order) == 1:
return order[0]
else:
raise InvalidOrderException(f"Could not get stoploss order for id {order_id}")
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def cancel_stoploss_order(self, order_id: str, pair: str) -> Dict:
if self._config['dry_run']:
return {}
try:
return self._api.cancel_order(order_id, pair, params={'type': 'stop'})
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
f'Could not cancel order. Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e

View File

@ -421,8 +421,8 @@ class FreqtradeBot:
# running get_signal on historical data fetched # running get_signal on historical data fetched
(buy, sell) = self.strategy.get_signal( (buy, sell) = self.strategy.get_signal(
pair, self.strategy.ticker_interval, pair, self.strategy.timeframe,
self.dataprovider.ohlcv(pair, self.strategy.ticker_interval)) self.dataprovider.ohlcv(pair, self.strategy.timeframe))
if buy and not sell: if buy and not sell:
stake_amount = self.get_trade_stake_amount(pair) stake_amount = self.get_trade_stake_amount(pair)
@ -547,7 +547,7 @@ class FreqtradeBot:
exchange=self.exchange.id, exchange=self.exchange.id,
open_order_id=order_id, open_order_id=order_id,
strategy=self.strategy.get_strategy_name(), strategy=self.strategy.get_strategy_name(),
ticker_interval=timeframe_to_minutes(self.config['ticker_interval']) timeframe=timeframe_to_minutes(self.config['timeframe'])
) )
# Update fees if order is closed # Update fees if order is closed
@ -698,8 +698,8 @@ class FreqtradeBot:
if (config_ask_strategy.get('use_sell_signal', True) or if (config_ask_strategy.get('use_sell_signal', True) or
config_ask_strategy.get('ignore_roi_if_buy_signal', False)): config_ask_strategy.get('ignore_roi_if_buy_signal', False)):
(buy, sell) = self.strategy.get_signal( (buy, sell) = self.strategy.get_signal(
trade.pair, self.strategy.ticker_interval, trade.pair, self.strategy.timeframe,
self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval)) self.dataprovider.ohlcv(trade.pair, self.strategy.timeframe))
if config_ask_strategy.get('use_order_book', False): if config_ask_strategy.get('use_order_book', False):
order_book_min = config_ask_strategy.get('order_book_min', 1) order_book_min = config_ask_strategy.get('order_book_min', 1)
@ -773,18 +773,18 @@ class FreqtradeBot:
try: try:
# First we check if there is already a stoploss on exchange # First we check if there is already a stoploss on exchange
stoploss_order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) \ stoploss_order = self.exchange.get_stoploss_order(trade.stoploss_order_id, trade.pair) \
if trade.stoploss_order_id else None if trade.stoploss_order_id else None
except InvalidOrderException as exception: except InvalidOrderException as exception:
logger.warning('Unable to fetch stoploss order: %s', exception) logger.warning('Unable to fetch stoploss order: %s', exception)
# We check if stoploss order is fulfilled # We check if stoploss order is fulfilled
if stoploss_order and stoploss_order['status'] == 'closed': if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
self.update_trade_state(trade, stoploss_order, sl_order=True) self.update_trade_state(trade, stoploss_order, sl_order=True)
# Lock pair for one candle to prevent immediate rebuys # Lock pair for one candle to prevent immediate rebuys
self.strategy.lock_pair(trade.pair, self.strategy.lock_pair(trade.pair,
timeframe_to_next_date(self.config['ticker_interval'])) timeframe_to_next_date(self.config['timeframe']))
self._notify_sell(trade, "stoploss") self._notify_sell(trade, "stoploss")
return True return True
@ -804,7 +804,7 @@ class FreqtradeBot:
return False return False
# If stoploss order is canceled for some reason we add it # If stoploss order is canceled for some reason we add it
if stoploss_order and stoploss_order['status'] == 'canceled': if stoploss_order and stoploss_order['status'] in ('canceled', 'cancelled'):
if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss, if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss,
rate=trade.stop_loss): rate=trade.stop_loss):
return False return False
@ -837,7 +837,7 @@ class FreqtradeBot:
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s}) ' logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s}) '
'in order to add another one ...', order['id']) 'in order to add another one ...', order['id'])
try: try:
self.exchange.cancel_order(order['id'], trade.pair) self.exchange.cancel_stoploss_order(order['id'], trade.pair)
except InvalidOrderException: except InvalidOrderException:
logger.exception(f"Could not cancel stoploss order {order['id']} " logger.exception(f"Could not cancel stoploss order {order['id']} "
f"for pair {trade.pair}") f"for pair {trade.pair}")
@ -1065,7 +1065,7 @@ class FreqtradeBot:
# First cancelling stoploss on exchange ... # First cancelling stoploss on exchange ...
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
try: try:
self.exchange.cancel_order(trade.stoploss_order_id, trade.pair) self.exchange.cancel_stoploss_order(trade.stoploss_order_id, trade.pair)
except InvalidOrderException: except InvalidOrderException:
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
@ -1092,7 +1092,7 @@ class FreqtradeBot:
Trade.session.flush() Trade.session.flush()
# Lock pair for one candle to prevent immediate rebuys # Lock pair for one candle to prevent immediate rebuys
self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['ticker_interval'])) self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['timeframe']))
self._notify_sell(trade, order_type) self._notify_sell(trade, order_type)

View File

@ -95,10 +95,10 @@ class Backtesting:
self.strategylist.append(StrategyResolver.load_strategy(self.config)) self.strategylist.append(StrategyResolver.load_strategy(self.config))
validate_config_consistency(self.config) validate_config_consistency(self.config)
if "ticker_interval" not in self.config: if "timeframe" not in self.config:
raise OperationalException("Timeframe (ticker interval) needs to be set in either " raise OperationalException("Timeframe (ticker interval) needs to be set in either "
"configuration or as cli argument `--ticker-interval 5m`") "configuration or as cli argument `--timeframe 5m`")
self.timeframe = str(self.config.get('ticker_interval')) self.timeframe = str(self.config.get('timeframe'))
self.timeframe_min = timeframe_to_minutes(self.timeframe) self.timeframe_min = timeframe_to_minutes(self.timeframe)
# Get maximum required startup period # Get maximum required startup period

View File

@ -12,7 +12,7 @@ from math import ceil
from collections import OrderedDict from collections import OrderedDict
from operator import itemgetter from operator import itemgetter
from pathlib import Path from pathlib import Path
from pprint import pprint from pprint import pformat
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import rapidjson import rapidjson
@ -230,6 +230,9 @@ class Hyperopt:
if space in ['buy', 'sell']: if space in ['buy', 'sell']:
result_dict.setdefault('params', {}).update(space_params) result_dict.setdefault('params', {}).update(space_params)
elif space == 'roi': elif space == 'roi':
# TODO: get rid of OrderedDict when support for python 3.6 will be
# dropped (dicts keep the order as the language feature)
# Convert keys in min_roi dict to strings because # Convert keys in min_roi dict to strings because
# rapidjson cannot dump dicts with integer keys... # rapidjson cannot dump dicts with integer keys...
# OrderedDict is used to keep the numeric order of the items # OrderedDict is used to keep the numeric order of the items
@ -244,11 +247,24 @@ class Hyperopt:
def _params_pretty_print(params, space: str, header: str) -> None: def _params_pretty_print(params, space: str, header: str) -> None:
if space in params: if space in params:
space_params = Hyperopt._space_params(params, space, 5) space_params = Hyperopt._space_params(params, space, 5)
params_result = f"\n# {header}\n"
if space == 'stoploss': if space == 'stoploss':
print(header, space_params.get('stoploss')) params_result += f"stoploss = {space_params.get('stoploss')}"
elif space == 'roi':
# TODO: get rid of OrderedDict when support for python 3.6 will be
# dropped (dicts keep the order as the language feature)
minimal_roi_result = rapidjson.dumps(
OrderedDict(
(str(k), v) for k, v in space_params.items()
),
default=str, indent=4, number_mode=rapidjson.NM_NATIVE)
params_result += f"minimal_roi = {minimal_roi_result}"
else: else:
print(header) params_result += f"{space}_params = {pformat(space_params, indent=4)}"
pprint(space_params, indent=4) params_result = params_result.replace("}", "\n}").replace("{", "{\n ")
params_result = params_result.replace("\n", "\n ")
print(params_result)
@staticmethod @staticmethod
def _space_params(params, space: str, r: int = None) -> Dict: def _space_params(params, space: str, r: int = None) -> Dict:

View File

@ -31,13 +31,15 @@ class IHyperOpt(ABC):
Class attributes you can use: Class attributes you can use:
ticker_interval -> int: value of the ticker interval to use for the strategy ticker_interval -> int: value of the ticker interval to use for the strategy
""" """
ticker_interval: str ticker_interval: str # DEPRECATED
timeframe: str
def __init__(self, config: dict) -> None: def __init__(self, config: dict) -> None:
self.config = config self.config = config
# Assign ticker_interval to be used in hyperopt # Assign ticker_interval to be used in hyperopt
IHyperOpt.ticker_interval = str(config['ticker_interval']) IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
IHyperOpt.timeframe = str(config['timeframe'])
@staticmethod @staticmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable: def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
@ -218,9 +220,10 @@ class IHyperOpt(ABC):
# Why do I still need such shamanic mantras in modern python? # Why do I still need such shamanic mantras in modern python?
def __getstate__(self): def __getstate__(self):
state = self.__dict__.copy() state = self.__dict__.copy()
state['ticker_interval'] = self.ticker_interval state['timeframe'] = self.timeframe
return state return state
def __setstate__(self, state): def __setstate__(self, state):
self.__dict__.update(state) self.__dict__.update(state)
IHyperOpt.ticker_interval = state['ticker_interval'] IHyperOpt.ticker_interval = state['timeframe']
IHyperOpt.timeframe = state['timeframe']

View File

@ -14,7 +14,7 @@ class IHyperOptLoss(ABC):
Interface for freqtrade hyperopt Loss functions. Interface for freqtrade hyperopt Loss functions.
Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.) Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.)
""" """
ticker_interval: str timeframe: str
@staticmethod @staticmethod
@abstractmethod @abstractmethod

View File

@ -131,6 +131,6 @@ class PairListManager():
def create_pair_list(self, pairs: List[str], timeframe: str = None) -> ListPairsWithTimeframes: def create_pair_list(self, pairs: List[str], timeframe: str = None) -> ListPairsWithTimeframes:
""" """
Create list of pair tuples with (pair, ticker_interval) Create list of pair tuples with (pair, timeframe)
""" """
return [(pair, timeframe or self._config['ticker_interval']) for pair in pairs] return [(pair, timeframe or self._config['timeframe']) for pair in pairs]

View File

@ -86,7 +86,7 @@ def check_migrate(engine) -> None:
logger.debug(f'trying {table_back_name}') logger.debug(f'trying {table_back_name}')
# Check for latest column # Check for latest column
if not has_column(cols, 'sell_order_status'): if not has_column(cols, 'timeframe'):
logger.info(f'Running database migration - backup available as {table_back_name}') logger.info(f'Running database migration - backup available as {table_back_name}')
fee_open = get_column_def(cols, 'fee_open', 'fee') fee_open = get_column_def(cols, 'fee_open', 'fee')
@ -107,7 +107,12 @@ def check_migrate(engine) -> None:
min_rate = get_column_def(cols, 'min_rate', 'null') min_rate = get_column_def(cols, 'min_rate', 'null')
sell_reason = get_column_def(cols, 'sell_reason', 'null') sell_reason = get_column_def(cols, 'sell_reason', 'null')
strategy = get_column_def(cols, 'strategy', 'null') strategy = get_column_def(cols, 'strategy', 'null')
ticker_interval = get_column_def(cols, 'ticker_interval', 'null') # If ticker-interval existed use that, else null.
if has_column(cols, 'ticker_interval'):
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
else:
timeframe = get_column_def(cols, 'timeframe', 'null')
open_trade_price = get_column_def(cols, 'open_trade_price', open_trade_price = get_column_def(cols, 'open_trade_price',
f'amount * open_rate * (1 + {fee_open})') f'amount * open_rate * (1 + {fee_open})')
close_profit_abs = get_column_def( close_profit_abs = get_column_def(
@ -133,7 +138,7 @@ def check_migrate(engine) -> None:
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update, stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, sell_order_status, strategy, max_rate, min_rate, sell_reason, sell_order_status, strategy,
ticker_interval, open_trade_price, close_profit_abs timeframe, open_trade_price, close_profit_abs
) )
select id, lower(exchange), select id, lower(exchange),
case case
@ -155,7 +160,7 @@ def check_migrate(engine) -> None:
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
{sell_order_status} sell_order_status, {sell_order_status} sell_order_status,
{strategy} strategy, {ticker_interval} ticker_interval, {strategy} strategy, {timeframe} timeframe,
{open_trade_price} open_trade_price, {close_profit_abs} close_profit_abs {open_trade_price} open_trade_price, {close_profit_abs} close_profit_abs
from {table_back_name} from {table_back_name}
""") """)
@ -232,7 +237,7 @@ class Trade(_DECL_BASE):
sell_reason = Column(String, nullable=True) sell_reason = Column(String, nullable=True)
sell_order_status = Column(String, nullable=True) sell_order_status = Column(String, nullable=True)
strategy = Column(String, nullable=True) strategy = Column(String, nullable=True)
ticker_interval = Column(Integer, nullable=True) timeframe = Column(Integer, nullable=True)
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@ -253,7 +258,8 @@ class Trade(_DECL_BASE):
'amount': round(self.amount, 8), 'amount': round(self.amount, 8),
'stake_amount': round(self.stake_amount, 8), 'stake_amount': round(self.stake_amount, 8),
'strategy': self.strategy, 'strategy': self.strategy,
'ticker_interval': self.ticker_interval, 'ticker_interval': self.timeframe, # DEPRECATED
'timeframe': self.timeframe,
'fee_open': self.fee_open, 'fee_open': self.fee_open,
'fee_open_cost': self.fee_open_cost, 'fee_open_cost': self.fee_open_cost,
@ -374,7 +380,7 @@ class Trade(_DECL_BASE):
elif order_type in ('market', 'limit') and order['side'] == 'sell': elif order_type in ('market', 'limit') and order['side'] == 'sell':
self.close(order['price']) self.close(order['price'])
logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self) logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self)
elif order_type in ('stop_loss_limit', 'stop-loss'): elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'):
self.stoploss_order_id = None self.stoploss_order_id = None
self.close_rate_requested = self.stop_loss self.close_rate_requested = self.stop_loss
logger.info('%s is hit for %s.', order_type.upper(), self) logger.info('%s is hit for %s.', order_type.upper(), self)

View File

@ -45,7 +45,7 @@ def init_plotscript(config):
data = load_data( data = load_data(
datadir=config.get("datadir"), datadir=config.get("datadir"),
pairs=pairs, pairs=pairs,
timeframe=config.get('ticker_interval', '5m'), timeframe=config.get('timeframe', '5m'),
timerange=timerange, timerange=timerange,
data_format=config.get('dataformat_ohlcv', 'json'), data_format=config.get('dataformat_ohlcv', 'json'),
) )
@ -487,7 +487,7 @@ def load_and_plot_trades(config: Dict[str, Any]):
plot_config=strategy.plot_config if hasattr(strategy, 'plot_config') else {} plot_config=strategy.plot_config if hasattr(strategy, 'plot_config') else {}
) )
store_plot_file(fig, filename=generate_plot_filename(pair, config['ticker_interval']), store_plot_file(fig, filename=generate_plot_filename(pair, config['timeframe']),
directory=config['user_data_dir'] / "plot") directory=config['user_data_dir'] / "plot")
logger.info('End of plotting process. %s plots generated', pair_counter) logger.info('End of plotting process. %s plots generated', pair_counter)
@ -515,6 +515,6 @@ def plot_profit(config: Dict[str, Any]) -> None:
# Create an average close price of all the pairs that were involved. # Create an average close price of all the pairs that were involved.
# this could be useful to gauge the overall market trend # this could be useful to gauge the overall market trend
fig = generate_profit_graph(plot_elements["pairs"], plot_elements["ohlcv"], fig = generate_profit_graph(plot_elements["pairs"], plot_elements["ohlcv"],
trades, config.get('ticker_interval', '5m')) trades, config.get('timeframe', '5m'))
store_plot_file(fig, filename='freqtrade-profit-plot.html', store_plot_file(fig, filename='freqtrade-profit-plot.html',
directory=config['user_data_dir'] / "plot", auto_open=True) directory=config['user_data_dir'] / "plot", auto_open=True)

View File

@ -77,8 +77,9 @@ class HyperOptLossResolver(IResolver):
config, kwargs={}, config, kwargs={},
extra_dir=config.get('hyperopt_path')) extra_dir=config.get('hyperopt_path'))
# Assign ticker_interval to be used in hyperopt # Assign timeframe to be used in hyperopt
hyperoptloss.__class__.ticker_interval = str(config['ticker_interval']) hyperoptloss.__class__.ticker_interval = str(config['timeframe'])
hyperoptloss.__class__.timeframe = str(config['timeframe'])
if not hasattr(hyperoptloss, 'hyperopt_loss_function'): if not hasattr(hyperoptloss, 'hyperopt_loss_function'):
raise OperationalException( raise OperationalException(

View File

@ -50,11 +50,19 @@ class StrategyResolver(IResolver):
if 'ask_strategy' not in config: if 'ask_strategy' not in config:
config['ask_strategy'] = {} config['ask_strategy'] = {}
if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'):
# Assign ticker_interval to timeframe to keep compatibility
if 'timeframe' not in config:
logger.warning(
"DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'."
)
strategy.timeframe = strategy.ticker_interval
# Set attributes # Set attributes
# Check if we need to override configuration # Check if we need to override configuration
# (Attribute name, default, subkey) # (Attribute name, default, subkey)
attributes = [("minimal_roi", {"0": 10.0}, None), attributes = [("minimal_roi", {"0": 10.0}, None),
("ticker_interval", None, None), ("timeframe", None, None),
("stoploss", None, None), ("stoploss", None, None),
("trailing_stop", None, None), ("trailing_stop", None, None),
("trailing_stop_positive", None, None), ("trailing_stop_positive", None, None),
@ -80,6 +88,9 @@ class StrategyResolver(IResolver):
StrategyResolver._override_attribute_helper(strategy, config, StrategyResolver._override_attribute_helper(strategy, config,
attribute, default) attribute, default)
# Assign deprecated variable - to not break users code relying on this.
strategy.ticker_interval = strategy.timeframe
# Loop this list again to have output combined # Loop this list again to have output combined
for attribute, _, subkey in attributes: for attribute, _, subkey in attributes:
if subkey and attribute in config[subkey]: if subkey and attribute in config[subkey]:

View File

@ -101,7 +101,8 @@ class RPC:
'trailing_stop_positive': config.get('trailing_stop_positive'), 'trailing_stop_positive': config.get('trailing_stop_positive'),
'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'), 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'),
'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached'), 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached'),
'ticker_interval': config['ticker_interval'], 'ticker_interval': config['timeframe'], # DEPRECATED
'timeframe': config['timeframe'],
'exchange': config['exchange']['name'], 'exchange': config['exchange']['name'],
'strategy': config['strategy'], 'strategy': config['strategy'],
'forcebuy_enabled': config.get('forcebuy_enable', False), 'forcebuy_enabled': config.get('forcebuy_enable', False),

View File

@ -72,7 +72,7 @@ class RPCManager:
minimal_roi = config['minimal_roi'] minimal_roi = config['minimal_roi']
stoploss = config['stoploss'] stoploss = config['stoploss']
trailing_stop = config['trailing_stop'] trailing_stop = config['trailing_stop']
ticker_interval = config['ticker_interval'] timeframe = config['timeframe']
exchange_name = config['exchange']['name'] exchange_name = config['exchange']['name']
strategy_name = config.get('strategy', '') strategy_name = config.get('strategy', '')
self.send_msg({ self.send_msg({
@ -81,7 +81,7 @@ class RPCManager:
f'*Stake per trade:* `{stake_amount} {stake_currency}`\n' f'*Stake per trade:* `{stake_amount} {stake_currency}`\n'
f'*Minimum ROI:* `{minimal_roi}`\n' f'*Minimum ROI:* `{minimal_roi}`\n'
f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n' f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n'
f'*Ticker Interval:* `{ticker_interval}`\n' f'*Timeframe:* `{timeframe}`\n'
f'*Strategy:* `{strategy_name}`' f'*Strategy:* `{strategy_name}`'
}) })
self.send_msg({ self.send_msg({

View File

@ -669,7 +669,7 @@ class Telegram(RPC):
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n" f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n" f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
f"{sl_info}" f"{sl_info}"
f"*Ticker Interval:* `{val['ticker_interval']}`\n" f"*Timeframe:* `{val['timeframe']}`\n"
f"*Strategy:* `{val['strategy']}`\n" f"*Strategy:* `{val['strategy']}`\n"
f"*Current state:* `{val['state']}`" f"*Current state:* `{val['state']}`"
) )

View File

@ -62,7 +62,7 @@ class IStrategy(ABC):
Attributes you can use: Attributes you can use:
minimal_roi -> Dict: Minimal ROI designed for the strategy minimal_roi -> Dict: Minimal ROI designed for the strategy
stoploss -> float: optimal stoploss designed for the strategy stoploss -> float: optimal stoploss designed for the strategy
ticker_interval -> str: value of the timeframe (ticker interval) to use with the strategy timeframe -> str: value of the timeframe (ticker interval) to use with the strategy
""" """
# Strategy interface version # Strategy interface version
# Default to version 2 # Default to version 2
@ -85,8 +85,9 @@ class IStrategy(ABC):
trailing_stop_positive_offset: float = 0.0 trailing_stop_positive_offset: float = 0.0
trailing_only_offset_is_reached = False trailing_only_offset_is_reached = False
# associated ticker interval # associated timeframe
ticker_interval: str ticker_interval: str # DEPRECATED
timeframe: str
# Optional order types # Optional order types
order_types: Dict = { order_types: Dict = {

View File

@ -4,7 +4,7 @@
"stake_amount": {{ stake_amount }}, "stake_amount": {{ stake_amount }},
"tradable_balance_ratio": 0.99, "tradable_balance_ratio": 0.99,
"fiat_display_currency": "{{ fiat_display_currency }}", "fiat_display_currency": "{{ fiat_display_currency }}",
"ticker_interval": "{{ ticker_interval }}", "timeframe": "{{ timeframe }}",
"dry_run": {{ dry_run | lower }}, "dry_run": {{ dry_run | lower }},
"cancel_open_orders_on_exit": false, "cancel_open_orders_on_exit": false,
"unfilledtimeout": { "unfilledtimeout": {

View File

@ -51,8 +51,8 @@ class {{ strategy }}(IStrategy):
# trailing_stop_positive = 0.01 # trailing_stop_positive = 0.01
# trailing_stop_positive_offset = 0.0 # Disabled / not configured # trailing_stop_positive_offset = 0.0 # Disabled / not configured
# Optimal ticker interval for the strategy. # Optimal timeframe for the strategy.
ticker_interval = '5m' timeframe = '5m'
# Run "populate_indicators()" only for new candle. # Run "populate_indicators()" only for new candle.
process_only_new_candles = False process_only_new_candles = False

View File

@ -53,7 +53,7 @@ class SampleStrategy(IStrategy):
# trailing_stop_positive_offset = 0.0 # Disabled / not configured # trailing_stop_positive_offset = 0.0 # Disabled / not configured
# Optimal ticker interval for the strategy. # Optimal ticker interval for the strategy.
ticker_interval = '5m' timeframe = '5m'
# Run "populate_indicators()" only for new candle. # Run "populate_indicators()" only for new candle.
process_only_new_candles = False process_only_new_candles = False

View File

@ -1,6 +1,6 @@
# requirements without requirements installable via conda # requirements without requirements installable via conda
# mainly used for Raspberry pi installs # mainly used for Raspberry pi installs
ccxt==1.29.52 ccxt==1.30.2
SQLAlchemy==1.3.17 SQLAlchemy==1.3.17
python-telegram-bot==12.7 python-telegram-bot==12.7
arrow==0.15.6 arrow==0.15.6

View File

@ -4,13 +4,13 @@
-r requirements-hyperopt.txt -r requirements-hyperopt.txt
coveralls==2.0.0 coveralls==2.0.0
flake8==3.8.2 flake8==3.8.3
flake8-type-annotations==0.1.0 flake8-type-annotations==0.1.0
flake8-tidy-imports==4.1.0 flake8-tidy-imports==4.1.0
mypy==0.780 mypy==0.780
pytest==5.4.3 pytest==5.4.3
pytest-asyncio==0.12.0 pytest-asyncio==0.12.0
pytest-cov==2.9.0 pytest-cov==2.10.0
pytest-mock==3.1.1 pytest-mock==3.1.1
pytest-random-order==1.0.4 pytest-random-order==1.0.4

View File

@ -63,7 +63,7 @@ setup(name='freqtrade',
tests_require=['pytest', 'pytest-asyncio', 'pytest-cov', 'pytest-mock', ], tests_require=['pytest', 'pytest-asyncio', 'pytest-cov', 'pytest-mock', ],
install_requires=[ install_requires=[
# from requirements-common.txt # from requirements-common.txt
'ccxt>=1.18.1080', 'ccxt>=1.24.96',
'SQLAlchemy', 'SQLAlchemy',
'python-telegram-bot', 'python-telegram-bot',
'arrow', 'arrow',

View File

@ -44,7 +44,7 @@ def test_start_new_config(mocker, caplog, exchange):
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'stake_amount': 100, 'stake_amount': 100,
'fiat_display_currency': 'EUR', 'fiat_display_currency': 'EUR',
'ticker_interval': '15m', 'timeframe': '15m',
'dry_run': True, 'dry_run': True,
'exchange_name': exchange, 'exchange_name': exchange,
'exchange_key': 'sampleKey', 'exchange_key': 'sampleKey',
@ -68,7 +68,7 @@ def test_start_new_config(mocker, caplog, exchange):
result = rapidjson.loads(wt_mock.call_args_list[0][0][0], result = rapidjson.loads(wt_mock.call_args_list[0][0][0],
parse_mode=rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS) parse_mode=rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS)
assert result['exchange']['name'] == exchange assert result['exchange']['name'] == exchange
assert result['ticker_interval'] == '15m' assert result['timeframe'] == '15m'
def test_start_new_config_exists(mocker, caplog): def test_start_new_config_exists(mocker, caplog):

View File

@ -9,7 +9,7 @@
"fiat_display_currency": "USD", // C++-style comment "fiat_display_currency": "USD", // C++-style comment
"amount_reserve_percent" : 0.05, // And more, tabs before this comment "amount_reserve_percent" : 0.05, // And more, tabs before this comment
"dry_run": false, "dry_run": false,
"ticker_interval": "5m", "timeframe": "5m",
"trailing_stop": false, "trailing_stop": false,
"trailing_stop_positive": 0.005, "trailing_stop_positive": 0.005,
"trailing_stop_positive_offset": 0.0051, "trailing_stop_positive_offset": 0.0051,

View File

@ -56,6 +56,7 @@ def patched_configuration_load_config_file(mocker, config) -> None:
def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> None: def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> None:
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
@ -247,7 +248,7 @@ def default_conf(testdatadir):
"stake_currency": "BTC", "stake_currency": "BTC",
"stake_amount": 0.001, "stake_amount": 0.001,
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"ticker_interval": '5m', "timeframe": '5m',
"dry_run": True, "dry_run": True,
"cancel_open_orders_on_exit": False, "cancel_open_orders_on_exit": False,
"minimal_roi": { "minimal_roi": {

View File

@ -12,7 +12,7 @@ from tests.conftest import get_patched_exchange
def test_ohlcv(mocker, default_conf, ohlcv_history): def test_ohlcv(mocker, default_conf, ohlcv_history):
default_conf["runmode"] = RunMode.DRY_RUN default_conf["runmode"] = RunMode.DRY_RUN
timeframe = default_conf["ticker_interval"] timeframe = default_conf["timeframe"]
exchange = get_patched_exchange(mocker, default_conf) exchange = get_patched_exchange(mocker, default_conf)
exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history
exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history
@ -53,47 +53,47 @@ def test_historic_ohlcv(mocker, default_conf, ohlcv_history):
def test_get_pair_dataframe(mocker, default_conf, ohlcv_history): def test_get_pair_dataframe(mocker, default_conf, ohlcv_history):
default_conf["runmode"] = RunMode.DRY_RUN default_conf["runmode"] = RunMode.DRY_RUN
ticker_interval = default_conf["ticker_interval"] timeframe = default_conf["timeframe"]
exchange = get_patched_exchange(mocker, default_conf) exchange = get_patched_exchange(mocker, default_conf)
exchange._klines[("XRP/BTC", ticker_interval)] = ohlcv_history exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ohlcv_history exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.DRY_RUN assert dp.runmode == RunMode.DRY_RUN
assert ohlcv_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval)) assert ohlcv_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", timeframe))
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame)
assert dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval) is not ohlcv_history assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe) is not ohlcv_history
assert not dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval).empty assert not dp.get_pair_dataframe("UNITTEST/BTC", timeframe).empty
assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty
# Test with and without parameter # Test with and without parameter
assert dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval)\ assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe)\
.equals(dp.get_pair_dataframe("UNITTEST/BTC")) .equals(dp.get_pair_dataframe("UNITTEST/BTC"))
default_conf["runmode"] = RunMode.LIVE default_conf["runmode"] = RunMode.LIVE
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.LIVE assert dp.runmode == RunMode.LIVE
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame)
assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty
historymock = MagicMock(return_value=ohlcv_history) historymock = MagicMock(return_value=ohlcv_history)
mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock) mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock)
default_conf["runmode"] = RunMode.BACKTEST default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.BACKTEST assert dp.runmode == RunMode.BACKTEST
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame)
# assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty # assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty
def test_available_pairs(mocker, default_conf, ohlcv_history): def test_available_pairs(mocker, default_conf, ohlcv_history):
exchange = get_patched_exchange(mocker, default_conf) exchange = get_patched_exchange(mocker, default_conf)
ticker_interval = default_conf["ticker_interval"] timeframe = default_conf["timeframe"]
exchange._klines[("XRP/BTC", ticker_interval)] = ohlcv_history exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ohlcv_history exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf, exchange)
assert len(dp.available_pairs) == 2 assert len(dp.available_pairs) == 2
assert dp.available_pairs == [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval), ] assert dp.available_pairs == [("XRP/BTC", timeframe), ("UNITTEST/BTC", timeframe), ]
def test_refresh(mocker, default_conf, ohlcv_history): def test_refresh(mocker, default_conf, ohlcv_history):
@ -101,10 +101,10 @@ def test_refresh(mocker, default_conf, ohlcv_history):
mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock) mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock)
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, id="binance")
ticker_interval = default_conf["ticker_interval"] timeframe = default_conf["timeframe"]
pairs = [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval)] pairs = [("XRP/BTC", timeframe), ("UNITTEST/BTC", timeframe)]
pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")] pairs_non_trad = [("ETH/USDT", timeframe), ("BTC/TUSD", "1h")]
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf, exchange)
dp.refresh(pairs) dp.refresh(pairs)

View File

@ -354,7 +354,7 @@ def test_init(default_conf, mocker) -> None:
assert {} == load_data( assert {} == load_data(
datadir=Path(''), datadir=Path(''),
pairs=[], pairs=[],
timeframe=default_conf['ticker_interval'] timeframe=default_conf['timeframe']
) )
@ -363,13 +363,13 @@ def test_init_with_refresh(default_conf, mocker) -> None:
refresh_data( refresh_data(
datadir=Path(''), datadir=Path(''),
pairs=[], pairs=[],
timeframe=default_conf['ticker_interval'], timeframe=default_conf['timeframe'],
exchange=exchange exchange=exchange
) )
assert {} == load_data( assert {} == load_data(
datadir=Path(''), datadir=Path(''),
pairs=[], pairs=[],
timeframe=default_conf['ticker_interval'] timeframe=default_conf['timeframe']
) )

View File

@ -27,7 +27,7 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
#################################################################### ####################################################################
tests_start_time = arrow.get(2018, 10, 3) tests_start_time = arrow.get(2018, 10, 3)
ticker_interval_in_minute = 60 timeframe_in_minute = 60
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7} _ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
# Helpers for this test file # Helpers for this test file
@ -49,7 +49,7 @@ def _build_dataframe(buy_ohlc_sell_matrice):
'date': tests_start_time.shift( 'date': tests_start_time.shift(
minutes=( minutes=(
ohlc[0] * ohlc[0] *
ticker_interval_in_minute)).timestamp * timeframe_in_minute)).timestamp *
1000, 1000,
'buy': ohlc[1], 'buy': ohlc[1],
'open': ohlc[2], 'open': ohlc[2],
@ -70,7 +70,7 @@ def _build_dataframe(buy_ohlc_sell_matrice):
def _time_on_candle(number): def _time_on_candle(number):
return np.datetime64(tests_start_time.shift( return np.datetime64(tests_start_time.shift(
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms') minutes=(number * timeframe_in_minute)).timestamp * 1000, 'ms')
# End helper functions # End helper functions
@ -262,7 +262,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m',
NEOBTC = [ NEOBTC = [
[ [
tests_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000, tests_start_time.shift(minutes=(x * timeframe_in_minute)).timestamp * 1000,
math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base,
math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base + 0.0001,
math.sin(x * hz) / 1000 + base - 0.0001, math.sin(x * hz) / 1000 + base - 0.0001,
@ -274,7 +274,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m',
base = 0.002 base = 0.002
LTCBTC = [ LTCBTC = [
[ [
tests_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000, tests_start_time.shift(minutes=(x * timeframe_in_minute)).timestamp * 1000,
math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base,
math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base + 0.0001,
math.sin(x * hz) / 1000 + base - 0.0001, math.sin(x * hz) / 1000 + base - 0.0001,

View File

@ -25,7 +25,7 @@ from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_patched_exchange, log_has, log_has_re from tests.conftest import get_patched_exchange, log_has, log_has_re
# Make sure to always keep one exchange here which is NOT subclassed!! # Make sure to always keep one exchange here which is NOT subclassed!!
EXCHANGES = ['bittrex', 'binance', 'kraken', ] EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx']
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines # Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
@ -319,7 +319,12 @@ def test_set_sandbox_exception(default_conf, mocker):
def test__load_async_markets(default_conf, mocker, caplog): def test__load_async_markets(default_conf, mocker, caplog):
exchange = get_patched_exchange(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange._init_ccxt')
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
exchange = Exchange(default_conf)
exchange._api_async.load_markets = get_mock_coro(None) exchange._api_async.load_markets = get_mock_coro(None)
exchange._load_async_markets() exchange._load_async_markets()
assert exchange._api_async.load_markets.call_count == 1 assert exchange._api_async.load_markets.call_count == 1
@ -365,6 +370,7 @@ def test_reload_markets(default_conf, mocker, caplog):
default_conf['exchange']['markets_refresh_interval'] = 10 default_conf['exchange']['markets_refresh_interval'] = 10
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance", exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance",
mock_markets=False) mock_markets=False)
exchange._load_async_markets = MagicMock()
exchange._last_markets_refresh = arrow.utcnow().timestamp exchange._last_markets_refresh = arrow.utcnow().timestamp
updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}} updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}}
@ -373,11 +379,13 @@ def test_reload_markets(default_conf, mocker, caplog):
# less than 10 minutes have passed, no reload # less than 10 minutes have passed, no reload
exchange.reload_markets() exchange.reload_markets()
assert exchange.markets == initial_markets assert exchange.markets == initial_markets
assert exchange._load_async_markets.call_count == 0
# more than 10 minutes have passed, reload is executed # more than 10 minutes have passed, reload is executed
exchange._last_markets_refresh = arrow.utcnow().timestamp - 15 * 60 exchange._last_markets_refresh = arrow.utcnow().timestamp - 15 * 60
exchange.reload_markets() exchange.reload_markets()
assert exchange.markets == updated_markets assert exchange.markets == updated_markets
assert exchange._load_async_markets.call_count == 1
assert log_has('Performing scheduled market reload..', caplog) assert log_has('Performing scheduled market reload..', caplog)
@ -578,7 +586,7 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog):
('5m'), ("1m"), ("15m"), ("1h") ('5m'), ("1m"), ("15m"), ("1h")
]) ])
def test_validate_timeframes(default_conf, mocker, timeframe): def test_validate_timeframes(default_conf, mocker, timeframe):
default_conf["ticker_interval"] = timeframe default_conf["timeframe"] = timeframe
api_mock = MagicMock() api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange') id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock type(api_mock).id = id_mock
@ -596,7 +604,7 @@ def test_validate_timeframes(default_conf, mocker, timeframe):
def test_validate_timeframes_failed(default_conf, mocker): def test_validate_timeframes_failed(default_conf, mocker):
default_conf["ticker_interval"] = "3m" default_conf["timeframe"] = "3m"
api_mock = MagicMock() api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange') id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock type(api_mock).id = id_mock
@ -613,7 +621,7 @@ def test_validate_timeframes_failed(default_conf, mocker):
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r"Invalid timeframe '3m'. This exchange supports.*"): match=r"Invalid timeframe '3m'. This exchange supports.*"):
Exchange(default_conf) Exchange(default_conf)
default_conf["ticker_interval"] = "15s" default_conf["timeframe"] = "15s"
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r"Timeframes < 1m are currently not supported by Freqtrade."): match=r"Timeframes < 1m are currently not supported by Freqtrade."):
@ -621,7 +629,7 @@ def test_validate_timeframes_failed(default_conf, mocker):
def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker): def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
default_conf["ticker_interval"] = "3m" default_conf["timeframe"] = "3m"
api_mock = MagicMock() api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange') id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock type(api_mock).id = id_mock
@ -641,7 +649,7 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker): def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
default_conf["ticker_interval"] = "3m" default_conf["timeframe"] = "3m"
api_mock = MagicMock() api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange') id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock type(api_mock).id = id_mock
@ -662,7 +670,7 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
def test_validate_timeframes_not_in_config(default_conf, mocker): def test_validate_timeframes_not_in_config(default_conf, mocker):
del default_conf["ticker_interval"] del default_conf["timeframe"]
api_mock = MagicMock() api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange') id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock type(api_mock).id = id_mock
@ -1278,7 +1286,8 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
# one_call calculation * 1.8 should do 2 calls # one_call calculation * 1.8 should do 2 calls
since = 5 * 60 * 500 * 1.8
since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8
ret = exchange.get_historic_ohlcv(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000)) ret = exchange.get_historic_ohlcv(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000))
assert exchange._async_get_candle_history.call_count == 2 assert exchange._async_get_candle_history.call_count == 2
@ -1370,7 +1379,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
# exchange = Exchange(default_conf) # exchange = Exchange(default_conf)
await async_ccxt_exception(mocker, default_conf, MagicMock(), await async_ccxt_exception(mocker, default_conf, MagicMock(),
"_async_get_candle_history", "fetch_ohlcv", "_async_get_candle_history", "fetch_ohlcv",
pair='ABCD/BTC', timeframe=default_conf['ticker_interval']) pair='ABCD/BTC', timeframe=default_conf['timeframe'])
api_mock = MagicMock() api_mock = MagicMock()
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
@ -1500,7 +1509,7 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data)) sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data))
# Test the OHLCV data sort # Test the OHLCV data sort
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe'])
assert res[0] == 'ETH/BTC' assert res[0] == 'ETH/BTC'
res_ohlcv = res[2] res_ohlcv = res[2]
@ -1537,9 +1546,9 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
# Reset sort mock # Reset sort mock
sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data))
# Test the OHLCV data sort # Test the OHLCV data sort
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe'])
assert res[0] == 'ETH/BTC' assert res[0] == 'ETH/BTC'
assert res[1] == default_conf['ticker_interval'] assert res[1] == default_conf['timeframe']
res_ohlcv = res[2] res_ohlcv = res[2]
# Sorted not called again - data is already in order # Sorted not called again - data is already in order
assert sort_mock.call_count == 0 assert sort_mock.call_count == 0
@ -1753,6 +1762,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {}
assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {}
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
@ -1837,6 +1847,25 @@ def test_cancel_order(default_conf, mocker, exchange_name):
order_id='_', pair='TKN/BTC') order_id='_', pair='TKN/BTC')
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value=123)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC') == 123
with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC')
assert api_mock.cancel_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
"cancel_stoploss_order", "cancel_order",
order_id='_', pair='TKN/BTC')
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_order(default_conf, mocker, exchange_name): def test_get_order(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True default_conf['dry_run'] = True
@ -1866,6 +1895,38 @@ def test_get_order(default_conf, mocker, exchange_name):
order_id='_', pair='TKN/BTC') order_id='_', pair='TKN/BTC')
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_stoploss_order(default_conf, mocker, exchange_name):
# Don't test FTX here - that needs a seperate test
if exchange_name == 'ftx':
return
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
exchange._dry_run_open_orders['X'] = order
assert exchange.get_stoploss_order('X', 'TKN/BTC').myid == 123
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
exchange.get_stoploss_order('Y', 'TKN/BTC')
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.fetch_order = MagicMock(return_value=456)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.get_stoploss_order('X', 'TKN/BTC') == 456
with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_stoploss_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'get_stoploss_order', 'fetch_order',
order_id='_', pair='TKN/BTC')
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_name(default_conf, mocker, exchange_name): def test_name(default_conf, mocker, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)

163
tests/exchange/test_ftx.py Normal file
View File

@ -0,0 +1,163 @@
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
from random import randint
from unittest.mock import MagicMock
import ccxt
import pytest
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from tests.conftest import get_patched_exchange
from .test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop'
def test_stoploss_order_ftx(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 190
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
assert api_mock.create_order.call_count == 1
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={'stoploss': 'limit'})
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params']
assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == 217.8
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
with pytest.raises(OperationalException, match=r".*DeadBeef.*"):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
def test_stoploss_order_dry_run_ftx(default_conf, mocker):
api_mock = MagicMock()
default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
assert 'id' in order
assert 'info' in order
assert 'type' in order
assert order['type'] == STOPLOSS_ORDERTYPE
assert order['price'] == 220
assert order['amount'] == 1
def test_stoploss_adjust_ftx(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
order = {
'type': STOPLOSS_ORDERTYPE,
'price': 1500,
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
# Test with invalid order case ...
order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(1501, order)
def test_get_stoploss_order(default_conf, mocker):
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
exchange._dry_run_open_orders['X'] = order
assert exchange.get_stoploss_order('X', 'TKN/BTC').myid == 123
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
exchange.get_stoploss_order('Y', 'TKN/BTC')
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': '456'}])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
assert exchange.get_stoploss_order('X', 'TKN/BTC')['status'] == '456'
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'Y', 'status': '456'}])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"):
exchange.get_stoploss_order('X', 'TKN/BTC')['status']
with pytest.raises(InvalidOrderException):
api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
exchange.get_stoploss_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_orders.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx',
'get_stoploss_order', 'fetch_orders',
order_id='_', pair='TKN/BTC')

View File

@ -11,6 +11,8 @@ from freqtrade.exceptions import (DependencyException, InvalidOrderException,
from tests.conftest import get_patched_exchange from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers from tests.exchange.test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop-loss'
def test_buy_kraken_trading_agreement(default_conf, mocker): def test_buy_kraken_trading_agreement(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
@ -159,7 +161,6 @@ def test_get_balances_prod(default_conf, mocker):
def test_stoploss_order_kraken(default_conf, mocker): def test_stoploss_order_kraken(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop-loss'
api_mock.create_order = MagicMock(return_value={ api_mock.create_order = MagicMock(return_value={
'id': order_id, 'id': order_id,
@ -187,7 +188,7 @@ def test_stoploss_order_kraken(default_conf, mocker):
assert 'info' in order assert 'info' in order
assert order['id'] == order_id assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == order_type assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220 assert api_mock.create_order.call_args_list[0][1]['price'] == 220
@ -218,7 +219,6 @@ def test_stoploss_order_kraken(default_conf, mocker):
def test_stoploss_order_dry_run_kraken(default_conf, mocker): def test_stoploss_order_dry_run_kraken(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
order_type = 'stop-loss'
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
@ -233,7 +233,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
assert 'info' in order assert 'info' in order
assert 'type' in order assert 'type' in order
assert order['type'] == order_type assert order['type'] == STOPLOSS_ORDERTYPE
assert order['price'] == 220 assert order['price'] == 220
assert order['amount'] == 1 assert order['amount'] == 1
@ -241,7 +241,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
def test_stoploss_adjust_kraken(mocker, default_conf): def test_stoploss_adjust_kraken(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id='kraken') exchange = get_patched_exchange(mocker, default_conf, id='kraken')
order = { order = {
'type': 'stop-loss', 'type': STOPLOSS_ORDERTYPE,
'price': 1500, 'price': 1500,
} }
assert exchange.stoploss_adjust(1501, order) assert exchange.stoploss_adjust(1501, order)

View File

@ -360,7 +360,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
""" """
default_conf["stoploss"] = data.stop_loss default_conf["stoploss"] = data.stop_loss
default_conf["minimal_roi"] = data.roi default_conf["minimal_roi"] = data.roi
default_conf["ticker_interval"] = tests_timeframe default_conf["timeframe"] = tests_timeframe
default_conf["trailing_stop"] = data.trailing_stop default_conf["trailing_stop"] = data.trailing_stop
default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
# Only add this to configuration If it's necessary # Only add this to configuration If it's necessary

View File

@ -81,7 +81,7 @@ def load_data_test(what, testdatadir):
def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None: def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None:
patch_exchange(mocker) patch_exchange(mocker)
config['ticker_interval'] = '1m' config['timeframe'] = '1m'
backtesting = Backtesting(config) backtesting = Backtesting(config)
data = load_data_test(contour, testdatadir) data = load_data_test(contour, testdatadir)
@ -165,7 +165,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
assert 'pair_whitelist' in config['exchange'] assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config assert 'datadir' in config
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'ticker_interval' in config assert 'timeframe' in config
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog)
assert 'position_stacking' not in config assert 'position_stacking' not in config
@ -189,7 +189,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'DefaultStrategy', '--strategy', 'DefaultStrategy',
'--datadir', '/foo/bar', '--datadir', '/foo/bar',
'--ticker-interval', '1m', '--timeframe', '1m',
'--enable-position-stacking', '--enable-position-stacking',
'--disable-max-market-positions', '--disable-max-market-positions',
'--timerange', ':100', '--timerange', ':100',
@ -208,8 +208,8 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
assert config['runmode'] == RunMode.BACKTEST assert config['runmode'] == RunMode.BACKTEST
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'ticker_interval' in config assert 'timeframe' in config
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
caplog) caplog)
assert 'position_stacking' in config assert 'position_stacking' in config
@ -286,9 +286,9 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
assert not backtesting.strategy.order_types["stoploss_on_exchange"] assert not backtesting.strategy.order_types["stoploss_on_exchange"]
def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> None: def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
del default_conf['ticker_interval'] del default_conf['timeframe']
default_conf['strategy_list'] = ['DefaultStrategy', default_conf['strategy_list'] = ['DefaultStrategy',
'SampleStrategy'] 'SampleStrategy']
@ -338,7 +338,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC'])) PropertyMock(return_value=['UNITTEST/BTC']))
default_conf['ticker_interval'] = '1m' default_conf['timeframe'] = '1m'
default_conf['datadir'] = testdatadir default_conf['datadir'] = testdatadir
default_conf['export'] = None default_conf['export'] = None
default_conf['timerange'] = '-1510694220' default_conf['timerange'] = '-1510694220'
@ -368,7 +368,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC'])) PropertyMock(return_value=['UNITTEST/BTC']))
default_conf['ticker_interval'] = "1m" default_conf['timeframe'] = "1m"
default_conf['datadir'] = testdatadir default_conf['datadir'] = testdatadir
default_conf['export'] = None default_conf['export'] = None
default_conf['timerange'] = '20180101-20180102' default_conf['timerange'] = '20180101-20180102'
@ -388,7 +388,7 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) ->
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=[])) PropertyMock(return_value=[]))
default_conf['ticker_interval'] = "1m" default_conf['timeframe'] = "1m"
default_conf['datadir'] = testdatadir default_conf['datadir'] = testdatadir
default_conf['export'] = None default_conf['export'] = None
default_conf['timerange'] = '20180101-20180102' default_conf['timerange'] = '20180101-20180102'
@ -454,7 +454,7 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
t["close_rate"], 6) < round(ln.iloc[0]["high"], 6)) t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
def test_backtest_1min_ticker_interval(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None:
default_conf['ask_strategy']['use_sell_signal'] = False default_conf['ask_strategy']['use_sell_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker) patch_exchange(mocker)
@ -535,7 +535,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
pair='UNITTEST/BTC', datadir=testdatadir) pair='UNITTEST/BTC', datadir=testdatadir)
default_conf['ticker_interval'] = '1m' default_conf['timeframe'] = '1m'
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.strategy.advise_buy = _trend_alternate # Override backtesting.strategy.advise_buy = _trend_alternate # Override
backtesting.strategy.advise_sell = _trend_alternate # Override backtesting.strategy.advise_sell = _trend_alternate # Override
@ -574,7 +574,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
# Remove data for one pair from the beginning of the data # Remove data for one pair from the beginning of the data
data[pair] = data[pair][tres:].reset_index() data[pair] = data[pair][tres:].reset_index()
default_conf['ticker_interval'] = '5m' default_conf['timeframe'] = '5m'
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.strategy.advise_buy = _trend_alternate_hold # Override backtesting.strategy.advise_buy = _trend_alternate_hold # Override
@ -625,7 +625,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'DefaultStrategy', '--strategy', 'DefaultStrategy',
'--datadir', str(testdatadir), '--datadir', str(testdatadir),
'--ticker-interval', '1m', '--timeframe', '1m',
'--timerange', '1510694220-1510700340', '--timerange', '1510694220-1510700340',
'--enable-position-stacking', '--enable-position-stacking',
'--disable-max-market-positions' '--disable-max-market-positions'
@ -634,7 +634,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
start_backtesting(args) start_backtesting(args)
# check the logs, that will contain the backtest result # check the logs, that will contain the backtest result
exists = [ exists = [
'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
'Ignoring max_open_trades (--disable-max-market-positions was used) ...', 'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
'Parameter --timerange detected: 1510694220-1510700340 ...', 'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...', f'Using data directory: {testdatadir} ...',
@ -678,7 +678,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'--config', 'config.json', '--config', 'config.json',
'--datadir', str(testdatadir), '--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--ticker-interval', '1m', '--timeframe', '1m',
'--timerange', '1510694220-1510700340', '--timerange', '1510694220-1510700340',
'--enable-position-stacking', '--enable-position-stacking',
'--disable-max-market-positions', '--disable-max-market-positions',
@ -697,7 +697,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
# check the logs, that will contain the backtest result # check the logs, that will contain the backtest result
exists = [ exists = [
'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
'Ignoring max_open_trades (--disable-max-market-positions was used) ...', 'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
'Parameter --timerange detected: 1510694220-1510700340 ...', 'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...', f'Using data directory: {testdatadir} ...',
@ -767,7 +767,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'--config', 'config.json', '--config', 'config.json',
'--datadir', str(testdatadir), '--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
'--ticker-interval', '1m', '--timeframe', '1m',
'--timerange', '1510694220-1510700340', '--timerange', '1510694220-1510700340',
'--enable-position-stacking', '--enable-position-stacking',
'--disable-max-market-positions', '--disable-max-market-positions',
@ -780,7 +780,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
# check the logs, that will contain the backtest result # check the logs, that will contain the backtest result
exists = [ exists = [
'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
'Ignoring max_open_trades (--disable-max-market-positions was used) ...', 'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
'Parameter --timerange detected: 1510694220-1510700340 ...', 'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...', f'Using data directory: {testdatadir} ...',

View File

@ -29,7 +29,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
assert 'pair_whitelist' in config['exchange'] assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config assert 'datadir' in config
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'ticker_interval' in config assert 'timeframe' in config
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog)
assert 'timerange' not in config assert 'timerange' not in config
@ -48,7 +48,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'DefaultStrategy', '--strategy', 'DefaultStrategy',
'--datadir', '/foo/bar', '--datadir', '/foo/bar',
'--ticker-interval', '1m', '--timeframe', '1m',
'--timerange', ':100', '--timerange', ':100',
'--stoplosses=-0.01,-0.10,-0.001' '--stoplosses=-0.01,-0.10,-0.001'
] ]
@ -62,8 +62,8 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N
assert 'datadir' in config assert 'datadir' in config
assert config['runmode'] == RunMode.EDGE assert config['runmode'] == RunMode.EDGE
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'ticker_interval' in config assert 'timeframe' in config
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
caplog) caplog)
assert 'timerange' in config assert 'timerange' in config

View File

@ -94,7 +94,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca
assert 'pair_whitelist' in config['exchange'] assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config assert 'datadir' in config
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'ticker_interval' in config assert 'timeframe' in config
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog)
assert 'position_stacking' not in config assert 'position_stacking' not in config
@ -117,7 +117,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
'--config', 'config.json', '--config', 'config.json',
'--hyperopt', 'DefaultHyperOpt', '--hyperopt', 'DefaultHyperOpt',
'--datadir', '/foo/bar', '--datadir', '/foo/bar',
'--ticker-interval', '1m', '--timeframe', '1m',
'--timerange', ':100', '--timerange', ':100',
'--enable-position-stacking', '--enable-position-stacking',
'--disable-max-market-positions', '--disable-max-market-positions',
@ -136,8 +136,8 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
assert config['runmode'] == RunMode.HYPEROPT assert config['runmode'] == RunMode.HYPEROPT
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'ticker_interval' in config assert 'timeframe' in config
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
caplog) caplog)
assert 'position_stacking' in config assert 'position_stacking' in config
@ -197,7 +197,8 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
"Using populate_sell_trend from the strategy.", caplog) "Using populate_sell_trend from the strategy.", caplog)
assert log_has("Hyperopt class does not provide populate_buy_trend() method. " assert log_has("Hyperopt class does not provide populate_buy_trend() method. "
"Using populate_buy_trend from the strategy.", caplog) "Using populate_buy_trend from the strategy.", caplog)
assert hasattr(x, "ticker_interval") assert hasattr(x, "ticker_interval") # DEPRECATED
assert hasattr(x, "timeframe")
def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None: def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None:
@ -544,7 +545,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
) )
patch_exchange(mocker) patch_exchange(mocker)
# Co-test loading timeframe from strategy # Co-test loading timeframe from strategy
del default_conf['ticker_interval'] del default_conf['timeframe']
default_conf.update({'config': 'config.json.example', default_conf.update({'config': 'config.json.example',
'hyperopt': 'DefaultHyperOpt', 'hyperopt': 'DefaultHyperOpt',
'epochs': 1, 'epochs': 1,

View File

@ -70,6 +70,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'max_rate': ANY, 'max_rate': ANY,
'strategy': ANY, 'strategy': ANY,
'ticker_interval': ANY, 'ticker_interval': ANY,
'timeframe': ANY,
'open_order_id': ANY, 'open_order_id': ANY,
'close_date': None, 'close_date': None,
'close_date_hum': None, 'close_date_hum': None,
@ -132,6 +133,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'max_rate': ANY, 'max_rate': ANY,
'strategy': ANY, 'strategy': ANY,
'ticker_interval': ANY, 'ticker_interval': ANY,
'timeframe': ANY,
'open_order_id': ANY, 'open_order_id': ANY,
'close_date': None, 'close_date': None,
'close_date_hum': None, 'close_date_hum': None,

View File

@ -323,6 +323,7 @@ def test_api_show_config(botclient, mocker):
assert 'dry_run' in rc.json assert 'dry_run' in rc.json
assert rc.json['exchange'] == 'bittrex' assert rc.json['exchange'] == 'bittrex'
assert rc.json['ticker_interval'] == '5m' assert rc.json['ticker_interval'] == '5m'
assert rc.json['timeframe'] == '5m'
assert rc.json['state'] == 'running' assert rc.json['state'] == 'running'
assert not rc.json['trailing_stop'] assert not rc.json['trailing_stop']
assert 'bid_strategy' in rc.json assert 'bid_strategy' in rc.json
@ -567,6 +568,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'sell_order_status': None, 'sell_order_status': None,
'strategy': 'DefaultStrategy', 'strategy': 'DefaultStrategy',
'ticker_interval': 5, 'ticker_interval': 5,
'timeframe': 5,
'exchange': 'bittrex', 'exchange': 'bittrex',
}] }]
@ -690,6 +692,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'sell_order_status': None, 'sell_order_status': None,
'strategy': None, 'strategy': None,
'ticker_interval': None, 'ticker_interval': None,
'timeframe': None,
'exchange': 'bittrex', 'exchange': 'bittrex',
} }

View File

@ -29,7 +29,7 @@ class DefaultStrategy(IStrategy):
stoploss = -0.10 stoploss = -0.10
# Optimal ticker interval for the strategy # Optimal ticker interval for the strategy
ticker_interval = '5m' timeframe = '5m'
# Optional order type mapping # Optional order type mapping
order_types = { order_types = {

View File

@ -31,6 +31,7 @@ class TestStrategyLegacy(IStrategy):
stoploss = -0.10 stoploss = -0.10
# Optimal ticker interval for the strategy # Optimal ticker interval for the strategy
# Keep the legacy value here to test compatibility
ticker_interval = '5m' ticker_interval = '5m'
def populate_indicators(self, dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame) -> DataFrame:

View File

@ -6,7 +6,7 @@ from .strats.default_strategy import DefaultStrategy
def test_default_strategy_structure(): def test_default_strategy_structure():
assert hasattr(DefaultStrategy, 'minimal_roi') assert hasattr(DefaultStrategy, 'minimal_roi')
assert hasattr(DefaultStrategy, 'stoploss') assert hasattr(DefaultStrategy, 'stoploss')
assert hasattr(DefaultStrategy, 'ticker_interval') assert hasattr(DefaultStrategy, 'timeframe')
assert hasattr(DefaultStrategy, 'populate_indicators') assert hasattr(DefaultStrategy, 'populate_indicators')
assert hasattr(DefaultStrategy, 'populate_buy_trend') assert hasattr(DefaultStrategy, 'populate_buy_trend')
assert hasattr(DefaultStrategy, 'populate_sell_trend') assert hasattr(DefaultStrategy, 'populate_sell_trend')
@ -18,7 +18,7 @@ def test_default_strategy(result):
metadata = {'pair': 'ETH/BTC'} metadata = {'pair': 'ETH/BTC'}
assert type(strategy.minimal_roi) is dict assert type(strategy.minimal_roi) is dict
assert type(strategy.stoploss) is float assert type(strategy.stoploss) is float
assert type(strategy.ticker_interval) is str assert type(strategy.timeframe) is str
indicators = strategy.populate_indicators(result, metadata) indicators = strategy.populate_indicators(result, metadata)
assert type(indicators) is DataFrame assert type(indicators) is DataFrame
assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame

View File

@ -54,12 +54,12 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history):
def test_get_signal_empty(default_conf, mocker, caplog): def test_get_signal_empty(default_conf, mocker, caplog):
assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], assert (False, False) == _STRATEGY.get_signal('foo', default_conf['timeframe'],
DataFrame()) DataFrame())
assert log_has('Empty candle (OHLCV) data for pair foo', caplog) assert log_has('Empty candle (OHLCV) data for pair foo', caplog)
caplog.clear() caplog.clear()
assert (False, False) == _STRATEGY.get_signal('bar', default_conf['ticker_interval'], assert (False, False) == _STRATEGY.get_signal('bar', default_conf['timeframe'],
[]) [])
assert log_has('Empty candle (OHLCV) data for pair bar', caplog) assert log_has('Empty candle (OHLCV) data for pair bar', caplog)
@ -70,7 +70,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ohlcv_his
_STRATEGY, '_analyze_ticker_internal', _STRATEGY, '_analyze_ticker_internal',
side_effect=ValueError('xyz') side_effect=ValueError('xyz')
) )
assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], assert (False, False) == _STRATEGY.get_signal('foo', default_conf['timeframe'],
ohlcv_history) ohlcv_history)
assert log_has_re(r'Strategy caused the following exception: xyz.*', caplog) assert log_has_re(r'Strategy caused the following exception: xyz.*', caplog)
@ -83,7 +83,7 @@ def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ohlcv_history)
) )
mocker.patch.object(_STRATEGY, 'assert_df') mocker.patch.object(_STRATEGY, 'assert_df')
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'],
ohlcv_history) ohlcv_history)
assert log_has('Empty dataframe for pair xyz', caplog) assert log_has('Empty dataframe for pair xyz', caplog)
@ -104,7 +104,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history):
return_value=mocked_history return_value=mocked_history
) )
mocker.patch.object(_STRATEGY, 'assert_df') mocker.patch.object(_STRATEGY, 'assert_df')
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'],
ohlcv_history) ohlcv_history)
assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog)
@ -124,7 +124,7 @@ def test_assert_df_raise(default_conf, mocker, caplog, ohlcv_history):
_STRATEGY, 'assert_df', _STRATEGY, 'assert_df',
side_effect=StrategyError('Dataframe returned...') side_effect=StrategyError('Dataframe returned...')
) )
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'],
ohlcv_history) ohlcv_history)
assert log_has('Unable to analyze candle (OHLCV) data for pair xyz: Dataframe returned...', assert log_has('Unable to analyze candle (OHLCV) data for pair xyz: Dataframe returned...',
caplog) caplog)

View File

@ -105,8 +105,9 @@ def test_strategy(result, default_conf):
assert strategy.stoploss == -0.10 assert strategy.stoploss == -0.10
assert default_conf['stoploss'] == -0.10 assert default_conf['stoploss'] == -0.10
assert strategy.timeframe == '5m'
assert strategy.ticker_interval == '5m' assert strategy.ticker_interval == '5m'
assert default_conf['ticker_interval'] == '5m' assert default_conf['timeframe'] == '5m'
df_indicators = strategy.advise_indicators(result, metadata=metadata) df_indicators = strategy.advise_indicators(result, metadata=metadata)
assert 'adx' in df_indicators assert 'adx' in df_indicators
@ -176,19 +177,19 @@ def test_strategy_override_trailing_stop_positive(caplog, default_conf):
caplog) caplog)
def test_strategy_override_ticker_interval(caplog, default_conf): def test_strategy_override_timeframe(caplog, default_conf):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
default_conf.update({ default_conf.update({
'strategy': 'DefaultStrategy', 'strategy': 'DefaultStrategy',
'ticker_interval': 60, 'timeframe': 60,
'stake_currency': 'ETH' 'stake_currency': 'ETH'
}) })
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.ticker_interval == 60 assert strategy.timeframe == 60
assert strategy.stake_currency == 'ETH' assert strategy.stake_currency == 'ETH'
assert log_has("Override strategy 'ticker_interval' with value in config file: 60.", assert log_has("Override strategy 'timeframe' with value in config file: 60.",
caplog) caplog)
@ -357,8 +358,9 @@ def test_deprecate_populate_indicators(result, default_conf):
@pytest.mark.filterwarnings("ignore:deprecated") @pytest.mark.filterwarnings("ignore:deprecated")
def test_call_deprecated_function(result, monkeypatch, default_conf): def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
default_location = Path(__file__).parent / "strats" default_location = Path(__file__).parent / "strats"
del default_conf['timeframe']
default_conf.update({'strategy': 'TestStrategyLegacy', default_conf.update({'strategy': 'TestStrategyLegacy',
'strategy_path': default_location}) 'strategy_path': default_location})
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
@ -369,6 +371,8 @@ def test_call_deprecated_function(result, monkeypatch, default_conf):
assert strategy._buy_fun_len == 2 assert strategy._buy_fun_len == 2
assert strategy._sell_fun_len == 2 assert strategy._sell_fun_len == 2
assert strategy.INTERFACE_VERSION == 1 assert strategy.INTERFACE_VERSION == 1
assert strategy.timeframe == '5m'
assert strategy.ticker_interval == '5m'
indicator_df = strategy.advise_indicators(result, metadata=metadata) indicator_df = strategy.advise_indicators(result, metadata=metadata)
assert isinstance(indicator_df, DataFrame) assert isinstance(indicator_df, DataFrame)
@ -382,6 +386,9 @@ def test_call_deprecated_function(result, monkeypatch, default_conf):
assert isinstance(selldf, DataFrame) assert isinstance(selldf, DataFrame)
assert 'sell' in selldf assert 'sell' in selldf
assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.",
caplog)
def test_strategy_interface_versioning(result, monkeypatch, default_conf): def test_strategy_interface_versioning(result, monkeypatch, default_conf):
default_conf.update({'strategy': 'DefaultStrategy'}) default_conf.update({'strategy': 'DefaultStrategy'})

View File

@ -131,7 +131,7 @@ def test_parse_args_backtesting_custom() -> None:
assert call_args["verbosity"] == 0 assert call_args["verbosity"] == 0
assert call_args["command"] == 'backtesting' assert call_args["command"] == 'backtesting'
assert call_args["func"] is not None assert call_args["func"] is not None
assert call_args["ticker_interval"] == '1m' assert call_args["timeframe"] == '1m'
assert type(call_args["strategy_list"]) is list assert type(call_args["strategy_list"]) is list
assert len(call_args["strategy_list"]) == 2 assert len(call_args["strategy_list"]) == 2

View File

@ -87,7 +87,7 @@ def test_load_config_file_error_range(default_conf, mocker, caplog) -> None:
assert isinstance(x, str) assert isinstance(x, str)
assert (x == '{"max_open_trades": 1, "stake_currency": "BTC", ' assert (x == '{"max_open_trades": 1, "stake_currency": "BTC", '
'"stake_amount": .001, "fiat_display_currency": "USD", ' '"stake_amount": .001, "fiat_display_currency": "USD", '
'"ticker_interval": "5m", "dry_run": true, ') '"timeframe": "5m", "dry_run": true, "cance')
def test__args_to_config(caplog): def test__args_to_config(caplog):
@ -401,8 +401,8 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
assert 'datadir' in config assert 'datadir' in config
assert 'user_data_dir' in config assert 'user_data_dir' in config
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'ticker_interval' in config assert 'timeframe' in config
assert not log_has('Parameter -i/--ticker-interval detected ...', caplog) assert not log_has('Parameter -i/--timeframe detected ...', caplog)
assert 'position_stacking' not in config assert 'position_stacking' not in config
assert not log_has('Parameter --enable-position-stacking detected ...', caplog) assert not log_has('Parameter --enable-position-stacking detected ...', caplog)
@ -448,8 +448,8 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
assert log_has('Using user-data directory: {} ...'.format(Path("/tmp/freqtrade")), caplog) assert log_has('Using user-data directory: {} ...'.format(Path("/tmp/freqtrade")), caplog)
assert 'user_data_dir' in config assert 'user_data_dir' in config
assert 'ticker_interval' in config assert 'timeframe' in config
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
caplog) caplog)
assert 'position_stacking' in config assert 'position_stacking' in config
@ -494,8 +494,8 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
assert 'pair_whitelist' in config['exchange'] assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config assert 'datadir' in config
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'ticker_interval' in config assert 'timeframe' in config
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...',
caplog) caplog)
assert 'strategy_list' in config assert 'strategy_list' in config
@ -1140,3 +1140,25 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
'sectionB', 'deprecated_setting') 'sectionB', 'deprecated_setting')
assert not log_has_re('DEPRECATED', caplog) assert not log_has_re('DEPRECATED', caplog)
assert default_conf['sectionA']['new_setting'] == 'valA' assert default_conf['sectionA']['new_setting'] == 'valA'
def test_process_deprecated_ticker_interval(mocker, default_conf, caplog):
message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval."
config = deepcopy(default_conf)
process_temporary_deprecated_settings(config)
assert not log_has(message, caplog)
del config['timeframe']
config['ticker_interval'] = '15m'
process_temporary_deprecated_settings(config)
assert log_has(message, caplog)
assert config['ticker_interval'] == '15m'
config = deepcopy(default_conf)
# Have both timeframe and ticker interval in config
# Can also happen when using ticker_interval in configuration, and --timeframe as cli argument
config['timeframe'] = '5m'
config['ticker_interval'] = '4h'
with pytest.raises(OperationalException,
match=r"Both 'timeframe' and 'ticker_interval' detected."):
process_temporary_deprecated_settings(config)

View File

@ -924,7 +924,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
assert refresh_mock.call_count == 1 assert refresh_mock.call_count == 1
assert ("BTC/ETH", "1m") in refresh_mock.call_args[0][0] assert ("BTC/ETH", "1m") in refresh_mock.call_args[0][0]
assert ("ETH/USDT", "1h") in refresh_mock.call_args[0][0] assert ("ETH/USDT", "1h") in refresh_mock.call_args[0][0]
assert ("ETH/BTC", default_conf["ticker_interval"]) in refresh_mock.call_args[0][0] assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0]
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [ @pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [
@ -1126,7 +1126,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
trade.stoploss_order_id = 100 trade.stoploss_order_id = 100
hanging_stoploss_order = MagicMock(return_value={'status': 'open'}) hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
mocker.patch('freqtrade.exchange.Exchange.get_order', hanging_stoploss_order) mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', hanging_stoploss_order)
assert freqtrade.handle_stoploss_on_exchange(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.stoploss_order_id == 100 assert trade.stoploss_order_id == 100
@ -1139,7 +1139,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
trade.stoploss_order_id = 100 trade.stoploss_order_id = 100
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'}) canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
mocker.patch('freqtrade.exchange.Exchange.get_order', canceled_stoploss_order) mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', canceled_stoploss_order)
stoploss.reset_mock() stoploss.reset_mock()
assert freqtrade.handle_stoploss_on_exchange(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False
@ -1164,7 +1164,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
'average': 2, 'average': 2,
'amount': limit_buy_order['amount'], 'amount': limit_buy_order['amount'],
}) })
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hit)
assert freqtrade.handle_stoploss_on_exchange(trade) is True assert freqtrade.handle_stoploss_on_exchange(trade) is True
assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog) assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog)
assert trade.stoploss_order_id is None assert trade.stoploss_order_id is None
@ -1183,7 +1183,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
# It should try to add stoploss order # It should try to add stoploss order
trade.stoploss_order_id = 100 trade.stoploss_order_id = 100
stoploss.reset_mock() stoploss.reset_mock()
mocker.patch('freqtrade.exchange.Exchange.get_order', side_effect=InvalidOrderException()) mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order',
side_effect=InvalidOrderException())
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
freqtrade.handle_stoploss_on_exchange(trade) freqtrade.handle_stoploss_on_exchange(trade)
assert stoploss.call_count == 1 assert stoploss.call_count == 1
@ -1214,7 +1215,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}), sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee, get_fee=fee,
get_order=MagicMock(return_value={'status': 'canceled'}), get_stoploss_order=MagicMock(return_value={'status': 'canceled'}),
stoploss=MagicMock(side_effect=DependencyException()), stoploss=MagicMock(side_effect=DependencyException()),
) )
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
@ -1331,7 +1332,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
} }
}) })
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hanging)
# stoploss initially at 5% # stoploss initially at 5%
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -1346,7 +1347,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock() stoploss_order_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss_order_mock) mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss_order_mock)
# stoploss should not be updated as the interval is 60 seconds # stoploss should not be updated as the interval is 60 seconds
@ -1429,8 +1430,9 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
'stopPrice': '0.1' 'stopPrice': '0.1'
} }
} }
mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException()) mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) side_effect=InvalidOrderException())
mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hanging)
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog) assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog)
@ -1439,7 +1441,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
# Fail creating stoploss order # Fail creating stoploss order
caplog.clear() caplog.clear()
cancel_mock = mocker.patch("freqtrade.exchange.Exchange.cancel_order", MagicMock()) cancel_mock = mocker.patch("freqtrade.exchange.Exchange.cancel_stoploss_order", MagicMock())
mocker.patch("freqtrade.exchange.Exchange.stoploss", side_effect=DependencyException()) mocker.patch("freqtrade.exchange.Exchange.stoploss", side_effect=DependencyException())
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert cancel_mock.call_count == 1 assert cancel_mock.call_count == 1
@ -1510,7 +1512,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
} }
}) })
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hanging)
# stoploss initially at 20% as edge dictated it. # stoploss initially at 20% as edge dictated it.
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -1519,7 +1521,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock() stoploss_order_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock) mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
# price goes down 5% # price goes down 5%
@ -2632,7 +2634,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None: def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException()) mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
side_effect=InvalidOrderException())
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300)) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300))
sellmock = MagicMock() sellmock = MagicMock()
patch_exchange(mocker) patch_exchange(mocker)
@ -2680,7 +2683,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
amount_to_precision=lambda s, x, y: y, amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y,
stoploss=stoploss, stoploss=stoploss,
cancel_order=cancel_order, cancel_stoploss_order=cancel_order,
) )
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
@ -2771,7 +2774,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f
"fee": None, "fee": None,
"trades": None "trades": None
}) })
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_executed) mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_executed)
freqtrade.exit_positions(trades) freqtrade.exit_positions(trades)
assert trade.stoploss_order_id is None assert trade.stoploss_order_id is None

View File

@ -62,8 +62,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
get_fee=fee, get_fee=fee,
amount_to_precision=lambda s, x, y: y, amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y,
get_order=stoploss_order_mock, get_stoploss_order=stoploss_order_mock,
cancel_order=cancel_order_mock, cancel_stoploss_order=cancel_order_mock,
) )
mocker.patch.multiple( mocker.patch.multiple(

View File

@ -35,12 +35,12 @@ def test_parse_args_backtesting(mocker) -> None:
main(['backtesting']) main(['backtesting'])
assert backtesting_mock.call_count == 1 assert backtesting_mock.call_count == 1
call_args = backtesting_mock.call_args[0][0] call_args = backtesting_mock.call_args[0][0]
assert call_args["config"] == ['config.json'] assert call_args['config'] == ['config.json']
assert call_args["verbosity"] == 0 assert call_args['verbosity'] == 0
assert call_args["command"] == 'backtesting' assert call_args['command'] == 'backtesting'
assert call_args["func"] is not None assert call_args['func'] is not None
assert callable(call_args["func"]) assert callable(call_args['func'])
assert call_args["ticker_interval"] is None assert call_args['timeframe'] is None
def test_main_start_hyperopt(mocker) -> None: def test_main_start_hyperopt(mocker) -> None:

View File

@ -469,6 +469,7 @@ def test_migrate_old(mocker, default_conf, fee):
assert trade.fee_open_currency is None assert trade.fee_open_currency is None
assert trade.fee_close_cost is None assert trade.fee_close_cost is None
assert trade.fee_close_currency is None assert trade.fee_close_currency is None
assert trade.timeframe is None
trade = Trade.query.filter(Trade.id == 2).first() trade = Trade.query.filter(Trade.id == 2).first()
assert trade.close_rate is not None assert trade.close_rate is not None
@ -512,11 +513,11 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
);""" );"""
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee, insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee,
open_rate, stake_amount, amount, open_date, open_rate, stake_amount, amount, open_date,
stop_loss, initial_stop_loss, max_rate) stop_loss, initial_stop_loss, max_rate, ticker_interval)
VALUES ('binance', 'ETC/BTC', 1, {fee}, VALUES ('binance', 'ETC/BTC', 1, {fee},
0.00258580, {stake}, {amount}, 0.00258580, {stake}, {amount},
'2019-11-28 12:44:24.000000', '2019-11-28 12:44:24.000000',
0.0, 0.0, 0.0) 0.0, 0.0, 0.0, '5m')
""".format(fee=fee.return_value, """.format(fee=fee.return_value,
stake=default_conf.get("stake_amount"), stake=default_conf.get("stake_amount"),
amount=amount amount=amount
@ -554,7 +555,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert trade.initial_stop_loss == 0.0 assert trade.initial_stop_loss == 0.0
assert trade.sell_reason is None assert trade.sell_reason is None
assert trade.strategy is None assert trade.strategy is None
assert trade.ticker_interval is None assert trade.timeframe == '5m'
assert trade.stoploss_order_id is None assert trade.stoploss_order_id is None
assert trade.stoploss_last_update is None assert trade.stoploss_last_update is None
assert log_has("trying trades_bak1", caplog) assert log_has("trying trades_bak1", caplog)
@ -776,6 +777,7 @@ def test_to_json(default_conf, fee):
'max_rate': None, 'max_rate': None,
'strategy': None, 'strategy': None,
'ticker_interval': None, 'ticker_interval': None,
'timeframe': None,
'exchange': 'bittrex', 'exchange': 'bittrex',
} }
@ -837,6 +839,7 @@ def test_to_json(default_conf, fee):
'sell_order_status': None, 'sell_order_status': None,
'strategy': None, 'strategy': None,
'ticker_interval': None, 'ticker_interval': None,
'timeframe': None,
'exchange': 'bittrex', 'exchange': 'bittrex',
} }

View File

@ -47,7 +47,7 @@ def generate_empty_figure():
def test_init_plotscript(default_conf, mocker, testdatadir): def test_init_plotscript(default_conf, mocker, testdatadir):
default_conf['timerange'] = "20180110-20180112" default_conf['timerange'] = "20180110-20180112"
default_conf['trade_source'] = "file" default_conf['trade_source'] = "file"
default_conf['ticker_interval'] = "5m" default_conf['timeframe'] = "5m"
default_conf["datadir"] = testdatadir default_conf["datadir"] = testdatadir
default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" default_conf['exportfilename'] = testdatadir / "backtest-result_test.json"
ret = init_plotscript(default_conf) ret = init_plotscript(default_conf)

File diff suppressed because one or more lines are too long