Merge branch 'develop' into pr/hroff-1902/3478
This commit is contained in:
@@ -15,7 +15,7 @@ ARGS_STRATEGY = ["strategy", "strategy_path"]
|
||||
|
||||
ARGS_TRADE = ["db_url", "sd_notify", "dry_run"]
|
||||
|
||||
ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange",
|
||||
ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange",
|
||||
"max_open_trades", "stake_amount", "fee"]
|
||||
|
||||
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
|
||||
@@ -59,10 +59,10 @@ ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchang
|
||||
|
||||
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
|
||||
"db_url", "trade_source", "export", "exportfilename",
|
||||
"timerange", "ticker_interval", "no_trades"]
|
||||
"timerange", "timeframe", "no_trades"]
|
||||
|
||||
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
||||
"trade_source", "ticker_interval"]
|
||||
"trade_source", "timeframe"]
|
||||
|
||||
ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"]
|
||||
|
||||
@@ -318,7 +318,7 @@ class Arguments:
|
||||
# Add list-timeframes subcommand
|
||||
list_timeframes_cmd = subparsers.add_parser(
|
||||
'list-timeframes',
|
||||
help='Print available ticker intervals (timeframes) for the exchange.',
|
||||
help='Print available timeframes for the exchange.',
|
||||
parents=[_common_parser],
|
||||
)
|
||||
list_timeframes_cmd.set_defaults(func=start_list_timeframes)
|
||||
|
@@ -75,8 +75,8 @@ def ask_user_config() -> Dict[str, Any]:
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "ticker_interval",
|
||||
"message": "Please insert your timeframe (ticker interval):",
|
||||
"name": "timeframe",
|
||||
"message": "Please insert your desired timeframe (e.g. 5m):",
|
||||
"default": "5m",
|
||||
},
|
||||
{
|
||||
|
@@ -110,8 +110,8 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
action='store_true',
|
||||
),
|
||||
# Optimize common
|
||||
"ticker_interval": Arg(
|
||||
'-i', '--ticker-interval',
|
||||
"timeframe": Arg(
|
||||
'-i', '--timeframe', '--ticker-interval',
|
||||
help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).',
|
||||
),
|
||||
"timerange": Arg(
|
||||
|
@@ -102,8 +102,8 @@ def start_list_timeframes(args: Dict[str, Any]) -> None:
|
||||
Print ticker intervals (timeframes) available on Exchange
|
||||
"""
|
||||
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
||||
# Do not use ticker_interval set in the config
|
||||
config['ticker_interval'] = None
|
||||
# Do not use timeframe set in the config
|
||||
config['timeframe'] = None
|
||||
|
||||
# Init exchange
|
||||
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
|
||||
|
@@ -25,7 +25,6 @@ def start_test_pairlist(args: Dict[str, Any]) -> None:
|
||||
results = {}
|
||||
for curr in quote_currencies:
|
||||
config['stake_currency'] = curr
|
||||
# Do not use ticker_interval set in the config
|
||||
pairlists = PairListManager(exchange, config)
|
||||
pairlists.refresh_pairlist()
|
||||
results[curr] = pairlists.whitelist
|
||||
|
@@ -204,9 +204,9 @@ class Configuration:
|
||||
def _process_optimize_options(self, config: Dict[str, Any]) -> None:
|
||||
|
||||
# This will override the strategy configuration
|
||||
self._args_to_config(config, argname='ticker_interval',
|
||||
logstring='Parameter -i/--ticker-interval detected ... '
|
||||
'Using ticker_interval: {} ...')
|
||||
self._args_to_config(config, argname='timeframe',
|
||||
logstring='Parameter -i/--timeframe detected ... '
|
||||
'Using timeframe: {} ...')
|
||||
|
||||
self._args_to_config(config, argname='position_stacking',
|
||||
logstring='Parameter --enable-position-stacking detected ...')
|
||||
@@ -242,8 +242,8 @@ class Configuration:
|
||||
self._args_to_config(config, argname='strategy_list',
|
||||
logstring='Using strategy list of {} strategies', logfun=len)
|
||||
|
||||
self._args_to_config(config, argname='ticker_interval',
|
||||
logstring='Overriding ticker interval with Command line argument')
|
||||
self._args_to_config(config, argname='timeframe',
|
||||
logstring='Overriding timeframe with Command line argument')
|
||||
|
||||
self._args_to_config(config, argname='export',
|
||||
logstring='Parameter --export detected: {} ...')
|
||||
|
@@ -67,3 +67,14 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
||||
"'tradable_balance_ratio' and remove 'capital_available_percentage' "
|
||||
"from the edge configuration."
|
||||
)
|
||||
if 'ticker_interval' in config:
|
||||
logger.warning(
|
||||
"DEPRECATED: "
|
||||
"Please use 'timeframe' instead of 'ticker_interval."
|
||||
)
|
||||
if 'timeframe' in config:
|
||||
raise OperationalException(
|
||||
"Both 'timeframe' and 'ticker_interval' detected."
|
||||
"Please remove 'ticker_interval' from your configuration to continue operating."
|
||||
)
|
||||
config['timeframe'] = config['ticker_interval']
|
||||
|
@@ -71,7 +71,7 @@ CONF_SCHEMA = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'max_open_trades': {'type': ['integer', 'number'], 'minimum': -1},
|
||||
'ticker_interval': {'type': 'string'},
|
||||
'timeframe': {'type': 'string'},
|
||||
'stake_currency': {'type': 'string'},
|
||||
'stake_amount': {
|
||||
'type': ['number', 'string'],
|
||||
@@ -303,6 +303,7 @@ CONF_SCHEMA = {
|
||||
|
||||
SCHEMA_TRADE_REQUIRED = [
|
||||
'exchange',
|
||||
'timeframe',
|
||||
'max_open_trades',
|
||||
'stake_currency',
|
||||
'stake_amount',
|
||||
|
@@ -103,7 +103,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame:
|
||||
"open_rate", "close_rate", "amount", "duration", "sell_reason",
|
||||
"fee_open", "fee_close", "open_rate_requested", "close_rate_requested",
|
||||
"stake_amount", "max_rate", "min_rate", "id", "exchange",
|
||||
"stop_loss", "initial_stop_loss", "strategy", "ticker_interval"]
|
||||
"stop_loss", "initial_stop_loss", "strategy", "timeframe"]
|
||||
|
||||
trades = pd.DataFrame([(t.pair,
|
||||
t.open_date.replace(tzinfo=timezone.utc),
|
||||
@@ -121,7 +121,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame:
|
||||
t.min_rate,
|
||||
t.id, t.exchange,
|
||||
t.stop_loss, t.initial_stop_loss,
|
||||
t.strategy, t.ticker_interval
|
||||
t.strategy, t.timeframe
|
||||
)
|
||||
for t in Trade.get_trades().all()],
|
||||
columns=columns)
|
||||
|
@@ -236,12 +236,12 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to:
|
||||
from freqtrade.data.history.idatahandler import get_datahandler
|
||||
src = get_datahandler(config['datadir'], convert_from)
|
||||
trg = get_datahandler(config['datadir'], convert_to)
|
||||
timeframes = config.get('timeframes', [config.get('ticker_interval')])
|
||||
timeframes = config.get('timeframes', [config.get('timeframe')])
|
||||
logger.info(f"Converting candle (OHLCV) for timeframe {timeframes}")
|
||||
|
||||
if 'pairs' not in config:
|
||||
config['pairs'] = []
|
||||
# Check timeframes or fall back to ticker_interval.
|
||||
# Check timeframes or fall back to timeframe.
|
||||
for timeframe in timeframes:
|
||||
config['pairs'].extend(src.ohlcv_get_pairs(config['datadir'],
|
||||
timeframe))
|
||||
|
@@ -55,7 +55,7 @@ class DataProvider:
|
||||
Use False only for read-only operations (where the dataframe is not modified)
|
||||
"""
|
||||
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
||||
return self._exchange.klines((pair, timeframe or self._config['ticker_interval']),
|
||||
return self._exchange.klines((pair, timeframe or self._config['timeframe']),
|
||||
copy=copy)
|
||||
else:
|
||||
return DataFrame()
|
||||
@@ -67,7 +67,7 @@ class DataProvider:
|
||||
:param timeframe: timeframe to get data for
|
||||
"""
|
||||
return load_pair_history(pair=pair,
|
||||
timeframe=timeframe or self._config['ticker_interval'],
|
||||
timeframe=timeframe or self._config['timeframe'],
|
||||
datadir=self._config['datadir']
|
||||
)
|
||||
|
||||
|
@@ -98,14 +98,14 @@ class Edge:
|
||||
datadir=self.config['datadir'],
|
||||
pairs=pairs,
|
||||
exchange=self.exchange,
|
||||
timeframe=self.strategy.ticker_interval,
|
||||
timeframe=self.strategy.timeframe,
|
||||
timerange=self._timerange,
|
||||
)
|
||||
|
||||
data = load_data(
|
||||
datadir=self.config['datadir'],
|
||||
pairs=pairs,
|
||||
timeframe=self.strategy.ticker_interval,
|
||||
timeframe=self.strategy.timeframe,
|
||||
timerange=self._timerange,
|
||||
startup_candles=self.strategy.startup_candle_count,
|
||||
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
||||
|
@@ -79,7 +79,7 @@ class Exchange:
|
||||
|
||||
if config['dry_run']:
|
||||
logger.info('Instance is running with dry_run enabled')
|
||||
|
||||
logger.info(f"Using CCXT {ccxt.__version__}")
|
||||
exchange_config = config['exchange']
|
||||
|
||||
# Deep merge ft_has with default ft_has options
|
||||
@@ -115,7 +115,7 @@ class Exchange:
|
||||
|
||||
if validate:
|
||||
# Check if timeframe is available
|
||||
self.validate_timeframes(config.get('ticker_interval'))
|
||||
self.validate_timeframes(config.get('timeframe'))
|
||||
|
||||
# Initial markets load
|
||||
self._load_markets()
|
||||
@@ -285,6 +285,8 @@ class Exchange:
|
||||
logger.debug("Performing scheduled market reload..")
|
||||
try:
|
||||
self._api.load_markets(reload=True)
|
||||
# Also reload async markets to avoid issues with newly listed pairs
|
||||
self._load_async_markets(reload=True)
|
||||
self._last_markets_refresh = arrow.utcnow().timestamp
|
||||
except ccxt.BaseError:
|
||||
logger.exception("Could not reload markets.")
|
||||
@@ -959,6 +961,9 @@ class Exchange:
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
# Assign method to get_stoploss_order to allow easy overriding in other classes
|
||||
cancel_stoploss_order = cancel_order
|
||||
|
||||
def is_cancel_order_result_suitable(self, corder) -> bool:
|
||||
if not isinstance(corder, dict):
|
||||
return False
|
||||
@@ -1011,6 +1016,9 @@ class Exchange:
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
# Assign method to get_stoploss_order to allow easy overriding in other classes
|
||||
get_stoploss_order = get_order
|
||||
|
||||
@retrier
|
||||
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
|
||||
"""
|
||||
|
@@ -2,7 +2,12 @@
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
import ccxt
|
||||
|
||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
||||
OperationalException, TemporaryError)
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.exchange.common import retrier
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -10,5 +15,104 @@ logger = logging.getLogger(__name__)
|
||||
class Ftx(Exchange):
|
||||
|
||||
_ft_has: Dict = {
|
||||
"stoploss_on_exchange": True,
|
||||
"ohlcv_candle_limit": 1500,
|
||||
}
|
||||
|
||||
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
||||
"""
|
||||
Verify stop_loss against stoploss-order value (limit or price)
|
||||
Returns True if adjustment is necessary.
|
||||
"""
|
||||
return order['type'] == 'stop' and stop_loss > float(order['price'])
|
||||
|
||||
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
||||
"""
|
||||
Creates a stoploss order.
|
||||
depending on order_types.stoploss configuration, uses 'market' or limit order.
|
||||
|
||||
Limit orders are defined by having orderPrice set, otherwise a market order is used.
|
||||
"""
|
||||
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
|
||||
limit_rate = stop_price * limit_price_pct
|
||||
|
||||
ordertype = "stop"
|
||||
|
||||
stop_price = self.price_to_precision(pair, stop_price)
|
||||
|
||||
if self._config['dry_run']:
|
||||
dry_order = self.dry_run_order(
|
||||
pair, ordertype, "sell", amount, stop_price)
|
||||
return dry_order
|
||||
|
||||
try:
|
||||
params = self._params.copy()
|
||||
if order_types.get('stoploss', 'market') == 'limit':
|
||||
# set orderPrice to place limit order, otherwise it's a market order
|
||||
params['orderPrice'] = limit_rate
|
||||
|
||||
amount = self.amount_to_precision(pair, amount)
|
||||
|
||||
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
|
||||
amount=amount, price=stop_price, params=params)
|
||||
logger.info('stoploss order added for %s. '
|
||||
'stop price: %s.', pair, stop_price)
|
||||
return order
|
||||
except ccxt.InsufficientFunds as e:
|
||||
raise DependencyException(
|
||||
f'Insufficient funds to create {ordertype} sell order on market {pair}. '
|
||||
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
||||
f'Message: {e}') from e
|
||||
except ccxt.InvalidOrder as e:
|
||||
raise InvalidOrderException(
|
||||
f'Could not create {ordertype} sell order on market {pair}. '
|
||||
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
||||
f'Message: {e}') from e
|
||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
@retrier
|
||||
def get_stoploss_order(self, order_id: str, pair: str) -> Dict:
|
||||
if self._config['dry_run']:
|
||||
try:
|
||||
order = self._dry_run_open_orders[order_id]
|
||||
return order
|
||||
except KeyError as e:
|
||||
# Gracefully handle errors with dry-run orders.
|
||||
raise InvalidOrderException(
|
||||
f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e
|
||||
try:
|
||||
orders = self._api.fetch_orders(pair, None, params={'type': 'stop'})
|
||||
|
||||
order = [order for order in orders if order['id'] == order_id]
|
||||
if len(order) == 1:
|
||||
return order[0]
|
||||
else:
|
||||
raise InvalidOrderException(f"Could not get stoploss order for id {order_id}")
|
||||
|
||||
except ccxt.InvalidOrder as e:
|
||||
raise InvalidOrderException(
|
||||
f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
|
||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
@retrier
|
||||
def cancel_stoploss_order(self, order_id: str, pair: str) -> Dict:
|
||||
if self._config['dry_run']:
|
||||
return {}
|
||||
try:
|
||||
return self._api.cancel_order(order_id, pair, params={'type': 'stop'})
|
||||
except ccxt.InvalidOrder as e:
|
||||
raise InvalidOrderException(
|
||||
f'Could not cancel order. Message: {e}') from e
|
||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
@@ -421,8 +421,8 @@ class FreqtradeBot:
|
||||
|
||||
# running get_signal on historical data fetched
|
||||
(buy, sell) = self.strategy.get_signal(
|
||||
pair, self.strategy.ticker_interval,
|
||||
self.dataprovider.ohlcv(pair, self.strategy.ticker_interval))
|
||||
pair, self.strategy.timeframe,
|
||||
self.dataprovider.ohlcv(pair, self.strategy.timeframe))
|
||||
|
||||
if buy and not sell:
|
||||
stake_amount = self.get_trade_stake_amount(pair)
|
||||
@@ -547,7 +547,7 @@ class FreqtradeBot:
|
||||
exchange=self.exchange.id,
|
||||
open_order_id=order_id,
|
||||
strategy=self.strategy.get_strategy_name(),
|
||||
ticker_interval=timeframe_to_minutes(self.config['ticker_interval'])
|
||||
timeframe=timeframe_to_minutes(self.config['timeframe'])
|
||||
)
|
||||
|
||||
# Update fees if order is closed
|
||||
@@ -698,8 +698,8 @@ class FreqtradeBot:
|
||||
if (config_ask_strategy.get('use_sell_signal', True) or
|
||||
config_ask_strategy.get('ignore_roi_if_buy_signal', False)):
|
||||
(buy, sell) = self.strategy.get_signal(
|
||||
trade.pair, self.strategy.ticker_interval,
|
||||
self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval))
|
||||
trade.pair, self.strategy.timeframe,
|
||||
self.dataprovider.ohlcv(trade.pair, self.strategy.timeframe))
|
||||
|
||||
if config_ask_strategy.get('use_order_book', False):
|
||||
order_book_min = config_ask_strategy.get('order_book_min', 1)
|
||||
@@ -773,18 +773,18 @@ class FreqtradeBot:
|
||||
|
||||
try:
|
||||
# First we check if there is already a stoploss on exchange
|
||||
stoploss_order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) \
|
||||
stoploss_order = self.exchange.get_stoploss_order(trade.stoploss_order_id, trade.pair) \
|
||||
if trade.stoploss_order_id else None
|
||||
except InvalidOrderException as exception:
|
||||
logger.warning('Unable to fetch stoploss order: %s', exception)
|
||||
|
||||
# We check if stoploss order is fulfilled
|
||||
if stoploss_order and stoploss_order['status'] == 'closed':
|
||||
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
|
||||
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
|
||||
self.update_trade_state(trade, stoploss_order, sl_order=True)
|
||||
# Lock pair for one candle to prevent immediate rebuys
|
||||
self.strategy.lock_pair(trade.pair,
|
||||
timeframe_to_next_date(self.config['ticker_interval']))
|
||||
timeframe_to_next_date(self.config['timeframe']))
|
||||
self._notify_sell(trade, "stoploss")
|
||||
return True
|
||||
|
||||
@@ -804,7 +804,7 @@ class FreqtradeBot:
|
||||
return False
|
||||
|
||||
# If stoploss order is canceled for some reason we add it
|
||||
if stoploss_order and stoploss_order['status'] == 'canceled':
|
||||
if stoploss_order and stoploss_order['status'] in ('canceled', 'cancelled'):
|
||||
if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss,
|
||||
rate=trade.stop_loss):
|
||||
return False
|
||||
@@ -837,7 +837,7 @@ class FreqtradeBot:
|
||||
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s}) '
|
||||
'in order to add another one ...', order['id'])
|
||||
try:
|
||||
self.exchange.cancel_order(order['id'], trade.pair)
|
||||
self.exchange.cancel_stoploss_order(order['id'], trade.pair)
|
||||
except InvalidOrderException:
|
||||
logger.exception(f"Could not cancel stoploss order {order['id']} "
|
||||
f"for pair {trade.pair}")
|
||||
@@ -1065,7 +1065,7 @@ class FreqtradeBot:
|
||||
# First cancelling stoploss on exchange ...
|
||||
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
|
||||
try:
|
||||
self.exchange.cancel_order(trade.stoploss_order_id, trade.pair)
|
||||
self.exchange.cancel_stoploss_order(trade.stoploss_order_id, trade.pair)
|
||||
except InvalidOrderException:
|
||||
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
|
||||
|
||||
@@ -1092,7 +1092,7 @@ class FreqtradeBot:
|
||||
Trade.session.flush()
|
||||
|
||||
# Lock pair for one candle to prevent immediate rebuys
|
||||
self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['ticker_interval']))
|
||||
self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['timeframe']))
|
||||
|
||||
self._notify_sell(trade, order_type)
|
||||
|
||||
|
@@ -95,10 +95,10 @@ class Backtesting:
|
||||
self.strategylist.append(StrategyResolver.load_strategy(self.config))
|
||||
validate_config_consistency(self.config)
|
||||
|
||||
if "ticker_interval" not in self.config:
|
||||
if "timeframe" not in self.config:
|
||||
raise OperationalException("Timeframe (ticker interval) needs to be set in either "
|
||||
"configuration or as cli argument `--ticker-interval 5m`")
|
||||
self.timeframe = str(self.config.get('ticker_interval'))
|
||||
"configuration or as cli argument `--timeframe 5m`")
|
||||
self.timeframe = str(self.config.get('timeframe'))
|
||||
self.timeframe_min = timeframe_to_minutes(self.timeframe)
|
||||
|
||||
# Get maximum required startup period
|
||||
|
@@ -12,7 +12,7 @@ from math import ceil
|
||||
from collections import OrderedDict
|
||||
from operator import itemgetter
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
from pprint import pformat
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import rapidjson
|
||||
@@ -230,6 +230,9 @@ class Hyperopt:
|
||||
if space in ['buy', 'sell']:
|
||||
result_dict.setdefault('params', {}).update(space_params)
|
||||
elif space == 'roi':
|
||||
# TODO: get rid of OrderedDict when support for python 3.6 will be
|
||||
# dropped (dicts keep the order as the language feature)
|
||||
|
||||
# Convert keys in min_roi dict to strings because
|
||||
# rapidjson cannot dump dicts with integer keys...
|
||||
# OrderedDict is used to keep the numeric order of the items
|
||||
@@ -244,11 +247,24 @@ class Hyperopt:
|
||||
def _params_pretty_print(params, space: str, header: str) -> None:
|
||||
if space in params:
|
||||
space_params = Hyperopt._space_params(params, space, 5)
|
||||
params_result = f"\n# {header}\n"
|
||||
if space == 'stoploss':
|
||||
print(header, space_params.get('stoploss'))
|
||||
params_result += f"stoploss = {space_params.get('stoploss')}"
|
||||
elif space == 'roi':
|
||||
# TODO: get rid of OrderedDict when support for python 3.6 will be
|
||||
# dropped (dicts keep the order as the language feature)
|
||||
minimal_roi_result = rapidjson.dumps(
|
||||
OrderedDict(
|
||||
(str(k), v) for k, v in space_params.items()
|
||||
),
|
||||
default=str, indent=4, number_mode=rapidjson.NM_NATIVE)
|
||||
params_result += f"minimal_roi = {minimal_roi_result}"
|
||||
else:
|
||||
print(header)
|
||||
pprint(space_params, indent=4)
|
||||
params_result += f"{space}_params = {pformat(space_params, indent=4)}"
|
||||
params_result = params_result.replace("}", "\n}").replace("{", "{\n ")
|
||||
|
||||
params_result = params_result.replace("\n", "\n ")
|
||||
print(params_result)
|
||||
|
||||
@staticmethod
|
||||
def _space_params(params, space: str, r: int = None) -> Dict:
|
||||
|
@@ -31,13 +31,15 @@ class IHyperOpt(ABC):
|
||||
Class attributes you can use:
|
||||
ticker_interval -> int: value of the ticker interval to use for the strategy
|
||||
"""
|
||||
ticker_interval: str
|
||||
ticker_interval: str # DEPRECATED
|
||||
timeframe: str
|
||||
|
||||
def __init__(self, config: dict) -> None:
|
||||
self.config = config
|
||||
|
||||
# Assign ticker_interval to be used in hyperopt
|
||||
IHyperOpt.ticker_interval = str(config['ticker_interval'])
|
||||
IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
|
||||
IHyperOpt.timeframe = str(config['timeframe'])
|
||||
|
||||
@staticmethod
|
||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
@@ -218,9 +220,10 @@ class IHyperOpt(ABC):
|
||||
# Why do I still need such shamanic mantras in modern python?
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
state['ticker_interval'] = self.ticker_interval
|
||||
state['timeframe'] = self.timeframe
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
IHyperOpt.ticker_interval = state['ticker_interval']
|
||||
IHyperOpt.ticker_interval = state['timeframe']
|
||||
IHyperOpt.timeframe = state['timeframe']
|
||||
|
@@ -14,7 +14,7 @@ class IHyperOptLoss(ABC):
|
||||
Interface for freqtrade hyperopt Loss functions.
|
||||
Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.)
|
||||
"""
|
||||
ticker_interval: str
|
||||
timeframe: str
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
|
@@ -131,6 +131,6 @@ class PairListManager():
|
||||
|
||||
def create_pair_list(self, pairs: List[str], timeframe: str = None) -> ListPairsWithTimeframes:
|
||||
"""
|
||||
Create list of pair tuples with (pair, ticker_interval)
|
||||
Create list of pair tuples with (pair, timeframe)
|
||||
"""
|
||||
return [(pair, timeframe or self._config['ticker_interval']) for pair in pairs]
|
||||
return [(pair, timeframe or self._config['timeframe']) for pair in pairs]
|
||||
|
@@ -86,7 +86,7 @@ def check_migrate(engine) -> None:
|
||||
logger.debug(f'trying {table_back_name}')
|
||||
|
||||
# Check for latest column
|
||||
if not has_column(cols, 'sell_order_status'):
|
||||
if not has_column(cols, 'timeframe'):
|
||||
logger.info(f'Running database migration - backup available as {table_back_name}')
|
||||
|
||||
fee_open = get_column_def(cols, 'fee_open', 'fee')
|
||||
@@ -107,7 +107,12 @@ def check_migrate(engine) -> None:
|
||||
min_rate = get_column_def(cols, 'min_rate', 'null')
|
||||
sell_reason = get_column_def(cols, 'sell_reason', 'null')
|
||||
strategy = get_column_def(cols, 'strategy', 'null')
|
||||
ticker_interval = get_column_def(cols, 'ticker_interval', 'null')
|
||||
# If ticker-interval existed use that, else null.
|
||||
if has_column(cols, 'ticker_interval'):
|
||||
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
|
||||
else:
|
||||
timeframe = get_column_def(cols, 'timeframe', 'null')
|
||||
|
||||
open_trade_price = get_column_def(cols, 'open_trade_price',
|
||||
f'amount * open_rate * (1 + {fee_open})')
|
||||
close_profit_abs = get_column_def(
|
||||
@@ -133,7 +138,7 @@ def check_migrate(engine) -> None:
|
||||
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
||||
stoploss_order_id, stoploss_last_update,
|
||||
max_rate, min_rate, sell_reason, sell_order_status, strategy,
|
||||
ticker_interval, open_trade_price, close_profit_abs
|
||||
timeframe, open_trade_price, close_profit_abs
|
||||
)
|
||||
select id, lower(exchange),
|
||||
case
|
||||
@@ -155,7 +160,7 @@ def check_migrate(engine) -> None:
|
||||
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
||||
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
|
||||
{sell_order_status} sell_order_status,
|
||||
{strategy} strategy, {ticker_interval} ticker_interval,
|
||||
{strategy} strategy, {timeframe} timeframe,
|
||||
{open_trade_price} open_trade_price, {close_profit_abs} close_profit_abs
|
||||
from {table_back_name}
|
||||
""")
|
||||
@@ -232,7 +237,7 @@ class Trade(_DECL_BASE):
|
||||
sell_reason = Column(String, nullable=True)
|
||||
sell_order_status = Column(String, nullable=True)
|
||||
strategy = Column(String, nullable=True)
|
||||
ticker_interval = Column(Integer, nullable=True)
|
||||
timeframe = Column(Integer, nullable=True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -253,7 +258,8 @@ class Trade(_DECL_BASE):
|
||||
'amount': round(self.amount, 8),
|
||||
'stake_amount': round(self.stake_amount, 8),
|
||||
'strategy': self.strategy,
|
||||
'ticker_interval': self.ticker_interval,
|
||||
'ticker_interval': self.timeframe, # DEPRECATED
|
||||
'timeframe': self.timeframe,
|
||||
|
||||
'fee_open': self.fee_open,
|
||||
'fee_open_cost': self.fee_open_cost,
|
||||
@@ -374,7 +380,7 @@ class Trade(_DECL_BASE):
|
||||
elif order_type in ('market', 'limit') and order['side'] == 'sell':
|
||||
self.close(order['price'])
|
||||
logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self)
|
||||
elif order_type in ('stop_loss_limit', 'stop-loss'):
|
||||
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'):
|
||||
self.stoploss_order_id = None
|
||||
self.close_rate_requested = self.stop_loss
|
||||
logger.info('%s is hit for %s.', order_type.upper(), self)
|
||||
|
@@ -45,7 +45,7 @@ def init_plotscript(config):
|
||||
data = load_data(
|
||||
datadir=config.get("datadir"),
|
||||
pairs=pairs,
|
||||
timeframe=config.get('ticker_interval', '5m'),
|
||||
timeframe=config.get('timeframe', '5m'),
|
||||
timerange=timerange,
|
||||
data_format=config.get('dataformat_ohlcv', 'json'),
|
||||
)
|
||||
@@ -487,7 +487,7 @@ def load_and_plot_trades(config: Dict[str, Any]):
|
||||
plot_config=strategy.plot_config if hasattr(strategy, 'plot_config') else {}
|
||||
)
|
||||
|
||||
store_plot_file(fig, filename=generate_plot_filename(pair, config['ticker_interval']),
|
||||
store_plot_file(fig, filename=generate_plot_filename(pair, config['timeframe']),
|
||||
directory=config['user_data_dir'] / "plot")
|
||||
|
||||
logger.info('End of plotting process. %s plots generated', pair_counter)
|
||||
@@ -515,6 +515,6 @@ def plot_profit(config: Dict[str, Any]) -> None:
|
||||
# Create an average close price of all the pairs that were involved.
|
||||
# this could be useful to gauge the overall market trend
|
||||
fig = generate_profit_graph(plot_elements["pairs"], plot_elements["ohlcv"],
|
||||
trades, config.get('ticker_interval', '5m'))
|
||||
trades, config.get('timeframe', '5m'))
|
||||
store_plot_file(fig, filename='freqtrade-profit-plot.html',
|
||||
directory=config['user_data_dir'] / "plot", auto_open=True)
|
||||
|
@@ -77,8 +77,9 @@ class HyperOptLossResolver(IResolver):
|
||||
config, kwargs={},
|
||||
extra_dir=config.get('hyperopt_path'))
|
||||
|
||||
# Assign ticker_interval to be used in hyperopt
|
||||
hyperoptloss.__class__.ticker_interval = str(config['ticker_interval'])
|
||||
# Assign timeframe to be used in hyperopt
|
||||
hyperoptloss.__class__.ticker_interval = str(config['timeframe'])
|
||||
hyperoptloss.__class__.timeframe = str(config['timeframe'])
|
||||
|
||||
if not hasattr(hyperoptloss, 'hyperopt_loss_function'):
|
||||
raise OperationalException(
|
||||
|
@@ -50,11 +50,19 @@ class StrategyResolver(IResolver):
|
||||
if 'ask_strategy' not in config:
|
||||
config['ask_strategy'] = {}
|
||||
|
||||
if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'):
|
||||
# Assign ticker_interval to timeframe to keep compatibility
|
||||
if 'timeframe' not in config:
|
||||
logger.warning(
|
||||
"DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'."
|
||||
)
|
||||
strategy.timeframe = strategy.ticker_interval
|
||||
|
||||
# Set attributes
|
||||
# Check if we need to override configuration
|
||||
# (Attribute name, default, subkey)
|
||||
attributes = [("minimal_roi", {"0": 10.0}, None),
|
||||
("ticker_interval", None, None),
|
||||
("timeframe", None, None),
|
||||
("stoploss", None, None),
|
||||
("trailing_stop", None, None),
|
||||
("trailing_stop_positive", None, None),
|
||||
@@ -80,6 +88,9 @@ class StrategyResolver(IResolver):
|
||||
StrategyResolver._override_attribute_helper(strategy, config,
|
||||
attribute, default)
|
||||
|
||||
# Assign deprecated variable - to not break users code relying on this.
|
||||
strategy.ticker_interval = strategy.timeframe
|
||||
|
||||
# Loop this list again to have output combined
|
||||
for attribute, _, subkey in attributes:
|
||||
if subkey and attribute in config[subkey]:
|
||||
|
@@ -101,7 +101,8 @@ class RPC:
|
||||
'trailing_stop_positive': config.get('trailing_stop_positive'),
|
||||
'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'),
|
||||
'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached'),
|
||||
'ticker_interval': config['ticker_interval'],
|
||||
'ticker_interval': config['timeframe'], # DEPRECATED
|
||||
'timeframe': config['timeframe'],
|
||||
'exchange': config['exchange']['name'],
|
||||
'strategy': config['strategy'],
|
||||
'forcebuy_enabled': config.get('forcebuy_enable', False),
|
||||
|
@@ -72,7 +72,7 @@ class RPCManager:
|
||||
minimal_roi = config['minimal_roi']
|
||||
stoploss = config['stoploss']
|
||||
trailing_stop = config['trailing_stop']
|
||||
ticker_interval = config['ticker_interval']
|
||||
timeframe = config['timeframe']
|
||||
exchange_name = config['exchange']['name']
|
||||
strategy_name = config.get('strategy', '')
|
||||
self.send_msg({
|
||||
@@ -81,7 +81,7 @@ class RPCManager:
|
||||
f'*Stake per trade:* `{stake_amount} {stake_currency}`\n'
|
||||
f'*Minimum ROI:* `{minimal_roi}`\n'
|
||||
f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n'
|
||||
f'*Ticker Interval:* `{ticker_interval}`\n'
|
||||
f'*Timeframe:* `{timeframe}`\n'
|
||||
f'*Strategy:* `{strategy_name}`'
|
||||
})
|
||||
self.send_msg({
|
||||
|
@@ -669,7 +669,7 @@ class Telegram(RPC):
|
||||
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
|
||||
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
|
||||
f"{sl_info}"
|
||||
f"*Ticker Interval:* `{val['ticker_interval']}`\n"
|
||||
f"*Timeframe:* `{val['timeframe']}`\n"
|
||||
f"*Strategy:* `{val['strategy']}`\n"
|
||||
f"*Current state:* `{val['state']}`"
|
||||
)
|
||||
|
@@ -62,7 +62,7 @@ class IStrategy(ABC):
|
||||
Attributes you can use:
|
||||
minimal_roi -> Dict: Minimal ROI designed for the strategy
|
||||
stoploss -> float: optimal stoploss designed for the strategy
|
||||
ticker_interval -> str: value of the timeframe (ticker interval) to use with the strategy
|
||||
timeframe -> str: value of the timeframe (ticker interval) to use with the strategy
|
||||
"""
|
||||
# Strategy interface version
|
||||
# Default to version 2
|
||||
@@ -85,8 +85,9 @@ class IStrategy(ABC):
|
||||
trailing_stop_positive_offset: float = 0.0
|
||||
trailing_only_offset_is_reached = False
|
||||
|
||||
# associated ticker interval
|
||||
ticker_interval: str
|
||||
# associated timeframe
|
||||
ticker_interval: str # DEPRECATED
|
||||
timeframe: str
|
||||
|
||||
# Optional order types
|
||||
order_types: Dict = {
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"stake_amount": {{ stake_amount }},
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "{{ fiat_display_currency }}",
|
||||
"ticker_interval": "{{ ticker_interval }}",
|
||||
"timeframe": "{{ timeframe }}",
|
||||
"dry_run": {{ dry_run | lower }},
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"unfilledtimeout": {
|
||||
|
@@ -51,8 +51,8 @@ class {{ strategy }}(IStrategy):
|
||||
# trailing_stop_positive = 0.01
|
||||
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
|
||||
|
||||
# Optimal ticker interval for the strategy.
|
||||
ticker_interval = '5m'
|
||||
# Optimal timeframe for the strategy.
|
||||
timeframe = '5m'
|
||||
|
||||
# Run "populate_indicators()" only for new candle.
|
||||
process_only_new_candles = False
|
||||
|
@@ -53,7 +53,7 @@ class SampleStrategy(IStrategy):
|
||||
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
|
||||
|
||||
# Optimal ticker interval for the strategy.
|
||||
ticker_interval = '5m'
|
||||
timeframe = '5m'
|
||||
|
||||
# Run "populate_indicators()" only for new candle.
|
||||
process_only_new_candles = False
|
||||
|
Reference in New Issue
Block a user