diff --git a/.gitignore b/.gitignore index e5ac932f8..5334e7769 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ config*.json .hyperopt logfile.txt hyperopt_trials.pickle -user_data/ freqtrade-plot.html # Byte-compiled / optimized / DLL files diff --git a/README.md b/README.md index 1caa34a85..3ded0099a 100644 --- a/README.md +++ b/README.md @@ -56,24 +56,19 @@ Windows, macOS and Linux - [x] **Persistence**: Persistence is achieved through sqlite - [x] **Dry-run**: Run the bot without playing money. - [x] **Backtesting**: Run a simulation of your buy/sell strategy. -- [x] **Strategy Optimization**: Optimize your buy/sell strategy -parameters with Hyperopts. -- [x] **Whitelist crypto-currencies**: Select which crypto-currency you -want to trade. -- [x] **Blacklist crypto-currencies**: Select which crypto-currency you -want to avoid. +- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell +strategy parameters with real exchange data. +- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade. +- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid. - [x] **Manageable via Telegram**: Manage the bot with Telegram -- [x] **Display profit/loss in fiat**: Display your profit/loss in -33 fiat. -- [x] **Daily summary of profit/loss**: Provide a daily summary - of your profit/loss. -- [x] **Performance status report**: Provide a performance status of -your current trades. +- [x] **Display profit/loss in fiat**: Display your profit/loss in 33 fiat. +- [x] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss. +- [x] **Performance status report**: Provide a performance status of your current trades. -### Exchange supported -- [x] Bittrex -- [ ] Binance -- [ ] Others +### Exchange marketplaces supported +- [X] [Bittrex](https://bittrex.com/) +- [X] [Binance](https://www.binance.com/) +- [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ ## Quick start This quick start section is a very short explanation on how to test the @@ -144,8 +139,9 @@ to understand the requirements before sending your pull-requests. ### Bot commands ```bash -usage: main.py [-h] [-v] [--version] [-c PATH] [--dry-run-db] [--datadir PATH] - [--dynamic-whitelist [INT]] +usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] + [--strategy-path PATH] [--dynamic-whitelist [INT]] + [--dry-run-db] {backtesting,hyperopt} ... Simple High Frequency Trading Bot for crypto currencies @@ -161,13 +157,18 @@ optional arguments: --version show program's version number and exit -c PATH, --config PATH specify configuration file (default: config.json) - --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 + -d PATH, --datadir PATH + path to backtest data (default: + freqtrade/tests/testdata + -s NAME, --strategy NAME + specify strategy class name (default: DefaultStrategy) + --strategy-path PATH specify additional strategy lookup path --dynamic-whitelist [INT] dynamically generate and update whitelist based on 24h 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: - [How to run the bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#bot-commands) diff --git a/docs/backtesting.md b/docs/backtesting.md index b3783a665..df105bd77 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -53,9 +53,9 @@ python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180 **With a (custom) strategy file** ```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** ```bash diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 075413b21..b42df3ba3 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -9,7 +9,8 @@ it. ## 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] {backtesting,hyperopt} ... @@ -26,17 +27,18 @@ optional arguments: --version show program's version number and exit -c PATH, --config PATH specify configuration file (default: config.json) + -d PATH, --datadir PATH + path to backtest data (default: + freqtrade/tests/testdata -s NAME, --strategy NAME specify strategy class name (default: DefaultStrategy) --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] dynamically generate and update whitelist based on 24h 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? diff --git a/docs/configuration.md b/docs/configuration.md index 3d36947c3..a2df3f2fe 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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. | `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. -| `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.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. @@ -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 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? `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". @@ -96,7 +108,7 @@ creating trades. "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 "exchange": { "name": "bittrex", @@ -122,7 +134,7 @@ you run it in production mode. "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 "exchange": { "name": "bittrex", diff --git a/docs/installation.md b/docs/installation.md index a7e61bbe5..be8e2e501 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -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 ``` +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. diff --git a/freqtrade/fiat_convert.py b/freqtrade/fiat_convert.py index 7e74adcd2..17882f51a 100644 --- a/freqtrade/fiat_convert.py +++ b/freqtrade/fiat_convert.py @@ -8,6 +8,7 @@ import time from typing import Dict from coinmarketcap import Market +from requests.exceptions import RequestException logger = logging.getLogger(__name__) @@ -94,8 +95,8 @@ class CryptoToFiatConverter(object): coinlistings = self._coinmarketcap.listings() self._cryptomap = dict(map(lambda coin: (coin["symbol"], str(coin["id"])), coinlistings["data"])) - except ValueError: - logger.error("Could not load FIAT Cryptocurrency map") + except (ValueError, RequestException) as e: + 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: """ diff --git a/freqtrade/main.py b/freqtrade/main.py index 9639922f9..973ed031d 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -61,6 +61,7 @@ def set_loggers() -> None: :return: None """ logging.getLogger('requests.packages.urllib3').setLevel(logging.INFO) + logging.getLogger('ccxt.base.exchange').setLevel(logging.INFO) logging.getLogger('telegram').setLevel(logging.INFO) diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 168481920..f6f1ba47a 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -29,7 +29,7 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) - if stype[0] == 'index': start_index = start 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 if stype[1] == 'line': @@ -37,7 +37,7 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) - if stype[1] == 'index': stop_index = stop 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 if start_index > stop_index: @@ -100,15 +100,11 @@ def load_data(datadir: str, for pair in _pairs: pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange) - if not pairdata: - # download the tickerdata from exchange - download_backtesting_testdata(datadir, - pair=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 + if pairdata: + result[pair] = pairdata + else: + logger.warn('No data for pair %s, use --update-pairs-cached to download the data', pair) + return result diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index dd3eed001..fd28444d1 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -98,10 +98,11 @@ class RPC(object): trade.id, trade.pair, shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), - '{:.2f}%'.format(100 * trade.calc_profit_percent(current_rate)) + '{:.2f}%'.format(100 * trade.calc_profit_percent(current_rate)), + '{:.6f}'.format(trade.amount * current_rate) ]) - columns = ['ID', 'Pair', 'Since', 'Profit'] + columns = ['ID', 'Pair', 'Since', 'Profit', 'Value'] df_statuses = DataFrame.from_records(trades_list, columns=columns) df_statuses = df_statuses.set_index(columns[0]) # The style used throughout is to return a tuple diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py index e7f3b18fd..8624b500d 100644 --- a/freqtrade/tests/optimize/test_optimize.py +++ b/freqtrade/tests/optimize/test_optimize.py @@ -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') _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 log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples) _clean_test_file(file) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 1d1b3b39c..20e620d7d 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -106,6 +106,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: assert 'just now' in result['Since'].all() assert 'ETH/BTC' in result['Pair'].all() assert '-0.59%' in result['Profit'].all() + assert 'Value' in result def test_rpc_daily_profit(default_conf, update, ticker, fee, diff --git a/freqtrade/tests/test_fiat_convert.py b/freqtrade/tests/test_fiat_convert.py index f5be9daf0..b37ca0f5c 100644 --- a/freqtrade/tests/test_fiat_convert.py +++ b/freqtrade/tests/test_fiat_convert.py @@ -6,6 +6,8 @@ from unittest.mock import MagicMock import pytest +from requests.exceptions import RequestException + from freqtrade.fiat_convert import CryptoFiat, CryptoToFiatConverter from freqtrade.tests.conftest import patch_coinmarketcap @@ -133,6 +135,21 @@ def test_loadcryptomap(mocker): 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(): # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap diff --git a/requirements.txt b/requirements.txt index 3fe8d7a7e..7000f0eb1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -ccxt==1.14.24 -SQLAlchemy==1.2.7 +ccxt==1.14.96 +SQLAlchemy==1.2.8 python-telegram-bot==10.1.0 arrow==0.12.1 cachetools==2.1.0 @@ -12,7 +12,7 @@ scipy==1.1.0 jsonschema==2.6.0 numpy==1.14.3 TA-Lib==0.4.17 -pytest==3.5.1 +pytest==3.6.0 pytest-mock==1.10.0 pytest-cov==2.5.1 hyperopt==0.1 diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 108c0b609..8cfcb2915 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -159,6 +159,15 @@ def plot_analyzed_dataframe(args: Namespace) -> None: fillcolor="rgba(0,176,246,0.2)", line={'color': "transparent"}, ) + bb_middle = go.Scatter( + x=data.date, + y=data.bb_middleband, + name='BB middle', + fill="tonexty", + fillcolor="rgba(0,176,246,0.2)", + line={'color': "red"}, + ) + macd = go.Scattergl(x=data['date'], y=data['macd'], name='MACD') macdsignal = go.Scattergl(x=data['date'], y=data['macdsignal'], name='MACD signal') volume = go.Bar(x=data['date'], y=data['volume'], name='Volume') @@ -173,7 +182,9 @@ def plot_analyzed_dataframe(args: Namespace) -> None: fig.append_trace(candles, 1, 1) fig.append_trace(bb_lower, 1, 1) + fig.append_trace(bb_middle, 1, 1) fig.append_trace(bb_upper, 1, 1) + fig.append_trace(buys, 1, 1) fig.append_trace(sells, 1, 1) fig.append_trace(volume, 2, 1) diff --git a/setup.sh b/setup.sh index bdcec7186..1d06e8208 100755 --- a/setup.sh +++ b/setup.sh @@ -89,13 +89,21 @@ function config_generator () { echo "General configuration" 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 "Bittrex config generator" diff --git a/user_data/strategies/Long.py b/user_data/strategies/Long.py new file mode 100644 index 000000000..321218430 --- /dev/null +++ b/user_data/strategies/Long.py @@ -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 diff --git a/user_data/strategies/Quickie.py b/user_data/strategies/Quickie.py new file mode 100644 index 000000000..cb60bd652 --- /dev/null +++ b/user_data/strategies/Quickie.py @@ -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 diff --git a/user_data/strategies/Simple.py b/user_data/strategies/Simple.py new file mode 100644 index 000000000..a279dcb19 --- /dev/null +++ b/user_data/strategies/Simple.py @@ -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 diff --git a/user_data/strategies/ZLC.py b/user_data/strategies/ZLC.py new file mode 100644 index 000000000..d32421b3f --- /dev/null +++ b/user_data/strategies/ZLC.py @@ -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