Merge branch 'develop' of https://github.com/freqtrade/freqtrade into freqtrade-develop

This commit is contained in:
Italo
2022-02-01 01:06:57 +00:00
40 changed files with 176 additions and 71 deletions

View File

@@ -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

View File

@@ -76,12 +76,9 @@ def ask_user_config() -> Dict[str, Any]:
{
"type": "text",
"name": "max_open_trades",
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
"message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):",
"default": "3",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val),
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
if val == UNLIMITED_STAKE_AMOUNT
else val
"validate": lambda val: validate_is_int(val)
},
{
"type": "select",

View File

@@ -371,7 +371,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': {

View File

@@ -953,7 +953,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
@@ -963,7 +963,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:

View File

@@ -1,6 +1,6 @@
""" Kraken exchange subclass """
import logging
from typing import Any, Dict
from typing import Any, Dict, List
import ccxt
@@ -33,6 +33,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']:

View File

@@ -462,8 +462,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):
"""
@@ -471,6 +471,13 @@ class FreqtradeBot(LoggingMixin):
If the strategy triggers the adjustment, a new order gets issued.
Once that completes, the existing trade is modified to match new data.
"""
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)

View File

@@ -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

View File

@@ -381,7 +381,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 = sell_row[DATE_IDX].to_pydatetime()
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore

View File

@@ -372,7 +372,7 @@ class Hyperopt:
}
def get_optimizer(self, dimensions: List[Dimension], cpu_count) -> Optimizer:
estimator = self.custom_hyperopt.generate_estimator(dimensions)
estimator = self.custom_hyperopt.generate_estimator(dimensions=dimensions)
acq_optimizer = "sampling"
if isinstance(estimator, str):

View File

@@ -91,5 +91,5 @@ class HyperOptAuto(IHyperOpt):
def trailing_space(self) -> List['Dimension']:
return self._get_func('trailing_space')()
def generate_estimator(self, dimensions: List['Dimension']) -> EstimatorType:
return self._get_func('generate_estimator')(dimensions)
def generate_estimator(self, dimensions: List['Dimension'], **kwargs) -> EstimatorType:
return self._get_func('generate_estimator')(dimensions=dimensions, **kwargs)

View File

@@ -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

View File

@@ -569,8 +569,8 @@ class LocalTrade():
return float(f"{profit_ratio:.8f}")
def recalc_trade_from_orders(self):
# We need at least 2 orders for averaging amounts and rates.
if len(self.orders) < 2:
# We need at least 2 entry orders for averaging amounts and rates.
if len(self.select_filled_orders('buy')) < 2:
# Just in case, still recalc open trade value
self.recalc_open_trade_value()
return

View File

@@ -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,

View File

@@ -173,6 +173,8 @@ class ShowConfig(BaseModel):
bot_name: str
state: str
runmode: str
position_adjustment_enable: bool
max_entry_position_adjustment: int
class TradeSchema(BaseModel):

View File

@@ -214,7 +214,8 @@ def reload_config(rpc: RPC = Depends(get_rpc)):
@router.get('/pair_candles', response_model=PairHistory, tags=['candle data'])
def pair_candles(pair: str, timeframe: str, limit: Optional[int], rpc: RPC = Depends(get_rpc)):
def pair_candles(
pair: str, timeframe: str, limit: Optional[int] = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_analysed_dataframe(pair, timeframe, limit)

View File

@@ -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']

View File

@@ -136,7 +136,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
@@ -247,8 +252,11 @@ 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_str = ''
if self._config.get('max_entry_position_adjustment', -1) > 0:
max_buy_str = f"/{self._config['max_entry_position_adjustment'] + 1}"
filled_buys = trade.nr_of_successful_buys
detail_trade.append(f"{filled_buys}{max_buy_str}")
trades_list.append(detail_trade)
profitcol = "Profit"
if self._fiat_converter:

View File

@@ -1347,6 +1347,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"
@@ -1356,6 +1364,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']}`"

View File

@@ -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