Merge branch 'develop' into feat/short
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
__main__.py for Freqtrade
|
||||
To launch Freqtrade as a module
|
||||
|
||||
> python -m freqtrade (with Python >= 3.7)
|
||||
> python -m freqtrade (with Python >= 3.8)
|
||||
"""
|
||||
|
||||
from freqtrade import main
|
||||
|
@@ -377,7 +377,9 @@ CONF_SCHEMA = {
|
||||
'type': 'string',
|
||||
'enum': AVAILABLE_DATAHANDLERS,
|
||||
'default': 'jsongz'
|
||||
}
|
||||
},
|
||||
'position_adjustment_enable': {'type': 'boolean'},
|
||||
'max_entry_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1},
|
||||
},
|
||||
'definitions': {
|
||||
'exchange': {
|
||||
|
@@ -1114,7 +1114,7 @@ class Exchange:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
@retrier
|
||||
def get_tickers(self, cached: bool = False) -> Dict:
|
||||
def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict:
|
||||
"""
|
||||
:param cached: Allow cached result
|
||||
:return: fetch_tickers result
|
||||
@@ -1124,7 +1124,7 @@ class Exchange:
|
||||
if tickers:
|
||||
return tickers
|
||||
try:
|
||||
tickers = self._api.fetch_tickers()
|
||||
tickers = self._api.fetch_tickers(symbols)
|
||||
self._fetch_tickers_cache['fetch_tickers'] = tickers
|
||||
return tickers
|
||||
except ccxt.NotSupported as e:
|
||||
|
@@ -43,6 +43,12 @@ class Kraken(Exchange):
|
||||
return (parent_check and
|
||||
market.get('darkpool', False) is False)
|
||||
|
||||
def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict:
|
||||
# Only fetch tickers for current stake currency
|
||||
# Otherwise the request for kraken becomes too large.
|
||||
symbols = list(self.get_markets(quote_currencies=[self._config['stake_currency']]))
|
||||
return super().get_tickers(symbols=symbols, cached=cached)
|
||||
|
||||
@retrier
|
||||
def get_balances(self) -> dict:
|
||||
if self._config['dry_run']:
|
||||
|
@@ -9,7 +9,6 @@ from math import isclose
|
||||
from threading import Lock
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import arrow
|
||||
from schedule import Scheduler
|
||||
|
||||
from freqtrade import __version__, constants
|
||||
@@ -521,8 +520,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
try:
|
||||
self.check_and_call_adjust_trade_position(trade)
|
||||
except DependencyException as exception:
|
||||
logger.warning('Unable to adjust position of trade for %s: %s',
|
||||
trade.pair, exception)
|
||||
logger.warning(
|
||||
f"Unable to adjust position of trade for {trade.pair}: {exception}")
|
||||
|
||||
def check_and_call_adjust_trade_position(self, trade: Trade):
|
||||
"""
|
||||
@@ -531,6 +530,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
Once that completes, the existing trade is modified to match new data.
|
||||
"""
|
||||
# TODO-lev: Check what changes are necessary for DCA in relation to shorts.
|
||||
if self.strategy.max_entry_position_adjustment > -1:
|
||||
count_of_buys = trade.nr_of_successful_buys
|
||||
if count_of_buys > self.strategy.max_entry_position_adjustment:
|
||||
logger.debug(f"Max adjustment entries for {trade.pair} has been reached.")
|
||||
return
|
||||
else:
|
||||
logger.debug("Max adjustment entries is set to unlimited.")
|
||||
current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy")
|
||||
current_profit = trade.calc_profit_ratio(current_rate)
|
||||
|
||||
@@ -649,7 +655,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
pos_adjust = trade is not None
|
||||
|
||||
enter_limit_requested, stake_amount = self.get_valid_enter_price_and_stake(
|
||||
pair, price, stake_amount, side, trade_side, trade)
|
||||
pair, price, stake_amount, side, trade_side, enter_tag, trade)
|
||||
|
||||
if not stake_amount:
|
||||
return False
|
||||
@@ -680,8 +686,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.strategy.confirm_trade_entry, default_retval=True)(
|
||||
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
|
||||
time_in_force=time_in_force, current_time=datetime.now(timezone.utc),
|
||||
side=trade_side
|
||||
):
|
||||
entry_tag=enter_tag, side=trade_side):
|
||||
logger.info(f"User requested abortion of buying {pair}")
|
||||
return False
|
||||
amount = self.exchange.amount_to_precision(pair, amount)
|
||||
@@ -814,6 +819,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,
|
||||
entry_tag: Optional[str],
|
||||
trade: Optional[Trade]) -> Tuple[float, float]:
|
||||
if price:
|
||||
enter_limit_requested = price
|
||||
@@ -823,7 +829,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||
default_retval=proposed_enter_rate)(
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
proposed_rate=proposed_enter_rate)
|
||||
proposed_rate=proposed_enter_rate, entry_tag=entry_tag)
|
||||
|
||||
enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
|
||||
|
||||
@@ -844,7 +850,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
current_rate=enter_limit_requested, proposed_stake=stake_amount,
|
||||
min_stake=min_stake_amount, max_stake=max_stake_amount,
|
||||
side=trade_side
|
||||
entry_tag=entry_tag, side=trade_side
|
||||
)
|
||||
|
||||
stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount)
|
||||
@@ -1145,20 +1151,6 @@ class FreqtradeBot(LoggingMixin):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _check_timed_out(self, side: str, order: dict) -> bool:
|
||||
"""
|
||||
Check if timeout is active, and if the order is still open and timed out
|
||||
"""
|
||||
timeout = self.config.get('unfilledtimeout', {}).get(side)
|
||||
ordertime = arrow.get(order['datetime']).datetime
|
||||
if timeout is not None:
|
||||
timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes')
|
||||
timeout_kwargs = {timeout_unit: -timeout}
|
||||
timeout_threshold = arrow.utcnow().shift(**timeout_kwargs).datetime
|
||||
return (order['status'] == 'open' and order['side'] == side
|
||||
and ordertime < timeout_threshold)
|
||||
return False
|
||||
|
||||
def check_handle_timedout(self) -> None:
|
||||
"""
|
||||
Check if any orders are timed out and cancel if necessary
|
||||
@@ -1178,18 +1170,12 @@ class FreqtradeBot(LoggingMixin):
|
||||
fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order)
|
||||
is_entering = order['side'] == trade.enter_side
|
||||
not_closed = order['status'] == 'open' or fully_cancelled
|
||||
side = trade.enter_side if is_entering else trade.exit_side
|
||||
timed_out = self._check_timed_out(side, order)
|
||||
time_method = 'check_sell_timeout' if order['side'] == 'sell' else 'check_buy_timeout'
|
||||
time_method = 'sell' if order['side'] == 'sell' else 'buy'
|
||||
max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
|
||||
|
||||
if not_closed and (fully_cancelled or timed_out or (
|
||||
strategy_safe_wrapper(getattr(self.strategy, time_method), default_retval=False)(
|
||||
pair=trade.pair,
|
||||
trade=trade,
|
||||
order=order
|
||||
)
|
||||
)):
|
||||
if not_closed and (fully_cancelled or self.strategy.ft_check_timed_out(
|
||||
time_method, trade, order, datetime.now(timezone.utc))
|
||||
):
|
||||
if is_entering:
|
||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
else:
|
||||
|
@@ -9,8 +9,8 @@ from typing import Any, List
|
||||
|
||||
|
||||
# check min. python version
|
||||
if sys.version_info < (3, 7): # pragma: no cover
|
||||
sys.exit("Freqtrade requires Python version >= 3.7")
|
||||
if sys.version_info < (3, 8): # pragma: no cover
|
||||
sys.exit("Freqtrade requires Python version >= 3.8")
|
||||
|
||||
from freqtrade.commands import Arguments
|
||||
from freqtrade.exceptions import FreqtradeException, OperationalException
|
||||
|
@@ -434,7 +434,12 @@ class Backtesting:
|
||||
|
||||
# Check if we need to adjust our current positions
|
||||
if self.strategy.position_adjustment_enable:
|
||||
trade = self._get_adjust_trade_entry_for_candle(trade, sell_row)
|
||||
check_adjust_buy = True
|
||||
if self.strategy.max_entry_position_adjustment > -1:
|
||||
count_of_buys = trade.nr_of_successful_buys
|
||||
check_adjust_buy = (count_of_buys <= self.strategy.max_entry_position_adjustment)
|
||||
if check_adjust_buy:
|
||||
trade = self._get_adjust_trade_entry_for_candle(trade, sell_row)
|
||||
|
||||
sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime()
|
||||
enter = sell_row[SHORT_IDX] if trade.is_short else sell_row[LONG_IDX]
|
||||
@@ -533,12 +538,14 @@ class Backtesting:
|
||||
def _enter_trade(self, pair: str, row: Tuple, direction: str,
|
||||
stake_amount: Optional[float] = None,
|
||||
trade: Optional[LocalTrade] = None) -> Optional[LocalTrade]:
|
||||
|
||||
current_time = row[DATE_IDX].to_pydatetime()
|
||||
entry_tag = row[ENTER_TAG_IDX] if len(row) >= ENTER_TAG_IDX + 1 else None
|
||||
# let's call the custom entry price, using the open price as default price
|
||||
propose_rate = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||
default_retval=row[OPEN_IDX])(
|
||||
pair=pair, current_time=current_time,
|
||||
proposed_rate=row[OPEN_IDX]) # default value is the open rate
|
||||
proposed_rate=row[OPEN_IDX], entry_tag=entry_tag) # default value is the open rate
|
||||
|
||||
# Move rate to within the candle's low/high rate
|
||||
propose_rate = min(max(propose_rate, row[LOW_IDX]), row[HIGH_IDX])
|
||||
@@ -557,7 +564,7 @@ class Backtesting:
|
||||
default_retval=stake_amount)(
|
||||
pair=pair, current_time=current_time, current_rate=propose_rate,
|
||||
proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount,
|
||||
side=direction)
|
||||
entry_tag=entry_tag, side=direction)
|
||||
|
||||
stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount)
|
||||
|
||||
@@ -585,14 +592,13 @@ class Backtesting:
|
||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
||||
pair=pair, order_type=order_type, amount=stake_amount, rate=propose_rate,
|
||||
time_in_force=time_in_force, current_time=current_time,
|
||||
side=direction):
|
||||
entry_tag=entry_tag, side=direction):
|
||||
return None
|
||||
|
||||
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
|
||||
amount = round((stake_amount / propose_rate) * leverage, 8)
|
||||
if trade is None:
|
||||
# Enter trade
|
||||
has_buy_tag = len(row) >= ENTER_TAG_IDX + 1
|
||||
trade = LocalTrade(
|
||||
pair=pair,
|
||||
open_rate=propose_rate,
|
||||
@@ -602,7 +608,7 @@ class Backtesting:
|
||||
fee_open=self.fee,
|
||||
fee_close=self.fee,
|
||||
is_open=True,
|
||||
enter_tag=row[ENTER_TAG_IDX] if has_buy_tag else None,
|
||||
enter_tag=entry_tag,
|
||||
exchange=self._exchange_name,
|
||||
is_short=(direction == 'short'),
|
||||
trading_mode=self.trading_mode,
|
||||
@@ -619,6 +625,9 @@ class Backtesting:
|
||||
side="buy",
|
||||
order_type="market",
|
||||
status="closed",
|
||||
order_date=current_time,
|
||||
order_filled_date=current_time,
|
||||
order_update_date=current_time,
|
||||
price=propose_rate,
|
||||
average=propose_rate,
|
||||
amount=amount,
|
||||
|
@@ -367,7 +367,7 @@ class Hyperopt:
|
||||
}
|
||||
|
||||
def get_optimizer(self, dimensions: List[Dimension], cpu_count) -> Optimizer:
|
||||
estimator = self.custom_hyperopt.generate_estimator()
|
||||
estimator = self.custom_hyperopt.generate_estimator(dimensions=dimensions)
|
||||
|
||||
acq_optimizer = "sampling"
|
||||
if isinstance(estimator, str):
|
||||
|
@@ -91,5 +91,5 @@ class HyperOptAuto(IHyperOpt):
|
||||
def trailing_space(self) -> List['Dimension']:
|
||||
return self._get_func('trailing_space')()
|
||||
|
||||
def generate_estimator(self) -> EstimatorType:
|
||||
return self._get_func('generate_estimator')()
|
||||
def generate_estimator(self, dimensions: List['Dimension'], **kwargs) -> EstimatorType:
|
||||
return self._get_func('generate_estimator')(dimensions=dimensions, **kwargs)
|
||||
|
@@ -40,7 +40,7 @@ class IHyperOpt(ABC):
|
||||
IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
|
||||
IHyperOpt.timeframe = str(config['timeframe'])
|
||||
|
||||
def generate_estimator(self) -> EstimatorType:
|
||||
def generate_estimator(self, dimensions: List[Dimension], **kwargs) -> EstimatorType:
|
||||
"""
|
||||
Return base_estimator.
|
||||
Can be any of "GP", "RF", "ET", "GBRT" or an instance of a class
|
||||
|
@@ -804,18 +804,19 @@ class LocalTrade():
|
||||
|
||||
total_amount = 0.0
|
||||
total_stake = 0.0
|
||||
for temp_order in self.orders:
|
||||
if (temp_order.ft_is_open or
|
||||
(temp_order.ft_order_side != self.enter_side) or
|
||||
(temp_order.status not in NON_OPEN_EXCHANGE_STATES)):
|
||||
for o in self.orders:
|
||||
if (o.ft_is_open or
|
||||
(o.ft_order_side != self.enter_side) or
|
||||
(o.status not in NON_OPEN_EXCHANGE_STATES)):
|
||||
continue
|
||||
|
||||
tmp_amount = temp_order.amount
|
||||
if temp_order.filled is not None:
|
||||
tmp_amount = temp_order.filled
|
||||
if tmp_amount > 0.0 and temp_order.average is not None:
|
||||
tmp_amount = o.amount
|
||||
tmp_price = o.average or o.price
|
||||
if o.filled is not None:
|
||||
tmp_amount = o.filled
|
||||
if tmp_amount > 0.0 and tmp_price is not None:
|
||||
total_amount += tmp_amount
|
||||
total_stake += temp_order.average * tmp_amount
|
||||
total_stake += tmp_price * tmp_amount
|
||||
|
||||
if total_amount > 0:
|
||||
self.open_rate = total_stake / total_amount
|
||||
|
@@ -97,7 +97,8 @@ class StrategyResolver(IResolver):
|
||||
("sell_profit_offset", 0.0),
|
||||
("disable_dataframe_checks", False),
|
||||
("ignore_buying_expired_candle_after", 0),
|
||||
("position_adjustment_enable", False)
|
||||
("position_adjustment_enable", False),
|
||||
("max_entry_position_adjustment", -1),
|
||||
]
|
||||
for attribute, default in attributes:
|
||||
StrategyResolver._override_attribute_helper(strategy, config,
|
||||
|
@@ -175,6 +175,8 @@ class ShowConfig(BaseModel):
|
||||
bot_name: str
|
||||
state: str
|
||||
runmode: str
|
||||
position_adjustment_enable: bool
|
||||
max_entry_position_adjustment: int
|
||||
|
||||
|
||||
class TradeSchema(BaseModel):
|
||||
|
@@ -77,6 +77,9 @@ class CryptoToFiatConverter:
|
||||
else:
|
||||
return None
|
||||
found = [x for x in self._coinlistings if x['symbol'] == crypto_symbol]
|
||||
if crypto_symbol == 'eth':
|
||||
found = [x for x in self._coinlistings if x['id'] == 'ethereum']
|
||||
|
||||
if len(found) == 1:
|
||||
return found[0]['id']
|
||||
|
||||
|
@@ -138,7 +138,12 @@ class RPC:
|
||||
'ask_strategy': config.get('ask_strategy', {}),
|
||||
'bid_strategy': config.get('bid_strategy', {}),
|
||||
'state': str(botstate),
|
||||
'runmode': config['runmode'].value
|
||||
'runmode': config['runmode'].value,
|
||||
'position_adjustment_enable': config.get('position_adjustment_enable', False),
|
||||
'max_entry_position_adjustment': (
|
||||
config.get('max_entry_position_adjustment', -1)
|
||||
if config.get('max_entry_position_adjustment') != float('inf')
|
||||
else -1)
|
||||
}
|
||||
return val
|
||||
|
||||
@@ -252,8 +257,9 @@ class RPC:
|
||||
profit_str
|
||||
]
|
||||
if self._config.get('position_adjustment_enable', False):
|
||||
filled_buys = trade.select_filled_orders('buy')
|
||||
detail_trade.append(str(len(filled_buys)))
|
||||
max_buy = self._config['max_entry_position_adjustment'] + 1
|
||||
filled_buys = trade.nr_of_successful_buys
|
||||
detail_trade.append(f"{filled_buys}/{max_buy}")
|
||||
trades_list.append(detail_trade)
|
||||
profitcol = "Profit"
|
||||
if self._fiat_converter:
|
||||
|
@@ -1363,6 +1363,14 @@ class Telegram(RPCHandler):
|
||||
else:
|
||||
sl_info = f"*Stoploss:* `{val['stoploss']}`\n"
|
||||
|
||||
if val['position_adjustment_enable']:
|
||||
pa_info = (
|
||||
f"*Position adjustment:* On\n"
|
||||
f"*Max enter position adjustment:* `{val['max_entry_position_adjustment']}`\n"
|
||||
)
|
||||
else:
|
||||
pa_info = "*Position adjustment:* Off\n"
|
||||
|
||||
self._send_msg(
|
||||
f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n"
|
||||
f"*Exchange:* `{val['exchange']}`\n"
|
||||
@@ -1372,6 +1380,7 @@ class Telegram(RPCHandler):
|
||||
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
|
||||
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
|
||||
f"{sl_info}"
|
||||
f"{pa_info}"
|
||||
f"*Timeframe:* `{val['timeframe']}`\n"
|
||||
f"*Strategy:* `{val['strategy']}`\n"
|
||||
f"*Current state:* `{val['state']}`"
|
||||
|
@@ -108,6 +108,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
|
||||
# Position adjustment is disabled by default
|
||||
position_adjustment_enable: bool = False
|
||||
max_entry_position_adjustment: int = -1
|
||||
|
||||
# Number of seconds after which the candle will no longer result in a buy on expired candles
|
||||
ignore_buying_expired_candle_after: int = 0
|
||||
@@ -188,7 +189,17 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
"""
|
||||
return dataframe
|
||||
|
||||
def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool:
|
||||
def bot_loop_start(self, **kwargs) -> None:
|
||||
"""
|
||||
Called at the start of the bot iteration (one loop).
|
||||
Might be used to perform pair-independent tasks
|
||||
(e.g. gather some remote resource for comparison)
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
"""
|
||||
pass
|
||||
|
||||
def check_buy_timeout(self, pair: str, trade: Trade, order: dict,
|
||||
current_time: datetime, **kwargs) -> bool:
|
||||
"""
|
||||
Check buy timeout function callback.
|
||||
This method can be used to override the enter-timeout.
|
||||
@@ -201,12 +212,14 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
:param pair: Pair the trade is for
|
||||
:param trade: trade object.
|
||||
:param order: Order dictionary as returned from CCXT.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return bool: When True is returned, then the entry order is cancelled.
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool:
|
||||
def check_sell_timeout(self, pair: str, trade: Trade, order: dict,
|
||||
current_time: datetime, **kwargs) -> bool:
|
||||
"""
|
||||
Check sell timeout function callback.
|
||||
This method can be used to override the exit-timeout.
|
||||
@@ -219,22 +232,14 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
:param pair: Pair the trade is for
|
||||
:param trade: trade object.
|
||||
:param order: Order dictionary as returned from CCXT.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return bool: When True is returned, then the (long)sell/(short)buy-order is cancelled.
|
||||
"""
|
||||
return False
|
||||
|
||||
def bot_loop_start(self, **kwargs) -> None:
|
||||
"""
|
||||
Called at the start of the bot iteration (one loop).
|
||||
Might be used to perform pair-independent tasks
|
||||
(e.g. gather some remote resource for comparison)
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
"""
|
||||
pass
|
||||
|
||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||
time_in_force: str, current_time: datetime,
|
||||
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
|
||||
side: str, **kwargs) -> bool:
|
||||
"""
|
||||
Called right before placing a entry order.
|
||||
@@ -251,6 +256,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
:param rate: Rate that's going to be used when using limit orders
|
||||
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return bool: When True is returned, then the buy-order is placed on the exchange.
|
||||
@@ -309,7 +315,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
return self.stoploss
|
||||
|
||||
def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float,
|
||||
**kwargs) -> float:
|
||||
entry_tag: Optional[str], **kwargs) -> float:
|
||||
"""
|
||||
Custom entry price logic, returning the new entry price.
|
||||
|
||||
@@ -320,6 +326,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 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
|
||||
"""
|
||||
@@ -371,7 +378,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
|
||||
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||
proposed_stake: float, min_stake: float, max_stake: float,
|
||||
side: str, **kwargs) -> float:
|
||||
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
||||
"""
|
||||
Customize stake size for each new trade.
|
||||
|
||||
@@ -381,6 +388,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
: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.
|
||||
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
||||
:return: A stake size, which is between min_stake and max_stake.
|
||||
"""
|
||||
@@ -392,6 +400,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
"""
|
||||
Custom trade adjustment logic, returning the stake amount that a trade should be increased.
|
||||
This means extra buy orders with additional fees.
|
||||
Only called when `position_adjustment_enable` is set to True.
|
||||
|
||||
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
|
||||
|
||||
@@ -976,6 +985,29 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
else:
|
||||
return current_profit > roi
|
||||
|
||||
def ft_check_timed_out(self, side: str, trade: Trade, order: Dict,
|
||||
current_time: datetime) -> bool:
|
||||
"""
|
||||
FT Internal method.
|
||||
Check if timeout is active, and if the order is still open and timed out
|
||||
"""
|
||||
timeout = self.config.get('unfilledtimeout', {}).get(side)
|
||||
ordertime = arrow.get(order['datetime']).datetime
|
||||
if timeout is not None:
|
||||
timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes')
|
||||
timeout_kwargs = {timeout_unit: -timeout}
|
||||
timeout_threshold = current_time + timedelta(**timeout_kwargs)
|
||||
timedout = (order['status'] == 'open' and order['side'] == side
|
||||
and ordertime < timeout_threshold)
|
||||
if timedout:
|
||||
return True
|
||||
time_method = self.check_sell_timeout if order['side'] == 'sell' else self.check_buy_timeout
|
||||
|
||||
return strategy_safe_wrapper(time_method,
|
||||
default_retval=False)(
|
||||
pair=trade.pair, trade=trade, order=order,
|
||||
current_time=current_time)
|
||||
|
||||
def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
|
||||
"""
|
||||
Populates indicators for given candle (OHLCV) data (for multiple pairs)
|
||||
|
@@ -12,9 +12,47 @@ def bot_loop_start(self, **kwargs) -> None:
|
||||
"""
|
||||
pass
|
||||
|
||||
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||
def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate: float,
|
||||
entry_tag: 'Optional[str]', **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
|
||||
|
||||
: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 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
|
||||
"""
|
||||
return proposed_rate
|
||||
|
||||
def custom_exit_price(self, pair: str, trade: 'Trade',
|
||||
current_time: 'datetime', proposed_rate: float,
|
||||
current_profit: float, **kwargs) -> float:
|
||||
"""
|
||||
Custom exit price logic, returning the new exit 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 exit price
|
||||
|
||||
: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 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
|
||||
"""
|
||||
return proposed_rate
|
||||
|
||||
def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate: float,
|
||||
proposed_stake: float, min_stake: float, max_stake: float,
|
||||
side: str, **kwargs) -> float:
|
||||
side: str, entry_tag: 'Optional[str]', **kwargs) -> float:
|
||||
"""
|
||||
Customize stake size for each new trade.
|
||||
|
||||
@@ -24,6 +62,7 @@ def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: f
|
||||
: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.
|
||||
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
||||
:return: A stake size, which is between min_stake and max_stake.
|
||||
"""
|
||||
@@ -78,7 +117,7 @@ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', curre
|
||||
return None
|
||||
|
||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||
time_in_force: str, current_time: datetime,
|
||||
time_in_force: str, current_time: datetime, entry_tag: 'Optional[str]',
|
||||
side: str, **kwargs) -> bool:
|
||||
"""
|
||||
Called right before placing a entry order.
|
||||
@@ -95,6 +134,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
|
||||
:param rate: Rate that's going to be used when using limit orders
|
||||
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
|
||||
:param side: 'long' or 'short' - indicating the direction of the proposed trade
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return bool: When True is returned, then the buy-order is placed on the exchange.
|
||||
@@ -169,3 +209,26 @@ def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -
|
||||
:return bool: When True is returned, then the sell-order is cancelled.
|
||||
"""
|
||||
return False
|
||||
|
||||
def adjust_trade_position(self, trade: 'Trade', current_time: 'datetime',
|
||||
current_rate: float, current_profit: float, min_stake: float,
|
||||
max_stake: float, **kwargs) -> 'Optional[float]':
|
||||
"""
|
||||
Custom trade adjustment logic, returning the stake amount that a trade should be increased.
|
||||
This means extra buy orders with additional fees.
|
||||
Only called when `position_adjustment_enable` is set to True.
|
||||
|
||||
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
|
||||
|
||||
When not implemented by a strategy, returns None
|
||||
|
||||
:param trade: trade object.
|
||||
:param current_time: datetime object, containing the current datetime
|
||||
:param current_rate: Current buy rate.
|
||||
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||
:param min_stake: Minimal stake size allowed by exchange.
|
||||
:param max_stake: Balance available for trading.
|
||||
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||
:return float: Stake amount to adjust your trade
|
||||
"""
|
||||
return None
|
||||
|
Reference in New Issue
Block a user