Merge branch 'develop' into ccxt-parse_timeframe

This commit is contained in:
hroff-1902 2019-04-05 23:16:27 +03:00 committed by GitHub
commit 8cb1024ff6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 75 deletions

View File

@ -62,7 +62,7 @@ If you have updated the buy strategy, ie. changed the contents of
#### Sell optimization #### Sell optimization
Similar to the buy-signal above, sell-signals can also be optimized. Similar to the buy-signal above, sell-signals can also be optimized.
Place the corresponding settings into the following methods Place the corresponding settings into the following methods
* Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing. * Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing.
@ -163,7 +163,7 @@ running at least several thousand evaluations.
The `--spaces all` flag determines that all possible parameters should be optimized. Possibilities are listed below. The `--spaces all` flag determines that all possible parameters should be optimized. Possibilities are listed below.
!!! Warning !!! Warning
When switching parameters or changing configuration options, the file `user_data/hyperopt_results.pickle` should be removed. It's used to be able to continue interrupted calculations, but does not detect changes to settings or the hyperopt file. When switching parameters or changing configuration options, the file `user_data/hyperopt_results.pickle` should be removed. It's used to be able to continue interrupted calculations, but does not detect changes to settings or the hyperopt file.
### Execute Hyperopt with Different Ticker-Data Source ### Execute Hyperopt with Different Ticker-Data Source

View File

@ -1,5 +1,5 @@
# SQL Helper # SQL Helper
This page constains some help if you want to edit your sqlite db. This page contains some help if you want to edit your sqlite db.
## Install sqlite3 ## Install sqlite3
**Ubuntu/Debian installation** **Ubuntu/Debian installation**
@ -66,11 +66,11 @@ SELECT * FROM trades;
## Fix trade still open after a manual sell on the exchange ## Fix trade still open after a manual sell on the exchange
!!! Warning !!! Warning
Manually selling on the exchange should not be done by default, since the bot does not detect this and will try to sell anyway. Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. Whenever possible, forcesell <tradeid> should be used to accomplish the same thing.
/foresell <tradeid> should accomplish the same thing. It is strongly advised to backup your database file before making any manual changes.
!!! Note !!! Note
This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration. This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration.
```sql ```sql
UPDATE trades UPDATE trades

View File

@ -1,13 +1,13 @@
# Telegram usage # Telegram usage
This page explains how to command your bot with Telegram.
## Prerequisite ## Prerequisite
To control your bot with Telegram, you need first to To control your bot with Telegram, you need first to
[set up a Telegram bot](installation.md) [set up a Telegram bot](installation.md)
and add your Telegram API keys into your config file. and add your Telegram API keys into your config file.
## Telegram commands ## Telegram commands
Per default, the Telegram bot shows predefined commands. Some commands Per default, the Telegram bot shows predefined commands. Some commands
are only available by sending them to the bot. The table below list the are only available by sending them to the bot. The table below list the
official commands. You can ask at any moment for help with `/help`. official commands. You can ask at any moment for help with `/help`.

View File

@ -1,7 +1,5 @@
# Webhook usage # Webhook usage
This page explains how to configure your bot to talk to webhooks.
## Configuration ## Configuration
Enable webhooks by adding a webhook-section to your configuration file, and setting `webhook.enabled` to `true`. Enable webhooks by adding a webhook-section to your configuration file, and setting `webhook.enabled` to `true`.
@ -39,32 +37,30 @@ Different payloads can be configured for different events. Not all fields are ne
The fields in `webhook.webhookbuy` are filled when the bot executes a buy. Parameters are filled using string.format. The fields in `webhook.webhookbuy` are filled when the bot executes a buy. Parameters are filled using string.format.
Possible parameters are: Possible parameters are:
* exchange * `exchange`
* pair * `pair`
* limit * `limit`
* stake_amount * `stake_amount`
* stake_amount_fiat * `stake_currency`
* stake_currency * `fiat_currency`
* fiat_currency
### Webhooksell ### Webhooksell
The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format. The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format.
Possible parameters are: Possible parameters are:
* exchange * `exchange`
* pair * `pair`
* gain * `gain`
* limit * `limit`
* amount * `amount`
* open_rate * `open_rate`
* current_rate * `current_rate`
* profit_amount * `profit_amount`
* profit_percent * `profit_percent`
* profit_fiat * `stake_currency`
* stake_currency * `fiat_currency`
* fiat_currency * `sell_reason`
* sell_reason
### Webhookstatus ### Webhookstatus

View File

@ -210,6 +210,32 @@ class Backtesting(object):
logger.info('Dumping backtest results to %s', recordfilename) logger.info('Dumping backtest results to %s', recordfilename)
file_dump_json(recordfilename, records) file_dump_json(recordfilename, records)
def _get_ticker_list(self, processed) -> Dict[str, DataFrame]:
"""
Helper function to convert a processed tickerlist into a list for performance reasons.
Used by backtest() - so keep this optimized for performance.
"""
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high']
ticker: Dict = {}
# Create ticker dict
for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
ticker_data = self.advise_sell(
self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
# to avoid using data from future, we buy/sell with signal from previous candle
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)
ticker_data.loc[:, 'sell'] = ticker_data['sell'].shift(1)
ticker_data.drop(ticker_data.head(1).index, inplace=True)
# Convert from Pandas to list for performance reasons
# (Looping Pandas is slow.)
ticker[pair] = [x for x in ticker_data.itertuples()]
return ticker
def _get_sell_trade_entry( def _get_sell_trade_entry(
self, pair: str, buy_row: DataFrame, self, pair: str, buy_row: DataFrame,
partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[BacktestResult]: partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[BacktestResult]:
@ -304,7 +330,6 @@ class Backtesting(object):
position_stacking: do we allow position stacking? (default: False) position_stacking: do we allow position stacking? (default: False)
:return: DataFrame :return: DataFrame
""" """
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high']
processed = args['processed'] processed = args['processed']
max_open_trades = args.get('max_open_trades', 0) max_open_trades = args.get('max_open_trades', 0)
position_stacking = args.get('position_stacking', False) position_stacking = args.get('position_stacking', False)
@ -312,54 +337,50 @@ class Backtesting(object):
end_date = args['end_date'] end_date = args['end_date']
trades = [] trades = []
trade_count_lock: Dict = {} trade_count_lock: Dict = {}
ticker: Dict = {}
pairs = []
# Create ticker dict
for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
ticker_data = self.advise_sell( # Dict of ticker-lists for performance (looping lists is a lot faster than dataframes)
self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() ticker: Dict = self._get_ticker_list(processed)
# to avoid using data from future, we buy/sell with signal from previous candle
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)
ticker_data.loc[:, 'sell'] = ticker_data['sell'].shift(1)
ticker_data.drop(ticker_data.head(1).index, inplace=True)
# Convert from Pandas to list for performance reasons
# (Looping Pandas is slow.)
ticker[pair] = [x for x in ticker_data.itertuples()]
pairs.append(pair)
lock_pair_until: Dict = {} lock_pair_until: Dict = {}
# Indexes per pair, so some pairs are allowed to have a missing start.
indexes: Dict = {}
tmp = start_date + timedelta(minutes=self.ticker_interval_mins) tmp = start_date + timedelta(minutes=self.ticker_interval_mins)
index = 0
# Loop timerange and test per pair # Loop timerange and get candle for each pair at that point in time
while tmp < end_date: while tmp < end_date:
# print(f"time: {tmp}")
for i, pair in enumerate(ticker): for i, pair in enumerate(ticker):
if pair not in indexes:
indexes[pair] = 0
try: try:
row = ticker[pair][index] row = ticker[pair][indexes[pair]]
except IndexError: except IndexError:
# missing Data for one pair ... # missing Data for one pair at the end.
# Warnings for this are shown by `validate_backtest_data` # Warnings for this are shown by `validate_backtest_data`
continue continue
# Waits until the time-counter reaches the start of the data for this pair.
if row.date > tmp.datetime:
continue
indexes[pair] += 1
if row.buy == 0 or row.sell == 1: if row.buy == 0 or row.sell == 1:
continue # skip rows where no buy signal or that would immediately sell off continue # skip rows where no buy signal or that would immediately sell off
if not position_stacking: if (not position_stacking and pair in lock_pair_until
if pair in lock_pair_until and row.date <= lock_pair_until[pair]: and row.date <= lock_pair_until[pair]):
continue # without positionstacking, we can only have one open trade per pair.
continue
if max_open_trades > 0: if max_open_trades > 0:
# Check if max_open_trades has already been reached for the given date # Check if max_open_trades has already been reached for the given date
if not trade_count_lock.get(row.date, 0) < max_open_trades: if not trade_count_lock.get(row.date, 0) < max_open_trades:
continue continue
trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1 trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1
trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][index + 1:], trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][indexes[pair]:],
trade_count_lock, args) trade_count_lock, args)
if trade_entry: if trade_entry:
@ -367,11 +388,10 @@ class Backtesting(object):
trades.append(trade_entry) trades.append(trade_entry)
else: else:
# Set lock_pair_until to end of testing period if trade could not be closed # Set lock_pair_until to end of testing period if trade could not be closed
# This happens only if the buy-signal was with the last candle lock_pair_until[pair] = end_date.datetime
lock_pair_until[pair] = end_date
# Move time one configured time_interval ahead.
tmp += timedelta(minutes=self.ticker_interval_mins) tmp += timedelta(minutes=self.ticker_interval_mins)
index += 1
return DataFrame.from_records(trades, columns=BacktestResult._fields) return DataFrame.from_records(trades, columns=BacktestResult._fields)
def start(self) -> None: def start(self) -> None:

View File

@ -122,8 +122,8 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades): for c, trade in enumerate(data.trades):
res = results.iloc[c] res = results.iloc[c]
assert res.exit_type == trade.sell_reason assert res.exit_type == trade.sell_reason
assert res.open_time == _get_frame_time_from_offset(trade.open_tick) assert arrow.get(res.open_time) == _get_frame_time_from_offset(trade.open_tick)
assert res.close_time == _get_frame_time_from_offset(trade.close_tick) assert arrow.get(res.close_time) == _get_frame_time_from_offset(trade.close_tick)
def test_adjust(mocker, edge_conf): def test_adjust(mocker, edge_conf):

View File

@ -33,7 +33,7 @@ class BTContainer(NamedTuple):
def _get_frame_time_from_offset(offset): def _get_frame_time_from_offset(offset):
return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_ticker_interval)) return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_ticker_interval))
).datetime.replace(tzinfo=None) ).datetime
def _build_backtest_dataframe(ticker_with_signals): def _build_backtest_dataframe(ticker_with_signals):

View File

@ -685,25 +685,32 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
assert len(results.loc[results.open_at_end]) == 0 assert len(results.loc[results.open_at_end]) == 0
def test_backtest_multi_pair(default_conf, fee, mocker): @pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC'])
@pytest.mark.parametrize("tres", [0, 20, 30])
def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair):
def _trend_alternate_hold(dataframe=None, metadata=None): def _trend_alternate_hold(dataframe=None, metadata=None):
""" """
Buy every 8th candle - sell every other 8th -2 (hold on to pairs a bit) Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
""" """
multi = 8 if metadata['pair'] in('ETH/BTC', 'LTC/BTC'):
multi = 20
else:
multi = 18
dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0) dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0) dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
if metadata['pair'] in('ETH/BTC', 'LTC/BTC'):
dataframe['buy'] = dataframe['buy'].shift(-4)
dataframe['sell'] = dataframe['sell'].shift(-4)
return dataframe return dataframe
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker) patch_exchange(mocker)
pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC'] pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
data = history.load_data(datadir=None, ticker_interval='5m', pairs=pairs) data = history.load_data(datadir=None, ticker_interval='5m', pairs=pairs)
# Only use 500 lines to increase performance
data = trim_dictlist(data, -500) data = trim_dictlist(data, -500)
# Remove data for one pair from the beginning of the data
data[pair] = data[pair][tres:]
# We need to enable sell-signal - otherwise it sells on ROI!! # We need to enable sell-signal - otherwise it sells on ROI!!
default_conf['experimental'] = {"use_sell_signal": True} default_conf['experimental'] = {"use_sell_signal": True}
default_conf['ticker_interval'] = '5m' default_conf['ticker_interval'] = '5m'

View File

@ -3,11 +3,12 @@ nav:
- About: index.md - About: index.md
- Installation: installation.md - Installation: installation.md
- Configuration: configuration.md - Configuration: configuration.md
- Start the bot: bot-usage.md
- Stoploss: stoploss.md
- Custom Strategy: bot-optimization.md - Custom Strategy: bot-optimization.md
- Telegram: telegram-usage.md - Stoploss: stoploss.md
- Web Hook: webhook-config.md - Start the bot: bot-usage.md
- Control the bot:
- Telegram: telegram-usage.md
- Web Hook: webhook-config.md
- Backtesting: backtesting.md - Backtesting: backtesting.md
- Hyperopt: hyperopt.md - Hyperopt: hyperopt.md
- Edge positioning: edge.md - Edge positioning: edge.md