Merge branch 'develop' into safe_sell_amount

This commit is contained in:
Matthias 2019-12-28 09:35:45 +01:00
commit 443fd8f7dd
54 changed files with 745 additions and 605 deletions

View File

@ -64,19 +64,17 @@ jobs:
pip install -e .
- name: Tests
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
COVERALLS_SERVICE_NAME: travis-ci
TRAVIS: "true"
run: |
pytest --random-order --cov=freqtrade --cov-config=.coveragerc
- name: Coveralls
if: startsWith(matrix.os, 'ubuntu')
env:
# Coveralls token. Not used as secret due to github not providing secrets to forked repositories
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
run: |
# Allow failure for coveralls
# Fake travis environment to get coveralls working correctly
export TRAVIS_PULL_REQUEST="https://github.com/${GITHUB_REPOSITORY}/pull/$(cat $GITHUB_EVENT_PATH | jq -r .number)"
export TRAVIS_BRANCH=${GITHUB_REF#"ref/heads"}
export CI_BRANCH=${GITHUB_REF#"ref/heads"}
echo "${TRAVIS_BRANCH}"
coveralls || true
coveralls -v || true
- name: Backtesting
run: |

View File

@ -1,4 +1,4 @@
FROM python:3.7.5-slim-stretch
FROM python:3.7.6-slim-stretch
RUN apt-get update \
&& apt-get -y install curl build-essential libssl-dev \

View File

@ -137,12 +137,12 @@ A backtesting result will look like that:
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 15 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
========================================================= SELL REASON STATS =========================================================
| Sell Reason | Count |
|:-------------------|--------:|
| trailing_stop_loss | 205 |
| stop_loss | 166 |
| sell_signal | 56 |
| force_sell | 2 |
| Sell Reason | Count | Profit | Loss |
|:-------------------|--------:|---------:|-------:|
| trailing_stop_loss | 205 | 150 | 55 |
| stop_loss | 166 | 0 | 166 |
| sell_signal | 56 | 36 | 20 |
| force_sell | 2 | 0 | 2 |
====================================================== LEFT OPEN TRADES REPORT ======================================================
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
@ -154,6 +154,7 @@ A backtesting result will look like that:
The 1st table contains all trades the bot made, including "left open trades".
The 2nd table contains a recap of sell reasons.
This table can tell you which area needs some additional work (i.e. all `sell_signal` trades are losses, so we should disable the sell-signal or work on improving that).
The 3rd table contains all trades the bot had to `forcesell` at the end of the backtest period to present a full picture.
This is necessary to simulate realistic behaviour, since the backtest period has to end at some point, while realistically, you could leave the bot running forever.

View File

@ -45,14 +45,17 @@ optional arguments:
-h, --help show this help message and exit
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///tradesv3.sqlite` for
Live Run mode, `sqlite://` for Dry Run).
Live Run mode, `sqlite:///tradesv3.dryrun.sqlite` for
Dry Run).
--sd-notify Notify systemd service manager.
--dry-run Enforce dry-run for trading (removes Exchange secrets
and simulates trades).
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified.
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default: `config.json`).
@ -68,6 +71,7 @@ Strategy arguments:
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
### How to specify which configuration file be used?
@ -192,8 +196,8 @@ Backtesting also uses the config specified via `-c/--config`.
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-i TICKER_INTERVAL]
[--timerange TIMERANGE] [--max_open_trades INT]
[--stake_amount STAKE_AMOUNT] [--fee FLOAT]
[--timerange TIMERANGE] [--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--eps] [--dmmp]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export EXPORT] [--export-filename PATH]
@ -205,10 +209,12 @@ optional arguments:
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--max_open_trades INT
Specify max_open_trades to use.
--stake_amount STAKE_AMOUNT
Specify stake_amount.
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--eps, --enable-position-stacking
@ -270,8 +276,8 @@ to find optimal parameter values for your stategy.
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--max_open_trades INT]
[--stake_amount STAKE_AMOUNT] [--fee FLOAT]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--hyperopt NAME] [--hyperopt-path PATH] [--eps]
[-e INT]
[--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
@ -286,10 +292,12 @@ optional arguments:
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--max_open_trades INT
Specify max_open_trades to use.
--stake_amount STAKE_AMOUNT
Specify stake_amount.
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--hyperopt NAME Specify hyperopt class name which will be used by the
@ -360,7 +368,7 @@ 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]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--max_open_trades INT] [--stake_amount STAKE_AMOUNT]
[--max-open-trades INT] [--stake-amount STAKE_AMOUNT]
[--fee FLOAT] [--stoplosses STOPLOSS_RANGE]
optional arguments:
@ -370,10 +378,12 @@ optional arguments:
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--max_open_trades INT
Specify max_open_trades to use.
--stake_amount STAKE_AMOUNT
Specify stake_amount.
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--stoplosses STOPLOSS_RANGE

View File

@ -38,8 +38,8 @@ The prevelance for all Options is as follows:
Mandatory parameters are marked as **Required**, which means that they are required to be set in one of the possible ways.
| Command | Description |
|----------|-------------|
| Parameter | Description |
|------------|-------------|
| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades).<br> ***Datatype:*** *Positive integer or -1.*
| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *String*
| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#understand-stake_amount). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Positive float or `"unlimited"`.*
@ -55,14 +55,14 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Float*
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> ***Datatype:*** *Float*
| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. <br> ***Datatype:*** *Integer*
| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. <br> ***Datatype:*** *Integer*
| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance).
| `bid_strategy.use_order_book` | Enable buying using the rates in Order Book Bids. <br> ***Datatype:*** *Boolean*
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. *Defaults to `1`.* <br> ***Datatype:*** *Positive Integer*
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. *Defaults to `0`.* <br> ***Datatype:*** *Float (as ratio)*
| `ask_strategy.use_order_book` | Enable selling of open trades using Order Book Asks. <br> ***Datatype:*** *Boolean*
| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> ***Datatype:*** *Integer*
| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> ***Datatype:*** *Integer*
| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook).
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled). <br> ***Datatype:*** *Boolean*
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled). <br>*Defaults to `1`.* <br> ***Datatype:*** *Positive Integer*
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market) <br> *Defaults to `0`.* <br> ***Datatype:*** *Float (as ratio)*
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> ***Datatype:*** *Boolean*
| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> ***Datatype:*** *Positive Integer*
| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> ***Datatype:*** *Positive Integer*
| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> ***Datatype:*** *Boolean*
@ -72,9 +72,9 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Dict*
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> ***Datatype:*** *String*
| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> ***Datatype:*** *Boolean*
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode. **Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode. **Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. **Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). <br> ***Datatype:*** *List*
| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). <br> ***Datatype:*** *List*
| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> ***Datatype:*** *Dict*
@ -84,19 +84,19 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> ***Datatype:*** *Boolean*
| `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists). <br>*Defaults to `StaticPairList`.* <br> ***Datatype:*** *List of Dicts*
| `telegram.enabled` | Enable the usage of Telegram. <br> ***Datatype:*** *Boolean*
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `webhook.enabled` | Enable usage of Webhook notifications <br> ***Datatype:*** *Boolean*
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> ***Datatype:*** *Boolean*
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> ***Datatype:*** *IPv4*
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br> ***Datatype:*** *Integer between 1024 and 65535*
| `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. **Keep it in secret, do not disclose publicly.**<br> ***Datatype:*** *String*
| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. **Keep it in secret, do not disclose publicly.**<br> ***Datatype:*** *String*
| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> ***Datatype:*** *String, SQLAlchemy connect string*
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>***Datatype:*** *Integer between 1024 and 65535*
| `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> ***Datatype:*** *String*
| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> ***Datatype:*** *String*
| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> ***Datatype:*** *String, SQLAlchemy connect string*
| `initial_state` | Defines the initial application state. More information below. <br>*Defaults to `stopped`.* <br> ***Datatype:*** *Enum, either `stopped` or `running`*
| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. <br> ***Datatype:*** *Boolean*
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> ***Datatype:*** *ClassName*
@ -124,6 +124,7 @@ Values set in the configuration file always overwrite values set in the strategy
* `order_time_in_force`
* `stake_currency`
* `stake_amount`
* `unfilledtimeout`
* `use_sell_signal` (ask_strategy)
* `sell_profit_only` (ask_strategy)
* `ignore_roi_if_buy_signal` (ask_strategy)
@ -207,13 +208,6 @@ before asking the strategy if we should buy or a sell an asset. After each wait
every opened trade wether or not we should sell, and for all the remaining pairs (either the dynamic list of pairs or
the static list of pairs) if we should buy.
### Understand ask_last_balance
The `ask_last_balance` configuration parameter sets the bidding price. Value `0.0` will use `ask` price, `1.0` will
use the `last` price and values between those interpolate between ask and last
price. Using `ask` price will guarantee quick success in bid, but bot will also
end up paying more then would probably have been necessary.
### 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.
@ -393,6 +387,54 @@ The valid values are:
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
```
## Prices used for orders
Prices for regular orders can be controlled via the parameter structures `bid_strategy` for buying and `ask_strategy` for selling.
Prices are always retrieved right before an order is placed, either by querying the exchange tickers or by using the orderbook data.
!!! Note
Orderbook data used by Freqtrade are the data retrieved from exchange by the ccxt's function `fetch_order_book()`, i.e. are usually data from the L2-aggregated orderbook, while the ticker data are the structures returned by the ccxt's `fetch_ticker()`/`fetch_tickers()` functions. Refer to the ccxt library [documentation](https://github.com/ccxt/ccxt/wiki/Manual#market-data) for more details.
### Buy price
#### Check depth of market
When check depth of market is enabled (`bid_strategy.check_depth_of_market.enabled=True`), the buy signals are filtered based on the orderbook depth (sum of all amounts) for each orderbook side.
Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) side depth and the resulting delta is compared to the value of the `bid_strategy.check_depth_of_market.bids_to_ask_delta` parameter. The buy order is only executed if the orderbook delta is greater than or equal to the configured delta value.
!!! Note
A delta value below 1 means that `ask` (sell) orderbook side depth is greater than the depth of the `bid` (buy) orderbook side, while a value greater than 1 means opposite (depth of the buy side is higher than the depth of the sell side).
#### Buy price with Orderbook enabled
When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and then uses the entry specified as `bid_strategy.order_book_top` on the `bid` (buy) side of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
#### Buy price without Orderbook enabled
When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `ask` (sell) price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `ask` price is not below the `last` price), it calculates a rate between `ask` and `last` price.
The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `ask` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price.
Using `ask` price often guarantees quicker success in the bid, but the bot can also end up paying more than what would have been necessary.
### Sell price
#### Sell price with Orderbook enabled
When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the `ask` orderbook side are validated for a profitable sell-possibility based on the strategy configuration and the sell order is placed at the first profitable spot.
The idea here is to place the sell order early, to be ahead in the queue.
A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number.
!!! Warning "Orderbook and stoploss_on_exchange"
Using `ask_strategy.order_book_max` higher than 1 may increase the risk, since an eventual [stoploss on exchange](#understand-order_types) will be needed to be cancelled as soon as the order is placed.
#### Sell price without Orderbook enabled
When not using orderbook (`ask_strategy.use_order_book=False`), the `bid` price from the ticker will be used as the sell price.
## Pairlists
Pairlists define the list of pairs that the bot should trade.

View File

@ -266,4 +266,29 @@ Once the PR against master is merged (best right after merging):
* Use the button "Draft a new release" in the Github UI (subsection releases).
* Use the version-number specified as tag.
* Use "master" as reference (this step comes after the above PR is merged).
* Use the above changelog as release comment (as codeblock).
* Use the above changelog as release comment (as codeblock)
### After-release
* Update version in develop by postfixing that with `-dev` (`2019.6 -> 2019.6-dev`).
* Create a PR against develop to update that branch.
## Releases
### pypi
To create a pypi release, please run the following commands:
Additional requirement: `wheel`, `twine` (for uploading), account on pypi with proper permissions.
``` bash
python setup.py sdist bdist_wheel
# For pypi test (to check if some change to the installation did work)
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
# For production:
twine upload dist/*
```
Please don't push non-releases to the productive / real pypi instance.

View File

@ -164,8 +164,7 @@ docker run -d \
```
!!! Note
db-url defaults to `sqlite:///tradesv3.sqlite` but it defaults to `sqlite://` if `dry_run=True` is being used.
To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
When using docker, it's best to specify `--db-url` explicitly to ensure that the database URL and the mounted database file match.
!!! Note
All available bot command line parameters can be added to the end of the `docker run` command.

View File

@ -1,4 +1,4 @@
# Edge positioning
# Edge positioning
This page explains how to use Edge Positioning module in your bot in order to enter into a trade only if the trade has a reasonable win rate and risk reward ratio, and consequently adjust your position size and stoploss.
@ -9,6 +9,7 @@ This page explains how to use Edge Positioning module in your bot in order to en
Edge does not consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else are ignored in its calculation.
## Introduction
Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose.
But it doesn't mean there is no rule, it only means rules should work "most of the time". Let's play a game: we toss a coin, heads: I give you 10$, tails: you give me 10$. Is it an interesting game? No, it's quite boring, isn't it?
@ -22,43 +23,61 @@ Let's complicate it more: you win 80% of the time but only 2$, I win 20% of the
The question is: How do you calculate that? How do you know if you wanna play?
The answer comes to two factors:
- Win Rate
- Risk Reward Ratio
### Win Rate
Win Rate (*W*) is is the mean over some amount of trades (*N*) what is the percentage of winning trades to total number of trades (note that we don't consider how much you gained but only if you won or not).
W = (Number of winning trades) / (Total number of trades) = (Number of winning trades) / N
```
W = (Number of winning trades) / (Total number of trades) = (Number of winning trades) / N
```
Complementary Loss Rate (*L*) is defined as
L = (Number of losing trades) / (Total number of trades) = (Number of losing trades) / N
```
L = (Number of losing trades) / (Total number of trades) = (Number of losing trades) / N
```
or, which is the same, as
L = 1 W
```
L = 1 W
```
### Risk Reward Ratio
Risk Reward Ratio (*R*) is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose:
R = Profit / Loss
```
R = Profit / Loss
```
Over time, on many trades, you can calculate your risk reward by dividing your average profit on winning trades by your average loss on losing trades:
Average profit = (Sum of profits) / (Number of winning trades)
```
Average profit = (Sum of profits) / (Number of winning trades)
Average loss = (Sum of losses) / (Number of losing trades)
Average loss = (Sum of losses) / (Number of losing trades)
R = (Average profit) / (Average loss)
R = (Average profit) / (Average loss)
```
### Expectancy
At this point we can combine *W* and *R* to create an expectancy ratio. This is a simple process of multiplying the risk reward ratio by the percentage of winning trades and subtracting the percentage of losing trades, which is calculated as follows:
Expectancy Ratio = (Risk Reward Ratio X Win Rate) Loss Rate = (R X W) L
```
Expectancy Ratio = (Risk Reward Ratio X Win Rate) Loss Rate = (R X W) L
```
So lets say your Win rate is 28% and your Risk Reward Ratio is 5:
Expectancy = (5 X 0.28) 0.72 = 0.68
```
Expectancy = (5 X 0.28) 0.72 = 0.68
```
Superficially, this means that on average you expect this strategys trades to return .68 times the size of your loses. This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ.
@ -69,6 +88,7 @@ You can also use this value to evaluate the effectiveness of modifications to th
**NOTICE:** It's important to keep in mind that Edge is testing your expectancy using historical data, there's no guarantee that you will have a similar edge in the future. It's still vital to do this testing in order to build confidence in your methodology, but be wary of "curve-fitting" your approach to the historical data as things are unlikely to play out the exact same way for future trades.
## How does it work?
If enabled in config, Edge will go through historical data with a range of stoplosses in order to find buy and sell/stoploss signals. It then calculates win rate and expectancy over *N* trades for each stoploss. Here is an example:
| Pair | Stoploss | Win Rate | Risk Reward Ratio | Expectancy |
@ -83,6 +103,7 @@ The goal here is to find the best stoploss for the strategy in order to have the
Edge module then forces stoploss value it evaluated to your strategy dynamically.
### Position size
Edge also dictates the stake amount for each trade to the bot according to the following factors:
- Allowed capital at risk
@ -90,13 +111,17 @@ Edge also dictates the stake amount for each trade to the bot according to the f
Allowed capital at risk is calculated as follows:
Allowed capital at risk = (Capital available_percentage) X (Allowed risk per trade)
```
Allowed capital at risk = (Capital available_percentage) X (Allowed risk per trade)
```
Stoploss is calculated as described above against historical data.
Your position size then will be:
Position size = (Allowed capital at risk) / Stoploss
```
Position size = (Allowed capital at risk) / Stoploss
```
Example:
@ -115,100 +140,30 @@ Available capital doesnt change before a position is sold. Lets assume tha
So the Bot receives another buy signal for trade 4 with a stoploss at 2% then your position size would be **0.055 / 0.02 = 2.75 ETH**.
## Configurations
Edge module has following configuration options:
#### enabled
If true, then Edge will run periodically.
(defaults to false)
#### process_throttle_secs
How often should Edge run in seconds?
(defaults to 3600 so one hour)
#### calculate_since_number_of_days
Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy
Note that it downloads historical data so increasing this number would lead to slowing down the bot.
(defaults to 7)
#### capital_available_percentage
This is the percentage of the total capital on exchange in stake currency.
As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital.
(defaults to 0.5)
#### allowed_risk
Percentage of allowed risk per trade.
(defaults to 0.01 so 1%)
#### stoploss_range_min
Minimum stoploss.
(defaults to -0.01)
#### stoploss_range_max
Maximum stoploss.
(defaults to -0.10)
#### stoploss_range_step
As an example if this is set to -0.01 then Edge will test the strategy for \[-0.01, -0,02, -0,03 ..., -0.09, -0.10\] ranges.
Note than having a smaller step means having a bigger range which could lead to slow calculation.
If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10.
(defaults to -0.01)
#### minimum_winrate
It filters out pairs which don't have at least minimum_winrate.
This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio.
(defaults to 0.60)
#### minimum_expectancy
It filters out pairs which have the expectancy lower than this number.
Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return.
(defaults to 0.20)
#### 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.
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.
(defaults to 10, it is highly recommended not to decrease this number)
#### 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.
**NOTICE:** While configuring this value, you should take into consideration your 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.).
(defaults to 1 day, i.e. to 60 * 24 = 1440 minutes)
#### 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.
(defaults to false)
| Parameter | Description |
|------------|-------------|
| `enabled` | If true, then Edge will run periodically. <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `process_throttle_secs` | How often should Edge run in seconds. <br>*Defaults to `3600` (once per hour).* <br> ***Datatype:*** *Integer*
| `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy. <br> **Note** that it downloads historical data so increasing this number would lead to slowing down the bot. <br>*Defaults to `7`.* <br> ***Datatype:*** *Integer*
| `capital_available_percentage` | This is the percentage of the total capital on exchange in stake currency. <br>As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital. <br>*Defaults to `0.5`.* <br> ***Datatype:*** *Float*
| `allowed_risk` | Ratio of allowed risk per trade. <br>*Defaults to `0.01` (1%)).* <br> ***Datatype:*** *Float*
| `stoploss_range_min` | Minimum stoploss. <br>*Defaults to `-0.01`.* <br> ***Datatype:*** *Float*
| `stoploss_range_max` | Maximum stoploss. <br>*Defaults to `-0.10`.* <br> ***Datatype:*** *Float*
| `stoploss_range_step` | As an example if this is set to -0.01 then Edge will test the strategy for `[-0.01, -0,02, -0,03 ..., -0.09, -0.10]` ranges. <br> **Note** than having a smaller step means having a bigger range which could lead to slow calculation. <br> If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10. <br>*Defaults to `-0.001`.* <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*
| `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 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*
| `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
You can run Edge independently in order to see in details the result. Here is an example:
```bash
``` bash
freqtrade edge
```

View File

@ -23,58 +23,43 @@ The `freqtrade plot-dataframe` subcommand shows an interactive graph with three
Possible arguments:
```
usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-p PAIRS [PAIRS ...]]
[--indicators1 INDICATORS1 [INDICATORS1 ...]]
[--indicators2 INDICATORS2 [INDICATORS2 ...]]
[--plot-limit INT] [--db-url PATH]
[--trade-source {DB,file}] [--export EXPORT]
[--export-filename PATH]
[--timerange TIMERANGE] [-i TICKER_INTERVAL]
usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-p PAIRS [PAIRS ...]] [--indicators1 INDICATORS1 [INDICATORS1 ...]]
[--indicators2 INDICATORS2 [INDICATORS2 ...]] [--plot-limit INT] [--db-url PATH]
[--trade-source {DB,file}] [--export EXPORT] [--export-filename PATH] [--timerange TIMERANGE]
[-i TICKER_INTERVAL]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-
separated.
Show profits for only these pairs. Pairs are space-separated.
--indicators1 INDICATORS1 [INDICATORS1 ...]
Set indicators from your strategy you want in the
first row of the graph. Space-separated list. Example:
Set indicators from your strategy you want in the first row of the graph. Space-separated list. Example:
`ema3 ema5`. Default: `['sma', 'ema3', 'ema5']`.
--indicators2 INDICATORS2 [INDICATORS2 ...]
Set indicators from your strategy you want in the
third row of the graph. Space-separated list. Example:
Set indicators from your strategy you want in the third row of the graph. Space-separated list. Example:
`fastd fastk`. Default: `['macd', 'macdsignal']`.
--plot-limit INT Specify tick limit for plotting. Notice: too high
values cause huge files. Default: 750.
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///tradesv3.sqlite` for
Live Run mode, `sqlite://` for Dry Run).
--plot-limit INT Specify tick limit for plotting. Notice: too high values cause huge files. Default: 750.
--db-url PATH Override trades database URL, this is useful in custom deployments (default: `sqlite:///tradesv3.sqlite`
for Live Run mode, `sqlite:///tradesv3.dryrun.sqlite` for Dry Run).
--trade-source {DB,file}
Specify the source for trades (Can be DB or file
(backtest file)) Default: file
--export EXPORT Export backtest results, argument are: trades.
Example: `--export=trades`
Specify the source for trades (Can be DB or file (backtest file)) Default: file
--export EXPORT Export backtest results, argument are: trades. Example: `--export=trades`
--export-filename PATH
Save backtest results to the file with this filename
(default: `user_data/backtest_results/backtest-
result.json`). Requires `--export` to be set as well.
Example: `--export-filename=user_data/backtest_results
/backtest_today.json`
Save backtest results to the file with this filename. Requires `--export` to be set as well. Example:
`--export-filename=user_data/backtest_results/backtest_today.json`
--timerange TIMERANGE
Specify what timerange of data to use.
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified.
--logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default: `config.json`).
Multiple --config options may be used. Can be set to
Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to
`-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
@ -83,8 +68,7 @@ Common arguments:
Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name (default:
`DefaultStrategy`).
Specify strategy class name which will be used by the bot.
--strategy-path PATH Specify additional strategy lookup path.
```
@ -173,14 +157,14 @@ optional arguments:
--export EXPORT Export backtest results, argument are: trades.
Example: `--export=trades`
--export-filename PATH
Save backtest results to the file with this filename
(default: `user_data/backtest_results/backtest-
result.json`). Requires `--export` to be set as well.
Example: `--export-filename=user_data/backtest_results
/backtest_today.json`
Save backtest results to the file with this filename.
Requires `--export` to be set as well. Example:
`--export-filename=user_data/backtest_results/backtest
_today.json`
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///tradesv3.sqlite` for
Live Run mode, `sqlite://` for Dry Run).
Live Run mode, `sqlite:///tradesv3.dryrun.sqlite` for
Dry Run).
--trade-source {DB,file}
Specify the source for trades (Can be DB or file
(backtest file)) Default: file
@ -190,7 +174,9 @@ optional arguments:
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified.
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default: `config.json`).

View File

@ -455,6 +455,51 @@ Sample return value: ETH/BTC had 5 trades, with a total profit of 1.5% (ratio of
!!! Warning
Trade history is not available during backtesting or hyperopt.
### Prevent trades from happening for a specific pair
Freqtrade locks pairs automatically for the current candle (until that candle is over) when a pair is sold, preventing an immediate re-buy of that pair.
Locked pairs will show the message `Pair <pair> is currently locked.`.
#### Locking pairs from within the strategy
Sometimes it may be desired to lock a pair after certain events happen (e.g. multiple losing trades in a row).
Freqtrade has an easy method to do this from within the strategy, by calling `self.lock_pair(pair, until)`.
`until` must be a datetime object in the future, after which trading will be reenabled for that pair.
Locks can also be lifted manually, by calling `self.unlock_pair(pair)`.
To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.
!!! Note
Locked pairs are not persisted, so a restart of the bot, or calling `/reload_conf` will reset locked pairs.
!!! Warning
Locking pairs is not functioning during backtesting.
##### Pair locking example
``` python
from freqtrade.persistence import Trade
from datetime import timedelta, datetime, timezone
# Put the above lines a the top of the strategy file, next to all the other imports
# --------
# Within populate indicators (or populate_buy):
if self.config['runmode'] in ('live', 'dry_run'):
# fetch closed trades for the last 2 days
trades = Trade.get_trades([Trade.pair == metadata['pair'],
Trade.open_date > datetime.utcnow() - timedelta(days=2),
Trade.is_open == False,
]).all()
# Analyze the conditions you'd like to lock the pair .... will probably be different for every strategy
sumprofit = sum(trade.close_profit for trade in trades)
if sumprofit < 0:
# Lock pair for 12 hours
self.lock_pair(metadata['pair'], until=datetime.now(timezone.utc) + timedelta(hours=12))
```
### Print created dataframe
To inspect the created dataframe, you can issue a print-statement in either `populate_buy_trend()` or `populate_sell_trend()`.
@ -479,11 +524,6 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
Printing more than a few rows is also possible (simply use `print(dataframe)` instead of `print(dataframe.tail())`), however not recommended, as that will be very verbose (~500 lines per pair every 5 seconds).
### Where can i find a strategy template?
The strategy template is located in the file
[user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py).
### Specify custom strategy location
If you want to use a strategy from a different directory you can pass `--strategy-path`

View File

@ -44,9 +44,9 @@ candles.head()
```python
# Load strategy using values set above
from freqtrade.resolvers import StrategyResolver
strategy = StrategyResolver({'strategy': strategy_name,
'user_data_dir': user_data_dir,
'strategy_path': strategy_location}).strategy
strategy = StrategyResolver.load_strategy({'strategy': strategy_name,
'user_data_dir': user_data_dir,
'strategy_path': strategy_location})
# Generate buy/sell signals using strategy
df = strategy.analyze_ticker(candles, {'pair': pair})

View File

@ -118,14 +118,14 @@ AVAILABLE_CLI_OPTIONS = {
help='Specify what timerange of data to use.',
),
"max_open_trades": Arg(
'--max_open_trades',
help='Specify max_open_trades to use.',
'--max-open-trades',
help='Override the value of the `max_open_trades` configuration setting.',
type=int,
metavar='INT',
),
"stake_amount": Arg(
'--stake_amount',
help='Specify stake_amount.',
'--stake-amount',
help='Override the value of the `stake_amount` configuration setting.',
type=float,
),
# Backtesting

View File

@ -223,13 +223,13 @@ class Configuration:
logger.info('max_open_trades set to unlimited ...')
elif 'max_open_trades' in self.args and self.args["max_open_trades"]:
config.update({'max_open_trades': self.args["max_open_trades"]})
logger.info('Parameter --max_open_trades detected, '
logger.info('Parameter --max-open-trades detected, '
'overriding max_open_trades to: %s ...', config.get('max_open_trades'))
elif config['runmode'] in NON_UTIL_MODES:
logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
self._args_to_config(config, argname='stake_amount',
logstring='Parameter --stake_amount detected, '
logstring='Parameter --stake-amount detected, '
'overriding stake_amount to: {} ...')
self._args_to_config(config, argname='fee',
@ -403,7 +403,7 @@ class Configuration:
config['pairs'] = config.get('exchange', {}).get('pair_whitelist')
else:
# Fall back to /dl_path/pairs.json
pairs_file = Path(config['datadir']) / "pairs.json"
pairs_file = config['datadir'] / "pairs.json"
if pairs_file.exists():
with pairs_file.open('r') as f:
config['pairs'] = json_load(f)

View File

@ -9,7 +9,7 @@ from freqtrade.constants import USER_DATA_FILES
logger = logging.getLogger(__name__)
def create_datadir(config: Dict[str, Any], datadir: Optional[str] = None) -> str:
def create_datadir(config: Dict[str, Any], datadir: Optional[str] = None) -> Path:
folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data")
if not datadir:
@ -20,7 +20,7 @@ def create_datadir(config: Dict[str, Any], datadir: Optional[str] = None) -> str
if not folder.is_dir():
folder.mkdir(parents=True)
logger.info(f'Created data directory: {datadir}')
return str(folder)
return folder
def create_userdata_dir(directory: str, create_dir=False) -> Path:

View File

@ -10,7 +10,7 @@ HYPEROPT_EPOCH = 100 # epochs
RETRY_TIMEOUT = 30 # sec
DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss'
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite'
UNLIMITED_STAKE_AMOUNT = 'unlimited'
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
REQUIRED_ORDERTIF = ['buy', 'sell']

View File

@ -5,7 +5,6 @@ including Klines, tickers, historic data
Common Interface for bot and strategy to access data.
"""
import logging
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from pandas import DataFrame
@ -65,7 +64,7 @@ class DataProvider:
"""
return load_pair_history(pair=pair,
timeframe=timeframe or self._config['ticker_interval'],
datadir=Path(self._config['datadir'])
datadir=self._config['datadir']
)
def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame:

View File

@ -1,7 +1,6 @@
# pragma pylint: disable=W0603
""" Edge positioning package """
import logging
from pathlib import Path
from typing import Any, Dict, NamedTuple
import arrow
@ -96,7 +95,7 @@ class Edge:
if self._refresh_pairs:
history.refresh_data(
datadir=Path(self.config['datadir']),
datadir=self.config['datadir'],
pairs=pairs,
exchange=self.exchange,
timeframe=self.strategy.ticker_interval,
@ -104,7 +103,7 @@ class Edge:
)
data = history.load_data(
datadir=Path(self.config['datadir']),
datadir=self.config['datadir'],
pairs=pairs,
timeframe=self.strategy.ticker_interval,
timerange=self._timerange,

View File

@ -278,7 +278,15 @@ class Exchange:
raise OperationalException(
f'Pair {pair} is not available on {self.name}. '
f'Please remove {pair} from your whitelist.')
elif self.markets[pair].get('info', {}).get('IsRestricted', False):
# From ccxt Documentation:
# markets.info: An associative array of non-common market properties,
# including fees, rates, limits and other general market information.
# The internal info array is different for each particular market,
# its contents depend on the exchange.
# It can also be a string or similar ... so we need to verify that first.
elif (isinstance(self.markets[pair].get('info', None), dict)
and self.markets[pair].get('info', {}).get('IsRestricted', False)):
# Warn users about restricted pairs in whitelist.
# We cannot determine reliably if Users are affected.
logger.warning(f"Pair {pair} is restricted for some users on this exchange."
@ -524,7 +532,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier
def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
def fetch_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
if refresh or pair not in self._cached_ticker.keys():
try:
if pair not in self._api.markets or not self._api.markets[pair].get('active'):

View File

@ -55,12 +55,12 @@ class FreqtradeBot:
self.heartbeat_interval = self.config.get('internals', {}).get('heartbeat_interval', 60)
self.strategy: IStrategy = StrategyResolver(self.config).strategy
self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
# Check config consistency here since strategies can set certain options
validate_config_consistency(config)
self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
persistence.init(self.config.get('db_url', None),
clean_open_orders=self.config.get('dry_run', False))
@ -192,7 +192,7 @@ class FreqtradeBot:
else:
if not tick:
logger.info('Using Last Ask / Last Price')
ticker = self.exchange.get_ticker(pair)
ticker = self.exchange.fetch_ticker(pair)
else:
ticker = tick
if ticker['ask'] < ticker['last']:
@ -570,7 +570,7 @@ class FreqtradeBot:
"""
Get sell rate - either using get-ticker bid or first bid based on orderbook
The orderbook portion is only used for rpc messaging, which would otherwise fail
for BitMex (has no bid/ask in get_ticker)
for BitMex (has no bid/ask in fetch_ticker)
or remain static in any other case since it's not updating.
:return: Bid rate
"""
@ -582,7 +582,7 @@ class FreqtradeBot:
rate = order_book['bids'][0][0]
else:
rate = self.exchange.get_ticker(pair, refresh)['bid']
rate = self.exchange.fetch_ticker(pair, refresh)['bid']
return rate
def handle_trade(self, trade: Trade) -> bool:

View File

@ -60,7 +60,7 @@ class Backtesting:
# Reset keys for backtesting
remove_credentials(self.config)
self.strategylist: List[IStrategy] = []
self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
if config.get('fee'):
self.fee = config['fee']
@ -75,12 +75,12 @@ class Backtesting:
for strat in list(self.config['strategy_list']):
stratconf = deepcopy(self.config)
stratconf['strategy'] = strat
self.strategylist.append(StrategyResolver(stratconf).strategy)
self.strategylist.append(StrategyResolver.load_strategy(stratconf))
validate_config_consistency(stratconf)
else:
# No strategy list specified, only one strategy
self.strategylist.append(StrategyResolver(self.config).strategy)
self.strategylist.append(StrategyResolver.load_strategy(self.config))
validate_config_consistency(self.config)
if "ticker_interval" not in self.config:
@ -109,7 +109,7 @@ class Backtesting:
'timerange') is None else str(self.config.get('timerange')))
data = history.load_data(
datadir=Path(self.config['datadir']),
datadir=self.config['datadir'],
pairs=self.config['exchange']['pair_whitelist'],
timeframe=self.timeframe,
timerange=timerange,
@ -183,9 +183,11 @@ class Backtesting:
Generate small table outlining Backtest results
"""
tabular_data = []
headers = ['Sell Reason', 'Count']
headers = ['Sell Reason', 'Count', 'Profit', 'Loss']
for reason, count in results['sell_reason'].value_counts().iteritems():
tabular_data.append([reason.value, count])
profit = len(results[(results['sell_reason'] == reason) & (results['profit_abs'] >= 0)])
loss = len(results[(results['sell_reason'] == reason) & (results['profit_abs'] < 0)])
tabular_data.append([reason.value, count, profit, loss])
return tabulate(tabular_data, headers=headers, tablefmt="pipe")
def _generate_text_table_strategy(self, all_results: dict) -> str:

View File

@ -34,7 +34,7 @@ class EdgeCli:
remove_credentials(self.config)
self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
self.exchange = Exchange(self.config)
self.strategy = StrategyResolver(self.config).strategy
self.strategy = StrategyResolver.load_strategy(self.config)
validate_config_consistency(self.config)
@ -42,11 +42,9 @@ class EdgeCli:
# Set refresh_pairs to false for edge-cli (it must be true for edge)
self.edge._refresh_pairs = False
self.timerange = TimeRange.parse_timerange(None if self.config.get(
self.edge._timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange')))
self.edge._timerange = self.timerange
def _generate_edge_table(self, results: dict) -> str:
floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', '.d')

View File

@ -64,9 +64,9 @@ class Hyperopt:
self.backtesting = Backtesting(self.config)
self.custom_hyperopt = HyperOptResolver(self.config).hyperopt
self.custom_hyperopt = HyperOptResolver.load_hyperopt(self.config)
self.custom_hyperoptloss = HyperOptLossResolver(self.config).hyperoptloss
self.custom_hyperoptloss = HyperOptLossResolver.load_hyperoptloss(self.config)
self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function
self.trials_file = (self.config['user_data_dir'] /

View File

@ -28,13 +28,13 @@ class PairListManager():
if 'method' not in pl:
logger.warning(f"No method in {pl}")
continue
pairl = PairListResolver(pl.get('method'),
exchange=exchange,
pairlistmanager=self,
config=config,
pairlistconfig=pl,
pairlist_pos=len(self._pairlists)
).pairlist
pairl = PairListResolver.load_pairlist(pl.get('method'),
exchange=exchange,
pairlistmanager=self,
config=config,
pairlistconfig=pl,
pairlist_pos=len(self._pairlists)
)
self._tickers_needed = pairl.needstickers or self._tickers_needed
self._pairlists.append(pairl)

View File

@ -37,7 +37,7 @@ def init_plotscript(config):
timerange = TimeRange.parse_timerange(config.get("timerange"))
tickers = history.load_data(
datadir=Path(str(config.get("datadir"))),
datadir=config.get("datadir"),
pairs=pairs,
timeframe=config.get('ticker_interval', '5m'),
timerange=timerange,
@ -340,7 +340,7 @@ def load_and_plot_trades(config: Dict[str, Any]):
- Generate plot files
:return: None
"""
strategy = StrategyResolver(config).strategy
strategy = StrategyResolver.load_strategy(config)
plot_elements = init_plotscript(config)
trades = plot_elements['trades']

View File

@ -15,9 +15,8 @@ class ExchangeResolver(IResolver):
This class contains all the logic to load a custom exchange class
"""
__slots__ = ['exchange']
def __init__(self, exchange_name: str, config: dict, validate: bool = True) -> None:
@staticmethod
def load_exchange(exchange_name: str, config: dict, validate: bool = True) -> Exchange:
"""
Load the custom class from config parameter
:param config: configuration dictionary
@ -25,17 +24,20 @@ class ExchangeResolver(IResolver):
# Map exchange name to avoid duplicate classes for identical exchanges
exchange_name = MAP_EXCHANGE_CHILDCLASS.get(exchange_name, exchange_name)
exchange_name = exchange_name.title()
exchange = None
try:
self.exchange = self._load_exchange(exchange_name, kwargs={'config': config,
'validate': validate})
exchange = ExchangeResolver._load_exchange(exchange_name,
kwargs={'config': config,
'validate': validate})
except ImportError:
logger.info(
f"No {exchange_name} specific subclass found. Using the generic class instead.")
if not hasattr(self, "exchange"):
self.exchange = Exchange(config, validate=validate)
if not exchange:
exchange = Exchange(config, validate=validate)
return exchange
def _load_exchange(
self, exchange_name: str, kwargs: dict) -> Exchange:
@staticmethod
def _load_exchange(exchange_name: str, kwargs: dict) -> Exchange:
"""
Loads the specified exchange.
Only checks for exchanges exported in freqtrade.exchanges

View File

@ -20,11 +20,11 @@ class HyperOptResolver(IResolver):
"""
This class contains all the logic to load custom hyperopt class
"""
__slots__ = ['hyperopt']
def __init__(self, config: Dict) -> None:
@staticmethod
def load_hyperopt(config: Dict) -> IHyperOpt:
"""
Load the custom class from config parameter
Load the custom hyperopt class from config parameter
:param config: configuration dictionary
"""
if not config.get('hyperopt'):
@ -33,21 +33,23 @@ class HyperOptResolver(IResolver):
hyperopt_name = config['hyperopt']
self.hyperopt = self._load_hyperopt(hyperopt_name, config,
extra_dir=config.get('hyperopt_path'))
hyperopt = HyperOptResolver._load_hyperopt(hyperopt_name, config,
extra_dir=config.get('hyperopt_path'))
if not hasattr(self.hyperopt, 'populate_indicators'):
if not hasattr(hyperopt, 'populate_indicators'):
logger.warning("Hyperopt class does not provide populate_indicators() method. "
"Using populate_indicators from the strategy.")
if not hasattr(self.hyperopt, 'populate_buy_trend'):
if not hasattr(hyperopt, 'populate_buy_trend'):
logger.warning("Hyperopt class does not provide populate_buy_trend() method. "
"Using populate_buy_trend from the strategy.")
if not hasattr(self.hyperopt, 'populate_sell_trend'):
if not hasattr(hyperopt, 'populate_sell_trend'):
logger.warning("Hyperopt class does not provide populate_sell_trend() method. "
"Using populate_sell_trend from the strategy.")
return hyperopt
@staticmethod
def _load_hyperopt(
self, hyperopt_name: str, config: Dict, extra_dir: Optional[str] = None) -> IHyperOpt:
hyperopt_name: str, config: Dict, extra_dir: Optional[str] = None) -> IHyperOpt:
"""
Search and loads the specified hyperopt.
:param hyperopt_name: name of the module to import
@ -57,11 +59,12 @@ class HyperOptResolver(IResolver):
"""
current_path = Path(__file__).parent.parent.joinpath('optimize').resolve()
abs_paths = self.build_search_paths(config, current_path=current_path,
user_subdir=USERPATH_HYPEROPTS, extra_dir=extra_dir)
abs_paths = IResolver.build_search_paths(config, current_path=current_path,
user_subdir=USERPATH_HYPEROPTS,
extra_dir=extra_dir)
hyperopt = self._load_object(paths=abs_paths, object_type=IHyperOpt,
object_name=hyperopt_name, kwargs={'config': config})
hyperopt = IResolver._load_object(paths=abs_paths, object_type=IHyperOpt,
object_name=hyperopt_name, kwargs={'config': config})
if hyperopt:
return hyperopt
raise OperationalException(
@ -74,9 +77,9 @@ class HyperOptLossResolver(IResolver):
"""
This class contains all the logic to load custom hyperopt loss class
"""
__slots__ = ['hyperoptloss']
def __init__(self, config: Dict) -> None:
@staticmethod
def load_hyperoptloss(config: Dict) -> IHyperOptLoss:
"""
Load the custom class from config parameter
:param config: configuration dictionary
@ -86,20 +89,21 @@ class HyperOptLossResolver(IResolver):
# default hyperopt loss
hyperoptloss_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS
self.hyperoptloss = self._load_hyperoptloss(
hyperoptloss = HyperOptLossResolver._load_hyperoptloss(
hyperoptloss_name, config, extra_dir=config.get('hyperopt_path'))
# Assign ticker_interval to be used in hyperopt
self.hyperoptloss.__class__.ticker_interval = str(config['ticker_interval'])
hyperoptloss.__class__.ticker_interval = str(config['ticker_interval'])
if not hasattr(self.hyperoptloss, 'hyperopt_loss_function'):
if not hasattr(hyperoptloss, 'hyperopt_loss_function'):
raise OperationalException(
f"Found HyperoptLoss class {hyperoptloss_name} does not "
"implement `hyperopt_loss_function`.")
return hyperoptloss
def _load_hyperoptloss(
self, hyper_loss_name: str, config: Dict,
extra_dir: Optional[str] = None) -> IHyperOptLoss:
@staticmethod
def _load_hyperoptloss(hyper_loss_name: str, config: Dict,
extra_dir: Optional[str] = None) -> IHyperOptLoss:
"""
Search and loads the specified hyperopt loss class.
:param hyper_loss_name: name of the module to import
@ -109,11 +113,12 @@ class HyperOptLossResolver(IResolver):
"""
current_path = Path(__file__).parent.parent.joinpath('optimize').resolve()
abs_paths = self.build_search_paths(config, current_path=current_path,
user_subdir=USERPATH_HYPEROPTS, extra_dir=extra_dir)
abs_paths = IResolver.build_search_paths(config, current_path=current_path,
user_subdir=USERPATH_HYPEROPTS,
extra_dir=extra_dir)
hyperoptloss = self._load_object(paths=abs_paths, object_type=IHyperOptLoss,
object_name=hyper_loss_name)
hyperoptloss = IResolver._load_object(paths=abs_paths, object_type=IHyperOptLoss,
object_name=hyper_loss_name)
if hyperoptloss:
return hyperoptloss

View File

@ -17,7 +17,8 @@ class IResolver:
This class contains all the logic to load custom classes
"""
def build_search_paths(self, config, current_path: Path, user_subdir: Optional[str] = None,
@staticmethod
def build_search_paths(config, current_path: Path, user_subdir: Optional[str] = None,
extra_dir: Optional[str] = None) -> List[Path]:
abs_paths: List[Path] = [current_path]

View File

@ -18,23 +18,29 @@ class PairListResolver(IResolver):
This class contains all the logic to load custom PairList class
"""
__slots__ = ['pairlist']
def __init__(self, pairlist_name: str, exchange, pairlistmanager,
config: dict, pairlistconfig: dict, pairlist_pos: int) -> None:
@staticmethod
def load_pairlist(pairlist_name: str, exchange, pairlistmanager,
config: dict, pairlistconfig: dict, pairlist_pos: int) -> IPairList:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
Load the pairlist with pairlist_name
:param pairlist_name: Classname of the pairlist
:param exchange: Initialized exchange class
:param pairlistmanager: Initialized pairlist manager
:param config: configuration dictionary
:param pairlistconfig: Configuration dedicated to this pairlist
:param pairlist_pos: Position of the pairlist in the list of pairlists
:return: initialized Pairlist class
"""
self.pairlist = self._load_pairlist(pairlist_name, config,
kwargs={'exchange': exchange,
'pairlistmanager': pairlistmanager,
'config': config,
'pairlistconfig': pairlistconfig,
'pairlist_pos': pairlist_pos})
def _load_pairlist(
self, pairlist_name: str, config: dict, kwargs: dict) -> IPairList:
return PairListResolver._load_pairlist(pairlist_name, config,
kwargs={'exchange': exchange,
'pairlistmanager': pairlistmanager,
'config': config,
'pairlistconfig': pairlistconfig,
'pairlist_pos': pairlist_pos})
@staticmethod
def _load_pairlist(pairlist_name: str, config: dict, kwargs: dict) -> IPairList:
"""
Search and loads the specified pairlist.
:param pairlist_name: name of the module to import
@ -44,11 +50,11 @@ class PairListResolver(IResolver):
"""
current_path = Path(__file__).parent.parent.joinpath('pairlist').resolve()
abs_paths = self.build_search_paths(config, current_path=current_path,
user_subdir=None, extra_dir=None)
abs_paths = IResolver.build_search_paths(config, current_path=current_path,
user_subdir=None, extra_dir=None)
pairlist = self._load_object(paths=abs_paths, object_type=IPairList,
object_name=pairlist_name, kwargs=kwargs)
pairlist = IResolver._load_object(paths=abs_paths, object_type=IPairList,
object_name=pairlist_name, kwargs=kwargs)
if pairlist:
return pairlist
raise OperationalException(

View File

@ -20,12 +20,11 @@ logger = logging.getLogger(__name__)
class StrategyResolver(IResolver):
"""
This class contains all the logic to load custom strategy class
This class contains the logic to load custom strategy class
"""
__slots__ = ['strategy']
def __init__(self, config: Optional[Dict] = None) -> None:
@staticmethod
def load_strategy(config: Optional[Dict] = None) -> IStrategy:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
@ -37,9 +36,9 @@ class StrategyResolver(IResolver):
"the strategy class to use.")
strategy_name = config['strategy']
self.strategy: IStrategy = self._load_strategy(strategy_name,
config=config,
extra_dir=config.get('strategy_path'))
strategy: IStrategy = StrategyResolver._load_strategy(
strategy_name, config=config,
extra_dir=config.get('strategy_path'))
# make sure ask_strategy dict is available
if 'ask_strategy' not in config:
@ -61,15 +60,18 @@ class StrategyResolver(IResolver):
("stake_currency", None, False),
("stake_amount", None, False),
("startup_candle_count", None, False),
("unfilledtimeout", None, False),
("use_sell_signal", True, True),
("sell_profit_only", False, True),
("ignore_roi_if_buy_signal", False, True),
]
for attribute, default, ask_strategy in attributes:
if ask_strategy:
self._override_attribute_helper(config['ask_strategy'], attribute, default)
StrategyResolver._override_attribute_helper(strategy, config['ask_strategy'],
attribute, default)
else:
self._override_attribute_helper(config, attribute, default)
StrategyResolver._override_attribute_helper(strategy, config,
attribute, default)
# Loop this list again to have output combined
for attribute, _, exp in attributes:
@ -79,14 +81,16 @@ class StrategyResolver(IResolver):
logger.info("Strategy using %s: %s", attribute, config[attribute])
# Sort and apply type conversions
self.strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in strategy.minimal_roi.items()}.items(),
key=lambda t: t[0]))
self.strategy.stoploss = float(self.strategy.stoploss)
strategy.stoploss = float(strategy.stoploss)
self._strategy_sanity_validations()
StrategyResolver._strategy_sanity_validations(strategy)
return strategy
def _override_attribute_helper(self, config, attribute: str, default):
@staticmethod
def _override_attribute_helper(strategy, config, attribute: str, default):
"""
Override attributes in the strategy.
Prevalence:
@ -95,30 +99,32 @@ class StrategyResolver(IResolver):
- default (if not None)
"""
if attribute in config:
setattr(self.strategy, attribute, config[attribute])
setattr(strategy, attribute, config[attribute])
logger.info("Override strategy '%s' with value in config file: %s.",
attribute, config[attribute])
elif hasattr(self.strategy, attribute):
val = getattr(self.strategy, attribute)
elif hasattr(strategy, attribute):
val = getattr(strategy, attribute)
# None's cannot exist in the config, so do not copy them
if val is not None:
config[attribute] = val
# Explicitly check for None here as other "falsy" values are possible
elif default is not None:
setattr(self.strategy, attribute, default)
setattr(strategy, attribute, default)
config[attribute] = default
def _strategy_sanity_validations(self):
if not all(k in self.strategy.order_types for k in constants.REQUIRED_ORDERTYPES):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
@staticmethod
def _strategy_sanity_validations(strategy):
if not all(k in strategy.order_types for k in constants.REQUIRED_ORDERTYPES):
raise ImportError(f"Impossible to load Strategy '{strategy.__class__.__name__}'. "
f"Order-types mapping is incomplete.")
if not all(k in self.strategy.order_time_in_force for k in constants.REQUIRED_ORDERTIF):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
if not all(k in strategy.order_time_in_force for k in constants.REQUIRED_ORDERTIF):
raise ImportError(f"Impossible to load Strategy '{strategy.__class__.__name__}'. "
f"Order-time-in-force mapping is incomplete.")
def _load_strategy(
self, strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy:
@staticmethod
def _load_strategy(strategy_name: str,
config: dict, extra_dir: Optional[str] = None) -> IStrategy:
"""
Search and loads the specified strategy.
:param strategy_name: name of the module to import
@ -128,9 +134,9 @@ class StrategyResolver(IResolver):
"""
current_path = Path(__file__).parent.parent.joinpath('strategy').resolve()
abs_paths = self.build_search_paths(config, current_path=current_path,
user_subdir=constants.USERPATH_STRATEGY,
extra_dir=extra_dir)
abs_paths = IResolver.build_search_paths(config, current_path=current_path,
user_subdir=constants.USERPATH_STRATEGY,
extra_dir=extra_dir)
if ":" in strategy_name:
logger.info("loading base64 encoded strategy")
@ -148,8 +154,8 @@ class StrategyResolver(IResolver):
# register temp path with the bot
abs_paths.insert(0, temp.resolve())
strategy = self._load_object(paths=abs_paths, object_type=IStrategy,
object_name=strategy_name, kwargs={'config': config})
strategy = IResolver._load_object(paths=abs_paths, object_type=IStrategy,
object_name=strategy_name, kwargs={'config': config})
if strategy:
strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)

View File

@ -341,13 +341,14 @@ class RPC:
raise RPCException('All balances are zero.')
symbol = fiat_display_currency
value = self._fiat_converter.convert_amount(total, 'BTC',
value = self._fiat_converter.convert_amount(total, stake_currency,
symbol) if self._fiat_converter else 0
return {
'currencies': output,
'total': total,
'symbol': symbol,
'value': value,
'stake': stake_currency,
'note': 'Simulated balances' if self._freqtrade.config.get('dry_run', False) else ''
}

View File

@ -335,7 +335,7 @@ class Telegram(RPC):
output = ''
if self._config['dry_run']:
output += (
f"*Warning:*Simulated balances in Dry Mode.\n"
f"*Warning:* Simulated balances in Dry Mode.\n"
"This mode is still experimental!\n"
"Starting capital: "
f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n"
@ -358,7 +358,7 @@ class Telegram(RPC):
output += curr_output
output += "\n*Estimated Value*:\n" \
"\t`BTC: {total: .8f}`\n" \
"\t`{stake}: {total: .8f}`\n" \
"\t`{symbol}: {value: .2f}`\n".format(**result)
self._send_msg(output)
except RPCException as e:

View File

@ -168,11 +168,24 @@ class IStrategy(ABC):
"""
Locks pair until a given timestamp happens.
Locked pairs are not analyzed, and are prevented from opening new trades.
Locks can only count up (allowing users to lock pairs for a longer period of time).
To remove a lock from a pair, use `unlock_pair()`
:param pair: Pair to lock
:param until: datetime in UTC until the pair should be blocked from opening new trades.
Needs to be timezone aware `datetime.now(timezone.utc)`
"""
self._pair_locked_until[pair] = until
if pair not in self._pair_locked_until or self._pair_locked_until[pair] < until:
self._pair_locked_until[pair] = until
def unlock_pair(self, pair) -> None:
"""
Unlocks a pair previously locked using lock_pair.
Not used by freqtrade itself, but intended to be used if users lock pairs
manually from within the strategy, to allow an easy way to unlock pairs.
:param pair: Unlock pair to allow trading again
"""
if pair in self._pair_locked_until:
del self._pair_locked_until[pair]
def is_pair_locked(self, pair: str) -> bool:
"""

View File

@ -73,9 +73,9 @@
"source": [
"# Load strategy using values set above\n",
"from freqtrade.resolvers import StrategyResolver\n",
"strategy = StrategyResolver({'strategy': strategy_name,\n",
" 'user_data_dir': user_data_dir,\n",
" 'strategy_path': strategy_location}).strategy\n",
"strategy = StrategyResolver.load_strategy({'strategy': strategy_name,\n",
" 'user_data_dir': user_data_dir,\n",
" 'strategy_path': strategy_location})\n",
"\n",
"# Generate buy/sell signals using strategy\n",
"df = strategy.analyze_ticker(candles, {'pair': pair})\n",

View File

@ -191,29 +191,28 @@ def start_download_data(args: Dict[str, Any]) -> None:
"Downloading data requires a list of pairs. "
"Please check the documentation on how to configure this.")
dl_path = Path(config['datadir'])
logger.info(f'About to download pairs: {config["pairs"]}, '
f'intervals: {config["timeframes"]} to {dl_path}')
f'intervals: {config["timeframes"]} to {config["datadir"]}')
pairs_not_available: List[str] = []
# Init exchange
exchange = ExchangeResolver(config['exchange']['name'], config).exchange
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
try:
if config.get('download_trades'):
pairs_not_available = refresh_backtest_trades_data(
exchange, pairs=config["pairs"], datadir=Path(config['datadir']),
exchange, pairs=config["pairs"], datadir=config['datadir'],
timerange=timerange, erase=config.get("erase"))
# Convert downloaded trade data to different timeframes
convert_trades_to_ohlcv(
pairs=config["pairs"], timeframes=config["timeframes"],
datadir=Path(config['datadir']), timerange=timerange, erase=config.get("erase"))
datadir=config['datadir'], timerange=timerange, erase=config.get("erase"))
else:
pairs_not_available = refresh_backtest_ohlcv_data(
exchange, pairs=config["pairs"], timeframes=config["timeframes"],
datadir=Path(config['datadir']), timerange=timerange, erase=config.get("erase"))
datadir=config['datadir'], timerange=timerange, erase=config.get("erase"))
except KeyboardInterrupt:
sys.exit("SIGINT received, aborting ...")
@ -233,7 +232,7 @@ def start_list_timeframes(args: Dict[str, Any]) -> None:
config['ticker_interval'] = None
# Init exchange
exchange = ExchangeResolver(config['exchange']['name'], config, validate=False).exchange
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
if args['print_one_column']:
print('\n'.join(exchange.timeframes))
@ -252,7 +251,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
# Init exchange
exchange = ExchangeResolver(config['exchange']['name'], config, validate=False).exchange
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
# By default only active pairs/markets are to be shown
active_only = not args.get('list_pairs_all', False)
@ -333,7 +332,7 @@ def start_test_pairlist(args: Dict[str, Any]) -> None:
from freqtrade.pairlist.pairlistmanager import PairListManager
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
exchange = ExchangeResolver(config['exchange']['name'], config, validate=False).exchange
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
quote_currencies = args.get('quote_currencies')
if not quote_currencies:

View File

@ -58,13 +58,15 @@ class Wallets:
- Subtract currently tied up stake_amount in open trades
- update balances for currencies currently in trades
"""
# Recreate _wallets to reset closed trade balances
_wallets = {}
closed_trades = Trade.get_trades(Trade.is_open.is_(False)).all()
open_trades = Trade.get_trades(Trade.is_open.is_(True)).all()
tot_profit = sum([trade.calc_profit() for trade in closed_trades])
tot_in_trades = sum([trade.stake_amount for trade in open_trades])
current_stake = self.start_cap + tot_profit - tot_in_trades
self._wallets[self._config['stake_currency']] = Wallet(
_wallets[self._config['stake_currency']] = Wallet(
self._config['stake_currency'],
current_stake,
0,
@ -73,12 +75,13 @@ class Wallets:
for trade in open_trades:
curr = trade.pair.split('/')[0]
self._wallets[curr] = Wallet(
_wallets[curr] = Wallet(
curr,
trade.amount,
0,
trade.amount
)
self._wallets = _wallets
def _update_live(self) -> None:

View File

@ -1,7 +1,7 @@
# requirements without requirements installable via conda
# mainly used for Raspberry pi installs
ccxt==1.20.84
SQLAlchemy==1.3.11
ccxt==1.21.12
SQLAlchemy==1.3.12
python-telegram-bot==12.2.0
arrow==0.15.4
cachetools==4.0.0

View File

@ -7,7 +7,7 @@ coveralls==1.9.2
flake8==3.7.9
flake8-type-annotations==0.1.0
flake8-tidy-imports==3.1.0
mypy==0.750
mypy==0.761
pytest==5.3.2
pytest-asyncio==0.10.0
pytest-cov==2.8.1

View File

@ -2,7 +2,7 @@
-r requirements.txt
# Required for hyperopt
scipy==1.3.3
scipy==1.4.1
scikit-learn==0.22
scikit-optimize==0.5.2
filelock==3.0.12

View File

@ -1,5 +1,5 @@
# Load common requirements
-r requirements-common.txt
numpy==1.17.4
numpy==1.18.0
pandas==0.25.3

View File

@ -59,7 +59,7 @@ setup(name='freqtrade',
license='GPLv3',
packages=['freqtrade'],
setup_requires=['pytest-runner', 'numpy'],
tests_require=['pytest', 'pytest-mock', 'pytest-cov'],
tests_require=['pytest', 'pytest-asyncio', 'pytest-cov', 'pytest-mock', ],
install_requires=[
# from requirements-common.txt
'ccxt>=1.18.1080',
@ -99,8 +99,12 @@ setup(name='freqtrade',
],
},
classifiers=[
'Programming Language :: Python :: 3.6',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Topic :: Office/Business :: Financial :: Investment',
'Environment :: Console',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Operating System :: MacOS',
'Operating System :: Unix',
'Topic :: Office/Business :: Financial :: Investment',
])

View File

@ -77,7 +77,7 @@ def get_patched_exchange(mocker, config, api_mock=None, id='bittrex',
patch_exchange(mocker, api_mock, id, mock_markets)
config["exchange"]["name"] = id
try:
exchange = ExchangeResolver(id, config).exchange
exchange = ExchangeResolver.load_exchange(id, config)
except ImportError:
exchange = Exchange(config)
return exchange

View File

@ -124,19 +124,19 @@ def test_exchange_resolver(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
exchange = ExchangeResolver('Bittrex', default_conf).exchange
exchange = ExchangeResolver.load_exchange('Bittrex', default_conf)
assert isinstance(exchange, Exchange)
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
caplog.clear()
exchange = ExchangeResolver('kraken', default_conf).exchange
exchange = ExchangeResolver.load_exchange('kraken', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Kraken)
assert not isinstance(exchange, Binance)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
caplog)
exchange = ExchangeResolver('binance', default_conf).exchange
exchange = ExchangeResolver.load_exchange('binance', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Binance)
assert not isinstance(exchange, Kraken)
@ -145,7 +145,7 @@ def test_exchange_resolver(default_conf, mocker, caplog):
caplog)
# Test mapping
exchange = ExchangeResolver('binanceus', default_conf).exchange
exchange = ExchangeResolver.load_exchange('binanceus', default_conf)
assert isinstance(exchange, Exchange)
assert isinstance(exchange, Binance)
assert not isinstance(exchange, Kraken)
@ -363,8 +363,9 @@ def test_validate_pairs_exception(default_conf, mocker, caplog):
def test_validate_pairs_restricted(default_conf, mocker, caplog):
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {}, 'LTC/BTC': {}, 'NEO/BTC': {},
'XRP/BTC': {'info': {'IsRestricted': True}}
'ETH/BTC': {}, 'LTC/BTC': {},
'XRP/BTC': {'info': {'IsRestricted': True}},
'NEO/BTC': {'info': 'TestString'}, # info can also be a string ...
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
@ -977,7 +978,7 @@ def test_get_tickers(default_conf, mocker, exchange_name):
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_ticker(default_conf, mocker, exchange_name):
def test_fetch_ticker(default_conf, mocker, exchange_name):
api_mock = MagicMock()
tick = {
'symbol': 'ETH/BTC',
@ -989,7 +990,7 @@ def test_get_ticker(default_conf, mocker, exchange_name):
api_mock.markets = {'ETH/BTC': {'active': True}}
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
# retrieve original ticker
ticker = exchange.get_ticker(pair='ETH/BTC')
ticker = exchange.fetch_ticker(pair='ETH/BTC')
assert ticker['bid'] == 0.00001098
assert ticker['ask'] == 0.00001099
@ -1006,7 +1007,7 @@ def test_get_ticker(default_conf, mocker, exchange_name):
# if not caching the result we should get the same ticker
# if not fetching a new result we should get the cached ticker
ticker = exchange.get_ticker(pair='ETH/BTC')
ticker = exchange.fetch_ticker(pair='ETH/BTC')
assert api_mock.fetch_ticker.call_count == 1
assert ticker['bid'] == 0.5
@ -1018,19 +1019,19 @@ def test_get_ticker(default_conf, mocker, exchange_name):
# Test caching
api_mock.fetch_ticker = MagicMock()
exchange.get_ticker(pair='ETH/BTC', refresh=False)
exchange.fetch_ticker(pair='ETH/BTC', refresh=False)
assert api_mock.fetch_ticker.call_count == 0
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
"get_ticker", "fetch_ticker",
"fetch_ticker", "fetch_ticker",
pair='ETH/BTC', refresh=True)
api_mock.fetch_ticker = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_ticker(pair='ETH/BTC', refresh=True)
exchange.fetch_ticker(pair='ETH/BTC', refresh=True)
with pytest.raises(DependencyException, match=r'Pair XRP/ETH not available'):
exchange.get_ticker(pair='XRP/ETH', refresh=True)
exchange.fetch_ticker(pair='XRP/ETH', refresh=True)
@pytest.mark.parametrize("exchange_name", EXCHANGES)

View File

@ -394,8 +394,8 @@ def test_generate_text_table_sell_reason(default_conf, mocker):
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, 0.3],
'profit_abs': [0.2, 0.4, 0.5],
'profit_percent': [0.1, 0.2, -0.3],
'profit_abs': [0.2, 0.4, -0.5],
'trade_duration': [10, 30, 10],
'profit': [2, 0, 0],
'loss': [0, 0, 1],
@ -404,10 +404,10 @@ def test_generate_text_table_sell_reason(default_conf, mocker):
)
result_str = (
'| Sell Reason | Count |\n'
'|:--------------|--------:|\n'
'| roi | 2 |\n'
'| stop_loss | 1 |'
'| Sell Reason | Count | Profit | Loss |\n'
'|:--------------|--------:|---------:|-------:|\n'
'| roi | 2 | 2 | 0 |\n'
'| stop_loss | 1 | 0 | 1 |'
)
assert backtesting._generate_text_table_sell_reason(
data={'ETH/BTC': {}}, results=results) == result_str

View File

@ -163,7 +163,7 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
MagicMock(return_value=hyperopt(default_conf))
)
default_conf.update({'hyperopt': 'DefaultHyperOpt'})
x = HyperOptResolver(default_conf).hyperopt
x = HyperOptResolver.load_hyperopt(default_conf)
assert not hasattr(x, 'populate_indicators')
assert not hasattr(x, 'populate_buy_trend')
assert not hasattr(x, 'populate_sell_trend')
@ -180,7 +180,7 @@ def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None:
default_conf.update({'hyperopt': "NonExistingHyperoptClass"})
with pytest.raises(OperationalException, match=r'Impossible to load Hyperopt.*'):
HyperOptResolver(default_conf).hyperopt
HyperOptResolver.load_hyperopt(default_conf)
def test_hyperoptresolver_noname(default_conf):
@ -188,7 +188,7 @@ def test_hyperoptresolver_noname(default_conf):
with pytest.raises(OperationalException,
match="No Hyperopt set. Please use `--hyperopt` to specify "
"the Hyperopt class to use."):
HyperOptResolver(default_conf)
HyperOptResolver.load_hyperopt(default_conf)
def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None:
@ -198,7 +198,7 @@ def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None:
'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver._load_hyperoptloss',
MagicMock(return_value=hl)
)
x = HyperOptLossResolver(default_conf).hyperoptloss
x = HyperOptLossResolver.load_hyperoptloss(default_conf)
assert hasattr(x, "hyperopt_loss_function")
@ -206,7 +206,7 @@ def test_hyperoptlossresolver_wrongname(mocker, default_conf, caplog) -> None:
default_conf.update({'hyperopt_loss': "NonExistingLossClass"})
with pytest.raises(OperationalException, match=r'Impossible to load HyperoptLoss.*'):
HyperOptLossResolver(default_conf).hyperopt
HyperOptLossResolver.load_hyperoptloss(default_conf)
def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None:
@ -286,7 +286,7 @@ def test_start_filelock(mocker, default_conf, caplog) -> None:
def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_results) -> None:
hl = HyperOptLossResolver(default_conf).hyperoptloss
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, 600)
over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100)
under = hl.hyperopt_loss_function(hyperopt_results, 600 - 100)
@ -298,7 +298,7 @@ def test_loss_calculation_prefer_shorter_trades(default_conf, hyperopt_results)
resultsb = hyperopt_results.copy()
resultsb.loc[1, 'trade_duration'] = 20
hl = HyperOptLossResolver(default_conf).hyperoptloss
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
longer = hl.hyperopt_loss_function(hyperopt_results, 100)
shorter = hl.hyperopt_loss_function(resultsb, 100)
assert shorter < longer
@ -310,7 +310,7 @@ def test_loss_calculation_has_limited_profit(default_conf, hyperopt_results) ->
results_under = hyperopt_results.copy()
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
hl = HyperOptLossResolver(default_conf).hyperoptloss
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, 600)
over = hl.hyperopt_loss_function(results_over, 600)
under = hl.hyperopt_loss_function(results_under, 600)
@ -325,7 +325,7 @@ def test_sharpe_loss_prefers_higher_profits(default_conf, hyperopt_results) -> N
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
default_conf.update({'hyperopt_loss': 'SharpeHyperOptLoss'})
hl = HyperOptLossResolver(default_conf).hyperoptloss
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, len(hyperopt_results),
datetime(2019, 1, 1), datetime(2019, 5, 1))
over = hl.hyperopt_loss_function(results_over, len(hyperopt_results),
@ -343,7 +343,7 @@ def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results)
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
default_conf.update({'hyperopt_loss': 'OnlyProfitHyperOptLoss'})
hl = HyperOptLossResolver(default_conf).hyperoptloss
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
correct = hl.hyperopt_loss_function(hyperopt_results, len(hyperopt_results),
datetime(2019, 1, 1), datetime(2019, 5, 1))
over = hl.hyperopt_loss_function(results_over, len(hyperopt_results),

View File

@ -53,7 +53,8 @@ def test_load_pairlist_noexist(mocker, markets, default_conf):
with pytest.raises(OperationalException,
match=r"Impossible to load Pairlist 'NonexistingPairList'. "
r"This class does not exist or contains Python code errors."):
PairListResolver('NonexistingPairList', bot.exchange, plm, default_conf, {}, 1)
PairListResolver.load_pairlist('NonexistingPairList', bot.exchange, plm,
default_conf, {}, 1)
def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf):

View File

@ -29,7 +29,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
@ -65,7 +65,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_order': '(limit buy rem=0.00000000)'
} == results[0]
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
# invalidate ticker cache
rpc._freqtrade.exchange._cached_ticker = {}
@ -104,7 +104,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
@ -134,7 +134,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
assert 'ETH/BTC' == result[0][1]
assert '-0.59% (-0.09)' == result[0][3]
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
# invalidate ticker cache
rpc._freqtrade.exchange._cached_ticker = {}
@ -149,7 +149,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
@ -201,7 +201,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
@ -225,7 +225,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_up
fetch_ticker=ticker_sell_up
)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
@ -239,7 +239,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_up
fetch_ticker=ticker_sell_up
)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
@ -260,7 +260,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
assert prec_satoshi(stats['best_rate'], 6.2)
# Test non-available pair
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
# invalidate ticker cache
rpc._freqtrade.exchange._cached_ticker = {}
@ -287,7 +287,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
@ -306,7 +306,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_up,
fetch_ticker=ticker_sell_up,
get_fee=fee
)
trade.update(limit_sell_order)
@ -439,7 +439,7 @@ def test_rpc_start(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock()
fetch_ticker=MagicMock()
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@ -460,7 +460,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock()
fetch_ticker=MagicMock()
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@ -482,7 +482,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock()
fetch_ticker=MagicMock()
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@ -502,7 +502,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
cancel_order_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
cancel_order=cancel_order_mock,
get_order=MagicMock(
return_value={
@ -604,7 +604,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
@ -637,7 +637,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
@ -661,7 +661,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
buy=buy_mm
)

View File

@ -256,7 +256,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
@ -292,7 +292,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
@ -308,7 +308,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
@ -323,7 +323,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
@ -413,7 +413,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
@ -541,7 +541,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets)
)

View File

@ -150,7 +150,7 @@ def test_status(default_conf, update, mocker, fee, ticker,) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
msg_mock = MagicMock()
@ -204,7 +204,7 @@ def test_status(default_conf, update, mocker, fee, ticker,) -> None:
def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
msg_mock = MagicMock()
@ -254,7 +254,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
get_fee=fee,
)
@ -307,7 +307,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
msg_mock = MagicMock()
@ -373,7 +373,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker
fetch_ticker=ticker
)
msg_mock = MagicMock()
mocker.patch.multiple(
@ -411,7 +411,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
msg_mock = MagicMock()
@ -443,7 +443,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
msg_mock.reset_mock()
# Update the ticker with a market going up
mocker.patch('freqtrade.exchange.Exchange.get_ticker', ticker_sell_up)
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
@ -534,7 +534,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None
telegram._balance(update=update, context=MagicMock())
result = msg_mock.call_args_list[0][0][0]
assert msg_mock.call_count == 1
assert "*Warning:*Simulated balances in Dry Mode." in result
assert "*Warning:* Simulated balances in Dry Mode." in result
assert "Starting capital: `1000` BTC" in result
@ -700,7 +700,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
patch_whitelist(mocker, default_conf)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
@ -715,7 +715,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
assert trade
# Increase the price and sell it
mocker.patch('freqtrade.exchange.Exchange.get_ticker', ticker_sell_up)
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up)
# /forcesell 1
context = MagicMock()
@ -755,7 +755,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
@ -769,7 +769,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_down
fetch_ticker=ticker_sell_down
)
trade = Trade.query.first()
@ -812,7 +812,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
patch_whitelist(mocker, default_conf)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
default_conf['max_open_trades'] = 4
@ -963,7 +963,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@ -998,7 +998,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
get_fee=fee,
)

View File

@ -302,6 +302,19 @@ def test_is_pair_locked(default_conf):
# ETH/BTC locked for 4 minutes
assert strategy.is_pair_locked(pair)
# Test lock does not change
lock = strategy._pair_locked_until[pair]
strategy.lock_pair(pair, arrow.utcnow().shift(minutes=2).datetime)
assert lock == strategy._pair_locked_until[pair]
# XRP/BTC should not be locked now
pair = 'XRP/BTC'
assert not strategy.is_pair_locked(pair)
# Unlocking a pair that's not locked should not raise an error
strategy.unlock_pair(pair)
# Unlock original pair
pair = 'ETH/BTC'
strategy.unlock_pair(pair)
assert not strategy.is_pair_locked(pair)

View File

@ -39,8 +39,8 @@ def test_load_strategy(default_conf, result):
default_conf.update({'strategy': 'SampleStrategy',
'strategy_path': str(Path(__file__).parents[2] / 'freqtrade/templates')
})
resolver = StrategyResolver(default_conf)
assert 'rsi' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
strategy = StrategyResolver.load_strategy(default_conf)
assert 'rsi' in strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
def test_load_strategy_base64(result, caplog, default_conf):
@ -48,8 +48,8 @@ def test_load_strategy_base64(result, caplog, default_conf):
encoded_string = urlsafe_b64encode(file.read()).decode("utf-8")
default_conf.update({'strategy': 'SampleStrategy:{}'.format(encoded_string)})
resolver = StrategyResolver(default_conf)
assert 'rsi' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
strategy = StrategyResolver.load_strategy(default_conf)
assert 'rsi' in strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
# Make sure strategy was loaded from base64 (using temp directory)!!
assert log_has_re(r"Using resolved strategy SampleStrategy from '"
r".*(/|\\).*(/|\\)SampleStrategy\.py'\.\.\.", caplog)
@ -57,13 +57,13 @@ def test_load_strategy_base64(result, caplog, default_conf):
def test_load_strategy_invalid_directory(result, caplog, default_conf):
default_conf['strategy'] = 'DefaultStrategy'
resolver = StrategyResolver(default_conf)
extra_dir = Path.cwd() / 'some/path'
resolver._load_strategy('DefaultStrategy', config=default_conf, extra_dir=extra_dir)
strategy = StrategyResolver._load_strategy('DefaultStrategy', config=default_conf,
extra_dir=extra_dir)
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
assert 'rsi' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
assert 'rsi' in strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
def test_load_not_found_strategy(default_conf):
@ -71,7 +71,7 @@ def test_load_not_found_strategy(default_conf):
with pytest.raises(OperationalException,
match=r"Impossible to load Strategy 'NotFoundStrategy'. "
r"This class does not exist or contains Python code errors."):
StrategyResolver(default_conf)
StrategyResolver.load_strategy(default_conf)
def test_load_strategy_noname(default_conf):
@ -79,30 +79,30 @@ def test_load_strategy_noname(default_conf):
with pytest.raises(OperationalException,
match="No strategy set. Please use `--strategy` to specify "
"the strategy class to use."):
StrategyResolver(default_conf)
StrategyResolver.load_strategy(default_conf)
def test_strategy(result, default_conf):
default_conf.update({'strategy': 'DefaultStrategy'})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
metadata = {'pair': 'ETH/BTC'}
assert resolver.strategy.minimal_roi[0] == 0.04
assert strategy.minimal_roi[0] == 0.04
assert default_conf["minimal_roi"]['0'] == 0.04
assert resolver.strategy.stoploss == -0.10
assert strategy.stoploss == -0.10
assert default_conf['stoploss'] == -0.10
assert resolver.strategy.ticker_interval == '5m'
assert strategy.ticker_interval == '5m'
assert default_conf['ticker_interval'] == '5m'
df_indicators = resolver.strategy.advise_indicators(result, metadata=metadata)
df_indicators = strategy.advise_indicators(result, metadata=metadata)
assert 'adx' in df_indicators
dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata)
dataframe = strategy.advise_buy(df_indicators, metadata=metadata)
assert 'buy' in dataframe.columns
dataframe = resolver.strategy.advise_sell(df_indicators, metadata=metadata)
dataframe = strategy.advise_sell(df_indicators, metadata=metadata)
assert 'sell' in dataframe.columns
@ -114,9 +114,9 @@ def test_strategy_override_minimal_roi(caplog, default_conf):
"0": 0.5
}
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert resolver.strategy.minimal_roi[0] == 0.5
assert strategy.minimal_roi[0] == 0.5
assert log_has("Override strategy 'minimal_roi' with value in config file: {'0': 0.5}.", caplog)
@ -126,9 +126,9 @@ def test_strategy_override_stoploss(caplog, default_conf):
'strategy': 'DefaultStrategy',
'stoploss': -0.5
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert resolver.strategy.stoploss == -0.5
assert strategy.stoploss == -0.5
assert log_has("Override strategy 'stoploss' with value in config file: -0.5.", caplog)
@ -138,10 +138,10 @@ def test_strategy_override_trailing_stop(caplog, default_conf):
'strategy': 'DefaultStrategy',
'trailing_stop': True
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert resolver.strategy.trailing_stop
assert isinstance(resolver.strategy.trailing_stop, bool)
assert strategy.trailing_stop
assert isinstance(strategy.trailing_stop, bool)
assert log_has("Override strategy 'trailing_stop' with value in config file: True.", caplog)
@ -153,13 +153,13 @@ def test_strategy_override_trailing_stop_positive(caplog, default_conf):
'trailing_stop_positive_offset': -0.2
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert resolver.strategy.trailing_stop_positive == -0.1
assert strategy.trailing_stop_positive == -0.1
assert log_has("Override strategy 'trailing_stop_positive' with value in config file: -0.1.",
caplog)
assert resolver.strategy.trailing_stop_positive_offset == -0.2
assert strategy.trailing_stop_positive_offset == -0.2
assert log_has("Override strategy 'trailing_stop_positive' with value in config file: -0.1.",
caplog)
@ -172,10 +172,10 @@ def test_strategy_override_ticker_interval(caplog, default_conf):
'ticker_interval': 60,
'stake_currency': 'ETH'
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert resolver.strategy.ticker_interval == 60
assert resolver.strategy.stake_currency == 'ETH'
assert strategy.ticker_interval == 60
assert strategy.stake_currency == 'ETH'
assert log_has("Override strategy 'ticker_interval' with value in config file: 60.",
caplog)
@ -187,9 +187,9 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf):
'strategy': 'DefaultStrategy',
'process_only_new_candles': True
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert resolver.strategy.process_only_new_candles
assert strategy.process_only_new_candles
assert log_has("Override strategy 'process_only_new_candles' with value in config file: True.",
caplog)
@ -207,11 +207,11 @@ def test_strategy_override_order_types(caplog, default_conf):
'strategy': 'DefaultStrategy',
'order_types': order_types
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert resolver.strategy.order_types
assert strategy.order_types
for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']:
assert resolver.strategy.order_types[method] == order_types[method]
assert strategy.order_types[method] == order_types[method]
assert log_has("Override strategy 'order_types' with value in config file:"
" {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit',"
@ -225,7 +225,7 @@ def test_strategy_override_order_types(caplog, default_conf):
with pytest.raises(ImportError,
match=r"Impossible to load Strategy 'DefaultStrategy'. "
r"Order-types mapping is incomplete."):
StrategyResolver(default_conf)
StrategyResolver.load_strategy(default_conf)
def test_strategy_override_order_tif(caplog, default_conf):
@ -240,11 +240,11 @@ def test_strategy_override_order_tif(caplog, default_conf):
'strategy': 'DefaultStrategy',
'order_time_in_force': order_time_in_force
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert resolver.strategy.order_time_in_force
assert strategy.order_time_in_force
for method in ['buy', 'sell']:
assert resolver.strategy.order_time_in_force[method] == order_time_in_force[method]
assert strategy.order_time_in_force[method] == order_time_in_force[method]
assert log_has("Override strategy 'order_time_in_force' with value in config file:"
" {'buy': 'fok', 'sell': 'gtc'}.", caplog)
@ -257,7 +257,7 @@ def test_strategy_override_order_tif(caplog, default_conf):
with pytest.raises(ImportError,
match=r"Impossible to load Strategy 'DefaultStrategy'. "
r"Order-time-in-force mapping is incomplete."):
StrategyResolver(default_conf)
StrategyResolver.load_strategy(default_conf)
def test_strategy_override_use_sell_signal(caplog, default_conf):
@ -265,9 +265,9 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
default_conf.update({
'strategy': 'DefaultStrategy',
})
resolver = StrategyResolver(default_conf)
assert resolver.strategy.use_sell_signal
assert isinstance(resolver.strategy.use_sell_signal, bool)
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.use_sell_signal
assert isinstance(strategy.use_sell_signal, bool)
# must be inserted to configuration
assert 'use_sell_signal' in default_conf['ask_strategy']
assert default_conf['ask_strategy']['use_sell_signal']
@ -278,10 +278,10 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
'use_sell_signal': False,
},
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert not resolver.strategy.use_sell_signal
assert isinstance(resolver.strategy.use_sell_signal, bool)
assert not strategy.use_sell_signal
assert isinstance(strategy.use_sell_signal, bool)
assert log_has("Override strategy 'use_sell_signal' with value in config file: False.", caplog)
@ -290,9 +290,9 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
default_conf.update({
'strategy': 'DefaultStrategy',
})
resolver = StrategyResolver(default_conf)
assert not resolver.strategy.sell_profit_only
assert isinstance(resolver.strategy.sell_profit_only, bool)
strategy = StrategyResolver.load_strategy(default_conf)
assert not strategy.sell_profit_only
assert isinstance(strategy.sell_profit_only, bool)
# must be inserted to configuration
assert 'sell_profit_only' in default_conf['ask_strategy']
assert not default_conf['ask_strategy']['sell_profit_only']
@ -303,10 +303,10 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
'sell_profit_only': True,
},
})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
assert resolver.strategy.sell_profit_only
assert isinstance(resolver.strategy.sell_profit_only, bool)
assert strategy.sell_profit_only
assert isinstance(strategy.sell_profit_only, bool)
assert log_has("Override strategy 'sell_profit_only' with value in config file: True.", caplog)
@ -315,11 +315,11 @@ def test_deprecate_populate_indicators(result, default_conf):
default_location = path.join(path.dirname(path.realpath(__file__)))
default_conf.update({'strategy': 'TestStrategyLegacy',
'strategy_path': default_location})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
indicators = resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
indicators = strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
@ -328,7 +328,7 @@ def test_deprecate_populate_indicators(result, default_conf):
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
resolver.strategy.advise_buy(indicators, {'pair': 'ETH/BTC'})
strategy.advise_buy(indicators, {'pair': 'ETH/BTC'})
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
@ -337,7 +337,7 @@ def test_deprecate_populate_indicators(result, default_conf):
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
resolver.strategy.advise_sell(indicators, {'pair': 'ETH_BTC'})
strategy.advise_sell(indicators, {'pair': 'ETH_BTC'})
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated - check out the Sample strategy to see the current function headers!" \
@ -349,47 +349,47 @@ def test_call_deprecated_function(result, monkeypatch, default_conf):
default_location = path.join(path.dirname(path.realpath(__file__)))
default_conf.update({'strategy': 'TestStrategyLegacy',
'strategy_path': default_location})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
metadata = {'pair': 'ETH/BTC'}
# Make sure we are using a legacy function
assert resolver.strategy._populate_fun_len == 2
assert resolver.strategy._buy_fun_len == 2
assert resolver.strategy._sell_fun_len == 2
assert resolver.strategy.INTERFACE_VERSION == 1
assert strategy._populate_fun_len == 2
assert strategy._buy_fun_len == 2
assert strategy._sell_fun_len == 2
assert strategy.INTERFACE_VERSION == 1
indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata)
indicator_df = strategy.advise_indicators(result, metadata=metadata)
assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns
buydf = resolver.strategy.advise_buy(result, metadata=metadata)
buydf = strategy.advise_buy(result, metadata=metadata)
assert isinstance(buydf, DataFrame)
assert 'buy' in buydf.columns
selldf = resolver.strategy.advise_sell(result, metadata=metadata)
selldf = strategy.advise_sell(result, metadata=metadata)
assert isinstance(selldf, DataFrame)
assert 'sell' in selldf
def test_strategy_interface_versioning(result, monkeypatch, default_conf):
default_conf.update({'strategy': 'DefaultStrategy'})
resolver = StrategyResolver(default_conf)
strategy = StrategyResolver.load_strategy(default_conf)
metadata = {'pair': 'ETH/BTC'}
# Make sure we are using a legacy function
assert resolver.strategy._populate_fun_len == 3
assert resolver.strategy._buy_fun_len == 3
assert resolver.strategy._sell_fun_len == 3
assert resolver.strategy.INTERFACE_VERSION == 2
assert strategy._populate_fun_len == 3
assert strategy._buy_fun_len == 3
assert strategy._sell_fun_len == 3
assert strategy.INTERFACE_VERSION == 2
indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata)
indicator_df = strategy.advise_indicators(result, metadata=metadata)
assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns
buydf = resolver.strategy.advise_buy(result, metadata=metadata)
buydf = strategy.advise_buy(result, metadata=metadata)
assert isinstance(buydf, DataFrame)
assert 'buy' in buydf.columns
selldf = resolver.strategy.advise_sell(result, metadata=metadata)
selldf = strategy.advise_sell(result, metadata=metadata)
assert isinstance(selldf, DataFrame)
assert 'sell' in selldf

View File

@ -977,7 +977,7 @@ def test_pairlist_resolving_fallback(mocker):
assert config['pairs'] == ['ETH/BTC', 'XRP/BTC']
assert config['exchange']['name'] == 'binance'
assert config['datadir'] == str(Path.cwd() / "user_data/data/binance")
assert config['datadir'] == Path.cwd() / "user_data/data/binance"
@pytest.mark.parametrize("setting", [

View File

@ -157,7 +157,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker,
patch_wallet(mocker, free=default_conf['stake_amount'])
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee
)
@ -232,7 +232,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf
buy_price = limit_buy_order['price']
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': buy_price * 0.79,
'ask': buy_price * 0.79,
'last': buy_price * 0.79
@ -272,7 +272,7 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee,
buy_price = limit_buy_order['price']
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': buy_price * 0.85,
'ask': buy_price * 0.85,
'last': buy_price * 0.85
@ -304,7 +304,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker,
default_conf['max_open_trades'] = 2
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -467,7 +467,7 @@ def test_create_trades(default_conf, ticker, limit_buy_order, fee, mocker) -> No
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -501,7 +501,7 @@ def test_create_trades_no_stake_amount(default_conf, ticker, limit_buy_order,
patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -519,7 +519,7 @@ def test_create_trades_minimal_amount(default_conf, ticker, limit_buy_order,
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=buy_mock,
get_fee=fee,
)
@ -539,7 +539,7 @@ def test_create_trades_too_small_stake_amount(default_conf, ticker, limit_buy_or
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=buy_mock,
get_fee=fee,
)
@ -558,7 +558,7 @@ def test_create_trades_limit_reached(default_conf, ticker, limit_buy_order,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_balance=MagicMock(return_value=default_conf['stake_amount']),
get_fee=fee,
@ -579,7 +579,7 @@ def test_create_trades_no_pairs_let(default_conf, ticker, limit_buy_order, fee,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -600,7 +600,7 @@ def test_create_trades_no_pairs_in_whitelist(default_conf, ticker, limit_buy_ord
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -639,7 +639,7 @@ def test_create_trades_multiple_trades(default_conf, ticker,
default_conf['max_open_trades'] = max_open
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': "12355555"}),
get_fee=fee,
)
@ -658,7 +658,7 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker) -> None:
default_conf['max_open_trades'] = 4
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': "12355555"}),
get_fee=fee,
)
@ -684,7 +684,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_order=MagicMock(return_value=limit_buy_order),
get_fee=fee,
@ -718,7 +718,7 @@ def test_process_exchange_failures(default_conf, ticker, mocker) -> None:
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(side_effect=TemporaryError)
)
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
@ -735,7 +735,7 @@ def test_process_operational_exception(default_conf, ticker, mocker) -> None:
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(side_effect=OperationalException)
)
worker = Worker(args=None, config=default_conf)
@ -753,7 +753,7 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, fee, mock
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_order=MagicMock(return_value=limit_buy_order),
get_fee=fee,
@ -780,7 +780,7 @@ def test_process_trade_no_whitelist_pair(default_conf, ticker, limit_buy_order,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_order=MagicMock(return_value=limit_buy_order),
get_fee=fee,
@ -830,7 +830,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
refresh_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(side_effect=TemporaryError),
refresh_latest_ohlcv=refresh_mock,
)
@ -853,7 +853,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
def test_balance_fully_ask_side(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 0.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={'ask': 20, 'last': 10}))
assert freqtrade.get_target_bid('ETH/BTC') == 20
@ -862,7 +862,7 @@ def test_balance_fully_ask_side(mocker, default_conf) -> None:
def test_balance_fully_last_side(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={'ask': 20, 'last': 10}))
assert freqtrade.get_target_bid('ETH/BTC') == 10
@ -871,7 +871,7 @@ def test_balance_fully_last_side(mocker, default_conf) -> None:
def test_balance_bigger_last_ask(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={'ask': 5, 'last': 10}))
assert freqtrade.get_target_bid('ETH/BTC') == 5
@ -891,7 +891,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None:
buy_mm = MagicMock(return_value={'id': limit_buy_order['id']})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
@ -1000,7 +1000,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
@ -1100,7 +1100,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
@ -1134,7 +1134,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
sell_mock = MagicMock(return_value={'id': limit_sell_order['id']})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
@ -1177,7 +1177,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
@ -1231,7 +1231,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
assert freqtrade.handle_stoploss_on_exchange(trade) is False
# price jumped 2x
mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
'bid': 0.00002344,
'ask': 0.00002346,
'last': 0.00002344
@ -1271,7 +1271,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
@ -1342,7 +1342,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
edge_conf['dry_run_wallet'] = 999.9
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
@ -1406,7 +1406,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_order_mock)
# price goes down 5%
mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
'bid': 0.00001172 * 0.95,
'ask': 0.00001173 * 0.95,
'last': 0.00001172 * 0.95
@ -1422,7 +1422,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
cancel_order_mock.assert_not_called()
# price jumped 2x
mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
'bid': 0.00002344,
'ask': 0.00002346,
'last': 0.00002344
@ -1662,7 +1662,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
@ -1702,7 +1702,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -1755,7 +1755,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order,
patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -1787,7 +1787,7 @@ def test_handle_trade_use_sell_signal(
patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -1815,7 +1815,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -1843,7 +1843,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, op
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_order=MagicMock(return_value=limit_buy_order_old),
cancel_order=cancel_order_mock,
get_fee=fee
@ -1870,7 +1870,7 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, o
limit_buy_order_old.update({"status": "canceled"})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_order=MagicMock(return_value=limit_buy_order_old),
cancel_order=cancel_order_mock,
get_fee=fee
@ -1897,7 +1897,7 @@ def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_ord
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
fetch_ticker=ticker,
get_order=MagicMock(side_effect=DependencyException),
cancel_order=cancel_order_mock,
get_fee=fee
@ -1922,7 +1922,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_order=MagicMock(return_value=limit_sell_order_old),
cancel_order=cancel_order_mock
)
@ -1950,7 +1950,7 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_order=MagicMock(return_value=limit_sell_order_old),
cancel_order=cancel_order_mock
)
@ -1977,7 +1977,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order=cancel_order_mock
)
@ -2004,7 +2004,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order=cancel_order_mock,
get_trades_for_order=MagicMock(return_value=trades_for_order),
@ -2041,7 +2041,7 @@ def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order=cancel_order_mock,
get_trades_for_order=MagicMock(return_value=trades_for_order),
@ -2085,7 +2085,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_order=MagicMock(side_effect=requests.exceptions.RequestException('Oh snap')),
cancel_order=cancel_order_mock
)
@ -2175,7 +2175,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
patch_whitelist(mocker, default_conf)
@ -2191,7 +2191,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_up
fetch_ticker=ticker_sell_up
)
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI)
@ -2223,7 +2223,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
patch_whitelist(mocker, default_conf)
@ -2239,7 +2239,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_down
fetch_ticker=ticker_sell_down
)
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
@ -2273,7 +2273,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
patch_whitelist(mocker, default_conf)
@ -2289,7 +2289,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_down
fetch_ticker=ticker_sell_down
)
default_conf['dry_run'] = True
@ -2332,7 +2332,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
sell=sellmock
)
@ -2369,7 +2369,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
cancel_order = MagicMock(return_value=True)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
symbol_amount_prec=lambda s, x, y: y,
symbol_price_prec=lambda s, x, y: y,
@ -2393,7 +2393,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_up
fetch_ticker=ticker_sell_up
)
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
@ -2412,7 +2412,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
symbol_amount_prec=lambda s, x, y: y,
symbol_price_prec=lambda s, x, y: y,
@ -2476,7 +2476,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
patch_whitelist(mocker, default_conf)
@ -2492,7 +2492,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_up
fetch_ticker=ticker_sell_up
)
freqtrade.config['order_types']['sell'] = 'market'
@ -2530,7 +2530,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00002172,
'ask': 0.00002173,
'last': 0.00002172
@ -2562,7 +2562,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00002172,
'ask': 0.00002173,
'last': 0.00002172
@ -2592,7 +2592,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00000172,
'ask': 0.00000173,
'last': 0.00000172
@ -2621,7 +2621,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocke
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.0000172,
'ask': 0.0000173,
'last': 0.0000172
@ -2654,7 +2654,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00002172,
'ask': 0.00002173,
'last': 0.00002172
@ -2728,7 +2728,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf)
@ -2743,7 +2743,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker_sell_down
fetch_ticker=ticker_sell_down
)
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
@ -2764,7 +2764,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, mocker) ->
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.0000172,
'ask': 0.0000173,
'last': 0.0000172
@ -2798,7 +2798,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001099,
'ask': 0.00001099,
'last': 0.00001099
@ -2817,7 +2817,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker)
assert freqtrade.handle_trade(trade) is False
# Raise ticker above buy price
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
'bid': 0.00001099 * 1.5,
'ask': 0.00001099 * 1.5,
@ -2828,7 +2828,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker)
assert freqtrade.handle_trade(trade) is False
# Price fell
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
'bid': 0.00001099 * 1.1,
'ask': 0.00001099 * 1.1,
@ -2852,7 +2852,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': buy_price - 0.000001,
'ask': buy_price - 0.000001,
'last': buy_price - 0.000001
@ -2876,7 +2876,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee,
assert freqtrade.handle_trade(trade) is False
# Raise ticker above buy price
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
'bid': buy_price + 0.000003,
'ask': buy_price + 0.000003,
@ -2888,7 +2888,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee,
assert log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
assert trade.stop_loss == 0.0000138501
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
'bid': buy_price + 0.000002,
'ask': buy_price + 0.000002,
@ -2909,7 +2909,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': buy_price - 0.000001,
'ask': buy_price - 0.000001,
'last': buy_price - 0.000001
@ -2933,7 +2933,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
assert freqtrade.handle_trade(trade) is False
# Raise ticker above buy price
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
'bid': buy_price + 0.000003,
'ask': buy_price + 0.000003,
@ -2946,7 +2946,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
assert log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
assert trade.stop_loss == 0.0000138501
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
'bid': buy_price + 0.000002,
'ask': buy_price + 0.000002,
@ -2970,7 +2970,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': buy_price,
'ask': buy_price,
'last': buy_price
@ -2997,7 +2997,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee,
assert trade.stop_loss == 0.0000098910
# Raise ticker above buy price
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
'bid': buy_price + 0.0000004,
'ask': buy_price + 0.0000004,
@ -3011,7 +3011,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee,
assert trade.stop_loss == 0.0000098910
# price rises above the offset (rises 12% when the offset is 5.5%)
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
'bid': buy_price + 0.0000014,
'ask': buy_price + 0.0000014,
@ -3031,7 +3031,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order,
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00000172,
'ask': 0.00000173,
'last': 0.00000172
@ -3368,7 +3368,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee,
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -3405,7 +3405,7 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
@ -3428,7 +3428,7 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_order_book=order_book_l2,
get_ticker=ticker_mock,
fetch_ticker=ticker_mock,
)
default_conf['exchange']['name'] = 'binance'
@ -3452,7 +3452,7 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_order_book=order_book_l2,
get_ticker=ticker_mock,
fetch_ticker=ticker_mock,
)
default_conf['exchange']['name'] = 'binance'
@ -3502,7 +3502,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
@ -3533,7 +3533,7 @@ def test_get_sell_rate(default_conf, mocker, ticker, order_book_l2) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_order_book=order_book_l2,
get_ticker=ticker,
fetch_ticker=ticker,
)
pair = "ETH/BTC"
@ -3612,7 +3612,7 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)

View File

@ -55,7 +55,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
mocker.patch('freqtrade.exchange.Binance.stoploss_limit', stoploss_limit)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
symbol_amount_prec=lambda s, x, y: y,
symbol_price_prec=lambda s, x, y: y,
@ -118,15 +118,13 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
default_conf['max_open_trades'] = 5
default_conf['forcebuy_enable'] = True
default_conf['stake_amount'] = 'unlimited'
default_conf['dry_run_wallet'] = 1000
default_conf['exchange']['name'] = 'binance'
default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(
side_effect=[1000, 800, 600, 400, 200]
))
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
fetch_ticker=ticker,
get_fee=fee,
symbol_amount_prec=lambda s, x, y: y,
symbol_price_prec=lambda s, x, y: y,
@ -138,6 +136,14 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
update_trade_state=MagicMock(),
_notify_sell=MagicMock(),
)
should_sell_mock = MagicMock(side_effect=[
SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL),
SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
SellCheckTuple(sell_flag=None, sell_type=SellType.NONE)]
)
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtrade)
@ -158,3 +164,20 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
for trade in trades:
assert trade.stake_amount == 200
# Reset trade open order id's
trade.open_order_id = None
trades = Trade.get_open_trades()
assert len(trades) == 5
bals = freqtrade.wallets.get_all_balances()
freqtrade.process_maybe_execute_sells(trades)
trades = Trade.get_open_trades()
# One trade sold
assert len(trades) == 4
# Validate that balance of sold trade is not in dry-run balances anymore.
bals2 = freqtrade.wallets.get_all_balances()
assert bals != bals2
assert len(bals) == 6
assert len(bals2) == 5
assert 'LTC' in bals
assert 'LTC' not in bals2

View File

@ -100,7 +100,7 @@ def test_init_dryrun_db(default_conf, mocker):
init(default_conf['db_url'], default_conf['dry_run'])
assert create_engine_mock.call_count == 1
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite://'
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.dryrun.sqlite'
@pytest.mark.usefixtures("init_persistence")