Merge branch 'develop' into json-defaults
This commit is contained in:
commit
d16ccd7e37
@ -22,6 +22,7 @@ requirements:
|
|||||||
- requirements.txt
|
- requirements.txt
|
||||||
- requirements-dev.txt
|
- requirements-dev.txt
|
||||||
- requirements-plot.txt
|
- requirements-plot.txt
|
||||||
|
- requirements-pi.txt
|
||||||
|
|
||||||
|
|
||||||
# configure the branch prefix the bot is using
|
# configure the branch prefix the bot is using
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.7.2-slim-stretch
|
FROM python:3.7.3-slim-stretch
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get -y install curl build-essential libssl-dev \
|
&& apt-get -y install curl build-essential libssl-dev \
|
||||||
|
40
Dockerfile.pi
Normal file
40
Dockerfile.pi
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
FROM balenalib/raspberrypi3-debian:stretch
|
||||||
|
|
||||||
|
RUN [ "cross-build-start" ]
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get -y install wget curl build-essential libssl-dev libffi-dev \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
|
# Prepare environment
|
||||||
|
RUN mkdir /freqtrade
|
||||||
|
WORKDIR /freqtrade
|
||||||
|
|
||||||
|
# Install TA-lib
|
||||||
|
COPY build_helpers/ta-lib-0.4.0-src.tar.gz /freqtrade/
|
||||||
|
RUN tar -xzf /freqtrade/ta-lib-0.4.0-src.tar.gz \
|
||||||
|
&& cd /freqtrade/ta-lib/ \
|
||||||
|
&& ./configure \
|
||||||
|
&& make \
|
||||||
|
&& make install \
|
||||||
|
&& rm /freqtrade/ta-lib-0.4.0-src.tar.gz
|
||||||
|
|
||||||
|
ENV LD_LIBRARY_PATH /usr/local/lib
|
||||||
|
|
||||||
|
# Install berryconda
|
||||||
|
RUN wget https://github.com/jjhelmus/berryconda/releases/download/v2.0.0/Berryconda3-2.0.0-Linux-armv7l.sh \
|
||||||
|
&& bash ./Berryconda3-2.0.0-Linux-armv7l.sh -b \
|
||||||
|
&& rm Berryconda3-2.0.0-Linux-armv7l.sh
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY requirements-pi.txt /freqtrade/
|
||||||
|
RUN ~/berryconda3/bin/conda install -y numpy pandas scipy \
|
||||||
|
&& ~/berryconda3/bin/pip install -r requirements-pi.txt --no-cache-dir
|
||||||
|
|
||||||
|
# Install and execute
|
||||||
|
COPY . /freqtrade/
|
||||||
|
RUN ~/berryconda3/bin/pip install -e . --no-cache-dir
|
||||||
|
|
||||||
|
RUN [ "cross-build-end" ]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/root/berryconda3/bin/python","./freqtrade/main.py"]
|
@ -30,7 +30,8 @@
|
|||||||
"secret": "your_exchange_secret",
|
"secret": "your_exchange_secret",
|
||||||
"ccxt_config": {"enableRateLimit": true},
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
"enableRateLimit": false
|
"enableRateLimit": true,
|
||||||
|
"rateLimit": 500
|
||||||
},
|
},
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"ETH/BTC",
|
"ETH/BTC",
|
||||||
|
@ -30,7 +30,8 @@
|
|||||||
"secret": "your_exchange_secret",
|
"secret": "your_exchange_secret",
|
||||||
"ccxt_config": {"enableRateLimit": true},
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
"enableRateLimit": false
|
"enableRateLimit": true,
|
||||||
|
"rateLimit": 200
|
||||||
},
|
},
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"AST/BTC",
|
"AST/BTC",
|
||||||
|
@ -63,6 +63,7 @@
|
|||||||
"ccxt_config": {"enableRateLimit": true},
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
"enableRateLimit": false,
|
"enableRateLimit": false,
|
||||||
|
"rateLimit": 500,
|
||||||
"aiohttp_trust_env": false
|
"aiohttp_trust_env": false
|
||||||
},
|
},
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
|
@ -46,7 +46,6 @@ Mandatory Parameters are marked as **Required**.
|
|||||||
| `exchange.secret` | '' | API secret to use for the exchange. Only required when you are in production mode.
|
| `exchange.secret` | '' | API secret to use for the exchange. Only required when you are in production mode.
|
||||||
| `exchange.pair_whitelist` | [] | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param.
|
| `exchange.pair_whitelist` | [] | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param.
|
||||||
| `exchange.pair_blacklist` | [] | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param.
|
| `exchange.pair_blacklist` | [] | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param.
|
||||||
| `exchange.ccxt_rate_limit` | True | DEPRECATED!! Have CCXT handle Exchange rate limits. Depending on the exchange, having this to false can lead to temporary bans from the exchange.
|
|
||||||
| `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_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.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.
|
| `exchange.markets_refresh_interval` | 60 | The interval in minutes in which markets are reloaded.
|
||||||
@ -70,8 +69,8 @@ Mandatory Parameters are marked as **Required**.
|
|||||||
| `strategy` | DefaultStrategy | Defines Strategy class to use.
|
| `strategy` | DefaultStrategy | Defines Strategy class to use.
|
||||||
| `strategy_path` | null | Adds an additional strategy lookup path (must be a folder).
|
| `strategy_path` | null | Adds an additional strategy lookup path (must be a folder).
|
||||||
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
|
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
|
||||||
| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file.
|
|
||||||
| `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
|
| `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.
|
||||||
|
|
||||||
### Parameters in the strategy
|
### Parameters in the strategy
|
||||||
|
|
||||||
@ -217,6 +216,7 @@ The below is the default which is used if this is not configured in either strat
|
|||||||
the bot would recreate one.
|
the bot would recreate one.
|
||||||
|
|
||||||
### Understand order_time_in_force
|
### Understand order_time_in_force
|
||||||
|
|
||||||
The `order_time_in_force` configuration parameter defines the policy by which the order
|
The `order_time_in_force` configuration parameter defines the policy by which the order
|
||||||
is executed on the exchange. Three commonly used time in force are:
|
is executed on the exchange. Three commonly used time in force are:
|
||||||
|
|
||||||
@ -252,9 +252,9 @@ The possible values are: `gtc` (default), `fok` or `ioc`.
|
|||||||
This is an ongoing work. For now it is supported only for binance and only for buy orders.
|
This is an ongoing work. For now it is supported only for binance and only for buy orders.
|
||||||
Please don't change the default value unless you know what you are doing.
|
Please don't change the default value unless you know what you are doing.
|
||||||
|
|
||||||
### What values for exchange.name?
|
### Exchange configuration
|
||||||
|
|
||||||
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency
|
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency
|
||||||
exchange markets and trading APIs. The complete up-to-date list can be found in the
|
exchange markets and trading APIs. The complete up-to-date list can be found in the
|
||||||
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). However, the bot was tested
|
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). However, the bot was tested
|
||||||
with only Bittrex and Binance.
|
with only Bittrex and Binance.
|
||||||
@ -266,6 +266,30 @@ The bot was tested with the following exchanges:
|
|||||||
|
|
||||||
Feel free to test other exchanges and submit your PR to improve the bot.
|
Feel free to test other exchanges and submit your PR to improve the bot.
|
||||||
|
|
||||||
|
#### Sample exchange configuration
|
||||||
|
|
||||||
|
A exchange configuration for "binance" would look as follows:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"exchange": {
|
||||||
|
"name": "binance",
|
||||||
|
"key": "your_exchange_key",
|
||||||
|
"secret": "your_exchange_secret",
|
||||||
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
|
"ccxt_async_config": {
|
||||||
|
"enableRateLimit": true,
|
||||||
|
"rateLimit": 200
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration enables binance, as well as rate limiting to avoid bans from the exchange.
|
||||||
|
`"rateLimit": 200` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
Optimal settings for rate limiting depend on the exchange and the size of the whitelist, so an ideal parameter will vary on many other settings.
|
||||||
|
We try to provide sensible defaults per exchange where possible, if you encounter bans please make sure that `"enableRateLimit"` is enabled and increase the `"rateLimit"` parameter step by step.
|
||||||
|
|
||||||
|
|
||||||
### What values can be used for fiat_display_currency?
|
### What values can be used for fiat_display_currency?
|
||||||
|
|
||||||
The `fiat_display_currency` configuration parameter sets the base currency to use for the
|
The `fiat_display_currency` configuration parameter sets the base currency to use for the
|
||||||
|
@ -95,9 +95,9 @@ git checkout develop
|
|||||||
git checkout -b new_release
|
git checkout -b new_release
|
||||||
```
|
```
|
||||||
|
|
||||||
* edit `freqtrade/__init__.py` and add the desired version (for example `0.18.0`)
|
* Edit `freqtrade/__init__.py` and add the desired version (for example `0.18.0`)
|
||||||
* Commit this part
|
* Commit this part
|
||||||
* push that branch to the remote and create a PR
|
* push that branch to the remote and create a PR against the master branch
|
||||||
|
|
||||||
### create changelog from git commits
|
### create changelog from git commits
|
||||||
|
|
||||||
@ -108,10 +108,12 @@ git log --oneline --no-decorate --no-merges master..develop
|
|||||||
|
|
||||||
### Create github release / tag
|
### Create github release / tag
|
||||||
|
|
||||||
|
* Use the button "Draft a new release" in the Github UI (subsection releases)
|
||||||
* Use the version-number specified as tag.
|
* Use the version-number specified as tag.
|
||||||
* Use "master" as reference (this step comes after the above PR is merged).
|
* Use "master" as reference (this step comes after the above PR is merged).
|
||||||
* use the above changelog as release comment (as codeblock)
|
* Use the above changelog as release comment (as codeblock)
|
||||||
|
|
||||||
### After-release
|
### After-release
|
||||||
|
|
||||||
* update version in develop to next valid version and postfix that with `-dev` (`0.18.0 -> 0.18.1-dev`)
|
* Update version in develop to next valid version and postfix that with `-dev` (`0.18.0 -> 0.18.1-dev`).
|
||||||
|
* Create a PR against develop to update that branch.
|
||||||
|
26
docs/faq.md
26
docs/faq.md
@ -19,8 +19,7 @@ of course constantly aim to improve the bot but it will _always_ be a
|
|||||||
gamble, which should leave you with modest wins on monthly basis but
|
gamble, which should leave you with modest wins on monthly basis but
|
||||||
you can't say much from few trades.
|
you can't say much from few trades.
|
||||||
|
|
||||||
#### I’d like to change the stake amount. Can I just stop the bot with
|
#### I’d like to change the stake amount. Can I just stop the bot with /stop and then change the config.json and run it again?
|
||||||
/stop and then change the config.json and run it again?
|
|
||||||
|
|
||||||
Not quite. Trades are persisted to a database but the configuration is
|
Not quite. Trades are persisted to a database but the configuration is
|
||||||
currently only read when the bot is killed and restarted. `/stop` more
|
currently only read when the bot is killed and restarted. `/stop` more
|
||||||
@ -31,16 +30,16 @@ like pauses. You can stop your bot, adjust settings and start it again.
|
|||||||
That's great. We have a nice backtesting and hyperoptimizing setup. See
|
That's great. We have a nice backtesting and hyperoptimizing setup. See
|
||||||
the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands).
|
the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands).
|
||||||
|
|
||||||
#### Is there a setting to only SELL the coins being held and not
|
#### Is there a setting to only SELL the coins being held and not perform anymore BUYS?
|
||||||
perform anymore BUYS?
|
|
||||||
|
|
||||||
You can use the `/forcesell all` command from Telegram.
|
You can use the `/forcesell all` command from Telegram.
|
||||||
|
|
||||||
### Hyperopt module
|
### Hyperopt module
|
||||||
|
|
||||||
#### How many epoch do I need to get a good Hyperopt result?
|
#### How many epoch do I need to get a good Hyperopt result?
|
||||||
|
|
||||||
Per default Hyperopts without `-e` or `--epochs` parameter will only
|
Per default Hyperopts without `-e` or `--epochs` parameter will only
|
||||||
run 100 epochs, means 100 evals of your triggers, guards, .... Too few
|
run 100 epochs, means 100 evals of your triggers, guards, ... Too few
|
||||||
to find a great result (unless if you are very lucky), so you probably
|
to find a great result (unless if you are very lucky), so you probably
|
||||||
have to run it for 10.000 or more. But it will take an eternity to
|
have to run it for 10.000 or more. But it will take an eternity to
|
||||||
compute.
|
compute.
|
||||||
@ -64,10 +63,10 @@ Finding a great Hyperopt results takes time.
|
|||||||
If you wonder why it takes a while to find great hyperopt results
|
If you wonder why it takes a while to find great hyperopt results
|
||||||
|
|
||||||
This answer was written during the under the release 0.15.1, when we had:
|
This answer was written during the under the release 0.15.1, when we had:
|
||||||
|
|
||||||
- 8 triggers
|
- 8 triggers
|
||||||
- 9 guards: let's say we evaluate even 10 values from each
|
- 9 guards: let's say we evaluate even 10 values from each
|
||||||
- 1 stoploss calculation: let's say we want 10 values from that too to
|
- 1 stoploss calculation: let's say we want 10 values from that too to be evaluated
|
||||||
be evaluated
|
|
||||||
|
|
||||||
The following calculation is still very rough and not very precise
|
The following calculation is still very rough and not very precise
|
||||||
but it will give the idea. With only these triggers and guards there is
|
but it will give the idea. With only these triggers and guards there is
|
||||||
@ -82,10 +81,9 @@ of the search space.
|
|||||||
The Edge module is mostly a result of brainstorming of [@mishaker](https://github.com/mishaker) and [@creslinux](https://github.com/creslinux) freqtrade team members.
|
The Edge module is mostly a result of brainstorming of [@mishaker](https://github.com/mishaker) and [@creslinux](https://github.com/creslinux) freqtrade team members.
|
||||||
|
|
||||||
You can find further info on expectancy, winrate, risk management and position size in the following sources:
|
You can find further info on expectancy, winrate, risk management and position size in the following sources:
|
||||||
* https://www.tradeciety.com/ultimate-math-guide-for-traders/
|
- https://www.tradeciety.com/ultimate-math-guide-for-traders/
|
||||||
* http://www.vantharp.com/tharp-concepts/expectancy.asp
|
- http://www.vantharp.com/tharp-concepts/expectancy.asp
|
||||||
* https://samuraitradingacademy.com/trading-expectancy/
|
- https://samuraitradingacademy.com/trading-expectancy/
|
||||||
* https://www.learningmarkets.com/determining-expectancy-in-your-trading/
|
- https://www.learningmarkets.com/determining-expectancy-in-your-trading/
|
||||||
* http://www.lonestocktrader.com/make-money-trading-positive-expectancy/
|
- http://www.lonestocktrader.com/make-money-trading-positive-expectancy/
|
||||||
* https://www.babypips.com/trading/trade-expectancy-matter
|
- https://www.babypips.com/trading/trade-expectancy-matter
|
||||||
|
|
||||||
|
@ -315,7 +315,6 @@ Before installing FreqTrade on a Raspberry Pi running the official Raspbian Imag
|
|||||||
|
|
||||||
The following assumes that miniconda3 is installed and available in your environment. Last miniconda3 installation file use python 3.4, we will update to python 3.6 on this installation.
|
The following assumes that miniconda3 is installed and available in your environment. Last miniconda3 installation file use python 3.4, we will update to python 3.6 on this installation.
|
||||||
It's recommended to use (mini)conda for this as installation/compilation of `numpy`, `scipy` and `pandas` takes a long time.
|
It's recommended to use (mini)conda for this as installation/compilation of `numpy`, `scipy` and `pandas` takes a long time.
|
||||||
If you have installed it from (mini)conda, you can remove `numpy`, `scipy`, and `pandas` from `requirements.txt` before you install it with `pip`.
|
|
||||||
|
|
||||||
Additional package to install on your Raspbian, `libffi-dev` required by cryptography (from python-telegram-bot).
|
Additional package to install on your Raspbian, `libffi-dev` required by cryptography (from python-telegram-bot).
|
||||||
|
|
||||||
@ -327,7 +326,7 @@ conda activate freqtrade
|
|||||||
conda install scipy pandas numpy
|
conda install scipy pandas numpy
|
||||||
|
|
||||||
sudo apt install libffi-dev
|
sudo apt install libffi-dev
|
||||||
python3 -m pip install -r requirements.txt
|
python3 -m pip install -r requirements-pi.txt
|
||||||
python3 -m pip install -e .
|
python3 -m pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
""" FreqTrade bot """
|
""" FreqTrade bot """
|
||||||
__version__ = '0.18.2-dev'
|
__version__ = '0.18.5-dev'
|
||||||
|
|
||||||
|
|
||||||
class DependencyException(BaseException):
|
class DependencyException(BaseException):
|
||||||
|
@ -247,6 +247,22 @@ class Arguments(object):
|
|||||||
dest='timerange',
|
dest='timerange',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--max_open_trades',
|
||||||
|
help='Specify max_open_trades to use.',
|
||||||
|
default=None,
|
||||||
|
type=int,
|
||||||
|
dest='max_open_trades',
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--stake_amount',
|
||||||
|
help='Specify stake_amount.',
|
||||||
|
default=None,
|
||||||
|
type=float,
|
||||||
|
dest='stake_amount',
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hyperopt_options(parser: argparse.ArgumentParser) -> None:
|
def hyperopt_options(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
@ -267,7 +283,6 @@ class Arguments(object):
|
|||||||
dest='position_stacking',
|
dest='position_stacking',
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--dmmp', '--disable-max-market-positions',
|
'--dmmp', '--disable-max-market-positions',
|
||||||
help='Disable applying `max_open_trades` during backtest '
|
help='Disable applying `max_open_trades` during backtest '
|
||||||
@ -293,6 +308,13 @@ class Arguments(object):
|
|||||||
nargs='+',
|
nargs='+',
|
||||||
dest='spaces',
|
dest='spaces',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--print-all',
|
||||||
|
help='Print all results, not only the best ones.',
|
||||||
|
action='store_true',
|
||||||
|
dest='print_all',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
def _build_subcommands(self) -> None:
|
def _build_subcommands(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -9,11 +9,11 @@ from argparse import Namespace
|
|||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import ccxt
|
|
||||||
from jsonschema import Draft4Validator, validators
|
from jsonschema import Draft4Validator, validators
|
||||||
from jsonschema.exceptions import ValidationError, best_match
|
from jsonschema.exceptions import ValidationError, best_match
|
||||||
|
|
||||||
from freqtrade import OperationalException, constants
|
from freqtrade import OperationalException, constants
|
||||||
|
from freqtrade.exchange import is_exchange_supported, supported_exchanges
|
||||||
from freqtrade.misc import deep_merge_dicts
|
from freqtrade.misc import deep_merge_dicts
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ class Configuration(object):
|
|||||||
logger.info(f'Created data directory: {datadir}')
|
logger.info(f'Created data directory: {datadir}')
|
||||||
return datadir
|
return datadir
|
||||||
|
|
||||||
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]: # noqa: C901
|
||||||
"""
|
"""
|
||||||
Extract information for sys.argv and load Backtesting configuration
|
Extract information for sys.argv and load Backtesting configuration
|
||||||
:return: configuration as dictionary
|
:return: configuration as dictionary
|
||||||
@ -239,14 +239,24 @@ class Configuration(object):
|
|||||||
config.update({'position_stacking': True})
|
config.update({'position_stacking': True})
|
||||||
logger.info('Parameter --enable-position-stacking detected ...')
|
logger.info('Parameter --enable-position-stacking detected ...')
|
||||||
|
|
||||||
# If --disable-max-market-positions is used we add it to the configuration
|
# If --disable-max-market-positions or --max_open_trades is used we update configuration
|
||||||
if 'use_max_market_positions' in self.args and not self.args.use_max_market_positions:
|
if 'use_max_market_positions' in self.args and not self.args.use_max_market_positions:
|
||||||
config.update({'use_max_market_positions': False})
|
config.update({'use_max_market_positions': False})
|
||||||
logger.info('Parameter --disable-max-market-positions detected ...')
|
logger.info('Parameter --disable-max-market-positions detected ...')
|
||||||
logger.info('max_open_trades set to unlimited ...')
|
logger.info('max_open_trades set to unlimited ...')
|
||||||
|
elif 'max_open_trades' in self.args and self.args.max_open_trades:
|
||||||
|
config.update({'max_open_trades': self.args.max_open_trades})
|
||||||
|
logger.info('Parameter --max_open_trades detected, '
|
||||||
|
'overriding max_open_trades to: %s ...', config.get('max_open_trades'))
|
||||||
else:
|
else:
|
||||||
logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
|
logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
|
||||||
|
|
||||||
|
# If --stake_amount is used we update configuration
|
||||||
|
if 'stake_amount' in self.args and self.args.stake_amount:
|
||||||
|
config.update({'stake_amount': self.args.stake_amount})
|
||||||
|
logger.info('Parameter --stake_amount detected, overriding stake_amount to: %s ...',
|
||||||
|
config.get('stake_amount'))
|
||||||
|
|
||||||
# If --timerange is used we add it to the configuration
|
# If --timerange is used we add it to the configuration
|
||||||
if 'timerange' in self.args and self.args.timerange:
|
if 'timerange' in self.args and self.args.timerange:
|
||||||
config.update({'timerange': self.args.timerange})
|
config.update({'timerange': self.args.timerange})
|
||||||
@ -331,6 +341,10 @@ class Configuration(object):
|
|||||||
config.update({'spaces': self.args.spaces})
|
config.update({'spaces': self.args.spaces})
|
||||||
logger.info('Parameter -s/--spaces detected: %s', config.get('spaces'))
|
logger.info('Parameter -s/--spaces detected: %s', config.get('spaces'))
|
||||||
|
|
||||||
|
if 'print_all' in self.args and self.args.print_all:
|
||||||
|
config.update({'print_all': self.args.print_all})
|
||||||
|
logger.info('Parameter --print-all detected: %s', config.get('print_all'))
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]:
|
def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
@ -396,20 +410,16 @@ class Configuration(object):
|
|||||||
:return: True or raised an exception if the exchange if not supported
|
:return: True or raised an exception if the exchange if not supported
|
||||||
"""
|
"""
|
||||||
exchange = config.get('exchange', {}).get('name').lower()
|
exchange = config.get('exchange', {}).get('name').lower()
|
||||||
if exchange not in ccxt.exchanges:
|
if not is_exchange_supported(exchange):
|
||||||
|
|
||||||
exception_msg = f'Exchange "{exchange}" not supported.\n' \
|
exception_msg = f'Exchange "{exchange}" not supported.\n' \
|
||||||
f'The following exchanges are supported: {", ".join(ccxt.exchanges)}'
|
f'The following exchanges are supported: ' \
|
||||||
|
f'{", ".join(supported_exchanges())}'
|
||||||
|
|
||||||
logger.critical(exception_msg)
|
logger.critical(exception_msg)
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
exception_msg
|
exception_msg
|
||||||
)
|
)
|
||||||
# Depreciation warning
|
|
||||||
if 'ccxt_rate_limit' in config.get('exchange', {}):
|
|
||||||
logger.warning("`ccxt_rate_limit` has been deprecated in favor of "
|
|
||||||
"`ccxt_config` and `ccxt_async_config` and will be removed "
|
|
||||||
"in a future version.")
|
|
||||||
|
|
||||||
logger.debug('Exchange "%s" supported', exchange)
|
logger.debug('Exchange "%s" supported', exchange)
|
||||||
return True
|
return True
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
Functions to convert data from one format to another
|
Functions to convert data from one format to another
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
from freqtrade.misc import timeframe_to_minutes
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -58,6 +58,8 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str) -> Da
|
|||||||
using the previous close as price for "open", "high" "low" and "close", volume is set to 0
|
using the previous close as price for "open", "high" "low" and "close", volume is set to 0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
|
|
||||||
ohlc_dict = {
|
ohlc_dict = {
|
||||||
'open': 'first',
|
'open': 'first',
|
||||||
'high': 'max',
|
'high': 'max',
|
||||||
|
@ -37,23 +37,23 @@ class DataProvider(object):
|
|||||||
@property
|
@property
|
||||||
def available_pairs(self) -> List[Tuple[str, str]]:
|
def available_pairs(self) -> List[Tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
Return a list of tuples containing pair, tick_interval for which data is currently cached.
|
Return a list of tuples containing pair, ticker_interval for which data is currently cached.
|
||||||
Should be whitelist + open trades.
|
Should be whitelist + open trades.
|
||||||
"""
|
"""
|
||||||
return list(self._exchange._klines.keys())
|
return list(self._exchange._klines.keys())
|
||||||
|
|
||||||
def ohlcv(self, pair: str, tick_interval: str = None, copy: bool = True) -> DataFrame:
|
def ohlcv(self, pair: str, ticker_interval: str = None, copy: bool = True) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
get ohlcv data for the given pair as DataFrame
|
get ohlcv data for the given pair as DataFrame
|
||||||
Please check `available_pairs` to verify which pairs are currently cached.
|
Please check `available_pairs` to verify which pairs are currently cached.
|
||||||
:param pair: pair to get the data for
|
:param pair: pair to get the data for
|
||||||
:param tick_interval: ticker_interval to get pair for
|
:param ticker_interval: ticker_interval to get pair for
|
||||||
:param copy: copy dataframe before returning.
|
:param copy: copy dataframe before returning.
|
||||||
Use false only for RO operations (where the dataframe is not modified)
|
Use false only for RO operations (where the dataframe is not modified)
|
||||||
"""
|
"""
|
||||||
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
||||||
if tick_interval:
|
if ticker_interval:
|
||||||
pairtick = (pair, tick_interval)
|
pairtick = (pair, ticker_interval)
|
||||||
else:
|
else:
|
||||||
pairtick = (pair, self._config['ticker_interval'])
|
pairtick = (pair, self._config['ticker_interval'])
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ class DataProvider(object):
|
|||||||
"""
|
"""
|
||||||
get stored historic ohlcv data
|
get stored historic ohlcv data
|
||||||
:param pair: pair to get the data for
|
:param pair: pair to get the data for
|
||||||
:param tick_interval: ticker_interval to get pair for
|
:param ticker_interval: ticker_interval to get pair for
|
||||||
"""
|
"""
|
||||||
return load_pair_history(pair=pair,
|
return load_pair_history(pair=pair,
|
||||||
ticker_interval=ticker_interval,
|
ticker_interval=ticker_interval,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
Handle historic data (ohlcv).
|
Handle historic data (ohlcv).
|
||||||
includes:
|
|
||||||
|
Includes:
|
||||||
* load data for a pair (or a list of pairs) from disk
|
* load data for a pair (or a list of pairs) from disk
|
||||||
* download data from exchange and store to disk
|
* download data from exchange and store to disk
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, List, Dict, Tuple, Any
|
from typing import Optional, List, Dict, Tuple, Any
|
||||||
@ -15,8 +15,7 @@ from pandas import DataFrame
|
|||||||
from freqtrade import misc, OperationalException
|
from freqtrade import misc, OperationalException
|
||||||
from freqtrade.arguments import TimeRange
|
from freqtrade.arguments import TimeRange
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange, timeframe_to_minutes
|
||||||
from freqtrade.misc import timeframe_to_minutes
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -101,7 +100,7 @@ def load_pair_history(pair: str,
|
|||||||
download_pair_history(datadir=datadir,
|
download_pair_history(datadir=datadir,
|
||||||
exchange=exchange,
|
exchange=exchange,
|
||||||
pair=pair,
|
pair=pair,
|
||||||
tick_interval=ticker_interval,
|
ticker_interval=ticker_interval,
|
||||||
timerange=timerange)
|
timerange=timerange)
|
||||||
|
|
||||||
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
|
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
|
||||||
@ -151,7 +150,7 @@ def make_testdata_path(datadir: Optional[Path]) -> Path:
|
|||||||
return datadir or (Path(__file__).parent.parent / "tests" / "testdata").resolve()
|
return datadir or (Path(__file__).parent.parent / "tests" / "testdata").resolve()
|
||||||
|
|
||||||
|
|
||||||
def load_cached_data_for_updating(filename: Path, tick_interval: str,
|
def load_cached_data_for_updating(filename: Path, ticker_interval: str,
|
||||||
timerange: Optional[TimeRange]) -> Tuple[List[Any],
|
timerange: Optional[TimeRange]) -> Tuple[List[Any],
|
||||||
Optional[int]]:
|
Optional[int]]:
|
||||||
"""
|
"""
|
||||||
@ -165,7 +164,7 @@ def load_cached_data_for_updating(filename: Path, tick_interval: str,
|
|||||||
if timerange.starttype == 'date':
|
if timerange.starttype == 'date':
|
||||||
since_ms = timerange.startts * 1000
|
since_ms = timerange.startts * 1000
|
||||||
elif timerange.stoptype == 'line':
|
elif timerange.stoptype == 'line':
|
||||||
num_minutes = timerange.stopts * timeframe_to_minutes(tick_interval)
|
num_minutes = timerange.stopts * timeframe_to_minutes(ticker_interval)
|
||||||
since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000
|
since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000
|
||||||
|
|
||||||
# read the cached file
|
# read the cached file
|
||||||
@ -192,7 +191,7 @@ def load_cached_data_for_updating(filename: Path, tick_interval: str,
|
|||||||
def download_pair_history(datadir: Optional[Path],
|
def download_pair_history(datadir: Optional[Path],
|
||||||
exchange: Exchange,
|
exchange: Exchange,
|
||||||
pair: str,
|
pair: str,
|
||||||
tick_interval: str = '5m',
|
ticker_interval: str = '5m',
|
||||||
timerange: Optional[TimeRange] = None) -> bool:
|
timerange: Optional[TimeRange] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Download the latest ticker intervals from the exchange for the pair passed in parameters
|
Download the latest ticker intervals from the exchange for the pair passed in parameters
|
||||||
@ -202,7 +201,7 @@ def download_pair_history(datadir: Optional[Path],
|
|||||||
|
|
||||||
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
|
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
|
||||||
:param pair: pair to download
|
:param pair: pair to download
|
||||||
:param tick_interval: ticker interval
|
:param ticker_interval: ticker interval
|
||||||
:param timerange: range of time to download
|
:param timerange: range of time to download
|
||||||
:return: bool with success state
|
:return: bool with success state
|
||||||
|
|
||||||
@ -210,17 +209,17 @@ def download_pair_history(datadir: Optional[Path],
|
|||||||
try:
|
try:
|
||||||
path = make_testdata_path(datadir)
|
path = make_testdata_path(datadir)
|
||||||
filepair = pair.replace("/", "_")
|
filepair = pair.replace("/", "_")
|
||||||
filename = path.joinpath(f'{filepair}-{tick_interval}.json')
|
filename = path.joinpath(f'{filepair}-{ticker_interval}.json')
|
||||||
|
|
||||||
logger.info('Download the pair: "%s", Interval: %s', pair, tick_interval)
|
logger.info('Download the pair: "%s", Interval: %s', pair, ticker_interval)
|
||||||
|
|
||||||
data, since_ms = load_cached_data_for_updating(filename, tick_interval, timerange)
|
data, since_ms = load_cached_data_for_updating(filename, ticker_interval, timerange)
|
||||||
|
|
||||||
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
|
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
|
||||||
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
|
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
|
||||||
|
|
||||||
# Default since_ms to 30 days if nothing is given
|
# Default since_ms to 30 days if nothing is given
|
||||||
new_data = exchange.get_history(pair=pair, tick_interval=tick_interval,
|
new_data = exchange.get_history(pair=pair, ticker_interval=ticker_interval,
|
||||||
since_ms=since_ms if since_ms
|
since_ms=since_ms if since_ms
|
||||||
else
|
else
|
||||||
int(arrow.utcnow().shift(days=-30).float_timestamp) * 1000)
|
int(arrow.utcnow().shift(days=-30).float_timestamp) * 1000)
|
||||||
@ -233,5 +232,5 @@ def download_pair_history(datadir: Optional[Path],
|
|||||||
return True
|
return True
|
||||||
except BaseException:
|
except BaseException:
|
||||||
logger.info('Failed to download the pair: "%s", Interval: %s',
|
logger.info('Failed to download the pair: "%s", Interval: %s',
|
||||||
pair, tick_interval)
|
pair, ticker_interval)
|
||||||
return False
|
return False
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
from freqtrade.exchange.exchange import Exchange # noqa: F401
|
from freqtrade.exchange.exchange import Exchange # noqa: F401
|
||||||
|
from freqtrade.exchange.exchange import (is_exchange_supported, # noqa: F401
|
||||||
|
supported_exchanges)
|
||||||
|
from freqtrade.exchange.exchange import (timeframe_to_seconds, # noqa: F401
|
||||||
|
timeframe_to_minutes,
|
||||||
|
timeframe_to_msecs)
|
||||||
from freqtrade.exchange.kraken import Kraken # noqa: F401
|
from freqtrade.exchange.kraken import Kraken # noqa: F401
|
||||||
from freqtrade.exchange.binance import Binance # noqa: F401
|
from freqtrade.exchange.binance import Binance # noqa: F401
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# pragma pylint: disable=W0603
|
# pragma pylint: disable=W0603
|
||||||
""" Cryptocurrency Exchanges support """
|
"""
|
||||||
|
Cryptocurrency Exchanges support
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
import inspect
|
import inspect
|
||||||
from random import randint
|
from random import randint
|
||||||
@ -16,7 +18,6 @@ from pandas import DataFrame
|
|||||||
from freqtrade import (constants, DependencyException, OperationalException,
|
from freqtrade import (constants, DependencyException, OperationalException,
|
||||||
TemporaryError, InvalidOrderException)
|
TemporaryError, InvalidOrderException)
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.misc import timeframe_to_seconds, timeframe_to_msecs
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -138,7 +139,7 @@ class Exchange(object):
|
|||||||
# Find matching class for the given exchange name
|
# Find matching class for the given exchange name
|
||||||
name = exchange_config['name']
|
name = exchange_config['name']
|
||||||
|
|
||||||
if name not in ccxt_module.exchanges:
|
if not is_exchange_supported(name, ccxt_module):
|
||||||
raise OperationalException(f'Exchange {name} is not supported')
|
raise OperationalException(f'Exchange {name} is not supported')
|
||||||
|
|
||||||
ex_config = {
|
ex_config = {
|
||||||
@ -146,7 +147,6 @@ class Exchange(object):
|
|||||||
'secret': exchange_config.get('secret'),
|
'secret': exchange_config.get('secret'),
|
||||||
'password': exchange_config.get('password'),
|
'password': exchange_config.get('password'),
|
||||||
'uid': exchange_config.get('uid', ''),
|
'uid': exchange_config.get('uid', ''),
|
||||||
'enableRateLimit': exchange_config.get('ccxt_rate_limit', True)
|
|
||||||
}
|
}
|
||||||
if ccxt_kwargs:
|
if ccxt_kwargs:
|
||||||
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
|
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
|
||||||
@ -490,26 +490,26 @@ class Exchange(object):
|
|||||||
logger.info("returning cached ticker-data for %s", pair)
|
logger.info("returning cached ticker-data for %s", pair)
|
||||||
return self._cached_ticker[pair]
|
return self._cached_ticker[pair]
|
||||||
|
|
||||||
def get_history(self, pair: str, tick_interval: str,
|
def get_history(self, pair: str, ticker_interval: str,
|
||||||
since_ms: int) -> List:
|
since_ms: int) -> List:
|
||||||
"""
|
"""
|
||||||
Gets candle history using asyncio and returns the list of candles.
|
Gets candle history using asyncio and returns the list of candles.
|
||||||
Handles all async doing.
|
Handles all async doing.
|
||||||
"""
|
"""
|
||||||
return asyncio.get_event_loop().run_until_complete(
|
return asyncio.get_event_loop().run_until_complete(
|
||||||
self._async_get_history(pair=pair, tick_interval=tick_interval,
|
self._async_get_history(pair=pair, ticker_interval=ticker_interval,
|
||||||
since_ms=since_ms))
|
since_ms=since_ms))
|
||||||
|
|
||||||
async def _async_get_history(self, pair: str,
|
async def _async_get_history(self, pair: str,
|
||||||
tick_interval: str,
|
ticker_interval: str,
|
||||||
since_ms: int) -> List:
|
since_ms: int) -> List:
|
||||||
# Assume exchange returns 500 candles
|
# Assume exchange returns 500 candles
|
||||||
_LIMIT = 500
|
_LIMIT = 500
|
||||||
|
|
||||||
one_call = timeframe_to_msecs(tick_interval) * _LIMIT
|
one_call = timeframe_to_msecs(ticker_interval) * _LIMIT
|
||||||
logger.debug("one_call: %s msecs", one_call)
|
logger.debug("one_call: %s msecs", one_call)
|
||||||
input_coroutines = [self._async_get_candle_history(
|
input_coroutines = [self._async_get_candle_history(
|
||||||
pair, tick_interval, since) for since in
|
pair, ticker_interval, since) for since in
|
||||||
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
|
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
|
||||||
|
|
||||||
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
|
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
|
||||||
@ -549,14 +549,14 @@ class Exchange(object):
|
|||||||
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
|
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
|
||||||
continue
|
continue
|
||||||
pair = res[0]
|
pair = res[0]
|
||||||
tick_interval = res[1]
|
ticker_interval = res[1]
|
||||||
ticks = res[2]
|
ticks = res[2]
|
||||||
# keeping last candle time as last refreshed time of the pair
|
# keeping last candle time as last refreshed time of the pair
|
||||||
if ticks:
|
if ticks:
|
||||||
self._pairs_last_refresh_time[(pair, tick_interval)] = ticks[-1][0] // 1000
|
self._pairs_last_refresh_time[(pair, ticker_interval)] = ticks[-1][0] // 1000
|
||||||
# keeping parsed dataframe in cache
|
# keeping parsed dataframe in cache
|
||||||
self._klines[(pair, tick_interval)] = parse_ticker_dataframe(
|
self._klines[(pair, ticker_interval)] = parse_ticker_dataframe(
|
||||||
ticks, tick_interval, fill_missing=True)
|
ticks, ticker_interval, fill_missing=True)
|
||||||
return tickers
|
return tickers
|
||||||
|
|
||||||
def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool:
|
def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool:
|
||||||
@ -567,17 +567,17 @@ class Exchange(object):
|
|||||||
+ interval_in_sec) >= arrow.utcnow().timestamp)
|
+ interval_in_sec) >= arrow.utcnow().timestamp)
|
||||||
|
|
||||||
@retrier_async
|
@retrier_async
|
||||||
async def _async_get_candle_history(self, pair: str, tick_interval: str,
|
async def _async_get_candle_history(self, pair: str, ticker_interval: str,
|
||||||
since_ms: Optional[int] = None) -> Tuple[str, str, List]:
|
since_ms: Optional[int] = None) -> Tuple[str, str, List]:
|
||||||
"""
|
"""
|
||||||
Asyncronously gets candle histories using fetch_ohlcv
|
Asyncronously gets candle histories using fetch_ohlcv
|
||||||
returns tuple: (pair, tick_interval, ohlcv_list)
|
returns tuple: (pair, ticker_interval, ohlcv_list)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# fetch ohlcv asynchronously
|
# fetch ohlcv asynchronously
|
||||||
logger.debug("fetching %s, %s since %s ...", pair, tick_interval, since_ms)
|
logger.debug("fetching %s, %s since %s ...", pair, ticker_interval, since_ms)
|
||||||
|
|
||||||
data = await self._api_async.fetch_ohlcv(pair, timeframe=tick_interval,
|
data = await self._api_async.fetch_ohlcv(pair, timeframe=ticker_interval,
|
||||||
since=since_ms)
|
since=since_ms)
|
||||||
|
|
||||||
# Because some exchange sort Tickers ASC and other DESC.
|
# Because some exchange sort Tickers ASC and other DESC.
|
||||||
@ -589,9 +589,9 @@ class Exchange(object):
|
|||||||
data = sorted(data, key=lambda x: x[0])
|
data = sorted(data, key=lambda x: x[0])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logger.exception("Error loading %s. Result was %s.", pair, data)
|
logger.exception("Error loading %s. Result was %s.", pair, data)
|
||||||
return pair, tick_interval, []
|
return pair, ticker_interval, []
|
||||||
logger.debug("done fetching %s, %s ...", pair, tick_interval)
|
logger.debug("done fetching %s, %s ...", pair, ticker_interval)
|
||||||
return pair, tick_interval, data
|
return pair, ticker_interval, data
|
||||||
|
|
||||||
except ccxt.NotSupported as e:
|
except ccxt.NotSupported as e:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
@ -690,3 +690,34 @@ class Exchange(object):
|
|||||||
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
|
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
|
def is_exchange_supported(exchange: str, ccxt_module=None) -> bool:
|
||||||
|
return exchange in supported_exchanges(ccxt_module)
|
||||||
|
|
||||||
|
|
||||||
|
def supported_exchanges(ccxt_module=None) -> List[str]:
|
||||||
|
return ccxt_module.exchanges if ccxt_module is not None else ccxt.exchanges
|
||||||
|
|
||||||
|
|
||||||
|
def timeframe_to_seconds(ticker_interval: str) -> int:
|
||||||
|
"""
|
||||||
|
Translates the timeframe interval value written in the human readable
|
||||||
|
form ('1m', '5m', '1h', '1d', '1w', etc.) to the number
|
||||||
|
of seconds for one timeframe interval.
|
||||||
|
"""
|
||||||
|
return ccxt.Exchange.parse_timeframe(ticker_interval)
|
||||||
|
|
||||||
|
|
||||||
|
def timeframe_to_minutes(ticker_interval: str) -> int:
|
||||||
|
"""
|
||||||
|
Same as above, but returns minutes.
|
||||||
|
"""
|
||||||
|
return ccxt.Exchange.parse_timeframe(ticker_interval) // 60
|
||||||
|
|
||||||
|
|
||||||
|
def timeframe_to_msecs(ticker_interval: str) -> int:
|
||||||
|
"""
|
||||||
|
Same as above, but returns milliseconds.
|
||||||
|
"""
|
||||||
|
return ccxt.Exchange.parse_timeframe(ticker_interval) * 1000
|
||||||
|
@ -16,7 +16,7 @@ from freqtrade import (DependencyException, OperationalException, InvalidOrderEx
|
|||||||
from freqtrade.data.converter import order_book_to_dataframe
|
from freqtrade.data.converter import order_book_to_dataframe
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.edge import Edge
|
from freqtrade.edge import Edge
|
||||||
from freqtrade.misc import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.rpc import RPCManager, RPCMessageType
|
from freqtrade.rpc import RPCManager, RPCMessageType
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver
|
||||||
@ -460,7 +460,7 @@ class FreqtradeBot(object):
|
|||||||
def get_real_amount(self, trade: Trade, order: Dict) -> float:
|
def get_real_amount(self, trade: Trade, order: Dict) -> float:
|
||||||
"""
|
"""
|
||||||
Get real amount for the trade
|
Get real amount for the trade
|
||||||
Necessary for self.exchanges which charge fees in base currency (e.g. binance)
|
Necessary for exchanges which charge fees in base currency (e.g. binance)
|
||||||
"""
|
"""
|
||||||
order_amount = order['amount']
|
order_amount = order['amount']
|
||||||
# Only run for closed orders
|
# Only run for closed orders
|
||||||
@ -522,6 +522,10 @@ class FreqtradeBot(object):
|
|||||||
|
|
||||||
trade.update(order)
|
trade.update(order)
|
||||||
|
|
||||||
|
# Updating wallets when order is closed
|
||||||
|
if not trade.is_open:
|
||||||
|
self.wallets.update()
|
||||||
|
|
||||||
def get_sell_rate(self, pair: str, refresh: bool) -> float:
|
def get_sell_rate(self, pair: str, refresh: bool) -> float:
|
||||||
"""
|
"""
|
||||||
Get sell rate - either using get-ticker bid or first bid based on orderbook
|
Get sell rate - either using get-ticker bid or first bid based on orderbook
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
"""
|
"""
|
||||||
Various tool function for Freqtrade and scripts
|
Various tool function for Freqtrade and scripts
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import gzip
|
import gzip
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from ccxt import Exchange
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
import rapidjson
|
import rapidjson
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -132,26 +131,3 @@ def deep_merge_dicts(source, destination):
|
|||||||
destination[key] = value
|
destination[key] = value
|
||||||
|
|
||||||
return destination
|
return destination
|
||||||
|
|
||||||
|
|
||||||
def timeframe_to_seconds(ticker_interval: str) -> int:
|
|
||||||
"""
|
|
||||||
Translates the timeframe interval value written in the human readable
|
|
||||||
form ('1m', '5m', '1h', '1d', '1w', etc.) to the number
|
|
||||||
of seconds for one timeframe interval.
|
|
||||||
"""
|
|
||||||
return Exchange.parse_timeframe(ticker_interval)
|
|
||||||
|
|
||||||
|
|
||||||
def timeframe_to_minutes(ticker_interval: str) -> int:
|
|
||||||
"""
|
|
||||||
Same as above, but returns minutes.
|
|
||||||
"""
|
|
||||||
return Exchange.parse_timeframe(ticker_interval) // 60
|
|
||||||
|
|
||||||
|
|
||||||
def timeframe_to_msecs(ticker_interval: str) -> int:
|
|
||||||
"""
|
|
||||||
Same as above, but returns milliseconds.
|
|
||||||
"""
|
|
||||||
return Exchange.parse_timeframe(ticker_interval) * 1000
|
|
||||||
|
@ -19,12 +19,14 @@ from freqtrade.arguments import Arguments
|
|||||||
from freqtrade.configuration import Configuration
|
from freqtrade.configuration import Configuration
|
||||||
from freqtrade.data import history
|
from freqtrade.data import history
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.misc import file_dump_json, timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
|
from freqtrade.misc import file_dump_json
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from freqtrade.strategy.interface import SellType, IStrategy
|
from freqtrade.strategy.interface import SellType, IStrategy
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ class Hyperopt(Backtesting):
|
|||||||
"""
|
"""
|
||||||
Log results if it is better than any previous evaluation
|
Log results if it is better than any previous evaluation
|
||||||
"""
|
"""
|
||||||
if results['loss'] < self.current_best_loss:
|
if self.config.get('print_all', False) or results['loss'] < self.current_best_loss:
|
||||||
current = results['current_tries']
|
current = results['current_tries']
|
||||||
total = results['total_tries']
|
total = results['total_tries']
|
||||||
res = results['result']
|
res = results['result']
|
||||||
|
@ -157,12 +157,15 @@ class StrategyResolver(IResolver):
|
|||||||
getfullargspec(strategy.populate_indicators).args)
|
getfullargspec(strategy.populate_indicators).args)
|
||||||
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
|
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
|
||||||
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
|
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
|
||||||
|
try:
|
||||||
return import_strategy(strategy, config=config)
|
return import_strategy(strategy, config=config)
|
||||||
|
except TypeError as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Impossible to load strategy '{strategy}' from {_path}. Error: {e}")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
|
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
|
||||||
|
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Impossible to load Strategy '{}'. This class does not exist"
|
f"Impossible to load Strategy '{strategy_name}'. This class does not exist"
|
||||||
" or contains Python code errors".format(strategy_name)
|
" or contains Python code errors"
|
||||||
)
|
)
|
||||||
|
@ -20,6 +20,9 @@ logger = logging.getLogger(__name__)
|
|||||||
logger.debug('Included module rpc.telegram ...')
|
logger.debug('Included module rpc.telegram ...')
|
||||||
|
|
||||||
|
|
||||||
|
MAX_TELEGRAM_MESSAGE_LENGTH = 4096
|
||||||
|
|
||||||
|
|
||||||
def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
|
def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
|
||||||
"""
|
"""
|
||||||
Decorator to check if the message comes from the correct chat_id
|
Decorator to check if the message comes from the correct chat_id
|
||||||
@ -266,7 +269,8 @@ class Telegram(RPC):
|
|||||||
headers=[
|
headers=[
|
||||||
'Day',
|
'Day',
|
||||||
f'Profit {stake_cur}',
|
f'Profit {stake_cur}',
|
||||||
f'Profit {fiat_disp_cur}'
|
f'Profit {fiat_disp_cur}',
|
||||||
|
f'Trades'
|
||||||
],
|
],
|
||||||
tablefmt='simple')
|
tablefmt='simple')
|
||||||
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats_tab}</pre>'
|
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats_tab}</pre>'
|
||||||
@ -327,13 +331,20 @@ class Telegram(RPC):
|
|||||||
output = ''
|
output = ''
|
||||||
for currency in result['currencies']:
|
for currency in result['currencies']:
|
||||||
if currency['est_btc'] > 0.0001:
|
if currency['est_btc'] > 0.0001:
|
||||||
output += "*{currency}:*\n" \
|
curr_output = "*{currency}:*\n" \
|
||||||
"\t`Available: {available: .8f}`\n" \
|
"\t`Available: {available: .8f}`\n" \
|
||||||
"\t`Balance: {balance: .8f}`\n" \
|
"\t`Balance: {balance: .8f}`\n" \
|
||||||
"\t`Pending: {pending: .8f}`\n" \
|
"\t`Pending: {pending: .8f}`\n" \
|
||||||
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)
|
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)
|
||||||
else:
|
else:
|
||||||
output += "*{currency}:* not showing <1$ amount \n".format(**currency)
|
curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)
|
||||||
|
|
||||||
|
# Handle overflowing messsage length
|
||||||
|
if len(output + curr_output) >= MAX_TELEGRAM_MESSAGE_LENGTH:
|
||||||
|
self._send_msg(output, bot=bot)
|
||||||
|
output = curr_output
|
||||||
|
else:
|
||||||
|
output += curr_output
|
||||||
|
|
||||||
output += "\n*Estimated Value*:\n" \
|
output += "\n*Estimated Value*:\n" \
|
||||||
"\t`BTC: {total: .8f}`\n" \
|
"\t`BTC: {total: .8f}`\n" \
|
||||||
|
@ -6,6 +6,7 @@ from freqtrade.strategy.interface import IStrategy
|
|||||||
# Import Default-Strategy to have hyperopt correctly resolve
|
# Import Default-Strategy to have hyperopt correctly resolve
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy # noqa: F401
|
from freqtrade.strategy.default_strategy import DefaultStrategy # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -16,7 +17,6 @@ def import_strategy(strategy: IStrategy, config: dict) -> IStrategy:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Copy all attributes from base class and class
|
# Copy all attributes from base class and class
|
||||||
|
|
||||||
comb = {**strategy.__class__.__dict__, **strategy.__dict__}
|
comb = {**strategy.__class__.__dict__, **strategy.__dict__}
|
||||||
|
|
||||||
# Delete '_abc_impl' from dict as deepcopy fails on 3.7 with
|
# Delete '_abc_impl' from dict as deepcopy fails on 3.7 with
|
||||||
@ -26,6 +26,7 @@ def import_strategy(strategy: IStrategy, config: dict) -> IStrategy:
|
|||||||
del comb['_abc_impl']
|
del comb['_abc_impl']
|
||||||
|
|
||||||
attr = deepcopy(comb)
|
attr = deepcopy(comb)
|
||||||
|
|
||||||
# Adjust module name
|
# Adjust module name
|
||||||
attr['__module__'] = 'freqtrade.strategy'
|
attr['__module__'] = 'freqtrade.strategy'
|
||||||
|
|
||||||
|
@ -13,10 +13,11 @@ import arrow
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.misc import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.wallets import Wallets
|
from freqtrade.wallets import Wallets
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,31 +9,31 @@ from freqtrade.tests.conftest import get_patched_exchange
|
|||||||
|
|
||||||
def test_ohlcv(mocker, default_conf, ticker_history):
|
def test_ohlcv(mocker, default_conf, ticker_history):
|
||||||
default_conf["runmode"] = RunMode.DRY_RUN
|
default_conf["runmode"] = RunMode.DRY_RUN
|
||||||
tick_interval = default_conf["ticker_interval"]
|
ticker_interval = default_conf["ticker_interval"]
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
exchange._klines[("XRP/BTC", tick_interval)] = ticker_history
|
exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history
|
||||||
exchange._klines[("UNITTEST/BTC", tick_interval)] = ticker_history
|
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history
|
||||||
dp = DataProvider(default_conf, exchange)
|
dp = DataProvider(default_conf, exchange)
|
||||||
assert dp.runmode == RunMode.DRY_RUN
|
assert dp.runmode == RunMode.DRY_RUN
|
||||||
assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", tick_interval))
|
assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", ticker_interval))
|
||||||
assert isinstance(dp.ohlcv("UNITTEST/BTC", tick_interval), DataFrame)
|
assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame)
|
||||||
assert dp.ohlcv("UNITTEST/BTC", tick_interval) is not ticker_history
|
assert dp.ohlcv("UNITTEST/BTC", ticker_interval) is not ticker_history
|
||||||
assert dp.ohlcv("UNITTEST/BTC", tick_interval, copy=False) is ticker_history
|
assert dp.ohlcv("UNITTEST/BTC", ticker_interval, copy=False) is ticker_history
|
||||||
assert not dp.ohlcv("UNITTEST/BTC", tick_interval).empty
|
assert not dp.ohlcv("UNITTEST/BTC", ticker_interval).empty
|
||||||
assert dp.ohlcv("NONESENSE/AAA", tick_interval).empty
|
assert dp.ohlcv("NONESENSE/AAA", ticker_interval).empty
|
||||||
|
|
||||||
# Test with and without parameter
|
# Test with and without parameter
|
||||||
assert dp.ohlcv("UNITTEST/BTC", tick_interval).equals(dp.ohlcv("UNITTEST/BTC"))
|
assert dp.ohlcv("UNITTEST/BTC", ticker_interval).equals(dp.ohlcv("UNITTEST/BTC"))
|
||||||
|
|
||||||
default_conf["runmode"] = RunMode.LIVE
|
default_conf["runmode"] = RunMode.LIVE
|
||||||
dp = DataProvider(default_conf, exchange)
|
dp = DataProvider(default_conf, exchange)
|
||||||
assert dp.runmode == RunMode.LIVE
|
assert dp.runmode == RunMode.LIVE
|
||||||
assert isinstance(dp.ohlcv("UNITTEST/BTC", tick_interval), DataFrame)
|
assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame)
|
||||||
|
|
||||||
default_conf["runmode"] = RunMode.BACKTEST
|
default_conf["runmode"] = RunMode.BACKTEST
|
||||||
dp = DataProvider(default_conf, exchange)
|
dp = DataProvider(default_conf, exchange)
|
||||||
assert dp.runmode == RunMode.BACKTEST
|
assert dp.runmode == RunMode.BACKTEST
|
||||||
assert dp.ohlcv("UNITTEST/BTC", tick_interval).empty
|
assert dp.ohlcv("UNITTEST/BTC", ticker_interval).empty
|
||||||
|
|
||||||
|
|
||||||
def test_historic_ohlcv(mocker, default_conf, ticker_history):
|
def test_historic_ohlcv(mocker, default_conf, ticker_history):
|
||||||
@ -54,15 +54,15 @@ def test_historic_ohlcv(mocker, default_conf, ticker_history):
|
|||||||
def test_available_pairs(mocker, default_conf, ticker_history):
|
def test_available_pairs(mocker, default_conf, ticker_history):
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
|
|
||||||
tick_interval = default_conf["ticker_interval"]
|
ticker_interval = default_conf["ticker_interval"]
|
||||||
exchange._klines[("XRP/BTC", tick_interval)] = ticker_history
|
exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history
|
||||||
exchange._klines[("UNITTEST/BTC", tick_interval)] = ticker_history
|
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history
|
||||||
dp = DataProvider(default_conf, exchange)
|
dp = DataProvider(default_conf, exchange)
|
||||||
|
|
||||||
assert len(dp.available_pairs) == 2
|
assert len(dp.available_pairs) == 2
|
||||||
assert dp.available_pairs == [
|
assert dp.available_pairs == [
|
||||||
("XRP/BTC", tick_interval),
|
("XRP/BTC", ticker_interval),
|
||||||
("UNITTEST/BTC", tick_interval),
|
("UNITTEST/BTC", ticker_interval),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -71,10 +71,10 @@ def test_refresh(mocker, default_conf, ticker_history):
|
|||||||
mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock)
|
mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock)
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
||||||
tick_interval = default_conf["ticker_interval"]
|
ticker_interval = default_conf["ticker_interval"]
|
||||||
pairs = [("XRP/BTC", tick_interval), ("UNITTEST/BTC", tick_interval)]
|
pairs = [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval)]
|
||||||
|
|
||||||
pairs_non_trad = [("ETH/USDT", tick_interval), ("BTC/TUSD", "1h")]
|
pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")]
|
||||||
|
|
||||||
dp = DataProvider(default_conf, exchange)
|
dp = DataProvider(default_conf, exchange)
|
||||||
dp.refresh(pairs)
|
dp.refresh(pairs)
|
||||||
|
@ -242,10 +242,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf) -> Non
|
|||||||
|
|
||||||
assert download_pair_history(datadir=None, exchange=exchange,
|
assert download_pair_history(datadir=None, exchange=exchange,
|
||||||
pair='MEME/BTC',
|
pair='MEME/BTC',
|
||||||
tick_interval='1m')
|
ticker_interval='1m')
|
||||||
assert download_pair_history(datadir=None, exchange=exchange,
|
assert download_pair_history(datadir=None, exchange=exchange,
|
||||||
pair='CFI/BTC',
|
pair='CFI/BTC',
|
||||||
tick_interval='1m')
|
ticker_interval='1m')
|
||||||
assert not exchange._pairs_last_refresh_time
|
assert not exchange._pairs_last_refresh_time
|
||||||
assert os.path.isfile(file1_1) is True
|
assert os.path.isfile(file1_1) is True
|
||||||
assert os.path.isfile(file2_1) is True
|
assert os.path.isfile(file2_1) is True
|
||||||
@ -259,10 +259,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf) -> Non
|
|||||||
|
|
||||||
assert download_pair_history(datadir=None, exchange=exchange,
|
assert download_pair_history(datadir=None, exchange=exchange,
|
||||||
pair='MEME/BTC',
|
pair='MEME/BTC',
|
||||||
tick_interval='5m')
|
ticker_interval='5m')
|
||||||
assert download_pair_history(datadir=None, exchange=exchange,
|
assert download_pair_history(datadir=None, exchange=exchange,
|
||||||
pair='CFI/BTC',
|
pair='CFI/BTC',
|
||||||
tick_interval='5m')
|
ticker_interval='5m')
|
||||||
assert not exchange._pairs_last_refresh_time
|
assert not exchange._pairs_last_refresh_time
|
||||||
assert os.path.isfile(file1_5) is True
|
assert os.path.isfile(file1_5) is True
|
||||||
assert os.path.isfile(file2_5) is True
|
assert os.path.isfile(file2_5) is True
|
||||||
@ -280,8 +280,8 @@ def test_download_pair_history2(mocker, default_conf) -> None:
|
|||||||
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=tick)
|
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=tick)
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
download_pair_history(None, exchange, pair="UNITTEST/BTC", tick_interval='1m')
|
download_pair_history(None, exchange, pair="UNITTEST/BTC", ticker_interval='1m')
|
||||||
download_pair_history(None, exchange, pair="UNITTEST/BTC", tick_interval='3m')
|
download_pair_history(None, exchange, pair="UNITTEST/BTC", ticker_interval='3m')
|
||||||
assert json_dump_mock.call_count == 2
|
assert json_dump_mock.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
@ -298,7 +298,7 @@ def test_download_backtesting_data_exception(ticker_history, mocker, caplog, def
|
|||||||
|
|
||||||
assert not download_pair_history(datadir=None, exchange=exchange,
|
assert not download_pair_history(datadir=None, exchange=exchange,
|
||||||
pair='MEME/BTC',
|
pair='MEME/BTC',
|
||||||
tick_interval='1m')
|
ticker_interval='1m')
|
||||||
# clean files freshly downloaded
|
# clean files freshly downloaded
|
||||||
_clean_test_file(file1_1)
|
_clean_test_file(file1_1)
|
||||||
_clean_test_file(file1_5)
|
_clean_test_file(file1_5)
|
||||||
|
@ -941,8 +941,8 @@ def test_get_history(default_conf, mocker, caplog, exchange_name):
|
|||||||
]
|
]
|
||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
|
|
||||||
async def mock_candle_hist(pair, tick_interval, since_ms):
|
async def mock_candle_hist(pair, ticker_interval, since_ms):
|
||||||
return pair, tick_interval, tick
|
return pair, ticker_interval, tick
|
||||||
|
|
||||||
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
||||||
# one_call calculation * 1.8 should do 2 calls
|
# one_call calculation * 1.8 should do 2 calls
|
||||||
@ -1038,7 +1038,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
|
|||||||
# exchange = Exchange(default_conf)
|
# exchange = Exchange(default_conf)
|
||||||
await async_ccxt_exception(mocker, default_conf, MagicMock(),
|
await async_ccxt_exception(mocker, default_conf, MagicMock(),
|
||||||
"_async_get_candle_history", "fetch_ohlcv",
|
"_async_get_candle_history", "fetch_ohlcv",
|
||||||
pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
|
pair='ABCD/BTC', ticker_interval=default_conf['ticker_interval'])
|
||||||
|
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'):
|
with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'):
|
||||||
|
@ -3,7 +3,7 @@ from typing import NamedTuple, List
|
|||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.misc import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
|
|
||||||
ticker_start_time = arrow.get(2018, 10, 3)
|
ticker_start_time = arrow.get(2018, 10, 3)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from freqtrade import optimize
|
from freqtrade import optimize
|
||||||
from freqtrade.arguments import TimeRange
|
from freqtrade.arguments import TimeRange
|
||||||
from freqtrade.data import history
|
from freqtrade.data import history
|
||||||
from freqtrade.misc import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||||
from freqtrade.tests.conftest import log_has, patch_exchange
|
from freqtrade.tests.conftest import log_has, patch_exchange
|
||||||
|
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from random import randint
|
from random import choice, randint
|
||||||
|
from string import ascii_uppercase
|
||||||
from unittest.mock import MagicMock, PropertyMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
@ -20,7 +21,8 @@ from freqtrade.rpc import RPCMessageType
|
|||||||
from freqtrade.rpc.telegram import Telegram, authorized_only
|
from freqtrade.rpc.telegram import Telegram, authorized_only
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange)
|
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has,
|
||||||
|
patch_exchange)
|
||||||
from freqtrade.tests.test_freqtradebot import patch_get_signal
|
from freqtrade.tests.test_freqtradebot import patch_get_signal
|
||||||
|
|
||||||
|
|
||||||
@ -587,6 +589,45 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
|
|||||||
assert 'all balances are zero' in result
|
assert 'all balances are zero' in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_balance_handle_too_large_response(default_conf, update, mocker) -> None:
|
||||||
|
balances = []
|
||||||
|
for i in range(100):
|
||||||
|
curr = choice(ascii_uppercase) + choice(ascii_uppercase) + choice(ascii_uppercase)
|
||||||
|
balances.append({
|
||||||
|
'currency': curr,
|
||||||
|
'available': 1.0,
|
||||||
|
'pending': 0.5,
|
||||||
|
'balance': i,
|
||||||
|
'est_btc': 1
|
||||||
|
})
|
||||||
|
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={
|
||||||
|
'currencies': balances,
|
||||||
|
'total': 100.0,
|
||||||
|
'symbol': 100.0,
|
||||||
|
'value': 1000.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
|
_init=MagicMock(),
|
||||||
|
_send_msg=msg_mock
|
||||||
|
)
|
||||||
|
|
||||||
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
patch_get_signal(freqtradebot, (True, False))
|
||||||
|
|
||||||
|
telegram = Telegram(freqtradebot)
|
||||||
|
|
||||||
|
telegram._balance(bot=MagicMock(), update=update)
|
||||||
|
assert msg_mock.call_count > 1
|
||||||
|
# Test if wrap happens around 4000 -
|
||||||
|
# and each single currency-output is around 120 characters long so we need
|
||||||
|
# an offset to avoid random test failures
|
||||||
|
assert len(msg_mock.call_args_list[0][0][0]) < 4096
|
||||||
|
assert len(msg_mock.call_args_list[0][0][0]) > (4096 - 120)
|
||||||
|
|
||||||
|
|
||||||
def test_start_handle(default_conf, update, mocker) -> None:
|
def test_start_handle(default_conf, update, mocker) -> None:
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
||||||
import logging
|
import logging
|
||||||
|
import warnings
|
||||||
from base64 import urlsafe_b64encode
|
from base64 import urlsafe_b64encode
|
||||||
from os import path
|
from os import path
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import warnings
|
from unittest.mock import Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from freqtrade.resolvers import StrategyResolver
|
||||||
from freqtrade.strategy import import_strategy
|
from freqtrade.strategy import import_strategy
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||||
from freqtrade.strategy.interface import IStrategy
|
from freqtrade.strategy.interface import IStrategy
|
||||||
from freqtrade.resolvers import StrategyResolver
|
from freqtrade.tests.conftest import log_has_re
|
||||||
|
|
||||||
|
|
||||||
def test_import_strategy(caplog):
|
def test_import_strategy(caplog):
|
||||||
@ -94,6 +96,16 @@ def test_load_not_found_strategy():
|
|||||||
strategy._load_strategy(strategy_name='NotFoundStrategy', config={})
|
strategy._load_strategy(strategy_name='NotFoundStrategy', config={})
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_staticmethod_importerror(mocker, caplog):
|
||||||
|
mocker.patch("freqtrade.resolvers.strategy_resolver.import_strategy", Mock(
|
||||||
|
side_effect=TypeError("can't pickle staticmethod objects")))
|
||||||
|
with pytest.raises(ImportError,
|
||||||
|
match=r"Impossible to load Strategy 'DefaultStrategy'."
|
||||||
|
r" This class does not exist or contains Python code errors"):
|
||||||
|
StrategyResolver()
|
||||||
|
assert log_has_re(r".*Error: can't pickle staticmethod objects", caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_strategy(result):
|
def test_strategy(result):
|
||||||
config = {'strategy': 'DefaultStrategy'}
|
config = {'strategy': 'DefaultStrategy'}
|
||||||
|
|
||||||
|
@ -494,15 +494,6 @@ def test_check_exchange(default_conf, caplog) -> None:
|
|||||||
):
|
):
|
||||||
configuration.check_exchange(default_conf)
|
configuration.check_exchange(default_conf)
|
||||||
|
|
||||||
# Test ccxt_rate_limit depreciation
|
|
||||||
default_conf.get('exchange').update({'name': 'binance'})
|
|
||||||
default_conf['exchange']['ccxt_rate_limit'] = True
|
|
||||||
configuration.check_exchange(default_conf)
|
|
||||||
assert log_has("`ccxt_rate_limit` has been deprecated in favor of "
|
|
||||||
"`ccxt_config` and `ccxt_async_config` and will be removed "
|
|
||||||
"in a future version.",
|
|
||||||
caplog.record_tuples)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
|
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
|
@ -1407,7 +1407,8 @@ def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_
|
|||||||
amount=amount,
|
amount=amount,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
open_rate=0.245441,
|
open_rate=0.245441,
|
||||||
open_order_id="123456"
|
open_order_id="123456",
|
||||||
|
is_open=True,
|
||||||
)
|
)
|
||||||
freqtrade.update_trade_state(trade, limit_buy_order)
|
freqtrade.update_trade_state(trade, limit_buy_order)
|
||||||
assert trade.amount != amount
|
assert trade.amount != amount
|
||||||
@ -1432,6 +1433,35 @@ def test_update_trade_state_exception(mocker, default_conf,
|
|||||||
assert log_has('Could not update trade amount: ', caplog.record_tuples)
|
assert log_has('Could not update trade amount: ', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_trade_state_sell(default_conf, trades_for_order, limit_sell_order, mocker):
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||||
|
# get_order should not be called!!
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError))
|
||||||
|
wallet_mock = MagicMock()
|
||||||
|
mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock)
|
||||||
|
|
||||||
|
patch_exchange(mocker)
|
||||||
|
Trade.session = MagicMock()
|
||||||
|
amount = limit_sell_order["amount"]
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
wallet_mock.reset_mock()
|
||||||
|
trade = Trade(
|
||||||
|
pair='LTC/ETH',
|
||||||
|
amount=amount,
|
||||||
|
exchange='binance',
|
||||||
|
open_rate=0.245441,
|
||||||
|
fee_open=0.0025,
|
||||||
|
fee_close=0.0025,
|
||||||
|
open_order_id="123456",
|
||||||
|
is_open=True,
|
||||||
|
)
|
||||||
|
freqtrade.update_trade_state(trade, limit_sell_order)
|
||||||
|
assert trade.amount == limit_sell_order['amount']
|
||||||
|
# Wallet needs to be updated after closing a limit-sell order to reenable buying
|
||||||
|
assert wallet_mock.call_count == 1
|
||||||
|
assert not trade.is_open
|
||||||
|
|
||||||
|
|
||||||
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
|
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
|
||||||
fee, markets, mocker) -> None:
|
fee, markets, mocker) -> None:
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
flake8==3.7.7
|
flake8==3.7.7
|
||||||
flake8-type-annotations==0.1.0
|
flake8-type-annotations==0.1.0
|
||||||
flake8-tidy-imports==2.0.0
|
flake8-tidy-imports==2.0.0
|
||||||
pytest==4.4.0
|
pytest==4.4.1
|
||||||
pytest-mock==1.10.3
|
pytest-mock==1.10.4
|
||||||
pytest-asyncio==0.10.0
|
pytest-asyncio==0.10.0
|
||||||
pytest-cov==2.6.1
|
pytest-cov==2.6.1
|
||||||
coveralls==1.7.0
|
coveralls==1.7.0
|
||||||
mypy==0.700
|
mypy==0.701
|
||||||
|
23
requirements-pi.txt
Normal file
23
requirements-pi.txt
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
ccxt==1.18.486
|
||||||
|
SQLAlchemy==1.3.3
|
||||||
|
python-telegram-bot==11.1.0
|
||||||
|
arrow==0.13.1
|
||||||
|
cachetools==3.1.0
|
||||||
|
requests==2.21.0
|
||||||
|
urllib3==1.25
|
||||||
|
wrapt==1.11.1
|
||||||
|
scikit-learn==0.20.3
|
||||||
|
joblib==0.13.2
|
||||||
|
jsonschema==3.0.1
|
||||||
|
TA-Lib==0.4.17
|
||||||
|
tabulate==0.8.3
|
||||||
|
coinmarketcap==5.0.3
|
||||||
|
|
||||||
|
# Required for hyperopt
|
||||||
|
scikit-optimize==0.5.2
|
||||||
|
|
||||||
|
# find first, C search in arrays
|
||||||
|
py_find_1st==1.1.3
|
||||||
|
|
||||||
|
#Load ticker files 30% faster
|
||||||
|
python-rapidjson==0.7.0
|
@ -1,5 +1,5 @@
|
|||||||
# Include all requirements to run the bot.
|
# Include all requirements to run the bot.
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
plotly==3.7.1
|
plotly==3.8.1
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
ccxt==1.18.435
|
ccxt==1.18.486
|
||||||
SQLAlchemy==1.3.2
|
SQLAlchemy==1.3.3
|
||||||
python-telegram-bot==11.1.0
|
python-telegram-bot==11.1.0
|
||||||
arrow==0.13.1
|
arrow==0.13.1
|
||||||
cachetools==3.1.0
|
cachetools==3.1.0
|
||||||
requests==2.21.0
|
requests==2.21.0
|
||||||
urllib3==1.24.1
|
urllib3==1.25
|
||||||
wrapt==1.11.1
|
wrapt==1.11.1
|
||||||
numpy==1.16.2
|
numpy==1.16.3
|
||||||
pandas==0.24.2
|
pandas==0.24.2
|
||||||
scikit-learn==0.20.3
|
scikit-learn==0.20.3
|
||||||
joblib==0.13.2
|
joblib==0.13.2
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
"""This script generate json data"""
|
This script generates json data
|
||||||
|
"""
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -35,7 +36,7 @@ if args.config:
|
|||||||
config: Dict[str, Any] = {}
|
config: Dict[str, Any] = {}
|
||||||
# Now expecting a list of config filenames here, not a string
|
# Now expecting a list of config filenames here, not a string
|
||||||
for path in args.config:
|
for path in args.config:
|
||||||
print('Using config: %s ...', path)
|
print(f"Using config: {path}...")
|
||||||
# Merge config options, overwriting old values
|
# Merge config options, overwriting old values
|
||||||
config = deep_merge_dicts(configuration._load_config_file(path), config)
|
config = deep_merge_dicts(configuration._load_config_file(path), config)
|
||||||
|
|
||||||
@ -44,18 +45,20 @@ if args.config:
|
|||||||
config['exchange']['key'] = ''
|
config['exchange']['key'] = ''
|
||||||
config['exchange']['secret'] = ''
|
config['exchange']['secret'] = ''
|
||||||
else:
|
else:
|
||||||
config = {'stake_currency': '',
|
config = {
|
||||||
'dry_run': True,
|
'stake_currency': '',
|
||||||
'exchange': {
|
'dry_run': True,
|
||||||
'name': args.exchange,
|
'exchange': {
|
||||||
'key': '',
|
'name': args.exchange,
|
||||||
'secret': '',
|
'key': '',
|
||||||
'pair_whitelist': [],
|
'secret': '',
|
||||||
'ccxt_async_config': {
|
'pair_whitelist': [],
|
||||||
"enableRateLimit": False
|
'ccxt_async_config': {
|
||||||
}
|
'enableRateLimit': True,
|
||||||
}
|
'rateLimit': 200
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
dl_path = Path(DEFAULT_DL_PATH).joinpath(config['exchange']['name'])
|
dl_path = Path(DEFAULT_DL_PATH).joinpath(config['exchange']['name'])
|
||||||
@ -92,18 +95,18 @@ for pair in PAIRS:
|
|||||||
pairs_not_available.append(pair)
|
pairs_not_available.append(pair)
|
||||||
print(f"skipping pair {pair}")
|
print(f"skipping pair {pair}")
|
||||||
continue
|
continue
|
||||||
for tick_interval in timeframes:
|
for ticker_interval in timeframes:
|
||||||
pair_print = pair.replace('/', '_')
|
pair_print = pair.replace('/', '_')
|
||||||
filename = f'{pair_print}-{tick_interval}.json'
|
filename = f'{pair_print}-{ticker_interval}.json'
|
||||||
dl_file = dl_path.joinpath(filename)
|
dl_file = dl_path.joinpath(filename)
|
||||||
if args.erase and dl_file.exists():
|
if args.erase and dl_file.exists():
|
||||||
print(f'Deleting existing data for pair {pair}, interval {tick_interval}')
|
print(f'Deleting existing data for pair {pair}, interval {ticker_interval}')
|
||||||
dl_file.unlink()
|
dl_file.unlink()
|
||||||
|
|
||||||
print(f'downloading pair {pair}, interval {tick_interval}')
|
print(f'downloading pair {pair}, interval {ticker_interval}')
|
||||||
download_pair_history(datadir=dl_path, exchange=exchange,
|
download_pair_history(datadir=dl_path, exchange=exchange,
|
||||||
pair=pair,
|
pair=pair,
|
||||||
tick_interval=tick_interval,
|
ticker_interval=ticker_interval,
|
||||||
timerange=timerange)
|
timerange=timerange)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
|
"""
|
||||||
|
This script was adapted from ccxt here:
|
||||||
|
https://github.com/ccxt/ccxt/blob/master/examples/py/arbitrage-pairs.py
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
sys.path.append(root + '/python')
|
sys.path.append(root + '/python')
|
||||||
@ -49,6 +54,11 @@ def print_supported_exchanges():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
dump("Usage: python " + sys.argv[0], green('id'))
|
||||||
|
print_supported_exchanges()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
id = sys.argv[1] # get exchange id from command line arguments
|
id = sys.argv[1] # get exchange id from command line arguments
|
||||||
|
|
||||||
# check if the exchange is supported by ccxt
|
# check if the exchange is supported by ccxt
|
||||||
@ -87,5 +97,7 @@ try:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
dump('[' + type(e).__name__ + ']', str(e))
|
dump('[' + type(e).__name__ + ']', str(e))
|
||||||
|
dump(traceback.format_exc())
|
||||||
dump("Usage: python " + sys.argv[0], green('id'))
|
dump("Usage: python " + sys.argv[0], green('id'))
|
||||||
print_supported_exchanges()
|
print_supported_exchanges()
|
||||||
|
sys.exit(1)
|
||||||
|
@ -82,7 +82,7 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram
|
|||||||
return trades
|
return trades
|
||||||
|
|
||||||
|
|
||||||
def generate_plot_file(fig, pair, tick_interval, is_last) -> None:
|
def generate_plot_file(fig, pair, ticker_interval, is_last) -> None:
|
||||||
"""
|
"""
|
||||||
Generate a plot html file from pre populated fig plotly object
|
Generate a plot html file from pre populated fig plotly object
|
||||||
:return: None
|
:return: None
|
||||||
@ -90,7 +90,7 @@ def generate_plot_file(fig, pair, tick_interval, is_last) -> None:
|
|||||||
logger.info('Generate plot file for %s', pair)
|
logger.info('Generate plot file for %s', pair)
|
||||||
|
|
||||||
pair_name = pair.replace("/", "_")
|
pair_name = pair.replace("/", "_")
|
||||||
file_name = 'freqtrade-plot-' + pair_name + '-' + tick_interval + '.html'
|
file_name = 'freqtrade-plot-' + pair_name + '-' + ticker_interval + '.html'
|
||||||
|
|
||||||
Path("user_data/plots").mkdir(parents=True, exist_ok=True)
|
Path("user_data/plots").mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
@ -135,20 +135,20 @@ def get_tickers_data(strategy, exchange, pairs: List[str], args):
|
|||||||
:return: dictinnary of tickers. output format: {'pair': tickersdata}
|
:return: dictinnary of tickers. output format: {'pair': tickersdata}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tick_interval = strategy.ticker_interval
|
ticker_interval = strategy.ticker_interval
|
||||||
timerange = Arguments.parse_timerange(args.timerange)
|
timerange = Arguments.parse_timerange(args.timerange)
|
||||||
|
|
||||||
tickers = {}
|
tickers = {}
|
||||||
if args.live:
|
if args.live:
|
||||||
logger.info('Downloading pairs.')
|
logger.info('Downloading pairs.')
|
||||||
exchange.refresh_latest_ohlcv([(pair, tick_interval) for pair in pairs])
|
exchange.refresh_latest_ohlcv([(pair, ticker_interval) for pair in pairs])
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
tickers[pair] = exchange.klines((pair, tick_interval))
|
tickers[pair] = exchange.klines((pair, ticker_interval))
|
||||||
else:
|
else:
|
||||||
tickers = history.load_data(
|
tickers = history.load_data(
|
||||||
datadir=Path(str(_CONF.get("datadir"))),
|
datadir=Path(str(_CONF.get("datadir"))),
|
||||||
pairs=pairs,
|
pairs=pairs,
|
||||||
ticker_interval=tick_interval,
|
ticker_interval=ticker_interval,
|
||||||
refresh_pairs=_CONF.get('refresh_pairs', False),
|
refresh_pairs=_CONF.get('refresh_pairs', False),
|
||||||
timerange=timerange,
|
timerange=timerange,
|
||||||
exchange=Exchange(_CONF)
|
exchange=Exchange(_CONF)
|
||||||
@ -399,7 +399,7 @@ def analyse_and_plot_pairs(args: Namespace):
|
|||||||
strategy, exchange, pairs = get_trading_env(args)
|
strategy, exchange, pairs = get_trading_env(args)
|
||||||
# Set timerange to use
|
# Set timerange to use
|
||||||
timerange = Arguments.parse_timerange(args.timerange)
|
timerange = Arguments.parse_timerange(args.timerange)
|
||||||
tick_interval = strategy.ticker_interval
|
ticker_interval = strategy.ticker_interval
|
||||||
|
|
||||||
tickers = get_tickers_data(strategy, exchange, pairs, args)
|
tickers = get_tickers_data(strategy, exchange, pairs, args)
|
||||||
pair_counter = 0
|
pair_counter = 0
|
||||||
@ -422,7 +422,7 @@ def analyse_and_plot_pairs(args: Namespace):
|
|||||||
)
|
)
|
||||||
|
|
||||||
is_last = (False, True)[pair_counter == len(tickers)]
|
is_last = (False, True)[pair_counter == len(tickers)]
|
||||||
generate_plot_file(fig, pair, tick_interval, is_last)
|
generate_plot_file(fig, pair, ticker_interval, is_last)
|
||||||
|
|
||||||
logger.info('End of ploting process %s plots generated', pair_counter)
|
logger.info('End of ploting process %s plots generated', pair_counter)
|
||||||
|
|
||||||
|
@ -27,10 +27,12 @@ from plotly.offline import plot
|
|||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.configuration import Configuration
|
from freqtrade.configuration import Configuration
|
||||||
from freqtrade.data import history
|
from freqtrade.data import history
|
||||||
from freqtrade.misc import common_datearray, timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_seconds
|
||||||
|
from freqtrade.misc import common_datearray
|
||||||
from freqtrade.resolvers import StrategyResolver
|
from freqtrade.resolvers import StrategyResolver
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -76,7 +78,7 @@ def plot_profit(args: Namespace) -> None:
|
|||||||
in helping out to find a good algorithm.
|
in helping out to find a good algorithm.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We need to use the same pairs, same tick_interval
|
# We need to use the same pairs, same ticker_interval
|
||||||
# and same timeperiod as used in backtesting
|
# and same timeperiod as used in backtesting
|
||||||
# to match the tickerdata against the profits-results
|
# to match the tickerdata against the profits-results
|
||||||
timerange = Arguments.parse_timerange(args.timerange)
|
timerange = Arguments.parse_timerange(args.timerange)
|
||||||
@ -112,7 +114,7 @@ def plot_profit(args: Namespace) -> None:
|
|||||||
else:
|
else:
|
||||||
filter_pairs = config['exchange']['pair_whitelist']
|
filter_pairs = config['exchange']['pair_whitelist']
|
||||||
|
|
||||||
tick_interval = strategy.ticker_interval
|
ticker_interval = strategy.ticker_interval
|
||||||
pairs = config['exchange']['pair_whitelist']
|
pairs = config['exchange']['pair_whitelist']
|
||||||
|
|
||||||
if filter_pairs:
|
if filter_pairs:
|
||||||
@ -122,7 +124,7 @@ def plot_profit(args: Namespace) -> None:
|
|||||||
tickers = history.load_data(
|
tickers = history.load_data(
|
||||||
datadir=Path(str(config.get('datadir'))),
|
datadir=Path(str(config.get('datadir'))),
|
||||||
pairs=pairs,
|
pairs=pairs,
|
||||||
ticker_interval=tick_interval,
|
ticker_interval=ticker_interval,
|
||||||
refresh_pairs=False,
|
refresh_pairs=False,
|
||||||
timerange=timerange
|
timerange=timerange
|
||||||
)
|
)
|
||||||
@ -134,7 +136,7 @@ def plot_profit(args: Namespace) -> None:
|
|||||||
dates = common_datearray(dataframes)
|
dates = common_datearray(dataframes)
|
||||||
min_date = int(min(dates).timestamp())
|
min_date = int(min(dates).timestamp())
|
||||||
max_date = int(max(dates).timestamp())
|
max_date = int(max(dates).timestamp())
|
||||||
num_iterations = define_index(min_date, max_date, tick_interval) + 1
|
num_iterations = define_index(min_date, max_date, ticker_interval) + 1
|
||||||
|
|
||||||
# Make an average close price of all the pairs that was involved.
|
# Make an average close price of all the pairs that was involved.
|
||||||
# this could be useful to gauge the overall market trend
|
# this could be useful to gauge the overall market trend
|
||||||
@ -154,7 +156,7 @@ def plot_profit(args: Namespace) -> None:
|
|||||||
avgclose /= num
|
avgclose /= num
|
||||||
|
|
||||||
# make an profits-growth array
|
# make an profits-growth array
|
||||||
pg = make_profit_array(data, num_iterations, min_date, tick_interval, filter_pairs)
|
pg = make_profit_array(data, num_iterations, min_date, ticker_interval, filter_pairs)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Plot the pairs average close prices, and total profit growth
|
# Plot the pairs average close prices, and total profit growth
|
||||||
@ -178,7 +180,7 @@ def plot_profit(args: Namespace) -> None:
|
|||||||
fig.append_trace(profit, 2, 1)
|
fig.append_trace(profit, 2, 1)
|
||||||
|
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
pg = make_profit_array(data, num_iterations, min_date, tick_interval, [pair])
|
pg = make_profit_array(data, num_iterations, min_date, ticker_interval, [pair])
|
||||||
pair_profit = go.Scattergl(
|
pair_profit = go.Scattergl(
|
||||||
x=dates,
|
x=dates,
|
||||||
y=pg,
|
y=pg,
|
||||||
@ -189,11 +191,11 @@ def plot_profit(args: Namespace) -> None:
|
|||||||
plot(fig, filename=str(Path('user_data').joinpath('freqtrade-profit-plot.html')))
|
plot(fig, filename=str(Path('user_data').joinpath('freqtrade-profit-plot.html')))
|
||||||
|
|
||||||
|
|
||||||
def define_index(min_date: int, max_date: int, interval: str) -> int:
|
def define_index(min_date: int, max_date: int, ticker_interval: str) -> int:
|
||||||
"""
|
"""
|
||||||
Return the index of a specific date
|
Return the index of a specific date
|
||||||
"""
|
"""
|
||||||
interval_seconds = timeframe_to_seconds(interval)
|
interval_seconds = timeframe_to_seconds(ticker_interval)
|
||||||
return int((max_date - min_date) / interval_seconds)
|
return int((max_date - min_date) / interval_seconds)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user