merged stop loss support branch
This commit is contained in:
commit
209aa9141d
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,7 +6,6 @@ config*.json
|
|||||||
.hyperopt
|
.hyperopt
|
||||||
logfile.txt
|
logfile.txt
|
||||||
hyperopt_trials.pickle
|
hyperopt_trials.pickle
|
||||||
user_data/
|
|
||||||
freqtrade-plot.html
|
freqtrade-plot.html
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
|
45
README.md
45
README.md
@ -62,19 +62,14 @@ Windows, macOS and Linux
|
|||||||
- [x] **Persistence**: Persistence is achieved through sqlite
|
- [x] **Persistence**: Persistence is achieved through sqlite
|
||||||
- [x] **Dry-run**: Run the bot without playing money.
|
- [x] **Dry-run**: Run the bot without playing money.
|
||||||
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
|
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
|
||||||
- [x] **Strategy Optimization**: Optimize your buy/sell strategy
|
- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell
|
||||||
parameters with Hyperopts.
|
strategy parameters with real exchange data.
|
||||||
- [x] **Whitelist crypto-currencies**: Select which crypto-currency you
|
- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade.
|
||||||
want to trade.
|
- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid.
|
||||||
- [x] **Blacklist crypto-currencies**: Select which crypto-currency you
|
|
||||||
want to avoid.
|
|
||||||
- [x] **Manageable via Telegram**: Manage the bot with Telegram
|
- [x] **Manageable via Telegram**: Manage the bot with Telegram
|
||||||
- [x] **Display profit/loss in fiat**: Display your profit/loss in
|
- [x] **Display profit/loss in fiat**: Display your profit/loss in 33 fiat.
|
||||||
33 fiat.
|
- [x] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss.
|
||||||
- [x] **Daily summary of profit/loss**: Provide a daily summary
|
- [x] **Performance status report**: Provide a performance status of your current trades.
|
||||||
of your profit/loss.
|
|
||||||
- [x] **Performance status report**: Provide a performance status of
|
|
||||||
your current trades.
|
|
||||||
|
|
||||||
### Additional features in this branch
|
### Additional features in this branch
|
||||||
|
|
||||||
@ -102,10 +97,10 @@ your current trades.
|
|||||||
- [x] maybe a bug here or there I haven't fixed yet
|
- [x] maybe a bug here or there I haven't fixed yet
|
||||||
|
|
||||||
|
|
||||||
### Exchange supported
|
### Exchange marketplaces supported
|
||||||
- [x] Bittrex
|
- [X] [Bittrex](https://bittrex.com/)
|
||||||
- [ ] Binance
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [ ] Others
|
- [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
This quick start section is a very short explanation on how to test the
|
This quick start section is a very short explanation on how to test the
|
||||||
@ -176,8 +171,9 @@ to understand the requirements before sending your pull-requests.
|
|||||||
### Bot commands
|
### Bot commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
usage: main.py [-h] [-v] [--version] [-c PATH] [--dry-run-db] [--datadir PATH]
|
usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
|
||||||
[--dynamic-whitelist [INT]]
|
[--strategy-path PATH] [--dynamic-whitelist [INT]]
|
||||||
|
[--dry-run-db]
|
||||||
{backtesting,hyperopt} ...
|
{backtesting,hyperopt} ...
|
||||||
|
|
||||||
Simple High Frequency Trading Bot for crypto currencies
|
Simple High Frequency Trading Bot for crypto currencies
|
||||||
@ -193,13 +189,18 @@ optional arguments:
|
|||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
specify configuration file (default: config.json)
|
specify configuration file (default: config.json)
|
||||||
--dry-run-db Force dry run to use a local DB
|
-d PATH, --datadir PATH
|
||||||
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
path to backtest data (default:
|
||||||
only if dry_run is enabled.
|
freqtrade/tests/testdata
|
||||||
--datadir PATH path to backtest data (default freqdata/tests/testdata
|
-s NAME, --strategy NAME
|
||||||
|
specify strategy class name (default: DefaultStrategy)
|
||||||
|
--strategy-path PATH specify additional strategy lookup path
|
||||||
--dynamic-whitelist [INT]
|
--dynamic-whitelist [INT]
|
||||||
dynamically generate and update whitelist based on 24h
|
dynamically generate and update whitelist based on 24h
|
||||||
BaseVolume (Default 20 currencies)
|
BaseVolume (Default 20 currencies)
|
||||||
|
--dry-run-db Force dry run to use a local DB
|
||||||
|
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
||||||
|
only if dry_run is enabled.
|
||||||
```
|
```
|
||||||
More details on:
|
More details on:
|
||||||
- [How to run the bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
|
- [How to run the bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
|
||||||
|
@ -53,9 +53,9 @@ python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180
|
|||||||
|
|
||||||
**With a (custom) strategy file**
|
**With a (custom) strategy file**
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py -s currentstrategy backtesting
|
python3 ./freqtrade/main.py -s TestStrategy backtesting
|
||||||
```
|
```
|
||||||
Where `-s currentstrategy` refers to a filename `currentstrategy.py` in `freqtrade/user_data/strategies`
|
Where `-s TestStrategy` refers to the class name within the strategy file `test_strategy.py` found in the `freqtrade/user_data/strategies` directory
|
||||||
|
|
||||||
**Exporting trades to file**
|
**Exporting trades to file**
|
||||||
```bash
|
```bash
|
||||||
|
@ -9,7 +9,8 @@ it.
|
|||||||
|
|
||||||
## Bot commands
|
## Bot commands
|
||||||
```
|
```
|
||||||
usage: main.py [-h] [-c PATH] [-v] [--version] [--dynamic-whitelist [INT]]
|
usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
|
||||||
|
[--strategy-path PATH] [--dynamic-whitelist [INT]]
|
||||||
[--dry-run-db]
|
[--dry-run-db]
|
||||||
{backtesting,hyperopt} ...
|
{backtesting,hyperopt} ...
|
||||||
|
|
||||||
@ -26,17 +27,18 @@ optional arguments:
|
|||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
specify configuration file (default: config.json)
|
specify configuration file (default: config.json)
|
||||||
|
-d PATH, --datadir PATH
|
||||||
|
path to backtest data (default:
|
||||||
|
freqtrade/tests/testdata
|
||||||
-s NAME, --strategy NAME
|
-s NAME, --strategy NAME
|
||||||
specify strategy class name (default: DefaultStrategy)
|
specify strategy class name (default: DefaultStrategy)
|
||||||
--strategy-path PATH specify additional strategy lookup path
|
--strategy-path PATH specify additional strategy lookup path
|
||||||
--dry-run-db Force dry run to use a local DB
|
|
||||||
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
|
||||||
only if dry_run is enabled.
|
|
||||||
--datadir PATH
|
|
||||||
path to backtest data (default freqdata/tests/testdata
|
|
||||||
--dynamic-whitelist [INT]
|
--dynamic-whitelist [INT]
|
||||||
dynamically generate and update whitelist based on 24h
|
dynamically generate and update whitelist based on 24h
|
||||||
BaseVolume (Default 20 currencies)
|
BaseVolume (Default 20 currencies)
|
||||||
|
--dry-run-db Force dry run to use a local DB
|
||||||
|
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
||||||
|
only if dry_run is enabled.
|
||||||
```
|
```
|
||||||
|
|
||||||
### How to use a different config file?
|
### How to use a different config file?
|
||||||
|
@ -24,7 +24,7 @@ The table below will list all configuration parameters.
|
|||||||
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
|
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
|
||||||
| `unfilledtimeout` | 0 | No | How long (in minutes) the bot will wait for an unfilled order to complete, after which the order will be cancelled.
|
| `unfilledtimeout` | 0 | No | How long (in minutes) the bot will wait for an unfilled order to complete, after which the order will be cancelled.
|
||||||
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
|
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
|
||||||
| `exchange.name` | bittrex | Yes | Name of the exchange class to use.
|
| `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
|
||||||
| `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode.
|
| `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode.
|
||||||
| `exchange.secret` | secret | No | API secret to use for the exchange. Only required when you are in production mode.
|
| `exchange.secret` | secret | No | API secret to use for the exchange. Only required when you are in production mode.
|
||||||
| `exchange.pair_whitelist` | [] | No | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param.
|
| `exchange.pair_whitelist` | [] | No | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param.
|
||||||
@ -79,6 +79,18 @@ use the `last` price and values between those interpolate between ask and last
|
|||||||
price. Using `ask` price will guarantee quick success in bid, but bot will also
|
price. Using `ask` price will guarantee quick success in bid, but bot will also
|
||||||
end up paying more then would probably have been necessary.
|
end up paying more then would probably have been necessary.
|
||||||
|
|
||||||
|
### What values for exchange.name?
|
||||||
|
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency
|
||||||
|
exchange markets and trading APIs. The complete up-to-date list can be found in the
|
||||||
|
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). However, the bot was tested
|
||||||
|
with only Bittrex and Binance.
|
||||||
|
|
||||||
|
The bot was tested with the following exchanges:
|
||||||
|
- [Bittrex](https://bittrex.com/): "bittrex"
|
||||||
|
- [Binance](https://www.binance.com/): "binance"
|
||||||
|
|
||||||
|
Feel free to test other exchanges and submit your PR to improve the bot.
|
||||||
|
|
||||||
### What values for fiat_display_currency?
|
### What values for fiat_display_currency?
|
||||||
`fiat_display_currency` set the fiat to use for the conversion form coin to fiat in Telegram.
|
`fiat_display_currency` set the fiat to use for the conversion form coin to fiat in Telegram.
|
||||||
The valid value are: "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD".
|
The valid value are: "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD".
|
||||||
@ -96,7 +108,7 @@ creating trades.
|
|||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Remove your Bittrex API key (change them by fake api credentials)
|
3. Remove your Exchange API key (change them by fake api credentials)
|
||||||
```json
|
```json
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "bittrex",
|
"name": "bittrex",
|
||||||
@ -122,7 +134,7 @@ you run it in production mode.
|
|||||||
"dry_run": false,
|
"dry_run": false,
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Insert your Bittrex API key (change them by fake api keys)
|
3. Insert your Exchange API key (change them by fake api keys)
|
||||||
```json
|
```json
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "bittrex",
|
"name": "bittrex",
|
||||||
|
@ -132,6 +132,13 @@ You can run a one-off container that is immediately deleted upon exiting with th
|
|||||||
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
||||||
```
|
```
|
||||||
|
|
||||||
|
There is known issue in OSX Docker versions after 17.09.1, whereby /etc/localtime cannot be shared causing Docker to not start. A work-around for this is to start with the following cmd.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -e TZ=`ls -la /etc/localtime | cut -d/ -f8-9` -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
||||||
|
```
|
||||||
|
More information on this docker issue and work-around can be read here: https://github.com/docker/for-mac/issues/2396
|
||||||
|
|
||||||
In this example, the database will be created inside the docker instance and will be lost when you will refresh your image.
|
In this example, the database will be created inside the docker instance and will be lost when you will refresh your image.
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import time
|
|||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from coinmarketcap import Market
|
from coinmarketcap import Market
|
||||||
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -94,8 +95,8 @@ class CryptoToFiatConverter(object):
|
|||||||
coinlistings = self._coinmarketcap.listings()
|
coinlistings = self._coinmarketcap.listings()
|
||||||
self._cryptomap = dict(map(lambda coin: (coin["symbol"], str(coin["id"])),
|
self._cryptomap = dict(map(lambda coin: (coin["symbol"], str(coin["id"])),
|
||||||
coinlistings["data"]))
|
coinlistings["data"]))
|
||||||
except ValueError:
|
except (ValueError, RequestException) as e:
|
||||||
logger.error("Could not load FIAT Cryptocurrency map")
|
logger.error("Could not load FIAT Cryptocurrency map for the following problem: %s", e)
|
||||||
|
|
||||||
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:
|
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:
|
||||||
"""
|
"""
|
||||||
|
@ -61,6 +61,7 @@ def set_loggers() -> None:
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
logging.getLogger('requests.packages.urllib3').setLevel(logging.INFO)
|
logging.getLogger('requests.packages.urllib3').setLevel(logging.INFO)
|
||||||
|
logging.getLogger('ccxt.base.exchange').setLevel(logging.INFO)
|
||||||
logging.getLogger('telegram').setLevel(logging.INFO)
|
logging.getLogger('telegram').setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) -
|
|||||||
if stype[0] == 'index':
|
if stype[0] == 'index':
|
||||||
start_index = start
|
start_index = start
|
||||||
elif stype[0] == 'date':
|
elif stype[0] == 'date':
|
||||||
while tickerlist[start_index][0] < start * 1000:
|
while start_index < len(tickerlist) and tickerlist[start_index][0] < start * 1000:
|
||||||
start_index += 1
|
start_index += 1
|
||||||
|
|
||||||
if stype[1] == 'line':
|
if stype[1] == 'line':
|
||||||
@ -37,7 +37,7 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) -
|
|||||||
if stype[1] == 'index':
|
if stype[1] == 'index':
|
||||||
stop_index = stop
|
stop_index = stop
|
||||||
elif stype[1] == 'date':
|
elif stype[1] == 'date':
|
||||||
while tickerlist[stop_index-1][0] > stop * 1000:
|
while stop_index > 0 and tickerlist[stop_index-1][0] > stop * 1000:
|
||||||
stop_index -= 1
|
stop_index -= 1
|
||||||
|
|
||||||
if start_index > stop_index:
|
if start_index > stop_index:
|
||||||
@ -100,15 +100,11 @@ def load_data(datadir: str,
|
|||||||
|
|
||||||
for pair in _pairs:
|
for pair in _pairs:
|
||||||
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
|
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
|
||||||
if not pairdata:
|
if pairdata:
|
||||||
# download the tickerdata from exchange
|
result[pair] = pairdata
|
||||||
download_backtesting_testdata(datadir,
|
else:
|
||||||
pair=pair,
|
logger.warn('No data for pair %s, use --update-pairs-cached to download the data', pair)
|
||||||
tick_interval=ticker_interval,
|
|
||||||
timerange=timerange)
|
|
||||||
# and retry reading the pair
|
|
||||||
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
|
|
||||||
result[pair] = pairdata
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -149,11 +149,11 @@ class Trade(_DECL_BASE):
|
|||||||
close_date = Column(DateTime)
|
close_date = Column(DateTime)
|
||||||
open_order_id = Column(String)
|
open_order_id = Column(String)
|
||||||
# absolute value of the stop loss
|
# absolute value of the stop loss
|
||||||
stop_loss = Column(Float, nullable=False, default=0.0)
|
stop_loss = Column(Float, nullable=True, default=0.0)
|
||||||
# absolute value of the initial stop loss
|
# absolute value of the initial stop loss
|
||||||
initial_stop_loss = Column(Float, nullable=False, default=0.0)
|
initial_stop_loss = Column(Float, nullable=True, default=0.0)
|
||||||
# absolute value of the highest reached price
|
# absolute value of the highest reached price
|
||||||
max_rate = Column(Float, nullable=False, default=0.0)
|
max_rate = Column(Float, nullable=True, default=0.0)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Trade(id={}, pair={}, amount={:.8f}, open_rate={:.8f}, open_since={})'.format(
|
return 'Trade(id={}, pair={}, amount={:.8f}, open_rate={:.8f}, open_since={})'.format(
|
||||||
|
@ -84,6 +84,7 @@ def load_data_test(what):
|
|||||||
|
|
||||||
def simple_backtest(config, contour, num_results, mocker) -> None:
|
def simple_backtest(config, contour, num_results, mocker) -> None:
|
||||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
|
|
||||||
backtesting = Backtesting(config)
|
backtesting = Backtesting(config)
|
||||||
|
|
||||||
data = load_data_test(contour)
|
data = load_data_test(contour)
|
||||||
@ -97,6 +98,7 @@ def simple_backtest(config, contour, num_results, mocker) -> None:
|
|||||||
'realistic': True
|
'realistic': True
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# results :: <class 'pandas.core.frame.DataFrame'>
|
# results :: <class 'pandas.core.frame.DataFrame'>
|
||||||
assert len(results) == num_results
|
assert len(results) == num_results
|
||||||
|
|
||||||
|
@ -99,7 +99,20 @@ def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog) -> None:
|
|||||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
|
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
|
||||||
|
|
||||||
_backup_file(file)
|
_backup_file(file)
|
||||||
optimize.load_data(None, ticker_interval='1m', pairs=['MEME/BTC'])
|
# do not download a new pair if refresh_pairs isn't set
|
||||||
|
optimize.load_data(None,
|
||||||
|
ticker_interval='1m',
|
||||||
|
refresh_pairs=False,
|
||||||
|
pairs=['MEME/BTC'])
|
||||||
|
assert os.path.isfile(file) is False
|
||||||
|
assert log_has('No data for pair MEME/BTC, use --update-pairs-cached to download the data',
|
||||||
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
# download a new pair if refresh_pairs is set
|
||||||
|
optimize.load_data(None,
|
||||||
|
ticker_interval='1m',
|
||||||
|
refresh_pairs=True,
|
||||||
|
pairs=['MEME/BTC'])
|
||||||
assert os.path.isfile(file) is True
|
assert os.path.isfile(file) is True
|
||||||
assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
|
assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
|
||||||
_clean_test_file(file)
|
_clean_test_file(file)
|
||||||
|
@ -42,7 +42,8 @@ def test_load_strategy_custom_directory(result):
|
|||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
FileNotFoundError,
|
FileNotFoundError,
|
||||||
match="FileNotFoundError: [WinError 3] The system cannot find the path specified: '{}'".format(extra_dir)):
|
match="FileNotFoundError: [WinError 3] The system cannot find the "
|
||||||
|
"path specified: '{}'".format(extra_dir)):
|
||||||
resolver._load_strategy('TestStrategy', extra_dir)
|
resolver._load_strategy('TestStrategy', extra_dir)
|
||||||
else:
|
else:
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
|
@ -6,6 +6,8 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
from freqtrade.fiat_convert import CryptoFiat, CryptoToFiatConverter
|
from freqtrade.fiat_convert import CryptoFiat, CryptoToFiatConverter
|
||||||
from freqtrade.tests.conftest import patch_coinmarketcap
|
from freqtrade.tests.conftest import patch_coinmarketcap
|
||||||
|
|
||||||
@ -133,6 +135,21 @@ def test_loadcryptomap(mocker):
|
|||||||
assert fiat_convert._cryptomap["BTC"] == "1"
|
assert fiat_convert._cryptomap["BTC"] == "1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_fiat_init_network_exception(mocker):
|
||||||
|
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||||
|
listmock = MagicMock(side_effect=RequestException)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.fiat_convert.Market',
|
||||||
|
listings=listmock,
|
||||||
|
)
|
||||||
|
# with pytest.raises(RequestEsxception):
|
||||||
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
fiat_convert._cryptomap = {}
|
||||||
|
fiat_convert._load_cryptomap()
|
||||||
|
|
||||||
|
assert len(fiat_convert._cryptomap) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_without_network():
|
def test_fiat_convert_without_network():
|
||||||
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
|
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
|
||||||
|
|
||||||
|
@ -444,6 +444,8 @@ def test_migrate_new(default_conf, fee):
|
|||||||
close_profit FLOAT,
|
close_profit FLOAT,
|
||||||
stake_amount FLOAT NOT NULL,
|
stake_amount FLOAT NOT NULL,
|
||||||
amount FLOAT,
|
amount FLOAT,
|
||||||
|
initial_stop_loss FLOAT,
|
||||||
|
max_rate FLOAT,
|
||||||
open_date DATETIME NOT NULL,
|
open_date DATETIME NOT NULL,
|
||||||
close_date DATETIME,
|
close_date DATETIME,
|
||||||
open_order_id VARCHAR,
|
open_order_id VARCHAR,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
ccxt==1.14.24
|
ccxt==1.14.96
|
||||||
SQLAlchemy==1.2.7
|
SQLAlchemy==1.2.8
|
||||||
python-telegram-bot==10.1.0
|
python-telegram-bot==10.1.0
|
||||||
arrow==0.12.1
|
arrow==0.12.1
|
||||||
cachetools==2.1.0
|
cachetools==2.1.0
|
||||||
@ -12,7 +12,7 @@ scipy==1.1.0
|
|||||||
jsonschema==2.6.0
|
jsonschema==2.6.0
|
||||||
numpy==1.14.3
|
numpy==1.14.3
|
||||||
TA-Lib==0.4.17
|
TA-Lib==0.4.17
|
||||||
pytest==3.5.1
|
pytest==3.6.0
|
||||||
pytest-mock==1.10.0
|
pytest-mock==1.10.0
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.5.1
|
||||||
hyperopt==0.1
|
hyperopt==0.1
|
||||||
|
16
setup.sh
16
setup.sh
@ -89,13 +89,21 @@ function config_generator () {
|
|||||||
echo "General configuration"
|
echo "General configuration"
|
||||||
echo "-------------------------"
|
echo "-------------------------"
|
||||||
echo
|
echo
|
||||||
read -p "Max open trades: (Default: 3) " max_trades
|
default_max_trades=3
|
||||||
|
read -p "Max open trades: (Default: $default_max_trades) " max_trades
|
||||||
|
max_trades=${max_trades:-$default_max_trades}
|
||||||
|
|
||||||
read -p "Stake amount: (Default: 0.05) " stake_amount
|
default_stake_amount=0.05
|
||||||
|
read -p "Stake amount: (Default: $default_stake_amount) " stake_amount
|
||||||
|
stake_amount=${stake_amount:-$default_stake_amount}
|
||||||
|
|
||||||
read -p "Stake currency: (Default: BTC) " stake_currency
|
default_stake_currency="BTC"
|
||||||
|
read -p "Stake currency: (Default: $default_stake_currency) " stake_currency
|
||||||
|
stake_currency=${stake_currency:-$default_stake_currency}
|
||||||
|
|
||||||
read -p "Fiat currency: (Default: USD) " fiat_currency
|
default_fiat_currency="USD"
|
||||||
|
read -p "Fiat currency: (Default: $default_fiat_currency) " fiat_currency
|
||||||
|
fiat_currency=${fiat_currency:-$default_fiat_currency}
|
||||||
|
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
echo "Bittrex config generator"
|
echo "Bittrex config generator"
|
||||||
|
94
user_data/strategies/Long.py
Normal file
94
user_data/strategies/Long.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
|
||||||
|
# --- Do not remove these libs ---
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from typing import Dict, List
|
||||||
|
from hyperopt import hp
|
||||||
|
from functools import reduce
|
||||||
|
from pandas import DataFrame
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
import talib.abstract as ta
|
||||||
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
import numpy # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class Long(IStrategy):
|
||||||
|
"""
|
||||||
|
|
||||||
|
author@: Gert Wohlgemuth
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy.
|
||||||
|
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||||
|
minimal_roi = {
|
||||||
|
"60": 0.05,
|
||||||
|
"30": 0.06,
|
||||||
|
"20": 0.07,
|
||||||
|
"0": 0.08
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
# This attribute will be overridden if the config file contains "stoploss"
|
||||||
|
stoploss = -0.15
|
||||||
|
|
||||||
|
# Optimal ticker interval for the strategy
|
||||||
|
ticker_interval = 60
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
|
||||||
|
macd = ta.MACD(dataframe)
|
||||||
|
dataframe['macd'] = macd['macd']
|
||||||
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
|
dataframe['cci'] = ta.CCI(dataframe)
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=50)
|
||||||
|
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
|
||||||
|
# RSI
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe)
|
||||||
|
|
||||||
|
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
||||||
|
rsi = 0.1 * (dataframe['rsi'] - 50)
|
||||||
|
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
||||||
|
|
||||||
|
# SAR Parabol
|
||||||
|
dataframe['sar'] = ta.SAR(dataframe)
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['macd'] > dataframe['macdsignal']) &
|
||||||
|
(dataframe['macd'] > 0) &
|
||||||
|
(dataframe['cci'] <= 0.0)
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
# (dataframe['tema'] < dataframe['close'])
|
||||||
|
|
||||||
|
(dataframe['sar'] > dataframe['close']) &
|
||||||
|
(dataframe['fisher_rsi'] > 0.3)
|
||||||
|
),
|
||||||
|
'sell'] = 1
|
||||||
|
return dataframe
|
75
user_data/strategies/Quickie.py
Normal file
75
user_data/strategies/Quickie.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# --- Do not remove these libs ---
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from typing import Dict, List
|
||||||
|
from hyperopt import hp
|
||||||
|
from functools import reduce
|
||||||
|
from pandas import DataFrame
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
import talib.abstract as ta
|
||||||
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
|
||||||
|
|
||||||
|
class Quickie(IStrategy):
|
||||||
|
"""
|
||||||
|
|
||||||
|
author@: Gert Wohlgemuth
|
||||||
|
|
||||||
|
idea:
|
||||||
|
momentum based strategie. The main idea is that it closes trades very quickly, while avoiding excessive losses. Hence a rather moderate stop loss in this case
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy.
|
||||||
|
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||||
|
minimal_roi = {
|
||||||
|
"60": 0.005,
|
||||||
|
"10": 0.01,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
# This attribute will be overridden if the config file contains "stoploss"
|
||||||
|
stoploss = -0.25
|
||||||
|
|
||||||
|
# Optimal ticker interval for the strategy
|
||||||
|
ticker_interval = 5
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
|
|
||||||
|
dataframe['sma_200'] = ta.SMA(dataframe, timeperiod=200)
|
||||||
|
dataframe['sma_50'] = ta.SMA(dataframe, timeperiod=50)
|
||||||
|
|
||||||
|
|
||||||
|
# required for graphing
|
||||||
|
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 30) &
|
||||||
|
(dataframe['tema'] < dataframe['bb_middleband']) &
|
||||||
|
(dataframe['tema'] > dataframe['tema'].shift(1)) &
|
||||||
|
(dataframe['sma_200'] > dataframe['close'])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 70) &
|
||||||
|
(dataframe['tema'] > dataframe['bb_middleband']) &
|
||||||
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'sell'] = 1
|
||||||
|
return dataframe
|
76
user_data/strategies/Simple.py
Normal file
76
user_data/strategies/Simple.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# --- Do not remove these libs ---
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from typing import Dict, List
|
||||||
|
from hyperopt import hp
|
||||||
|
from functools import reduce
|
||||||
|
from pandas import DataFrame
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
import talib.abstract as ta
|
||||||
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
|
||||||
|
|
||||||
|
class Simple(IStrategy):
|
||||||
|
"""
|
||||||
|
|
||||||
|
author@: Gert Wohlgemuth
|
||||||
|
|
||||||
|
idea:
|
||||||
|
this strategy is based on the book, 'The Simple Strategy' and can be found in detail here:
|
||||||
|
|
||||||
|
https://www.amazon.com/Simple-Strategy-Powerful-Trading-Futures-ebook/dp/B00E66QPCG/ref=sr_1_1?ie=UTF8&qid=1525202675&sr=8-1&keywords=the+simple+strategy
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy.
|
||||||
|
# since this strategy is planned around 5 minutes, we assume any time we have a 5% profit we should call it a day
|
||||||
|
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||||
|
minimal_roi = {
|
||||||
|
"0": 0.01
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
# This attribute will be overridden if the config file contains "stoploss"
|
||||||
|
stoploss = -0.25
|
||||||
|
|
||||||
|
# Optimal ticker interval for the strategy
|
||||||
|
ticker_interval = 5
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
# MACD
|
||||||
|
macd = ta.MACD(dataframe)
|
||||||
|
dataframe['macd'] = macd['macd']
|
||||||
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
|
|
||||||
|
# RSI
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7)
|
||||||
|
|
||||||
|
# required for graphing
|
||||||
|
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=12, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(dataframe['macd'] > 0) # over 0
|
||||||
|
& (dataframe['macd'] > dataframe['macdsignal']) # over signal
|
||||||
|
& (dataframe['bb_upperband'] > dataframe['bb_upperband'].shift(1)) # pointed up
|
||||||
|
& (dataframe['rsi'] > 70) # optional filter, need to investigate
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
# different strategy used for sell points, due to be able to duplicate it to 100%
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['rsi'] > 80)
|
||||||
|
),
|
||||||
|
'sell'] = 1
|
||||||
|
return dataframe
|
90
user_data/strategies/ZLC.py
Normal file
90
user_data/strategies/ZLC.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# --- Do not remove these libs ---
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from typing import Dict, List
|
||||||
|
from hyperopt import hp
|
||||||
|
from functools import reduce
|
||||||
|
from pandas import DataFrame
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
import talib.abstract as ta
|
||||||
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
|
||||||
|
|
||||||
|
class ZLC(IStrategy):
|
||||||
|
"""
|
||||||
|
|
||||||
|
author@: Gert Wohlgemuth
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy.
|
||||||
|
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||||
|
minimal_roi = {
|
||||||
|
"60": 0.01,
|
||||||
|
"30": 0.03,
|
||||||
|
"20": 0.04,
|
||||||
|
"0": 0.01
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
# This attribute will be overridden if the config file contains "stoploss"
|
||||||
|
stoploss = -0.3
|
||||||
|
|
||||||
|
# Optimal ticker interval for the strategy
|
||||||
|
ticker_interval = 5
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
dataframe['cci-slow'] = ta.CCI(dataframe, timeperiod=25)
|
||||||
|
dataframe['cci-fast'] = ta.CCI(dataframe, timeperiod=50)
|
||||||
|
dataframe['expo'] = ta.EMA(dataframe, timeperiod=35)
|
||||||
|
|
||||||
|
# required for graphing
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
|
||||||
|
macd = ta.MACD(dataframe)
|
||||||
|
dataframe['macd'] = macd['macd']
|
||||||
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
#don't buy on peak tops
|
||||||
|
(dataframe['close'] < dataframe['bb_middleband'])
|
||||||
|
# this is the main concept of evaluating buys
|
||||||
|
& (dataframe['cci-fast'] > 0)
|
||||||
|
& (dataframe['cci-slow'] > 0)
|
||||||
|
& (dataframe['close'] > dataframe['expo'])
|
||||||
|
|
||||||
|
)
|
||||||
|
,
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(dataframe['close'] >= dataframe['bb_upperband']) |
|
||||||
|
(
|
||||||
|
(dataframe['cci-fast'] < 0)
|
||||||
|
& (dataframe['cci-slow'] < 0)
|
||||||
|
& (dataframe['close'] < dataframe['expo'])
|
||||||
|
|
||||||
|
)
|
||||||
|
,
|
||||||
|
'sell'] = 0
|
||||||
|
return dataframe
|
Loading…
Reference in New Issue
Block a user