Merge remote-tracking branch 'upstream/develop' into hyperopt-trailing-space

This commit is contained in:
hroff-1902 2019-12-01 03:28:23 +03:00
commit 69b0767165
71 changed files with 1505 additions and 487 deletions

View File

@ -1,6 +1,7 @@
[run]
omit =
scripts/*
freqtrade/templates/*
freqtrade/vendor/*
freqtrade/__main__.py
tests/*

View File

@ -73,18 +73,21 @@ jobs:
# 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 "${CI_BRANCH}"
echo "${TRAVIS_BRANCH}"
coveralls || true
- name: Backtesting
run: |
cp config.json.example config.json
freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy
freqtrade create-userdir --userdir user_data
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
- name: Hyperopt
run: |
cp config.json.example config.json
freqtrade create-userdir --userdir user_data
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt
- name: Flake8
@ -97,7 +100,7 @@ jobs:
- name: Slack Notification
uses: homoluctus/slatify@v1.8.0
if: always() && github.repository.fork == true
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI ${{ matrix.os }}*'
@ -141,11 +144,13 @@ jobs:
- name: Backtesting
run: |
cp config.json.example config.json
freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy
freqtrade create-userdir --userdir user_data
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
- name: Hyperopt
run: |
cp config.json.example config.json
freqtrade create-userdir --userdir user_data
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt
- name: Flake8
@ -158,7 +163,7 @@ jobs:
- name: Slack Notification
uses: homoluctus/slatify@v1.8.0
if: always() && github.repository.fork == true
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI windows*'
@ -178,7 +183,7 @@ jobs:
- name: Slack Notification
uses: homoluctus/slatify@v1.8.0
if: failure() && github.repository.fork == true
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade Docs*'
@ -219,7 +224,7 @@ jobs:
- name: Slack Notification
uses: homoluctus/slatify@v1.8.0
if: always() && github.repository.fork == true
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI Deploy*'

View File

@ -28,10 +28,12 @@ jobs:
name: pytest
- script:
- cp config.json.example config.json
- freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy
- freqtrade create-userdir --userdir user_data
- freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
name: backtest
- script:
- cp config.json.example config.json
- freqtrade create-userdir --userdir user_data
- freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt
name: hyperopt
- script: flake8

View File

@ -89,9 +89,9 @@ The bot allows you to use multiple configuration files by specifying multiple
defined in the latter configuration files override parameters with the same name
defined in the previous configuration files specified in the command line earlier.
For example, you can make a separate configuration file with your key and secrete
For example, you can make a separate configuration file with your key and secret
for the Exchange you use for trading, specify default configuration file with
empty key and secrete values while running in the Dry Mode (which does not actually
empty key and secret values while running in the Dry Mode (which does not actually
require them):
```bash
@ -104,7 +104,7 @@ and specify both configuration files when running in the normal Live Trade Mode:
freqtrade trade -c ./config.json -c path/to/secrets/keys.config.json
```
This could help you hide your private Exchange key and Exchange secrete on you local machine
This could help you hide your private Exchange key and Exchange secret on you local machine
by setting appropriate file permissions for the file which contains actual secrets and, additionally,
prevent unintended disclosure of sensitive private data when you publish examples
of your configuration in the project issues or in the Internet.

View File

@ -38,84 +38,92 @@ 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 | Default | Description |
|----------|---------|-------------|
| `max_open_trades` | 3 | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades)
| `stake_currency` | BTC | **Required.** Crypto-currency used for trading.
| `stake_amount` | 0.05 | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance.
| `amount_reserve_percent` | 0.05 | Reserve some amount in min pair stake amount. Default is 5%. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals.
| `ticker_interval` | [1m, 5m, 15m, 30m, 1h, 1d, ...] | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy).
| `fiat_display_currency` | USD | **Required.** Fiat currency used to show your profits. More information below.
| `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode.
| `dry_run_wallet` | 999.9 | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason.
| `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy).
| `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy).
| `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_stop_positive_offset` | 0 | 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).
| `trailing_only_offset_is_reached` | false | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `unfilledtimeout.buy` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
| `unfilledtimeout.sell` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
| `bid_strategy.ask_last_balance` | 0.0 | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance).
| `bid_strategy.use_order_book` | false | Allows buying of pair using the rates in Order Book Bids.
| `bid_strategy.order_book_top` | 0 | 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.
| `bid_strategy. check_depth_of_market.enabled` | false | Does not buy if the % difference of buy orders and sell orders is met in Order Book.
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | 0 | 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.
| `ask_strategy.use_order_book` | false | Allows selling of open traded pair using the rates in Order Book Asks.
| `ask_strategy.order_book_min` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `ask_strategy.order_book_max` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `ask_strategy.use_sell_signal` | true | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
| `ask_strategy.sell_profit_only` | false | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
| `ask_strategy.ignore_roi_if_buy_signal` | false | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
| `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
| `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
| `exchange.name` | | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
| `exchange.sandbox` | false | 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.
| `exchange.key` | '' | API key to use for the exchange. Only required when you are in production mode. ***Keep it in secrete, do not disclose publicly.***
| `exchange.secret` | '' | API secret to use for the exchange. Only required when you are in production mode. ***Keep it in secrete, do not disclose publicly.***
| `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 secrete, do not disclose publicly.***
| `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)).
| `exchange.pair_blacklist` | [] | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)).
| `exchange.ccxt_config` | None | 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)
| `exchange.ccxt_async_config` | None | Additional CCXT parameters passed to the async 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)
| `exchange.markets_refresh_interval` | 60 | The interval in minutes in which markets are reloaded.
| `edge` | false | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `experimental.block_bad_exchanges` | true | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
| `pairlists` | StaticPairList | Define one or more pairlists to be used. [More information below](#dynamic-pairlists).
| `telegram.enabled` | true | **Required.** Enable or not the usage of Telegram.
| `telegram.token` | token | Your Telegram bot token. Only required if `telegram.enabled` is `true`. ***Keep it in secrete, do not disclose publicly.***
| `telegram.chat_id` | chat_id | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. ***Keep it in secrete, do not disclose publicly.***
| `webhook.enabled` | false | Enable usage of Webhook notifications
| `webhook.url` | false | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
| `webhook.webhookbuy` | false | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhooksell` | false | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhookstatus` | false | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `db_url` | `sqlite:///tradesv3.sqlite`| Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`.
| `initial_state` | running | Defines the initial application state. More information below.
| `forcebuy_enable` | false | Enables the RPC Commands to force a buy. More information below.
| `strategy` | None | **Required** Defines Strategy class to use. Recommended to set via `--strategy NAME`.
| `strategy_path` | null | Adds an additional strategy lookup path (must be a directory).
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
| `internals.heartbeat_interval` | 60 | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages.
| `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file.
| `user_data_dir` | cwd()/user_data | Directory containing user data. Defaults to `./user_data/`.
| Command | 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"`.*
| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> ***Datatype:*** *Positive Float as ratio.*
| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *String*
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> ***Datatype:*** *String*
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> ***Datatype:*** *Boolean*
| `dry_run_wallet` | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. <br> ***Datatype:*** *Float*
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Dict*
| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Float (as ratio)*
| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Boolean*
| `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*
| `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*
| `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> ***Datatype:*** *Dict*
| `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.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*
| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async 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*
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> ***Datatype:*** *Positive Integer*
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `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*
| `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*
| `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*
| `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*
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> ***Datatype:*** *String*
| `internals.process_throttle_secs` | Set the process throttle. Value in second. <br>*Defaults to `5` seconds.* <br> ***Datatype:*** *Positive Integer*
| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> ***Datatype:*** *Positive Integer or 0*
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> ***Datatype:*** *Boolean*
| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. <br> ***Datatype:*** *String*
| `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> ***Datatype:*** *String*
### Parameters in the strategy
The following parameters can be set in either configuration file or strategy.
Values set in the configuration file always overwrite values set in the strategy.
* `ticker_interval`
* `minimal_roi`
* `ticker_interval`
* `stoploss`
* `trailing_stop`
* `trailing_stop_positive`
* `trailing_stop_positive_offset`
* `trailing_only_offset_is_reached`
* `process_only_new_candles`
* `order_types`
* `order_time_in_force`
* `stake_currency`
* `stake_amount`
* `use_sell_signal` (ask_strategy)
* `sell_profit_only` (ask_strategy)
* `ignore_roi_if_buy_signal` (ask_strategy)
@ -123,15 +131,19 @@ Values set in the configuration file always overwrite values set in the strategy
### Understand stake_amount
The `stake_amount` configuration parameter is an amount of crypto-currency your bot will use for each trade.
The minimal value is 0.0005. If there is not enough crypto-currency in
the account an exception is generated.
The minimal configuration value is 0.0001. Please check your exchange's trading minimums to avoid problems.
This setting works in combination with `max_open_trades`. The maximum capital engaged in trades is `stake_amount * max_open_trades`.
For example, the bot will at most use (0.05 BTC x 3) = 0.15 BTC, assuming a configuration of `max_open_trades=3` and `stake_amount=0.05`.
To allow the bot to trade all the available `stake_currency` in your account set
```json
"stake_amount" : "unlimited",
```
In this case a trade amount is calclulated as:
In this case a trade amount is calculated as:
```python
currency_balance / (max_open_trades - current_open_trades)
@ -472,7 +484,7 @@ creating trades on the exchange.
"db_url": "sqlite:///tradesv3.dryrun.sqlite",
```
3. Remove your Exchange API key and secrete (change them by empty values or fake credentials):
3. Remove your Exchange API key and secret (change them by empty values or fake credentials):
```json
"exchange": {

View File

@ -200,8 +200,8 @@ If the day shows the same day, then the last candle can be assumed as incomplete
To keep the jupyter notebooks aligned with the documentation, the following should be ran after updating a example notebook.
``` bash
jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace user_data/notebooks/strategy_analysis_example.ipynb
jupyter nbconvert --ClearOutputPreprocessor.enabled=True --to markdown user_data/notebooks/strategy_analysis_example.ipynb --stdout > docs/strategy_analysis_example.md
jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace freqtrade/templates/strategy_analysis_example.ipynb
jupyter nbconvert --ClearOutputPreprocessor.enabled=True --to markdown freqtrade/templates/strategy_analysis_example.ipynb --stdout > docs/strategy_analysis_example.md
```
## Continuous integration

View File

@ -15,10 +15,13 @@ To learn how to get data for the pairs and exchange you're interrested in, head
## Prepare Hyperopting
Before we start digging into Hyperopt, we recommend you to take a look at
the sample hyperopt file located in [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt.py).
the sample hyperopt file located in [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt.py).
Configuring hyperopt is similar to writing your own strategy, and many tasks will be similar and a lot of code can be copied across from the strategy.
The simplest way to get started is to use `freqtrade new-hyperopt --hyperopt AwesomeHyperopt`.
This will create a new hyperopt file from a template, which will be located under `user_data/hyperopts/AwesomeHyperopt.py`.
### Checklist on all tasks / possibilities in hyperopt
Depending on the space you want to optimize, only some of the below are required:
@ -422,7 +425,7 @@ These ranges should be sufficient in most cases. The minutes in the steps (ROI d
If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default.
Override the `roi_space()` method if you need components of the ROI tables to vary in other ranges. Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization if you need a different structure of the ROI tables or other amount of rows (steps). A sample for these methods can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py).
Override the `roi_space()` method if you need components of the ROI tables to vary in other ranges. Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization if you need a different structure of the ROI tables or other amount of rows (steps). A sample for these methods can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
### Understand Hyperopt Stoploss results
@ -457,7 +460,7 @@ If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimiza
If you have the `stoploss_space()` method in your custom hyperopt file, remove it in order to utilize Stoploss hyperoptimization space generated by Freqtrade by default.
Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py).
Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
### Understand Hyperopt Trailing Stop results

View File

@ -162,7 +162,7 @@ Clone the git repository:
```bash
git clone https://github.com/freqtrade/freqtrade.git
cd freqtrade
```
Optionally checkout the master branch to get the latest stable release:
@ -171,22 +171,24 @@ Optionally checkout the master branch to get the latest stable release:
git checkout master
```
#### 4. Initialize the configuration
```bash
cd freqtrade
cp config.json.example config.json
```
> *To edit the config please refer to [Bot Configuration](configuration.md).*
#### 5. Install python dependencies
#### 4. Install python dependencies
``` bash
python3 -m pip install --upgrade pip
python3 -m pip install -e .
```
#### 5. Initialize the configuration
```bash
# Initialize the user_directory
freqtrade create-userdir --userdir user_data/
cp config.json.example config.json
```
> *To edit the config please refer to [Bot Configuration](configuration.md).*
#### 6. Run the Bot
If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
@ -227,7 +229,7 @@ If that is not available on your system, feel free to try the instructions below
Make sure to use 64bit Windows and 64bit Python to avoid problems with backtesting or hyperopt due to the memory constraints 32bit applications have under Windows.
!!! Hint
Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Conda section](#using-conda) in this document.
Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Conda section](#using-conda) in this document for more information.
#### Clone the git repository

View File

@ -7,24 +7,28 @@ indicators.
This is very simple. Copy paste your strategy file into the directory `user_data/strategies`.
Let assume you have a class called `AwesomeStrategy` in the file `awesome-strategy.py`:
Let assume you have a class called `AwesomeStrategy` in the file `AwesomeStrategy.py`:
1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py`
1. Move your file into `user_data/strategies` (you should have `user_data/strategies/AwesomeStrategy.py`
2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name)
```bash
freqtrade trade --strategy AwesomeStrategy
```
## Change your strategy
## Develop your own strategy
The bot includes a default strategy file. However, we recommend you to
use your own file to not have to lose your parameters every time the default
strategy file will be updated on Github. Put your custom strategy file
into the directory `user_data/strategies`.
The bot includes a default strategy file.
Also, several other strategies are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies).
Best copy the test-strategy and modify this copy to avoid having bot-updates override your changes.
`cp user_data/strategies/sample_strategy.py user_data/strategies/awesome-strategy.py`
You will however most likely have your own idea for a strategy.
This document intends to help you develop one for yourself.
To get started, use `freqtrade new-strategy --strategy AwesomeStrategy`.
This will create a new strategy file from a template, which will be located under `user_data/strategies/AwesomeStrategy.py`.
!!! Note
This is just a template file, which will most likely not be profitable out of the box.
### Anatomy of a strategy
@ -48,7 +52,7 @@ Future versions will require this to be set.
freqtrade trade --strategy AwesomeStrategy
```
**For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/sample_strategy.py)
**For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py)
file as reference.**
!!! Note "Strategies and Backtesting"
@ -114,7 +118,7 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
```
!!! Note "Want more indicator examples?"
Look into the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/sample_strategy.py).
Look into the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py).
Then uncomment indicators you need.
### Strategy startup period
@ -478,7 +482,7 @@ Printing more than a few rows is also possible (simply use `print(dataframe)` i
### 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/user_data/strategies/sample_strategy.py).
[user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py).
### Specify custom strategy location

View File

@ -2,6 +2,116 @@
Besides the Live-Trade and Dry-Run run modes, the `backtesting`, `edge` and `hyperopt` optimization subcommands, and the `download-data` subcommand which prepares historical data, the bot contains a number of utility subcommands. They are described in this section.
## Create userdir
Creates the directory structure to hold your files for freqtrade.
Will also create strategy and hyperopt examples for you to get started.
Can be used multiple times - using `--reset` will reset the sample strategy and hyperopt files to their default state.
```
usage: freqtrade create-userdir [-h] [--userdir PATH] [--reset]
optional arguments:
-h, --help show this help message and exit
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
--reset Reset sample files to their original state.
```
!!! Warning
Using `--reset` may result in loss of data, since this will overwrite all sample files without asking again.
```
├── backtest_results
├── data
├── hyperopt_results
├── hyperopts
│   ├── sample_hyperopt_advanced.py
│   ├── sample_hyperopt_loss.py
│   └── sample_hyperopt.py
├── notebooks
│   └── strategy_analysis_example.ipynb
├── plot
└── strategies
└── sample_strategy.py
```
## Create new strategy
Creates a new strategy from a template similar to SampleStrategy.
The file will be named inline with your class name, and will not overwrite existing files.
Results will be located in `user_data/strategies/<strategyclassname>.py`.
### Sample usage of new-strategy
```bash
freqtrade new-strategy --strategy AwesomeStrategy
```
With custom user directory
```bash
freqtrade new-strategy --userdir ~/.freqtrade/ --strategy AwesomeStrategy
```
### new-strategy complete options
``` output
usage: freqtrade new-strategy [-h] [--userdir PATH] [-s NAME]
[--template {full,minimal}]
optional arguments:
-h, --help show this help message and exit
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--template {full,minimal}
Use a template which is either `minimal` or `full`
(containing multiple sample indicators). Default:
`full`.
```
## Create new hyperopt
Creates a new hyperopt from a template similar to SampleHyperopt.
The file will be named inline with your class name, and will not overwrite existing files.
Results will be located in `user_data/hyperopts/<classname>.py`.
### Sample usage of new-hyperopt
```bash
freqtrade new-hyperopt --hyperopt AwesomeHyperopt
```
With custom user directory
```bash
freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt
```
### new-hyperopt complete options
``` output
usage: freqtrade new-hyperopt [-h] [--userdir PATH] [--hyperopt NAME]
[--template {full,minimal}]
optional arguments:
-h, --help show this help message and exit
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
--hyperopt NAME Specify hyperopt class name which will be used by the
bot.
--template {full,minimal}
Use a template which is either `minimal` or `full`
(containing multiple sample indicators). Default:
`full`.
```
## List Exchanges
Use the `list-exchanges` subcommand to see the exchanges available for the bot.

View File

@ -37,7 +37,11 @@ ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"]
ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one_column",
"print_csv", "base_currencies", "quote_currencies", "list_pairs_all"]
ARGS_CREATE_USERDIR = ["user_data_dir"]
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchange",
"timeframes", "erase"]
@ -52,7 +56,7 @@ ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
NO_CONF_REQURIED = ["download-data", "list-timeframes", "list-markets", "list-pairs",
"plot-dataframe", "plot-profit"]
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges"]
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-hyperopt", "new-strategy"]
class Arguments:
@ -117,6 +121,7 @@ class Arguments:
from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge
from freqtrade.utils import (start_create_userdir, start_download_data,
start_list_exchanges, start_list_markets,
start_new_hyperopt, start_new_strategy,
start_list_timeframes, start_trading)
from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit
@ -158,6 +163,18 @@ class Arguments:
create_userdir_cmd.set_defaults(func=start_create_userdir)
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
# add new-strategy subcommand
build_strategy_cmd = subparsers.add_parser('new-strategy',
help="Create new strategy")
build_strategy_cmd.set_defaults(func=start_new_strategy)
self._build_args(optionlist=ARGS_BUILD_STRATEGY, parser=build_strategy_cmd)
# add new-hyperopt subcommand
build_hyperopt_cmd = subparsers.add_parser('new-hyperopt',
help="Create new hyperopt")
build_hyperopt_cmd.set_defaults(func=start_new_hyperopt)
self._build_args(optionlist=ARGS_BUILD_HYPEROPT, parser=build_hyperopt_cmd)
# Add list-exchanges subcommand
list_exchanges_cmd = subparsers.add_parser(
'list-exchanges',

View File

@ -62,6 +62,11 @@ AVAILABLE_CLI_OPTIONS = {
help='Path to userdata directory.',
metavar='PATH',
),
"reset": Arg(
'--reset',
help='Reset sample files to their original state.',
action='store_true',
),
# Main options
"strategy": Arg(
'-s', '--strategy',
@ -333,6 +338,14 @@ AVAILABLE_CLI_OPTIONS = {
help='Clean all existing data for the selected exchange/pairs/timeframes.',
action='store_true',
),
# Templating options
"template": Arg(
'--template',
help='Use a template which is either `minimal` or '
'`full` (containing multiple sample indicators). Default: `%(default)s`.',
choices=['full', 'minimal'],
default='full',
),
# Plot dataframe
"indicators1": Arg(
'--indicators1',

View File

@ -61,11 +61,16 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None:
:param conf: Config in JSON format
:return: Returns None if everything is ok, otherwise throw an OperationalException
"""
# validating trailing stoploss
_validate_trailing_stoploss(conf)
_validate_edge(conf)
_validate_whitelist(conf)
# validate configuration before returning
logger.info('Validating configuration ...')
validate_config_schema(conf)
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:

View File

@ -9,8 +9,6 @@ from typing import Any, Callable, Dict, List, Optional
from freqtrade import OperationalException, constants
from freqtrade.configuration.check_exchange import check_exchange
from freqtrade.configuration.config_validation import (validate_config_consistency,
validate_config_schema)
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
from freqtrade.configuration.directory_operations import (create_datadir,
create_userdata_dir)
@ -84,10 +82,6 @@ class Configuration:
if 'pairlists' not in config:
config['pairlists'] = []
# validate configuration before returning
logger.info('Validating configuration ...')
validate_config_schema(config)
return config
def load_config(self) -> Dict[str, Any]:
@ -118,8 +112,6 @@ class Configuration:
process_temporary_deprecated_settings(config)
validate_config_consistency(config)
return config
def _process_logging_options(self, config: Dict[str, Any]) -> None:

View File

@ -58,6 +58,13 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
process_deprecated_setting(config, 'ask_strategy', 'ignore_roi_if_buy_signal',
'experimental', 'ignore_roi_if_buy_signal')
if not config.get('pairlists') and not config.get('pairlists'):
config['pairlists'] = [{'method': 'StaticPairList'}]
logger.warning(
"DEPRECATED: "
"Pairlists must be defined explicitly in the future."
"Defaulting to StaticPairList for now.")
if config.get('pairlist', {}).get("method") == 'VolumePairList':
logger.warning(
"DEPRECATED: "

View File

@ -1,8 +1,10 @@
import logging
from typing import Any, Dict, Optional
import shutil
from pathlib import Path
from typing import Any, Dict, Optional
from freqtrade import OperationalException
from freqtrade.constants import USER_DATA_FILES
logger = logging.getLogger(__name__)
@ -31,7 +33,8 @@ def create_userdata_dir(directory: str, create_dir=False) -> Path:
:param create_dir: Create directory if it does not exist.
:return: Path object containing the directory
"""
sub_dirs = ["backtest_results", "data", "hyperopts", "hyperopt_results", "plot", "strategies", ]
sub_dirs = ["backtest_results", "data", "hyperopts", "hyperopt_results", "notebooks",
"plot", "strategies", ]
folder = Path(directory)
if not folder.is_dir():
if create_dir:
@ -48,3 +51,26 @@ def create_userdata_dir(directory: str, create_dir=False) -> Path:
if not subfolder.is_dir():
subfolder.mkdir(parents=False)
return folder
def copy_sample_files(directory: Path, overwrite: bool = False) -> None:
"""
Copy files from templates to User data directory.
:param directory: Directory to copy data to
:param overwrite: Overwrite existing sample files
"""
if not directory.is_dir():
raise OperationalException(f"Directory `{directory}` does not exist.")
sourcedir = Path(__file__).parents[1] / "templates"
for source, target in USER_DATA_FILES.items():
targetdir = directory / target
if not targetdir.is_dir():
raise OperationalException(f"Directory `{targetdir}` does not exist.")
targetfile = targetdir / source
if targetfile.exists():
if not overwrite:
logger.warning(f"File `{targetfile}` exists already, not deploying sample file.")
continue
else:
logger.warning(f"File `{targetfile}` exists already, overwriting.")
shutil.copy(str(sourcedir / source), str(targetfile))

View File

@ -6,7 +6,6 @@ bot constants
DEFAULT_CONFIG = 'config.json'
DEFAULT_EXCHANGE = 'bittrex'
PROCESS_THROTTLE_SECS = 5 # sec
DEFAULT_TICKER_INTERVAL = 5 # min
HYPEROPT_EPOCH = 100 # epochs
RETRY_TIMEOUT = 30 # sec
DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss'
@ -22,6 +21,18 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'P
DRY_RUN_WALLET = 999.9
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
USERPATH_HYPEROPTS = 'hyperopts'
USERPATH_STRATEGY = 'strategies'
# Soure files with destination directories within user-directory
USER_DATA_FILES = {
'sample_strategy.py': USERPATH_STRATEGY,
'sample_hyperopt_advanced.py': USERPATH_HYPEROPTS,
'sample_hyperopt_loss.py': USERPATH_HYPEROPTS,
'sample_hyperopt.py': USERPATH_HYPEROPTS,
'strategy_analysis_example.ipynb': 'notebooks',
}
TIMEFRAMES = [
'1m', '3m', '5m', '15m', '30m',
'1h', '2h', '4h', '6h', '8h', '12h',
@ -54,13 +65,13 @@ MINIMAL_CONFIG = {
CONF_SCHEMA = {
'type': 'object',
'properties': {
'max_open_trades': {'type': 'integer', 'minimum': -1},
'max_open_trades': {'type': ['integer', 'number'], 'minimum': -1},
'ticker_interval': {'type': 'string', 'enum': TIMEFRAMES},
'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']},
'stake_amount': {
"type": ["number", "string"],
"minimum": 0.0005,
"pattern": UNLIMITED_STAKE_AMOUNT
'type': ['number', 'string'],
'minimum': 0.0001,
'pattern': UNLIMITED_STAKE_AMOUNT
},
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
'dry_run': {'type': 'boolean'},
@ -82,8 +93,8 @@ CONF_SCHEMA = {
'unfilledtimeout': {
'type': 'object',
'properties': {
'buy': {'type': 'number', 'minimum': 3},
'sell': {'type': 'number', 'minimum': 10}
'buy': {'type': 'number', 'minimum': 1},
'sell': {'type': 'number', 'minimum': 1}
}
},
'bid_strategy': {
@ -95,7 +106,7 @@ CONF_SCHEMA = {
'maximum': 1,
'exclusiveMaximum': False,
'use_order_book': {'type': 'boolean'},
'order_book_top': {'type': 'number', 'maximum': 20, 'minimum': 1},
'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1},
'check_depth_of_market': {
'type': 'object',
'properties': {
@ -111,8 +122,8 @@ CONF_SCHEMA = {
'type': 'object',
'properties': {
'use_order_book': {'type': 'boolean'},
'order_book_min': {'type': 'number', 'minimum': 1},
'order_book_max': {'type': 'number', 'minimum': 1, 'maximum': 50},
'order_book_min': {'type': 'integer', 'minimum': 1},
'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50},
'use_sell_signal': {'type': 'boolean'},
'sell_profit_only': {'type': 'boolean'},
'ignore_roi_if_buy_signal': {'type': 'boolean'}
@ -185,8 +196,8 @@ CONF_SCHEMA = {
'listen_ip_address': {'format': 'ipv4'},
'listen_port': {
'type': 'integer',
"minimum": 1024,
"maximum": 65535
'minimum': 1024,
'maximum': 65535
},
'username': {'type': 'string'},
'password': {'type': 'string'},
@ -199,7 +210,7 @@ CONF_SCHEMA = {
'internals': {
'type': 'object',
'properties': {
'process_throttle_secs': {'type': 'number'},
'process_throttle_secs': {'type': 'integer'},
'interval': {'type': 'integer'},
'sd_notify': {'type': 'boolean'},
}
@ -241,32 +252,32 @@ CONF_SCHEMA = {
'edge': {
'type': 'object',
'properties': {
"enabled": {'type': 'boolean'},
"process_throttle_secs": {'type': 'integer', 'minimum': 600},
"calculate_since_number_of_days": {'type': 'integer'},
"allowed_risk": {'type': 'number'},
"capital_available_percentage": {'type': 'number'},
"stoploss_range_min": {'type': 'number'},
"stoploss_range_max": {'type': 'number'},
"stoploss_range_step": {'type': 'number'},
"minimum_winrate": {'type': 'number'},
"minimum_expectancy": {'type': 'number'},
"min_trade_number": {'type': 'number'},
"max_trade_duration_minute": {'type': 'integer'},
"remove_pumps": {'type': 'boolean'}
'enabled': {'type': 'boolean'},
'process_throttle_secs': {'type': 'integer', 'minimum': 600},
'calculate_since_number_of_days': {'type': 'integer'},
'allowed_risk': {'type': 'number'},
'capital_available_percentage': {'type': 'number'},
'stoploss_range_min': {'type': 'number'},
'stoploss_range_max': {'type': 'number'},
'stoploss_range_step': {'type': 'number'},
'minimum_winrate': {'type': 'number'},
'minimum_expectancy': {'type': 'number'},
'min_trade_number': {'type': 'number'},
'max_trade_duration_minute': {'type': 'integer'},
'remove_pumps': {'type': 'boolean'}
},
'required': ['process_throttle_secs', 'allowed_risk', 'capital_available_percentage']
}
},
'anyOf': [
{'required': ['exchange']}
],
'required': [
'exchange',
'max_open_trades',
'stake_currency',
'stake_amount',
'dry_run',
'bid_strategy',
'unfilledtimeout',
'stoploss',
'minimal_roi',
]
}

View File

@ -266,7 +266,11 @@ class FreqtradeBot:
amount_reserve_percent += self.strategy.stoploss
# it should not be more than 50%
amount_reserve_percent = max(amount_reserve_percent, 0.5)
return min(min_stake_amounts) / amount_reserve_percent
# The value returned should satisfy both limits: for amount (base currency) and
# for cost (quote, stake currency), so max() is used here.
# See also #2575 at github.
return max(min_stake_amounts) / amount_reserve_percent
def create_trades(self) -> bool:
"""

View File

@ -127,3 +127,16 @@ def round_dict(d, n):
def plural(num, singular: str, plural: str = None) -> str:
return singular if (num == 1 or num == -1) else plural or singular + 's'
def render_template(templatefile: str, arguments: dict = {}):
from jinja2 import Environment, PackageLoader, select_autoescape
env = Environment(
loader=PackageLoader('freqtrade', 'templates'),
autoescape=select_autoescape(['html', 'xml'])
)
template = env.get_template(templatefile)
return template.render(**arguments)

View File

@ -13,7 +13,8 @@ from pandas import DataFrame
from tabulate import tabulate
from freqtrade import OperationalException
from freqtrade.configuration import TimeRange, remove_credentials
from freqtrade.configuration import (TimeRange, remove_credentials,
validate_config_consistency)
from freqtrade.data import history
from freqtrade.data.dataprovider import DataProvider
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
@ -75,10 +76,12 @@ class Backtesting:
stratconf = deepcopy(self.config)
stratconf['strategy'] = strat
self.strategylist.append(StrategyResolver(stratconf).strategy)
validate_config_consistency(stratconf)
else:
# No strategy list specified, only one strategy
self.strategylist.append(StrategyResolver(self.config).strategy)
validate_config_consistency(self.config)
if "ticker_interval" not in self.config:
raise OperationalException("Ticker-interval needs to be set in either configuration "

View File

@ -9,7 +9,8 @@ from typing import Any, Dict
from tabulate import tabulate
from freqtrade import constants
from freqtrade.configuration import TimeRange, remove_credentials
from freqtrade.configuration import (TimeRange, remove_credentials,
validate_config_consistency)
from freqtrade.edge import Edge
from freqtrade.exchange import Exchange
from freqtrade.resolvers import StrategyResolver
@ -35,6 +36,8 @@ class EdgeCli:
self.exchange = Exchange(self.config)
self.strategy = StrategyResolver(self.config).strategy
validate_config_consistency(self.config)
self.edge = Edge(config, self.exchange, self.strategy)
# Set refresh_pairs to false for edge-cli (it must be true for edge)
self.edge._refresh_pairs = False

View File

@ -23,7 +23,7 @@ from skopt import Optimizer
from skopt.space import Dimension
from freqtrade.data.history import get_timeframe, trim_dataframe
from freqtrade.misc import round_dict
from freqtrade.misc import plural, round_dict
from freqtrade.optimize.backtesting import Backtesting
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F4
@ -77,6 +77,8 @@ class Hyperopt:
# Previous evaluations
self.trials: List = []
self.num_trials_saved = 0
# Populate functions here (hasattr is slow so should not be run during "regular" operations)
if hasattr(self.custom_hyperopt, 'populate_indicators'):
self.backtesting.strategy.advise_indicators = \
@ -132,13 +134,18 @@ class Hyperopt:
arg_dict = {dim.name: value for dim, value in zip(dimensions, params)}
return arg_dict
def save_trials(self) -> None:
def save_trials(self, final: bool = False) -> None:
"""
Save hyperopt trials to file
"""
if self.trials:
logger.info("Saving %d evaluations to '%s'", len(self.trials), self.trials_file)
num_trials = len(self.trials)
if num_trials > self.num_trials_saved:
logger.info(f"Saving {num_trials} {plural(num_trials, 'epoch')}.")
dump(self.trials, self.trials_file)
self.num_trials_saved = num_trials
if final:
logger.info(f"{num_trials} {plural(num_trials, 'epoch')} "
f"saved to '{self.trials_file}'.")
def read_trials(self) -> List:
"""
@ -153,6 +160,12 @@ class Hyperopt:
"""
Display Best hyperopt result
"""
# This is printed when Ctrl+C is pressed quickly, before first epochs have
# a chance to be evaluated.
if not self.trials:
print("No epochs evaluated yet, no best result.")
return
results = sorted(self.trials, key=itemgetter('loss'))
best_result = results[0]
params = best_result['params']
@ -209,12 +222,20 @@ class Hyperopt:
print('Trailing stop:')
pprint(self.space_params(params, 'trailing', 5), indent=4)
def is_best(self, results) -> bool:
return results['loss'] < self.current_best_loss
def log_results(self, results) -> None:
"""
Log results if it is better than any previous evaluation
"""
print_all = self.config.get('print_all', False)
is_best_loss = results['loss'] < self.current_best_loss
is_best_loss = self.is_best(results)
if not print_all:
print('.', end='' if results['current_epoch'] % 100 != 0 else None) # type: ignore
sys.stdout.flush()
if print_all or is_best_loss:
if is_best_loss:
self.current_best_loss = results['loss']
@ -229,13 +250,9 @@ class Hyperopt:
print(log_str)
else:
print(f'\n{log_str}')
else:
print('.', end='')
sys.stdout.flush()
def format_results_logstring(self, results) -> str:
# Output human-friendly index here (starting from 1)
current = results['current_epoch'] + 1
current = results['current_epoch']
total = self.total_epochs
res = results['results_explanation']
loss = results['loss']
@ -460,15 +477,19 @@ class Hyperopt:
self.opt.tell(asked, [v['loss'] for v in f_val])
self.fix_optimizer_models_list()
for j in range(jobs):
current = i * jobs + j
# Use human-friendly index here (starting from 1)
current = i * jobs + j + 1
val = f_val[j]
val['current_epoch'] = current
val['is_initial_point'] = current < INITIAL_POINTS
val['is_initial_point'] = current <= INITIAL_POINTS
logger.debug(f"Optimizer epoch evaluated: {val}")
is_best = self.is_best(val)
self.log_results(val)
self.trials.append(val)
logger.debug(f"Optimizer epoch evaluated: {val}")
if is_best or current % 100 == 0:
self.save_trials()
except KeyboardInterrupt:
print('User interrupted..')
self.save_trials()
self.save_trials(final=True)
self.log_trials_result()

View File

@ -8,7 +8,7 @@ from pathlib import Path
from typing import Optional, Dict
from freqtrade import OperationalException
from freqtrade.constants import DEFAULT_HYPEROPT_LOSS
from freqtrade.constants import DEFAULT_HYPEROPT_LOSS, USERPATH_HYPEROPTS
from freqtrade.optimize.hyperopt_interface import IHyperOpt
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
from freqtrade.resolvers import IResolver
@ -58,7 +58,7 @@ 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='hyperopts', extra_dir=extra_dir)
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})
@ -110,7 +110,7 @@ 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='hyperopts', extra_dir=extra_dir)
user_subdir=USERPATH_HYPEROPTS, extra_dir=extra_dir)
hyperoptloss = self._load_object(paths=abs_paths, object_type=IHyperOptLoss,
object_name=hyper_loss_name)

View File

@ -129,7 +129,8 @@ 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='strategies', extra_dir=extra_dir)
user_subdir=constants.USERPATH_STRATEGY,
extra_dir=extra_dir)
if ":" in strategy_name:
logger.info("loading base64 encoded strategy")

View File

@ -312,7 +312,7 @@ class ApiServer(RPC):
logger.info("LocalRPC - Profit Command Called")
stats = self._rpc_trade_statistics(self._config['stake_currency'],
self._config['fiat_display_currency']
self._config.get('fiat_display_currency')
)
return self.rest_dump(stats)
@ -354,7 +354,8 @@ class ApiServer(RPC):
Returns the current status of the trades in json format
"""
results = self._rpc_balance(self._config.get('fiat_display_currency', ''))
results = self._rpc_balance(self._config['stake_currency'],
self._config.get('fiat_display_currency', ''))
return self.rest_dump(results)
@require_login

View File

@ -297,34 +297,42 @@ class RPC:
'best_rate': round(bp_rate * 100, 2),
}
def _rpc_balance(self, fiat_display_currency: str) -> Dict:
def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:
""" Returns current account balance per crypto """
output = []
total = 0.0
for coin, balance in self._freqtrade.exchange.get_balances().items():
if not balance['total']:
try:
tickers = self._freqtrade.exchange.get_tickers()
except (TemporaryError, DependencyException):
raise RPCException('Error getting current tickers.')
for coin, balance in self._freqtrade.wallets.get_all_balances().items():
if not balance.total:
continue
if coin == 'BTC':
est_stake: float = 0
if coin == stake_currency:
rate = 1.0
est_stake = balance.total
else:
try:
pair = self._freqtrade.exchange.get_valid_pair_combination(coin, "BTC")
if pair.startswith("BTC"):
rate = 1.0 / self._freqtrade.get_sell_rate(pair, False)
else:
rate = self._freqtrade.get_sell_rate(pair, False)
pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency)
rate = tickers.get(pair, {}).get('bid', None)
if rate:
if pair.startswith(stake_currency):
rate = 1.0 / rate
est_stake = rate * balance.total
except (TemporaryError, DependencyException):
logger.warning(f" Could not get rate for pair {coin}.")
continue
est_btc: float = rate * balance['total']
total = total + est_btc
total = total + (est_stake or 0)
output.append({
'currency': coin,
'free': balance['free'] if balance['free'] is not None else 0,
'balance': balance['total'] if balance['total'] is not None else 0,
'used': balance['used'] if balance['used'] is not None else 0,
'est_btc': est_btc,
'free': balance.free if balance.free is not None else 0,
'balance': balance.total if balance.total is not None else 0,
'used': balance.used if balance.used is not None else 0,
'est_stake': est_stake or 0,
'stake': stake_currency,
})
if total == 0.0:
if self._freqtrade.config.get('dry_run', False):

View File

@ -325,15 +325,16 @@ class Telegram(RPC):
def _balance(self, update: Update, context: CallbackContext) -> None:
""" Handler for /balance """
try:
result = self._rpc_balance(self._config.get('fiat_display_currency', ''))
result = self._rpc_balance(self._config['stake_currency'],
self._config.get('fiat_display_currency', ''))
output = ''
for currency in result['currencies']:
if currency['est_btc'] > 0.0001:
if currency['est_stake'] > 0.0001:
curr_output = "*{currency}:*\n" \
"\t`Available: {free: .8f}`\n" \
"\t`Balance: {balance: .8f}`\n" \
"\t`Pending: {used: .8f}`\n" \
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)
"\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency)
else:
curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)

View File

@ -0,0 +1,127 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# --- Do not remove these libs ---
from functools import reduce
from typing import Any, Callable, Dict, List
import numpy as np # noqa
import pandas as pd # noqa
from pandas import DataFrame
from skopt.space import Categorical, Dimension, Integer, Real # noqa
from freqtrade.optimize.hyperopt_interface import IHyperOpt
# --------------------------------
# Add your lib to import here
import talib.abstract as ta # noqa
import freqtrade.vendor.qtpylib.indicators as qtpylib
class {{ hyperopt }}(IHyperOpt):
"""
This is a Hyperopt template to get you started.
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md
You should:
- Add any lib you need to build your hyperopt.
You must keep:
- The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator.
The roi_space, generate_roi_table, stoploss_space methods are no longer required to be
copied in every custom hyperopt. However, you may override them if you need the
'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade.
Sample implementation of these methods can be found in
https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py
"""
@staticmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the buy strategy parameters to be used by Hyperopt.
"""
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Buy strategy Hyperopt will build and use.
"""
conditions = []
# GUARDS AND TRENDS
{{ buy_guards | indent(12) }}
# TRIGGERS
if 'trigger' in params:
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
if params['trigger'] == 'sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['close'], dataframe['sar']
))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
return populate_buy_trend
@staticmethod
def indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching buy strategy parameters.
"""
return [
{{ buy_space | indent(12) }}
]
@staticmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the sell strategy parameters to be used by Hyperopt.
"""
def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Sell strategy Hyperopt will build and use.
"""
conditions = []
# GUARDS AND TRENDS
{{ sell_guards | indent(12) }}
# TRIGGERS
if 'sell-trigger' in params:
if params['sell-trigger'] == 'sell-bb_upper':
conditions.append(dataframe['close'] > dataframe['bb_upperband'])
if params['sell-trigger'] == 'sell-macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
))
if params['sell-trigger'] == 'sell-sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['sar'], dataframe['close']
))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe
return populate_sell_trend
@staticmethod
def sell_indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching sell strategy parameters.
"""
return [
{{ sell_space | indent(12) }}
]

View File

@ -0,0 +1,138 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# --- Do not remove these libs ---
import numpy as np # noqa
import pandas as pd # noqa
from pandas import DataFrame
from freqtrade.strategy.interface import IStrategy
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class {{ strategy }}(IStrategy):
"""
This is a strategy template to get you started.
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
You can:
:return: a Dataframe with all mandatory indicators for the strategies
- Rename the class name (Do not forget to update class_name)
- Add any methods you want to build your strategy
- Add any lib you need to build your strategy
You must keep:
- the lib in the section "Do not remove these libs"
- the prototype for the methods: minimal_roi, stoploss, populate_indicators, populate_buy_trend,
populate_sell_trend, hyperopt_space, buy_strategy_generator
"""
# Strategy interface version - allow new iterations of the strategy interface.
# Check the documentation or the Sample strategy to get the latest version.
INTERFACE_VERSION = 2
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi".
minimal_roi = {
"60": 0.01,
"30": 0.02,
"0": 0.04
}
# Optimal stoploss designed for the strategy.
# This attribute will be overridden if the config file contains "stoploss".
stoploss = -0.10
# Trailing stoploss
trailing_stop = False
# trailing_stop_positive = 0.01
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
# Optimal ticker interval for the strategy.
ticker_interval = '5m'
# Run "populate_indicators()" only for new candle.
process_only_new_candles = False
# These values can be overridden in the "ask_strategy" section in the config.
use_sell_signal = True
sell_profit_only = False
ignore_roi_if_buy_signal = False
# Number of candles the strategy requires before producing valid signals
startup_candle_count: int = 20
# Optional order type mapping.
order_types = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
# Optional order time in force.
order_time_in_force = {
'buy': 'gtc',
'sell': 'gtc'
}
def informative_pairs(self):
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
These pair/interval combinations are non-tradeable, unless they are part
of the whitelist as well.
For more information, please consult the documentation
:return: List of tuples in the format (pair, interval)
Sample: return [("ETH/USDT", "5m"),
("BTC/USDT", "15m"),
]
"""
return []
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
{{ indicators | indent(8) }}
return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
{{ buy_trend | indent(16) }}
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
{{ sell_trend | indent(16) }}
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
'sell'] = 1
return dataframe

View File

@ -1,16 +1,21 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# --- Do not remove these libs ---
from functools import reduce
from typing import Any, Callable, Dict, List
import numpy as np # noqa
import talib.abstract as ta
import pandas as pd # noqa
from pandas import DataFrame
from skopt.space import Categorical, Dimension, Integer, Real # noqa
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.optimize.hyperopt_interface import IHyperOpt
# --------------------------------
# Add your lib to import here
import talib.abstract as ta # noqa
import freqtrade.vendor.qtpylib.indicators as qtpylib
class SampleHyperOpt(IHyperOpt):
"""

View File

@ -1,18 +1,21 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# --- Do not remove these libs ---
from functools import reduce
from math import exp
from typing import Any, Callable, Dict, List
from datetime import datetime
import numpy as np# noqa F401
import talib.abstract as ta
import numpy as np # noqa
import pandas as pd # noqa
from pandas import DataFrame
from skopt.space import Categorical, Dimension, Integer, Real
from skopt.space import Categorical, Dimension, Integer, Real # noqa
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.optimize.hyperopt_interface import IHyperOpt
# --------------------------------
# Add your lib to import here
import talib.abstract as ta # noqa
import freqtrade.vendor.qtpylib.indicators as qtpylib
class AdvancedSampleHyperOpt(IHyperOpt):
"""

View File

@ -1,13 +1,16 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
import numpy as np # noqa
import pandas as pd # noqa
from pandas import DataFrame
# --------------------------------
from freqtrade.strategy.interface import IStrategy
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy # noqa
# This class is a sample. Feel free to customize it.
@ -110,19 +113,18 @@ class SampleStrategy(IStrategy):
# ADX
dataframe['adx'] = ta.ADX(dataframe)
"""
# Aroon, Aroon Oscillator
aroon = ta.AROON(dataframe)
dataframe['aroonup'] = aroon['aroonup']
dataframe['aroondown'] = aroon['aroondown']
dataframe['aroonosc'] = ta.AROONOSC(dataframe)
# # Aroon, Aroon Oscillator
# aroon = ta.AROON(dataframe)
# dataframe['aroonup'] = aroon['aroonup']
# dataframe['aroondown'] = aroon['aroondown']
# dataframe['aroonosc'] = ta.AROONOSC(dataframe)
# Awesome oscillator
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
# # Awesome oscillator
# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
# # Commodity Channel Index: values Oversold:<-100, Overbought:>100
# dataframe['cci'] = ta.CCI(dataframe)
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
dataframe['cci'] = ta.CCI(dataframe)
"""
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
@ -132,42 +134,39 @@ class SampleStrategy(IStrategy):
# MFI
dataframe['mfi'] = ta.MFI(dataframe)
"""
# Minus Directional Indicator / Movement
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # Minus Directional Indicator / Movement
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# Plus Directional Indicator / Movement
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # Plus Directional Indicator / Movement
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# ROC
dataframe['roc'] = ta.ROC(dataframe)
# # ROC
# dataframe['roc'] = ta.ROC(dataframe)
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
rsi = 0.1 * (dataframe['rsi'] - 50)
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# # Stoch
# stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd']
# dataframe['slowk'] = stoch['slowk']
# Stoch
stoch = ta.STOCH(dataframe)
dataframe['slowd'] = stoch['slowd']
dataframe['slowk'] = stoch['slowk']
"""
# Stoch fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
"""
# Stoch RSI
stoch_rsi = ta.STOCHRSI(dataframe)
dataframe['fastd_rsi'] = stoch_rsi['fastd']
dataframe['fastk_rsi'] = stoch_rsi['fastk']
"""
# # Stoch RSI
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
# Overlap Studies
# ------------------------------------
@ -178,17 +177,16 @@ class SampleStrategy(IStrategy):
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
"""
# EMA - Exponential Moving Average
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# # EMA - Exponential Moving Average
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# # SMA - Simple Moving Average
# dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
# SMA - Simple Moving Average
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
"""
# SAR Parabol
dataframe['sar'] = ta.SAR(dataframe)
@ -204,65 +202,57 @@ class SampleStrategy(IStrategy):
# Pattern Recognition - Bullish candlestick patterns
# ------------------------------------
"""
# Hammer: values [0, 100]
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
# Inverted Hammer: values [0, 100]
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
# Dragonfly Doji: values [0, 100]
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
# Piercing Line: values [0, 100]
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
# Morningstar: values [0, 100]
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# Three White Soldiers: values [0, 100]
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
"""
# # Hammer: values [0, 100]
# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
# # Inverted Hammer: values [0, 100]
# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
# # Dragonfly Doji: values [0, 100]
# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
# # Piercing Line: values [0, 100]
# dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
# # Morningstar: values [0, 100]
# dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# # Three White Soldiers: values [0, 100]
# dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
# Pattern Recognition - Bearish candlestick patterns
# ------------------------------------
"""
# Hanging Man: values [0, 100]
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
# Shooting Star: values [0, 100]
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
# Gravestone Doji: values [0, 100]
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
# Dark Cloud Cover: values [0, 100]
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
# Evening Doji Star: values [0, 100]
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
# Evening Star: values [0, 100]
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
"""
# # Hanging Man: values [0, 100]
# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
# # Shooting Star: values [0, 100]
# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
# # Gravestone Doji: values [0, 100]
# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
# # Dark Cloud Cover: values [0, 100]
# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
# # Evening Doji Star: values [0, 100]
# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
# # Evening Star: values [0, 100]
# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
# Pattern Recognition - Bullish/Bearish candlestick patterns
# ------------------------------------
"""
# Three Line Strike: values [0, -100, 100]
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
# Spinning Top: values [0, -100, 100]
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# Engulfing: values [0, -100, 100]
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# Harami: values [0, -100, 100]
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# Three Outside Up/Down: values [0, -100, 100]
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# Three Inside Up/Down: values [0, -100, 100]
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
"""
# # Three Line Strike: values [0, -100, 100]
# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
# # Spinning Top: values [0, -100, 100]
# dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# # Engulfing: values [0, -100, 100]
# dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# # Harami: values [0, -100, 100]
# dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# # Three Outside Up/Down: values [0, -100, 100]
# dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# # Three Inside Up/Down: values [0, -100, 100]
# dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
# Chart type
# ------------------------------------
"""
# Heikinashi stategy
heikinashi = qtpylib.heikinashi(dataframe)
dataframe['ha_open'] = heikinashi['open']
dataframe['ha_close'] = heikinashi['close']
dataframe['ha_high'] = heikinashi['high']
dataframe['ha_low'] = heikinashi['low']
"""
# # Chart type
# # ------------------------------------
# # Heikinashi stategy
# heikinashi = qtpylib.heikinashi(dataframe)
# dataframe['ha_open'] = heikinashi['open']
# dataframe['ha_close'] = heikinashi['close']
# dataframe['ha_high'] = heikinashi['high']
# dataframe['ha_low'] = heikinashi['low']
# Retrieve best bid and best ask from the orderbook
# ------------------------------------

View File

@ -0,0 +1,3 @@
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30
(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising

View File

@ -0,0 +1 @@
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30

View File

@ -0,0 +1,8 @@
if params.get('mfi-enabled'):
conditions.append(dataframe['mfi'] < params['mfi-value'])
if params.get('fastd-enabled'):
conditions.append(dataframe['fastd'] < params['fastd-value'])
if params.get('adx-enabled'):
conditions.append(dataframe['adx'] > params['adx-value'])
if params.get('rsi-enabled'):
conditions.append(dataframe['rsi'] < params['rsi-value'])

View File

@ -0,0 +1,2 @@
if params.get('rsi-enabled'):
conditions.append(dataframe['rsi'] < params['rsi-value'])

View File

@ -0,0 +1,9 @@
Integer(10, 25, name='mfi-value'),
Integer(15, 45, name='fastd-value'),
Integer(20, 50, name='adx-value'),
Integer(20, 40, name='rsi-value'),
Categorical([True, False], name='mfi-enabled'),
Categorical([True, False], name='fastd-enabled'),
Categorical([True, False], name='adx-enabled'),
Categorical([True, False], name='rsi-enabled'),
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger')

View File

@ -0,0 +1,3 @@
Integer(20, 40, name='rsi-value'),
Categorical([True, False], name='rsi-enabled'),
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger')

View File

@ -0,0 +1,8 @@
if params.get('sell-mfi-enabled'):
conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
if params.get('sell-fastd-enabled'):
conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
if params.get('sell-adx-enabled'):
conditions.append(dataframe['adx'] < params['sell-adx-value'])
if params.get('sell-rsi-enabled'):
conditions.append(dataframe['rsi'] > params['sell-rsi-value'])

View File

@ -0,0 +1,2 @@
if params.get('sell-rsi-enabled'):
conditions.append(dataframe['rsi'] > params['sell-rsi-value'])

View File

@ -0,0 +1,11 @@
Integer(75, 100, name='sell-mfi-value'),
Integer(50, 100, name='sell-fastd-value'),
Integer(50, 100, name='sell-adx-value'),
Integer(60, 100, name='sell-rsi-value'),
Categorical([True, False], name='sell-mfi-enabled'),
Categorical([True, False], name='sell-fastd-enabled'),
Categorical([True, False], name='sell-adx-enabled'),
Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper',
'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger')

View File

@ -0,0 +1,5 @@
Integer(60, 100, name='sell-rsi-value'),
Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper',
'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger')

View File

@ -0,0 +1,161 @@
# Momentum Indicators
# ------------------------------------
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# ADX
dataframe['adx'] = ta.ADX(dataframe)
# # Aroon, Aroon Oscillator
# aroon = ta.AROON(dataframe)
# dataframe['aroonup'] = aroon['aroonup']
# dataframe['aroondown'] = aroon['aroondown']
# dataframe['aroonosc'] = ta.AROONOSC(dataframe)
# # Awesome oscillator
# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
# # Commodity Channel Index: values Oversold:<-100, Overbought:>100
# dataframe['cci'] = ta.CCI(dataframe)
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# MFI
dataframe['mfi'] = ta.MFI(dataframe)
# # Minus Directional Indicator / Movement
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # Plus Directional Indicator / Movement
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # ROC
# dataframe['roc'] = ta.ROC(dataframe)
# # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# # Stoch
# stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd']
# dataframe['slowk'] = stoch['slowk']
# Stoch fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# # Stoch RSI
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
# Overlap Studies
# ------------------------------------
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
# # EMA - Exponential Moving Average
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# # SMA - Simple Moving Average
# dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
# SAR Parabol
dataframe['sar'] = ta.SAR(dataframe)
# TEMA - Triple Exponential Moving Average
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
# Cycle Indicator
# ------------------------------------
# Hilbert Transform Indicator - SineWave
hilbert = ta.HT_SINE(dataframe)
dataframe['htsine'] = hilbert['sine']
dataframe['htleadsine'] = hilbert['leadsine']
# Pattern Recognition - Bullish candlestick patterns
# ------------------------------------
# # Hammer: values [0, 100]
# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
# # Inverted Hammer: values [0, 100]
# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
# # Dragonfly Doji: values [0, 100]
# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
# # Piercing Line: values [0, 100]
# dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
# # Morningstar: values [0, 100]
# dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# # Three White Soldiers: values [0, 100]
# dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
# Pattern Recognition - Bearish candlestick patterns
# ------------------------------------
# # Hanging Man: values [0, 100]
# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
# # Shooting Star: values [0, 100]
# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
# # Gravestone Doji: values [0, 100]
# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
# # Dark Cloud Cover: values [0, 100]
# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
# # Evening Doji Star: values [0, 100]
# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
# # Evening Star: values [0, 100]
# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
# Pattern Recognition - Bullish/Bearish candlestick patterns
# ------------------------------------
# # Three Line Strike: values [0, -100, 100]
# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
# # Spinning Top: values [0, -100, 100]
# dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# # Engulfing: values [0, -100, 100]
# dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# # Harami: values [0, -100, 100]
# dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# # Three Outside Up/Down: values [0, -100, 100]
# dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# # Three Inside Up/Down: values [0, -100, 100]
# dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
# # Chart type
# # ------------------------------------
# # Heikinashi stategy
# heikinashi = qtpylib.heikinashi(dataframe)
# dataframe['ha_open'] = heikinashi['open']
# dataframe['ha_close'] = heikinashi['close']
# dataframe['ha_high'] = heikinashi['high']
# dataframe['ha_low'] = heikinashi['low']
# Retrieve best bid and best ask from the orderbook
# ------------------------------------
"""
# first check if dataprovider is available
if self.dp:
if self.dp.runmode in ('live', 'dry_run'):
ob = self.dp.orderbook(metadata['pair'], 1)
dataframe['best_bid'] = ob['bids'][0][0]
dataframe['best_ask'] = ob['asks'][0][0]
"""

View File

@ -0,0 +1,17 @@
# Momentum Indicators
# ------------------------------------
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# Retrieve best bid and best ask from the orderbook
# ------------------------------------
"""
# first check if dataprovider is available
if self.dp:
if self.dp.runmode in ('live', 'dry_run'):
ob = self.dp.orderbook(metadata['pair'], 1)
dataframe['best_bid'] = ob['bids'][0][0]
dataframe['best_ask'] = ob['asks'][0][0]
"""

View File

@ -0,0 +1,3 @@
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling

View File

@ -0,0 +1 @@
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70

View File

@ -1,3 +1,4 @@
import csv
import logging
import sys
from collections import OrderedDict
@ -5,19 +6,21 @@ from pathlib import Path
from typing import Any, Dict, List
import arrow
import csv
import rapidjson
from tabulate import tabulate
from freqtrade import OperationalException
from freqtrade.configuration import Configuration, TimeRange, remove_credentials
from freqtrade.configuration.directory_operations import create_userdata_dir
from freqtrade.configuration import (Configuration, TimeRange,
remove_credentials)
from freqtrade.configuration.directory_operations import (copy_sample_files,
create_userdata_dir)
from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGY
from freqtrade.data.history import (convert_trades_to_ohlcv,
refresh_backtest_ohlcv_data,
refresh_backtest_trades_data)
from freqtrade.exchange import (available_exchanges, ccxt_exchanges, market_is_active,
symbol_is_pair)
from freqtrade.misc import plural
from freqtrade.exchange import (available_exchanges, ccxt_exchanges,
market_is_active, symbol_is_pair)
from freqtrade.misc import plural, render_template
from freqtrade.resolvers import ExchangeResolver
from freqtrade.state import RunMode
@ -81,12 +84,95 @@ def start_create_userdir(args: Dict[str, Any]) -> None:
:return: None
"""
if "user_data_dir" in args and args["user_data_dir"]:
create_userdata_dir(args["user_data_dir"], create_dir=True)
userdir = create_userdata_dir(args["user_data_dir"], create_dir=True)
copy_sample_files(userdir, overwrite=args["reset"])
else:
logger.warning("`create-userdir` requires --userdir to be set.")
sys.exit(1)
def deploy_new_strategy(strategy_name, strategy_path: Path, subtemplate: str):
"""
Deploy new strategy from template to strategy_path
"""
indicators = render_template(templatefile=f"subtemplates/indicators_{subtemplate}.j2",)
buy_trend = render_template(templatefile=f"subtemplates/buy_trend_{subtemplate}.j2",)
sell_trend = render_template(templatefile=f"subtemplates/sell_trend_{subtemplate}.j2",)
strategy_text = render_template(templatefile='base_strategy.py.j2',
arguments={"strategy": strategy_name,
"indicators": indicators,
"buy_trend": buy_trend,
"sell_trend": sell_trend,
})
logger.info(f"Writing strategy to `{strategy_path}`.")
strategy_path.write_text(strategy_text)
def start_new_strategy(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if "strategy" in args and args["strategy"]:
if args["strategy"] == "DefaultStrategy":
raise OperationalException("DefaultStrategy is not allowed as name.")
new_path = config['user_data_dir'] / USERPATH_STRATEGY / (args["strategy"] + ".py")
if new_path.exists():
raise OperationalException(f"`{new_path}` already exists. "
"Please choose another Strategy Name.")
deploy_new_strategy(args['strategy'], new_path, args['template'])
else:
raise OperationalException("`new-strategy` requires --strategy to be set.")
def deploy_new_hyperopt(hyperopt_name, hyperopt_path: Path, subtemplate: str):
"""
Deploys a new hyperopt template to hyperopt_path
"""
buy_guards = render_template(
templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2",)
sell_guards = render_template(
templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2",)
buy_space = render_template(
templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2",)
sell_space = render_template(
templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2",)
strategy_text = render_template(templatefile='base_hyperopt.py.j2',
arguments={"hyperopt": hyperopt_name,
"buy_guards": buy_guards,
"sell_guards": sell_guards,
"buy_space": buy_space,
"sell_space": sell_space,
})
logger.info(f"Writing hyperopt to `{hyperopt_path}`.")
hyperopt_path.write_text(strategy_text)
def start_new_hyperopt(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if "hyperopt" in args and args["hyperopt"]:
if args["hyperopt"] == "DefaultHyperopt":
raise OperationalException("DefaultHyperopt is not allowed as name.")
new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args["hyperopt"] + ".py")
if new_path.exists():
raise OperationalException(f"`{new_path}` already exists. "
"Please choose another Strategy Name.")
deploy_new_hyperopt(args['hyperopt'], new_path, args['template'])
else:
raise OperationalException("`new-hyperopt` requires --hyperopt to be set.")
def start_download_data(args: Dict[str, Any]) -> None:
"""
Download data (former download_backtest_data.py script)

View File

@ -2,7 +2,7 @@
""" Wallet """
import logging
from typing import Dict, NamedTuple
from typing import Dict, NamedTuple, Any
from freqtrade.exchange import Exchange
from freqtrade import constants
@ -72,3 +72,6 @@ class Wallets:
)
logger.info('Wallets synced.')
def get_all_balances(self) -> Dict[str, Any]:
return self._wallets

View File

@ -1,6 +1,6 @@
# requirements without requirements installable via conda
# mainly used for Raspberry pi installs
ccxt==1.19.54
ccxt==1.19.86
SQLAlchemy==1.3.11
python-telegram-bot==12.2.0
arrow==0.15.4
@ -8,10 +8,11 @@ cachetools==3.1.1
requests==2.22.0
urllib3==1.25.7
wrapt==1.11.2
jsonschema==3.1.1
jsonschema==3.2.0
TA-Lib==0.4.17
tabulate==0.8.6
coinmarketcap==5.0.3
jinja2==2.10.3
# find first, C search in arrays
py_find_1st==1.1.4

View File

@ -8,10 +8,10 @@ flake8==3.7.9
flake8-type-annotations==0.1.0
flake8-tidy-imports==3.1.0
mypy==0.740
pytest==5.2.4
pytest==5.3.0
pytest-asyncio==0.10.0
pytest-cov==2.8.1
pytest-mock==1.11.2
pytest-mock==1.12.1
pytest-random-order==1.0.4
# Convert jupyter notebooks to markdown documents

View File

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

View File

@ -78,6 +78,7 @@ setup(name='freqtrade',
'python-rapidjson',
'sdnotify',
'colorama',
'jinja2',
# from requirements.txt
'numpy',
'pandas',

View File

@ -325,7 +325,7 @@ def get_markets():
},
'price': 500000,
'cost': {
'min': 1,
'min': 0.0001,
'max': 500000,
},
},
@ -351,7 +351,7 @@ def get_markets():
},
'price': 500000,
'cost': {
'min': 1,
'min': 0.0001,
'max': 500000,
},
},
@ -376,7 +376,7 @@ def get_markets():
},
'price': 500000,
'cost': {
'min': 1,
'min': 0.0001,
'max': 500000,
},
},
@ -401,7 +401,7 @@ def get_markets():
},
'price': 500000,
'cost': {
'min': 1,
'min': 0.0001,
'max': 500000,
},
},
@ -426,7 +426,7 @@ def get_markets():
},
'price': 500000,
'cost': {
'min': 1,
'min': 0.0001,
'max': 500000,
},
},
@ -451,7 +451,7 @@ def get_markets():
},
'price': 500000,
'cost': {
'min': 1,
'min': 0.0001,
'max': 500000,
},
},
@ -479,7 +479,7 @@ def get_markets():
'max': None
},
'cost': {
'min': 0.001,
'min': 0.0001,
'max': None
}
},
@ -980,6 +980,28 @@ def tickers():
'quoteVolume': 62.68220262,
'info': {}
},
'BTC/USDT': {
'symbol': 'BTC/USDT',
'timestamp': 1573758371399,
'datetime': '2019-11-14T19:06:11.399Z',
'high': 8800.0,
'low': 8582.6,
'bid': 8648.16,
'bidVolume': 0.238771,
'ask': 8648.72,
'askVolume': 0.016253,
'vwap': 8683.13647806,
'open': 8759.7,
'close': 8648.72,
'last': 8648.72,
'previousClose': 8759.67,
'change': -110.98,
'percentage': -1.267,
'average': None,
'baseVolume': 35025.943355,
'quoteVolume': 304135046.4242901,
'info': {}
},
'ETH/USDT': {
'symbol': 'ETH/USDT',
'timestamp': 1522014804118,
@ -1067,7 +1089,29 @@ def tickers():
'baseVolume': 59698.79897,
'quoteVolume': 29132399.743954,
'info': {}
}
},
'XRP/BTC': {
'symbol': 'XRP/BTC',
'timestamp': 1573758257534,
'datetime': '2019-11-14T19:04:17.534Z',
'high': 3.126e-05,
'low': 3.061e-05,
'bid': 3.093e-05,
'bidVolume': 27901.0,
'ask': 3.095e-05,
'askVolume': 10551.0,
'vwap': 3.091e-05,
'open': 3.119e-05,
'close': 3.094e-05,
'last': 3.094e-05,
'previousClose': 3.117e-05,
'change': -2.5e-07,
'percentage': -0.802,
'average': None,
'baseVolume': 37334921.0,
'quoteVolume': 1154.19266394,
'info': {}
},
})
@ -1317,8 +1361,8 @@ def rpc_balance():
'used': 0.0
},
'XRP': {
'total': 1.0,
'free': 1.0,
'total': 0.1,
'free': 0.01,
'used': 0.0
},
'EUR': {

View File

@ -869,6 +869,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'backtesting',
'--config', 'config.json',
'--datadir', str(testdatadir),
'--strategy-path', str(Path(__file__).parents[2] / 'freqtrade/templates'),
'--ticker-interval', '1m',
'--timerange', '1510694220-1510700340',
'--enable-position-stacking',

View File

@ -360,7 +360,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
hyperopt.log_results(
{
'loss': 1,
'current_epoch': 1,
'current_epoch': 2, # This starts from 1 (in a human-friendly manner)
'results_explanation': 'foo.',
'is_initial_point': False
}
@ -374,6 +374,7 @@ def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None:
hyperopt.log_results(
{
'loss': 3,
'current_epoch': 1,
}
)
assert caplog.record_tuples == []
@ -382,13 +383,19 @@ def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None:
def test_save_trials_saves_trials(mocker, hyperopt, testdatadir, caplog) -> None:
trials = create_trials(mocker, hyperopt, testdatadir)
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
hyperopt.trials = trials
hyperopt.save_trials()
trials_file = testdatadir / 'optimize' / 'ut_trials.pickle'
assert log_has(f"Saving 1 evaluations to '{trials_file}'", caplog)
hyperopt.trials = trials
hyperopt.save_trials(final=True)
assert log_has("Saving 1 epoch.", caplog)
assert log_has(f"1 epoch saved to '{trials_file}'.", caplog)
mock_dump.assert_called_once()
hyperopt.trials = trials + trials
hyperopt.save_trials(final=True)
assert log_has("Saving 2 epochs.", caplog)
assert log_has(f"2 epochs saved to '{trials_file}'.", caplog)
def test_read_trials_returns_trials_file(mocker, hyperopt, testdatadir, caplog) -> None:
trials = create_trials(mocker, hyperopt, testdatadir)

View File

@ -100,7 +100,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co
markets=PropertyMock(return_value=shitcoinmarkets),
)
# argument: use the whitelist dynamically by exchange-volume
whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']
whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']
bot.pairlists.refresh_pairlist()
assert whitelist == bot.pairlists.whitelist
@ -135,10 +135,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
@pytest.mark.parametrize("pairlists,base_currency,whitelist_result", [
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']),
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']),
# Different sorting depending on quote or bid volume
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}],
"BTC", ['HOT/BTC', 'FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']),
"BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']),
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
"USDT", ['ETH/USDT']),
# No pair for ETH ...
@ -146,19 +146,19 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
"ETH", []),
# Precisionfilter and quote volume
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']),
{"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
# Precisionfilter bid
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"},
{"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']),
{"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']),
# PriceFilter and VolumePairList
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "PriceFilter", "low_price_ratio": 0.03}],
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']),
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
# Hot is removed by precision_filter, Fuel by low_price_filter.
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
([{"method": "VolumePairList", "number_assets": 6, "sort_key": "quoteVolume"},
{"method": "PrecisionFilter"},
{"method": "PriceFilter", "low_price_ratio": 0.02}
], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']),
], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
# StaticPairlist Only
([{"method": "StaticPairList"},
], "BTC", ['ETH/BTC', 'TKN/BTC']),
@ -285,12 +285,7 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers):
def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):
del whitelist_conf['pairlists'][0]['method']
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
with pytest.raises(OperationalException,
match=r"No Pairlist defined!"):
get_patched_freqtradebot(mocker, whitelist_conf)
assert log_has_re("No method in .*", caplog)
whitelist_conf['pairlists'] = []

View File

@ -355,29 +355,18 @@ def test_rpc_balance_handle_error(default_conf, mocker):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=mock_balance),
get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
get_tickers=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter()
result = rpc._rpc_balance(default_conf['fiat_display_currency'])
assert prec_satoshi(result['total'], 12)
assert prec_satoshi(result['value'], 180000)
assert 'USD' == result['symbol']
assert result['currencies'] == [{
'currency': 'BTC',
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'est_btc': 12.0,
}]
assert result['total'] == 12.0
with pytest.raises(RPCException, match="Error getting current tickers."):
rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
def test_rpc_balance_handle(default_conf, mocker):
def test_rpc_balance_handle(default_conf, mocker, tickers):
mock_balance = {
'BTC': {
'free': 10.0,
@ -389,7 +378,7 @@ def test_rpc_balance_handle(default_conf, mocker):
'total': 5.0,
'used': 4.0,
},
'PAX': {
'USDT': {
'free': 5.0,
'total': 10.0,
'used': 5.0,
@ -405,10 +394,9 @@ def test_rpc_balance_handle(default_conf, mocker):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=mock_balance),
get_ticker=MagicMock(
side_effect=lambda p, r: {'bid': 100} if p == "BTC/PAX" else {'bid': 0.01}),
get_tickers=tickers,
get_valid_pair_combination=MagicMock(
side_effect=lambda a, b: f"{b}/{a}" if a == "PAX" else f"{a}/{b}")
side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}")
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@ -416,30 +404,35 @@ def test_rpc_balance_handle(default_conf, mocker):
rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter()
result = rpc._rpc_balance(default_conf['fiat_display_currency'])
assert prec_satoshi(result['total'], 12.15)
assert prec_satoshi(result['value'], 182250)
result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
assert prec_satoshi(result['total'], 12.309096315)
assert prec_satoshi(result['value'], 184636.44472997)
assert 'USD' == result['symbol']
assert result['currencies'] == [
{'currency': 'BTC',
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'est_btc': 12.0,
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'est_stake': 12.0,
'stake': 'BTC',
},
{'free': 1.0,
'balance': 5.0,
'currency': 'ETH',
'est_btc': 0.05,
'used': 4.0
'est_stake': 0.30794,
'used': 4.0,
'stake': 'BTC',
},
{'free': 5.0,
'balance': 10.0,
'currency': 'PAX',
'est_btc': 0.1,
'used': 5.0}
'currency': 'USDT',
'est_stake': 0.0011563153318162476,
'used': 5.0,
'stake': 'BTC',
}
]
assert result['total'] == 12.15
assert result['total'] == 12.309096315331816
def test_rpc_start(mocker, default_conf) -> None:
@ -697,8 +690,8 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None
pair = 'XRP/BTC'
# Test not buying
default_conf['stake_amount'] = 0.0000001
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
freqtradebot.config['stake_amount'] = 0.0000001
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
pair = 'TKN/BTC'

View File

@ -23,7 +23,7 @@ _TEST_PASS = "SuperSecurePassword1!"
def botclient(default_conf, mocker):
default_conf.update({"api_server": {"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": "8080",
"listen_port": 8080,
"username": _TEST_USER,
"password": _TEST_PASS,
}})
@ -133,7 +133,10 @@ def test_api__init__(default_conf, mocker):
def test_api_run(default_conf, mocker, caplog):
default_conf.update({"api_server": {"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": "8080"}})
"listen_port": 8080,
"username": "TestUser",
"password": "testPass",
}})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock())
@ -146,7 +149,7 @@ def test_api_run(default_conf, mocker, caplog):
apiserver.run()
assert server_mock.call_count == 1
assert server_mock.call_args_list[0][0][0] == "127.0.0.1"
assert server_mock.call_args_list[0][0][1] == "8080"
assert server_mock.call_args_list[0][0][1] == 8080
assert isinstance(server_mock.call_args_list[0][0][2], Flask)
assert hasattr(apiserver, "srv")
@ -158,14 +161,14 @@ def test_api_run(default_conf, mocker, caplog):
server_mock.reset_mock()
apiserver._config.update({"api_server": {"enabled": True,
"listen_ip_address": "0.0.0.0",
"listen_port": "8089",
"listen_port": 8089,
"password": "",
}})
apiserver.run()
assert server_mock.call_count == 1
assert server_mock.call_args_list[0][0][0] == "0.0.0.0"
assert server_mock.call_args_list[0][0][1] == "8089"
assert server_mock.call_args_list[0][0][1] == 8089
assert isinstance(server_mock.call_args_list[0][0][2], Flask)
assert log_has("Starting HTTP Server at 0.0.0.0:8089", caplog)
assert log_has("Starting Local Rest Server.", caplog)
@ -186,7 +189,10 @@ def test_api_run(default_conf, mocker, caplog):
def test_api_cleanup(default_conf, mocker, caplog):
default_conf.update({"api_server": {"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": "8080"}})
"listen_port": 8080,
"username": "TestUser",
"password": "testPass",
}})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock())
mocker.patch('freqtrade.rpc.api_server.make_server', MagicMock())
@ -224,28 +230,10 @@ def test_api_stopbuy(botclient):
def test_api_balance(botclient, mocker, rpc_balance):
ftbot, client = botclient
def mock_ticker(symbol, refresh):
if symbol == 'BTC/USDT':
return {
'bid': 10000.00,
'ask': 10000.00,
'last': 10000.00,
}
elif symbol == 'XRP/BTC':
return {
'bid': 0.00001,
'ask': 0.00001,
'last': 0.00001,
}
return {
'bid': 0.1,
'ask': 0.1,
'last': 0.1,
}
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance)
mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker)
mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination',
side_effect=lambda a, b: f"{a}/{b}")
ftbot.wallets.update()
rc = client_get(client, f"{BASE_URI}/balance")
assert_response(rc)
@ -256,7 +244,8 @@ def test_api_balance(botclient, mocker, rpc_balance):
'free': 12.0,
'balance': 12.0,
'used': 0.0,
'est_btc': 12.0,
'est_stake': 12.0,
'stake': 'BTC',
}

View File

@ -173,7 +173,10 @@ def test_init_apiserver_enabled(mocker, default_conf, caplog) -> None:
default_conf["telegram"]["enabled"] = False
default_conf["api_server"] = {"enabled": True,
"listen_ip_address": "127.0.0.1",
"listen_port": "8080"}
"listen_port": 8080,
"username": "TestUser",
"password": "TestPass",
}
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
# Sleep to allow the thread to start

View File

@ -144,9 +144,9 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
def test_status(default_conf, update, mocker, fee, ticker,) -> None:
update.message.chat.id = 123
update.message.chat.id = "123"
default_conf['telegram']['enabled'] = False
default_conf['telegram']['chat_id'] = 123
default_conf['telegram']['chat_id'] = "123"
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@ -461,29 +461,10 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0]
def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance) -> None:
def mock_ticker(symbol, refresh):
if symbol == 'BTC/USDT':
return {
'bid': 10000.00,
'ask': 10000.00,
'last': 10000.00,
}
elif symbol == 'XRP/BTC':
return {
'bid': 0.00001,
'ask': 0.00001,
'last': 0.00001,
}
return {
'bid': 0.1,
'ask': 0.1,
'last': 0.1,
}
def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance)
mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker)
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination',
side_effect=lambda a, b: f"{a}/{b}")
@ -564,7 +545,8 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
'free': 1.0,
'used': 0.5,
'balance': i,
'est_btc': 1
'est_stake': 1,
'stake': 'BTC',
})
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={
'currencies': balances,

View File

@ -113,7 +113,7 @@ def test_send_msg(default_conf, mocker):
def test_exception_send_msg(default_conf, mocker, caplog):
default_conf["webhook"] = get_webhook_dict()
default_conf["webhook"]["webhookbuy"] = None
del default_conf["webhook"]["webhookbuy"]
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION})

View File

@ -36,13 +36,15 @@ def test_search_strategy():
def test_load_strategy(default_conf, result):
default_conf.update({'strategy': 'SampleStrategy'})
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'})
def test_load_strategy_base64(result, caplog, default_conf):
with open("user_data/strategies/sample_strategy.py", "rb") as file:
with (Path(__file__).parents[2] / 'freqtrade/templates/sample_strategy.py').open("rb") as file:
encoded_string = urlsafe_b64encode(file.read()).decode("utf-8")
default_conf.update({'strategy': 'SampleStrategy:{}'.format(encoded_string)})
@ -54,10 +56,10 @@ def test_load_strategy_base64(result, caplog, default_conf):
def test_load_strategy_invalid_directory(result, caplog, default_conf):
default_conf['strategy'] = 'SampleStrategy'
default_conf['strategy'] = 'DefaultStrategy'
resolver = StrategyResolver(default_conf)
extra_dir = Path.cwd() / 'some/path'
resolver._load_strategy('SampleStrategy', config=default_conf, extra_dir=extra_dir)
resolver._load_strategy('DefaultStrategy', config=default_conf, extra_dir=extra_dir)
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)

View File

@ -17,8 +17,6 @@ from freqtrade.configuration.config_validation import validate_config_schema
from freqtrade.configuration.deprecated_settings import (
check_conflicting_settings, process_deprecated_setting,
process_temporary_deprecated_settings)
from freqtrade.configuration.directory_operations import (create_datadir,
create_userdata_dir)
from freqtrade.configuration.load_config import load_config_file
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
from freqtrade.loggers import _set_loggers
@ -42,10 +40,16 @@ def test_load_config_invalid_pair(default_conf) -> None:
def test_load_config_missing_attributes(default_conf) -> None:
default_conf.pop('exchange')
conf = deepcopy(default_conf)
conf.pop('exchange')
with pytest.raises(ValidationError, match=r".*'exchange' is a required property.*"):
validate_config_schema(default_conf)
validate_config_schema(conf)
conf = deepcopy(default_conf)
conf.pop('stake_currency')
with pytest.raises(ValidationError, match=r".*'stake_currency' is a required property.*"):
validate_config_schema(conf)
def test_load_config_incorrect_stake_amount(default_conf) -> None:
@ -102,7 +106,6 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
assert validated_conf['max_open_trades'] == 0
assert 'internals' in validated_conf
assert log_has('Validating configuration ...', caplog)
def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None:
@ -134,7 +137,6 @@ def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None:
assert validated_conf['exchange']['pair_whitelist'] == conf2['exchange']['pair_whitelist']
assert 'internals' in validated_conf
assert log_has('Validating configuration ...', caplog)
def test_from_config(default_conf, mocker, caplog) -> None:
@ -161,7 +163,6 @@ def test_from_config(default_conf, mocker, caplog) -> None:
assert validated_conf['exchange']['pair_whitelist'] == conf2['exchange']['pair_whitelist']
assert validated_conf['fiat_display_currency'] == "EUR"
assert 'internals' in validated_conf
assert log_has('Validating configuration ...', caplog)
assert isinstance(validated_conf['user_data_dir'], Path)
@ -193,7 +194,6 @@ def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) ->
assert validated_conf['max_open_trades'] > 999999999
assert validated_conf['max_open_trades'] == float('inf')
assert log_has('Validating configuration ...', caplog)
assert "runmode" in validated_conf
assert validated_conf['runmode'] == RunMode.DRY_RUN
@ -670,45 +670,6 @@ def test_validate_default_conf(default_conf) -> None:
validate(default_conf, constants.CONF_SCHEMA, Draft4Validator)
def test_create_datadir(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=False))
md = mocker.patch.object(Path, 'mkdir', MagicMock())
create_datadir(default_conf, '/foo/bar')
assert md.call_args[1]['parents'] is True
assert log_has('Created data directory: /foo/bar', caplog)
def test_create_userdata_dir(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=False))
md = mocker.patch.object(Path, 'mkdir', MagicMock())
x = create_userdata_dir('/tmp/bar', create_dir=True)
assert md.call_count == 7
assert md.call_args[1]['parents'] is False
assert log_has(f'Created user-data directory: {Path("/tmp/bar")}', caplog)
assert isinstance(x, Path)
assert str(x) == str(Path("/tmp/bar"))
def test_create_userdata_dir_exists(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=True))
md = mocker.patch.object(Path, 'mkdir', MagicMock())
create_userdata_dir('/tmp/bar')
assert md.call_count == 0
def test_create_userdata_dir_exists_exception(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=False))
md = mocker.patch.object(Path, 'mkdir', MagicMock())
with pytest.raises(OperationalException,
match=r'Directory `.{1,2}tmp.{1,2}bar` does not exist.*'):
create_userdata_dir('/tmp/bar', create_dir=False)
assert md.call_count == 0
def test_validate_tsl(default_conf):
default_conf['stoploss'] = 0.0
with pytest.raises(OperationalException, match='The config stoploss needs to be different '

View File

@ -0,0 +1,91 @@
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from freqtrade import OperationalException
from freqtrade.configuration.directory_operations import (copy_sample_files,
create_datadir,
create_userdata_dir)
from tests.conftest import log_has, log_has_re
def test_create_datadir(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=False))
md = mocker.patch.object(Path, 'mkdir', MagicMock())
create_datadir(default_conf, '/foo/bar')
assert md.call_args[1]['parents'] is True
assert log_has('Created data directory: /foo/bar', caplog)
def test_create_userdata_dir(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=False))
md = mocker.patch.object(Path, 'mkdir', MagicMock())
x = create_userdata_dir('/tmp/bar', create_dir=True)
assert md.call_count == 8
assert md.call_args[1]['parents'] is False
assert log_has(f'Created user-data directory: {Path("/tmp/bar")}', caplog)
assert isinstance(x, Path)
assert str(x) == str(Path("/tmp/bar"))
def test_create_userdata_dir_exists(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=True))
md = mocker.patch.object(Path, 'mkdir', MagicMock())
create_userdata_dir('/tmp/bar')
assert md.call_count == 0
def test_create_userdata_dir_exists_exception(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=False))
md = mocker.patch.object(Path, 'mkdir', MagicMock())
with pytest.raises(OperationalException,
match=r'Directory `.{1,2}tmp.{1,2}bar` does not exist.*'):
create_userdata_dir('/tmp/bar', create_dir=False)
assert md.call_count == 0
def test_copy_sample_files(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=True))
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
copymock = mocker.patch('shutil.copy', MagicMock())
copy_sample_files(Path('/tmp/bar'))
assert copymock.call_count == 5
assert copymock.call_args_list[0][0][1] == str(
Path('/tmp/bar') / 'strategies/sample_strategy.py')
assert copymock.call_args_list[1][0][1] == str(
Path('/tmp/bar') / 'hyperopts/sample_hyperopt_advanced.py')
assert copymock.call_args_list[2][0][1] == str(
Path('/tmp/bar') / 'hyperopts/sample_hyperopt_loss.py')
assert copymock.call_args_list[3][0][1] == str(
Path('/tmp/bar') / 'hyperopts/sample_hyperopt.py')
assert copymock.call_args_list[4][0][1] == str(
Path('/tmp/bar') / 'notebooks/strategy_analysis_example.ipynb')
def test_copy_sample_files_errors(mocker, default_conf, caplog) -> None:
mocker.patch.object(Path, "is_dir", MagicMock(return_value=False))
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
mocker.patch('shutil.copy', MagicMock())
with pytest.raises(OperationalException,
match=r"Directory `.{1,2}tmp.{1,2}bar` does not exist\."):
copy_sample_files(Path('/tmp/bar'))
mocker.patch.object(Path, "is_dir", MagicMock(side_effect=[True, False]))
with pytest.raises(OperationalException,
match=r"Directory `.{1,2}tmp.{1,2}bar.{1,2}strategies` does not exist\."):
copy_sample_files(Path('/tmp/bar'))
mocker.patch.object(Path, "is_dir", MagicMock(return_value=True))
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
copy_sample_files(Path('/tmp/bar'))
assert log_has_re(r"File `.*` exists already, not deploying sample file\.", caplog)
caplog.clear()
copy_sample_files(Path('/tmp/bar'), overwrite=True)
assert log_has_re(r"File `.*` exists already, overwriting\.", caplog)

View File

@ -299,7 +299,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker,
limit_buy_order, fee) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
default_conf['stake_amount'] = 0.0000098751
default_conf['stake_amount'] = 0.00098751
default_conf['max_open_trades'] = 2
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@ -313,7 +313,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker,
trade = Trade.query.first()
assert trade is not None
assert trade.stake_amount == 0.0000098751
assert trade.stake_amount == 0.00098751
assert trade.is_open
assert trade.open_date is not None
@ -321,11 +321,11 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker,
trade = Trade.query.order_by(Trade.id.desc()).first()
assert trade is not None
assert trade.stake_amount == 0.0000098751
assert trade.stake_amount == 0.00098751
assert trade.is_open
assert trade.open_date is not None
assert Trade.total_open_trades_stakes() == 1.97502e-05
assert Trade.total_open_trades_stakes() == 1.97502e-03
def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
@ -334,6 +334,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
freqtrade = FreqtradeBot(default_conf)
freqtrade.strategy.stoploss = -0.05
markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}}
# no pair found
mocker.patch(
'freqtrade.exchange.Exchange.markets',
@ -425,7 +426,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
PropertyMock(return_value=markets)
)
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2)
assert result == min(2, 2 * 2) / 0.9
assert result == max(2, 2 * 2) / 0.9
# min amount and cost are set (amount is minial)
markets["ETH/BTC"]["limits"] = {
@ -437,7 +438,27 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
PropertyMock(return_value=markets)
)
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2)
assert result == min(8, 2 * 2) / 0.9
assert result == max(8, 2 * 2) / 0.9
def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
freqtrade = FreqtradeBot(default_conf)
freqtrade.strategy.stoploss = -0.05
markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}}
# Real Binance data
markets["ETH/BTC"]["limits"] = {
'cost': {'min': 0.0001},
'amount': {'min': 0.001}
}
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 0.020405)
assert round(result, 8) == round(max(0.0001, 0.001 * 0.020405) / 0.9, 8)
def test_create_trades(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
@ -522,8 +543,9 @@ def test_create_trades_too_small_stake_amount(default_conf, ticker, limit_buy_or
get_fee=fee,
)
default_conf['stake_amount'] = 0.000000005
freqtrade = FreqtradeBot(default_conf)
freqtrade.config['stake_amount'] = 0.000000005
patch_get_signal(freqtrade)
assert not freqtrade.create_trades()

View File

@ -212,9 +212,9 @@ def test_generate_plot_file(mocker, caplog):
fig = generate_empty_figure()
plot_mock = mocker.patch("freqtrade.plot.plotting.plot", MagicMock())
store_plot_file(fig, filename="freqtrade-plot-UNITTEST_BTC-5m.html",
directory=Path("user_data/plots"))
directory=Path("user_data/plot"))
expected_fn = str(Path("user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html"))
expected_fn = str(Path("user_data/plot/freqtrade-plot-UNITTEST_BTC-5m.html"))
assert plot_mock.call_count == 1
assert plot_mock.call_args[0][0] == fig
assert (plot_mock.call_args_list[0][1]['filename']

View File

@ -9,8 +9,9 @@ from freqtrade.state import RunMode
from freqtrade.utils import (setup_utils_configuration, start_create_userdir,
start_download_data, start_list_exchanges,
start_list_markets, start_list_timeframes,
start_new_hyperopt, start_new_strategy,
start_trading)
from tests.conftest import get_args, log_has, patch_exchange
from tests.conftest import get_args, log_has, log_has_re, patch_exchange
def test_setup_utils_configuration():
@ -442,6 +443,7 @@ def test_create_datadir_failed(caplog):
def test_create_datadir(caplog, mocker):
cud = mocker.patch("freqtrade.utils.create_userdata_dir", MagicMock())
csf = mocker.patch("freqtrade.utils.copy_sample_files", MagicMock())
args = [
"create-userdir",
"--userdir",
@ -450,9 +452,82 @@ def test_create_datadir(caplog, mocker):
start_create_userdir(get_args(args))
assert cud.call_count == 1
assert csf.call_count == 1
assert len(caplog.record_tuples) == 0
def test_start_new_strategy(mocker, caplog):
wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
args = [
"new-strategy",
"--strategy",
"CoolNewStrategy"
]
start_new_strategy(get_args(args))
assert wt_mock.call_count == 1
assert "CoolNewStrategy" in wt_mock.call_args_list[0][0][0]
assert log_has_re("Writing strategy to .*", caplog)
def test_start_new_strategy_DefaultStrat(mocker, caplog):
args = [
"new-strategy",
"--strategy",
"DefaultStrategy"
]
with pytest.raises(OperationalException,
match=r"DefaultStrategy is not allowed as name\."):
start_new_strategy(get_args(args))
def test_start_new_strategy_no_arg(mocker, caplog):
args = [
"new-strategy",
]
with pytest.raises(OperationalException,
match="`new-strategy` requires --strategy to be set."):
start_new_strategy(get_args(args))
def test_start_new_hyperopt(mocker, caplog):
wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
args = [
"new-hyperopt",
"--hyperopt",
"CoolNewhyperopt"
]
start_new_hyperopt(get_args(args))
assert wt_mock.call_count == 1
assert "CoolNewhyperopt" in wt_mock.call_args_list[0][0][0]
assert log_has_re("Writing hyperopt to .*", caplog)
def test_start_new_hyperopt_DefaultHyperopt(mocker, caplog):
args = [
"new-hyperopt",
"--hyperopt",
"DefaultHyperopt"
]
with pytest.raises(OperationalException,
match=r"DefaultHyperopt is not allowed as name\."):
start_new_hyperopt(get_args(args))
def test_start_new_hyperopt_no_arg(mocker, caplog):
args = [
"new-hyperopt",
]
with pytest.raises(OperationalException,
match="`new-hyperopt` requires --hyperopt to be set."):
start_new_hyperopt(get_args(args))
def test_download_data_keyboardInterrupt(mocker, caplog, markets):
dl_mock = mocker.patch('freqtrade.utils.refresh_backtest_ohlcv_data',
MagicMock(side_effect=KeyboardInterrupt))