Merge pull request #6607 from freqtrade/short_pricing
Short pricing updates
This commit is contained in:
@@ -103,14 +103,15 @@ def _validate_price_config(conf: Dict[str, Any]) -> None:
|
||||
"""
|
||||
When using market orders, price sides must be using the "other" side of the price
|
||||
"""
|
||||
# TODO-lev: check this again when determining how to migrate pricing strategies!
|
||||
# TODO: The below could be an enforced setting when using market orders
|
||||
if (conf.get('order_types', {}).get('entry') == 'market'
|
||||
and conf.get('bid_strategy', {}).get('price_side') != 'ask'):
|
||||
raise OperationalException('Market buy orders require bid_strategy.price_side = "ask".')
|
||||
and conf.get('entry_pricing', {}).get('price_side') not in ('ask', 'other')):
|
||||
raise OperationalException(
|
||||
'Market entry orders require entry_pricing.price_side = "other".')
|
||||
|
||||
if (conf.get('order_types', {}).get('exit') == 'market'
|
||||
and conf.get('ask_strategy', {}).get('price_side') != 'bid'):
|
||||
raise OperationalException('Market sell orders require ask_strategy.price_side = "bid".')
|
||||
and conf.get('exit_pricing', {}).get('price_side') not in ('bid', 'other')):
|
||||
raise OperationalException('Market exit orders require exit_pricing.price_side = "other".')
|
||||
|
||||
|
||||
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
||||
@@ -193,13 +194,13 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
|
||||
|
||||
|
||||
def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
||||
ask_strategy = conf.get('ask_strategy', {})
|
||||
ask_strategy = conf.get('exit_pricing', {})
|
||||
ob_min = ask_strategy.get('order_book_min')
|
||||
ob_max = ask_strategy.get('order_book_max')
|
||||
if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'):
|
||||
if ob_min != ob_max:
|
||||
raise OperationalException(
|
||||
"Using order_book_max != order_book_min in ask_strategy is no longer supported."
|
||||
"Using order_book_max != order_book_min in exit_pricing is no longer supported."
|
||||
"Please pick one value and use `order_book_top` in the future."
|
||||
)
|
||||
else:
|
||||
@@ -208,7 +209,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
||||
logger.warning(
|
||||
"DEPRECATED: "
|
||||
"Please use `order_book_top` instead of `order_book_min` and `order_book_max` "
|
||||
"for your `ask_strategy` configuration."
|
||||
"for your `exit_pricing` configuration."
|
||||
)
|
||||
|
||||
|
||||
@@ -217,6 +218,7 @@ def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None:
|
||||
_validate_time_in_force(conf)
|
||||
_validate_order_types(conf)
|
||||
_validate_unfilledtimeout(conf)
|
||||
_validate_pricing_rules(conf)
|
||||
|
||||
|
||||
def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
||||
@@ -279,3 +281,34 @@ def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
|
||||
]:
|
||||
|
||||
process_deprecated_setting(conf, 'unfilledtimeout', o, 'unfilledtimeout', n)
|
||||
|
||||
|
||||
def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
|
||||
|
||||
if conf.get('ask_strategy') or conf.get('bid_strategy'):
|
||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||
raise OperationalException(
|
||||
"Please migrate your pricing settings to use the new wording.")
|
||||
else:
|
||||
|
||||
logger.warning(
|
||||
"DEPRECATED: Using 'ask_strategy' and 'bid_strategy' is deprecated."
|
||||
"Please migrate your settings to use 'entry_pricing' and 'exit_pricing'."
|
||||
)
|
||||
conf['entry_pricing'] = {}
|
||||
for obj in list(conf.get('bid_strategy', {}).keys()):
|
||||
if obj == 'ask_last_balance':
|
||||
process_deprecated_setting(conf, 'bid_strategy', obj,
|
||||
'entry_pricing', 'price_last_balance')
|
||||
else:
|
||||
process_deprecated_setting(conf, 'bid_strategy', obj, 'entry_pricing', obj)
|
||||
del conf['bid_strategy']
|
||||
|
||||
conf['exit_pricing'] = {}
|
||||
for obj in list(conf.get('ask_strategy', {}).keys()):
|
||||
if obj == 'bid_last_balance':
|
||||
process_deprecated_setting(conf, 'ask_strategy', obj,
|
||||
'exit_pricing', 'price_last_balance')
|
||||
else:
|
||||
process_deprecated_setting(conf, 'ask_strategy', obj, 'exit_pricing', obj)
|
||||
del conf['ask_strategy']
|
||||
|
@@ -81,8 +81,6 @@ class Configuration:
|
||||
# Normalize config
|
||||
if 'internals' not in config:
|
||||
config['internals'] = {}
|
||||
if 'ask_strategy' not in config:
|
||||
config['ask_strategy'] = {}
|
||||
|
||||
if 'pairlists' not in config:
|
||||
config['pairlists'] = []
|
||||
|
@@ -21,7 +21,7 @@ UNLIMITED_STAKE_AMOUNT = 'unlimited'
|
||||
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
|
||||
REQUIRED_ORDERTIF = ['entry', 'exit']
|
||||
REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']
|
||||
ORDERBOOK_SIDES = ['ask', 'bid']
|
||||
PRICING_SIDES = ['ask', 'bid', 'same', 'other']
|
||||
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
|
||||
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
||||
HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
|
||||
@@ -171,16 +171,16 @@ CONF_SCHEMA = {
|
||||
'unit': {'type': 'string', 'enum': TIMEOUT_UNITS, 'default': 'minutes'}
|
||||
}
|
||||
},
|
||||
'bid_strategy': {
|
||||
'entry_pricing': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'ask_last_balance': {
|
||||
'price_last_balance': {
|
||||
'type': 'number',
|
||||
'minimum': 0,
|
||||
'maximum': 1,
|
||||
'exclusiveMaximum': False,
|
||||
},
|
||||
'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'bid'},
|
||||
'price_side': {'type': 'string', 'enum': PRICING_SIDES, 'default': 'same'},
|
||||
'use_order_book': {'type': 'boolean'},
|
||||
'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, },
|
||||
'check_depth_of_market': {
|
||||
@@ -193,11 +193,11 @@ CONF_SCHEMA = {
|
||||
},
|
||||
'required': ['price_side']
|
||||
},
|
||||
'ask_strategy': {
|
||||
'exit_pricing': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'ask'},
|
||||
'bid_last_balance': {
|
||||
'price_side': {'type': 'string', 'enum': PRICING_SIDES, 'default': 'same'},
|
||||
'price_last_balance': {
|
||||
'type': 'number',
|
||||
'minimum': 0,
|
||||
'maximum': 1,
|
||||
@@ -445,8 +445,8 @@ SCHEMA_TRADE_REQUIRED = [
|
||||
'last_stake_amount_min_ratio',
|
||||
'dry_run',
|
||||
'dry_run_wallet',
|
||||
'ask_strategy',
|
||||
'bid_strategy',
|
||||
'exit_pricing',
|
||||
'entry_pricing',
|
||||
'stoploss',
|
||||
'minimal_roi',
|
||||
'internals',
|
||||
|
@@ -111,8 +111,8 @@ class Exchange:
|
||||
# Cache values for 1800 to avoid frequent polling of the exchange for prices
|
||||
# Caching only applies to RPC methods, so prices for open trades are still
|
||||
# refreshed once every iteration.
|
||||
self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||
self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||
self._exit_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||
self._entry_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||
|
||||
# Holds candles
|
||||
self._klines: Dict[PairWithTimeframe, DataFrame] = {}
|
||||
@@ -184,8 +184,8 @@ class Exchange:
|
||||
self.required_candle_call_count = self.validate_required_startup_candles(
|
||||
config.get('startup_candle_count', 0), config.get('timeframe', ''))
|
||||
self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode)
|
||||
self.validate_pricing(config['ask_strategy'])
|
||||
self.validate_pricing(config['bid_strategy'])
|
||||
self.validate_pricing(config['exit_pricing'])
|
||||
self.validate_pricing(config['entry_pricing'])
|
||||
|
||||
# Converts the interval provided in minutes in config to seconds
|
||||
self.markets_refresh_interval: int = exchange_config.get(
|
||||
@@ -1438,7 +1438,8 @@ class Exchange:
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
def get_rate(self, pair: str, refresh: bool, side: str) -> float:
|
||||
def get_rate(self, pair: str, refresh: bool,
|
||||
side: Literal['entry', 'exit'], is_short: bool) -> float:
|
||||
"""
|
||||
Calculates bid/ask target
|
||||
bid rate - between current ask price and last price
|
||||
@@ -1450,9 +1451,10 @@ class Exchange:
|
||||
:return: float: Price
|
||||
:raises PricingError if orderbook price could not be determined.
|
||||
"""
|
||||
cache_rate: TTLCache = self._buy_rate_cache if side == "buy" else self._sell_rate_cache
|
||||
[strat_name, name] = ['bid_strategy', 'Buy'] if side == "buy" else ['ask_strategy', 'Sell']
|
||||
name = side.capitalize()
|
||||
strat_name = 'entry_pricing' if side == "entry" else 'exit_pricing'
|
||||
|
||||
cache_rate: TTLCache = self._entry_rate_cache if side == "entry" else self._exit_rate_cache
|
||||
if not refresh:
|
||||
rate = cache_rate.get(pair)
|
||||
# Check if cache has been invalidated
|
||||
@@ -1462,6 +1464,23 @@ class Exchange:
|
||||
|
||||
conf_strategy = self._config.get(strat_name, {})
|
||||
|
||||
price_side = conf_strategy['price_side']
|
||||
|
||||
if price_side in ('same', 'other'):
|
||||
price_map = {
|
||||
('entry', 'long', 'same'): 'bid',
|
||||
('entry', 'long', 'other'): 'ask',
|
||||
('entry', 'short', 'same'): 'ask',
|
||||
('entry', 'short', 'other'): 'bid',
|
||||
('exit', 'long', 'same'): 'ask',
|
||||
('exit', 'long', 'other'): 'bid',
|
||||
('exit', 'short', 'same'): 'bid',
|
||||
('exit', 'short', 'other'): 'ask',
|
||||
}
|
||||
price_side = price_map[(side, 'short' if is_short else 'long', price_side)]
|
||||
|
||||
price_side_word = price_side.capitalize()
|
||||
|
||||
if conf_strategy.get('use_order_book', False):
|
||||
|
||||
order_book_top = conf_strategy.get('order_book_top', 1)
|
||||
@@ -1469,26 +1488,25 @@ class Exchange:
|
||||
logger.debug('order_book %s', order_book)
|
||||
# top 1 = index 0
|
||||
try:
|
||||
rate = order_book[f"{conf_strategy['price_side']}s"][order_book_top - 1][0]
|
||||
rate = order_book[f"{price_side}s"][order_book_top - 1][0]
|
||||
except (IndexError, KeyError) as e:
|
||||
logger.warning(
|
||||
f"{name} Price at location {order_book_top} from orderbook could not be "
|
||||
f"determined. Orderbook: {order_book}"
|
||||
)
|
||||
raise PricingError from e
|
||||
price_side = {conf_strategy['price_side'].capitalize()}
|
||||
logger.debug(f"{name} price from orderbook {price_side}"
|
||||
logger.debug(f"{name} price from orderbook {price_side_word}"
|
||||
f"side - top {order_book_top} order book {side} rate {rate:.8f}")
|
||||
else:
|
||||
logger.debug(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price")
|
||||
logger.debug(f"Using Last {price_side_word} / Last Price")
|
||||
ticker = self.fetch_ticker(pair)
|
||||
ticker_rate = ticker[conf_strategy['price_side']]
|
||||
ticker_rate = ticker[price_side]
|
||||
if ticker['last'] and ticker_rate:
|
||||
if side == 'buy' and ticker_rate > ticker['last']:
|
||||
balance = conf_strategy.get('ask_last_balance', 0.0)
|
||||
if side == 'entry' and ticker_rate > ticker['last']:
|
||||
balance = conf_strategy.get('price_last_balance', 0.0)
|
||||
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
||||
elif side == 'sell' and ticker_rate < ticker['last']:
|
||||
balance = conf_strategy.get('bid_last_balance', 0.0)
|
||||
elif side == 'exit' and ticker_rate < ticker['last']:
|
||||
balance = conf_strategy.get('price_last_balance', 0.0)
|
||||
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
|
||||
rate = ticker_rate
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import traceback
|
||||
from datetime import datetime, time, timezone
|
||||
from math import isclose
|
||||
from threading import Lock
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Literal, Optional, Tuple
|
||||
|
||||
from schedule import Scheduler
|
||||
|
||||
@@ -459,7 +459,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
if signal:
|
||||
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
|
||||
|
||||
bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {})
|
||||
bid_check_dom = self.config.get('entry_pricing', {}).get('check_depth_of_market', {})
|
||||
if ((bid_check_dom.get('enabled', False)) and
|
||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||
if self._check_depth_of_market(pair, bid_check_dom, side=signal):
|
||||
@@ -511,7 +511,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
return
|
||||
else:
|
||||
logger.debug("Max adjustment entries is set to unlimited.")
|
||||
current_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.enter_side)
|
||||
current_rate = self.exchange.get_rate(
|
||||
trade.pair, side='entry', is_short=trade.is_short, refresh=True)
|
||||
current_profit = trade.calc_profit_ratio(current_rate)
|
||||
|
||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair,
|
||||
@@ -589,11 +590,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
time_in_force = self.strategy.order_time_in_force['entry']
|
||||
|
||||
[side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long']
|
||||
trade_side = 'short' if is_short else 'long'
|
||||
trade_side: Literal['long', 'short'] = 'short' if is_short else 'long'
|
||||
pos_adjust = trade is not None
|
||||
|
||||
enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake(
|
||||
pair, price, stake_amount, side, trade_side, enter_tag, trade)
|
||||
pair, price, stake_amount, trade_side, enter_tag, trade)
|
||||
|
||||
if not stake_amount:
|
||||
return False
|
||||
@@ -745,7 +746,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
def get_valid_enter_price_and_stake(
|
||||
self, pair: str, price: Optional[float], stake_amount: float,
|
||||
side: str, trade_side: str,
|
||||
trade_side: Literal['long', 'short'],
|
||||
entry_tag: Optional[str],
|
||||
trade: Optional[Trade]
|
||||
) -> Tuple[float, float, float]:
|
||||
@@ -754,7 +755,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
enter_limit_requested = price
|
||||
else:
|
||||
# Calculate price
|
||||
proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side=side)
|
||||
proposed_enter_rate = self.exchange.get_rate(
|
||||
pair, side='entry', is_short=(trade_side == 'short'), refresh=True)
|
||||
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||
default_retval=proposed_enter_rate)(
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
@@ -763,7 +765,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
|
||||
|
||||
if not enter_limit_requested:
|
||||
raise PricingError(f'Could not determine {side} price.')
|
||||
raise PricingError('Could not determine entry price.')
|
||||
|
||||
if trade is None:
|
||||
max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
|
||||
@@ -824,7 +826,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
current_rate = trade.open_rate_requested
|
||||
if self.dataprovider.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
||||
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side)
|
||||
current_rate = self.exchange.get_rate(
|
||||
trade.pair, side='entry', is_short=trade.is_short, refresh=False)
|
||||
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
@@ -853,7 +856,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
"""
|
||||
Sends rpc notification when a entry order cancel occurred.
|
||||
"""
|
||||
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side)
|
||||
current_rate = self.exchange.get_rate(
|
||||
trade.pair, side='entry', is_short=trade.is_short, refresh=False)
|
||||
msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
@@ -935,7 +939,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
)
|
||||
|
||||
logger.debug('checking exit')
|
||||
exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.exit_side)
|
||||
exit_rate = self.exchange.get_rate(
|
||||
trade.pair, side='exit', is_short=trade.is_short, refresh=True)
|
||||
if self._check_and_execute_exit(trade, exit_rate, enter, exit_, exit_tag):
|
||||
return True
|
||||
|
||||
@@ -1433,7 +1438,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||
# Use cached rates here - it was updated seconds ago.
|
||||
current_rate = self.exchange.get_rate(
|
||||
trade.pair, refresh=False, side=trade.exit_side) if not fill else None
|
||||
trade.pair, side='exit', is_short=trade.is_short, refresh=False) if not fill else None
|
||||
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||
gain = "profit" if profit_ratio > 0 else "loss"
|
||||
|
||||
@@ -1482,7 +1487,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.exit_side)
|
||||
current_rate = self.exchange.get_rate(
|
||||
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
|
||||
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||
gain = "profit" if profit_ratio > 0 else "loss"
|
||||
|
||||
|
@@ -115,9 +115,9 @@ class Hyperopt:
|
||||
|
||||
if HyperoptTools.has_space(self.config, 'sell'):
|
||||
# Make sure use_sell_signal is enabled
|
||||
if 'ask_strategy' not in self.config:
|
||||
self.config['ask_strategy'] = {}
|
||||
self.config['ask_strategy']['use_sell_signal'] = True
|
||||
if 'exit_pricing' not in self.config:
|
||||
self.config['exit_pricing'] = {}
|
||||
self.config['exit_pricing']['use_sell_signal'] = True
|
||||
|
||||
self.print_all = self.config.get('print_all', False)
|
||||
self.hyperopt_table_header = 0
|
||||
|
@@ -175,8 +175,8 @@ class ShowConfig(BaseModel):
|
||||
exchange: str
|
||||
strategy: Optional[str]
|
||||
forcebuy_enabled: bool
|
||||
ask_strategy: Dict[str, Any]
|
||||
bid_strategy: Dict[str, Any]
|
||||
exit_pricing: Dict[str, Any]
|
||||
entry_pricing: Dict[str, Any]
|
||||
bot_name: str
|
||||
state: str
|
||||
runmode: str
|
||||
|
@@ -136,8 +136,8 @@ class RPC:
|
||||
'exchange': config['exchange']['name'],
|
||||
'strategy': config['strategy'],
|
||||
'forcebuy_enabled': config.get('forcebuy_enable', False),
|
||||
'ask_strategy': config.get('ask_strategy', {}),
|
||||
'bid_strategy': config.get('bid_strategy', {}),
|
||||
'exit_pricing': config.get('exit_pricing', {}),
|
||||
'entry_pricing': config.get('entry_pricing', {}),
|
||||
'state': str(botstate),
|
||||
'runmode': config['runmode'].value,
|
||||
'position_adjustment_enable': config.get('position_adjustment_enable', False),
|
||||
@@ -171,7 +171,7 @@ class RPC:
|
||||
if trade.is_open:
|
||||
try:
|
||||
current_rate = self._freqtrade.exchange.get_rate(
|
||||
trade.pair, refresh=False, side=trade.exit_side)
|
||||
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
|
||||
except (ExchangeError, PricingError):
|
||||
current_rate = NAN
|
||||
else:
|
||||
@@ -231,7 +231,7 @@ class RPC:
|
||||
# calculate profit and send message to user
|
||||
try:
|
||||
current_rate = self._freqtrade.exchange.get_rate(
|
||||
trade.pair, refresh=False, side=trade.exit_side)
|
||||
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
|
||||
except (PricingError, ExchangeError):
|
||||
current_rate = NAN
|
||||
trade_profit = trade.calc_profit(current_rate)
|
||||
@@ -485,7 +485,7 @@ class RPC:
|
||||
# Get current rate
|
||||
try:
|
||||
current_rate = self._freqtrade.exchange.get_rate(
|
||||
trade.pair, refresh=False, side=trade.exit_side)
|
||||
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
|
||||
except (PricingError, ExchangeError):
|
||||
current_rate = NAN
|
||||
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
|
||||
@@ -705,7 +705,7 @@ class RPC:
|
||||
if not fully_canceled:
|
||||
# Get current rate and execute sell
|
||||
current_rate = self._freqtrade.exchange.get_rate(
|
||||
trade.pair, refresh=False, side=trade.exit_side)
|
||||
trade.pair, side='exit', is_short=trade.is_short, refresh=True)
|
||||
exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_SELL)
|
||||
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
||||
"forceexit", self._freqtrade.strategy.order_types["exit"])
|
||||
|
@@ -1490,8 +1490,8 @@ class Telegram(RPCHandler):
|
||||
f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n"
|
||||
f"*Max open Trades:* `{val['max_open_trades']}`\n"
|
||||
f"*Minimum ROI:* `{val['minimal_roi']}`\n"
|
||||
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
|
||||
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
|
||||
f"*Entry strategy:* ```\n{json.dumps(val['entry_pricing'])}```\n"
|
||||
f"*Exit strategy:* ```\n{json.dumps(val['exit_pricing'])}```\n"
|
||||
f"{sl_info}"
|
||||
f"{pa_info}"
|
||||
f"*Timeframe:* `{val['timeframe']}`\n"
|
||||
|
@@ -331,7 +331,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param trade: trade object.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return float: New stoploss value, relative to the current_rate
|
||||
@@ -349,7 +349,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return float: New entry price value if provided
|
||||
@@ -369,7 +369,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param trade: trade object.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return float: New exit price value if provided
|
||||
@@ -393,7 +393,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param trade: trade object.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return: To execute exit, return a string with custom sell reason or True. Otherwise return
|
||||
@@ -417,7 +417,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param trade: trade object.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return: To execute exit, return a string with custom sell reason or True. Otherwise return
|
||||
@@ -433,7 +433,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param proposed_stake: A stake amount proposed by the bot.
|
||||
:param min_stake: Minimal stake size allowed by exchange.
|
||||
:param max_stake: Balance available for trading.
|
||||
@@ -474,7 +474,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param proposed_leverage: A leverage proposed by the bot.
|
||||
:param max_leverage: Max leverage allowed on this pair
|
||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
||||
|
@@ -21,18 +21,18 @@
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"bid_strategy": {
|
||||
"price_side": "bid",
|
||||
"ask_last_balance": 0.0,
|
||||
"entry_pricing": {
|
||||
"price_side": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1,
|
||||
"price_last_balance": 0.0,
|
||||
"check_depth_of_market": {
|
||||
"enabled": false,
|
||||
"bids_to_ask_delta": 1
|
||||
}
|
||||
},
|
||||
"ask_strategy": {
|
||||
"price_side": "ask",
|
||||
"exit_pricing":{
|
||||
"price_side": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
|
@@ -64,7 +64,7 @@ class {{ strategy }}(IStrategy):
|
||||
# Run "populate_indicators()" only for new candle.
|
||||
process_only_new_candles = False
|
||||
|
||||
# These values can be overridden in the "ask_strategy" section in the config.
|
||||
# These values can be overridden in the config.
|
||||
use_sell_signal = True
|
||||
sell_profit_only = False
|
||||
ignore_roi_if_buy_signal = False
|
||||
|
@@ -64,7 +64,7 @@ class SampleStrategy(IStrategy):
|
||||
# Run "populate_indicators()" only for new candle.
|
||||
process_only_new_candles = False
|
||||
|
||||
# These values can be overridden in the "ask_strategy" section in the config.
|
||||
# These values can be overridden in the config.
|
||||
use_sell_signal = True
|
||||
sell_profit_only = False
|
||||
ignore_roi_if_buy_signal = False
|
||||
|
@@ -23,7 +23,7 @@ def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate:
|
||||
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return float: New entry price value if provided
|
||||
@@ -43,7 +43,7 @@ def custom_exit_price(self, pair: str, trade: 'Trade',
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param trade: trade object.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return float: New exit price value if provided
|
||||
@@ -58,7 +58,7 @@ def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate:
|
||||
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param proposed_stake: A stake amount proposed by the bot.
|
||||
:param min_stake: Minimal stake size allowed by exchange.
|
||||
:param max_stake: Balance available for trading.
|
||||
@@ -85,7 +85,7 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime',
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param trade: trade object.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return float: New stoploss value, relative to the current_rate
|
||||
@@ -108,7 +108,7 @@ def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', curre
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param trade: trade object.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return: To execute sell, return a string with custom sell reason or True. Otherwise return
|
||||
@@ -241,7 +241,7 @@ def leverage(self, pair: str, current_time: datetime, current_rate: float,
|
||||
|
||||
:param pair: Pair that's currently analyzed
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||
:param current_rate: Rate, calculated based on pricing settings in exit_pricing.
|
||||
:param proposed_leverage: A leverage proposed by the bot.
|
||||
:param max_leverage: Max leverage allowed on this pair
|
||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
||||
|
Reference in New Issue
Block a user