add feature custom entry price for live

This commit is contained in:
axel
2021-07-31 00:05:45 -04:00
parent dfc17f2bd1
commit f11f5d17e9
9 changed files with 222 additions and 1 deletions

View File

@@ -433,6 +433,8 @@ SCHEMA_MINIMAL_REQUIRED = [
CANCEL_REASON = {
"TIMEOUT": "cancelled due to timeout",
"ENTRYPRICECHANGED": "Custom entry price changed",
"EXITPRICECHANGED": "Custom exit price changed",
"PARTIALLY_FILLED_KEEP_OPEN": "partially filled - keeping order open",
"PARTIALLY_FILLED": "partially filled",
"FULLY_CANCELLED": "fully cancelled",

View File

@@ -169,6 +169,7 @@ class FreqtradeBot(LoggingMixin):
with self._sell_lock:
# Check and handle any timed out open orders
self.check_handle_timedout()
self.check_handle_custom_entryprice_outdated()
# Protect from collisions with forcesell.
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
@@ -480,6 +481,14 @@ class FreqtradeBot(LoggingMixin):
else:
# Calculate price
buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy")
if self.config.get('use_custom_entry_price', False):
buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
default_retval=stake_amount)(
pair=pair, current_time=datetime.now(timezone.utc),
current_rate=buy_rate)
buy_limit_requested = custom_entry_price
if not buy_limit_requested:
raise PricingError('Could not determine buy price.')
@@ -911,6 +920,70 @@ class FreqtradeBot(LoggingMixin):
order=order))):
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT'])
def _check_entryprice_outdated(self, side: str, order: dict) -> bool:
"""
Check if entry price is outdated by comparing it to the new prefered entry price
, and if the order is still open and price outdated
"""
#print("check_entryprice_outdated")
if self.config.get('use_custom_entry_price', False):
order_prefered_entry_price = order['price'] # order['trade']
#print(order)
#order_open_rate_requested = order.trade['open_rate_requested']
#print("order_trade_object : {}".format(order['trade']))
# get pep from strategy data provider
pair = order['symbol']
old_prefered_entry_price = order_prefered_entry_price
#new_prefered_entry_price = self.strategy.custom_info[pair]['pep_long'].iloc[-1] #buy_limit_requested
new_prefered_entry_price = self.strategy.entryprice
old_prefered_entry_price_rounded = self.exchange.price_to_precision(pair, order_prefered_entry_price)
new_prefered_entry_price_rounded = self.exchange.price_to_precision(pair, new_prefered_entry_price)
if old_prefered_entry_price_rounded != new_prefered_entry_price_rounded:
print("order['symbol']: {}".format(order['symbol']))
print("new_prefered_entry_price: {}, old_prefered_entry_price: {}".format(new_prefered_entry_price, old_prefered_entry_price))
print("rounded new pep: {}, rounded old pep: {}".format(new_prefered_entry_price_rounded, old_prefered_entry_price_rounded))
print("Delta in prefered entry price, order to cancel")
return True
else:
return False
else:
return False
def check_handle_custom_entryprice_outdated(self) -> None:
"""
Check if any orders prefered entryprice change and cancel if necessary
:return: None
"""
for trade in Trade.get_open_order_trades():
try:
if not trade.open_order_id:
continue
order = self.exchange.fetch_order(trade.open_order_id, trade.pair)
except (ExchangeError):
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
continue
fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order)
# Refresh entryprice value if order is open
if (order['status'] == 'open'):
self.strategy.entryprice = strategy_safe_wrapper(self.strategy.custom_entry_price)(
pair=trade.pair, current_time=datetime.now(timezone.utc),
current_rate=trade.open_rate_requested)
if (order['side'] == 'buy' and (order['status'] == 'open') and (
self._check_entryprice_outdated('buy', order))):
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['ENTRYPRICECHANGED'])
elif (order['side'] == 'sell' and (order['status'] == 'open') and (
self._check_entryprice_outdated('sell', order))):
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['EXITPRICECHANGED'])
def cancel_all_open_orders(self) -> None:
"""
Cancel all orders that are currently open

View File

@@ -79,6 +79,7 @@ class StrategyResolver(IResolver):
("trailing_stop_positive_offset", 0.0),
("trailing_only_offset_is_reached", None),
("use_custom_stoploss", None),
("use_custom_entry_price", None),
("process_only_new_candles", None),
("order_types", None),
("order_time_in_force", None),

View File

@@ -129,6 +129,7 @@ class ShowConfig(BaseModel):
trailing_stop_positive_offset: Optional[float]
trailing_only_offset_is_reached: Optional[bool]
use_custom_stoploss: Optional[bool]
use_custom_entry_price: Optional[bool]
timeframe: Optional[str]
timeframe_ms: int
timeframe_min: int

View File

@@ -116,6 +116,7 @@ class RPC:
'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'),
'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached'),
'use_custom_stoploss': config.get('use_custom_stoploss'),
'use_custom_entry_price': config.get('use_custom_entry_price'),
'bot_name': config.get('bot_name', 'freqtrade'),
'timeframe': config.get('timeframe'),
'timeframe_ms': timeframe_to_msecs(config['timeframe']

View File

@@ -69,6 +69,10 @@ class IStrategy(ABC, HyperStrategyMixin):
# associated stoploss
stoploss: float
# custom order price
entryprice: Optional[float] = None
exitprice: Optional[float] = None
# trailing stoploss
trailing_stop: bool = False
trailing_stop_positive: Optional[float] = None
@@ -280,6 +284,24 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
return self.stoploss
def custom_entry_price(self, pair: str, current_time: datetime, current_rate: float,
**kwargs) -> float:
"""
Custom entry price logic, returning the new entry price.
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
When not implemented by a strategy, returns None, orderbook is used to set entry price
Only called when use_custom_entry_price is set to True.
: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 **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New entry price value if provided
"""
return self.entryprice
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
"""
@@ -635,6 +657,42 @@ class IStrategy(ABC, HyperStrategyMixin):
# logger.debug(f"{trade.pair} - No sell signal.")
return SellCheckTuple(sell_type=SellType.NONE)
def entry_price_reached(self, pair: str, current_rate: float,
current_time: datetime, low: float = None,
high: float = None, side: str = "long") -> bool:
"""
Based on current candle low ,decides if entry price was reached
:param current_rate: current rate
:param low: Low value of this candle, only set in backtesting
:param high: High value of this candle, only set in backtesting
"""
if self.use_custom_entry_price:
entry_price_value = strategy_safe_wrapper(self.custom_entry_price, default_retval=None
)(pair=pair,
current_time=current_time,
current_rate=current_rate)
# Sanity check - error cases will return None
if side == "long":
if entry_price_value > low:
return True
else:
logger.info(f"Entry failed because entry price {entry_price_value} \
higher than candle low in long side")
return False
elif side == "short":
if entry_price_value < high:
return True
else:
logger.info(f"Entry failed because entry price {entry_price_value} \
higher than candle high in short side")
return False
else:
logger.warning("CustomEntryPrice function did not return valid entry price")
return False
def stop_loss_reached(self, current_rate: float, trade: Trade,
current_time: datetime, current_profit: float,
force_stoploss: float, low: float = None,