Merge branch 'freqtrade:develop' into feat_bt_cancel_entry_reporting
This commit is contained in:
@@ -40,7 +40,7 @@ class HDF5DataHandler(IDataHandler):
|
||||
return [
|
||||
(
|
||||
cls.rebuild_pair_from_filename(match[1]),
|
||||
match[2],
|
||||
cls.rebuild_timeframe_from_filename(match[2]),
|
||||
CandleType.from_string(match[3])
|
||||
) for match in _tmp if match and len(match.groups()) > 1]
|
||||
|
||||
@@ -109,7 +109,11 @@ class HDF5DataHandler(IDataHandler):
|
||||
)
|
||||
|
||||
if not filename.exists():
|
||||
return pd.DataFrame(columns=self._columns)
|
||||
# Fallback mode for 1M files
|
||||
filename = self._pair_data_filename(
|
||||
self._datadir, pair, timeframe, candle_type=candle_type, no_timeframe_modify=True)
|
||||
if not filename.exists():
|
||||
return pd.DataFrame(columns=self._columns)
|
||||
where = []
|
||||
if timerange:
|
||||
if timerange.starttype == 'date':
|
||||
|
@@ -26,7 +26,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class IDataHandler(ABC):
|
||||
|
||||
_OHLCV_REGEX = r'^([a-zA-Z_-]+)\-(\d+\S)\-?([a-zA-Z_]*)?(?=\.)'
|
||||
_OHLCV_REGEX = r'^([a-zA-Z_-]+)\-(\d+[a-zA-Z]{1,2})\-?([a-zA-Z_]*)?(?=\.)'
|
||||
|
||||
def __init__(self, datadir: Path) -> None:
|
||||
self._datadir = datadir
|
||||
@@ -193,10 +193,14 @@ class IDataHandler(ABC):
|
||||
datadir: Path,
|
||||
pair: str,
|
||||
timeframe: str,
|
||||
candle_type: CandleType
|
||||
candle_type: CandleType,
|
||||
no_timeframe_modify: bool = False
|
||||
) -> Path:
|
||||
pair_s = misc.pair_to_filename(pair)
|
||||
candle = ""
|
||||
if not no_timeframe_modify:
|
||||
timeframe = cls.timeframe_to_file(timeframe)
|
||||
|
||||
if candle_type != CandleType.SPOT:
|
||||
datadir = datadir.joinpath('futures')
|
||||
candle = f"-{candle_type}"
|
||||
@@ -210,6 +214,18 @@ class IDataHandler(ABC):
|
||||
filename = datadir.joinpath(f'{pair_s}-trades.{cls._get_file_extension()}')
|
||||
return filename
|
||||
|
||||
@staticmethod
|
||||
def timeframe_to_file(timeframe: str):
|
||||
return timeframe.replace('M', 'Mo')
|
||||
|
||||
@staticmethod
|
||||
def rebuild_timeframe_from_filename(timeframe: str) -> str:
|
||||
"""
|
||||
converts timeframe from disk to file
|
||||
Replaces mo with M (to avoid problems on case-insensitive filesystems)
|
||||
"""
|
||||
return re.sub('1mo', '1M', timeframe, flags=re.IGNORECASE)
|
||||
|
||||
@staticmethod
|
||||
def rebuild_pair_from_filename(pair: str) -> str:
|
||||
"""
|
||||
|
@@ -41,7 +41,7 @@ class JsonDataHandler(IDataHandler):
|
||||
return [
|
||||
(
|
||||
cls.rebuild_pair_from_filename(match[1]),
|
||||
match[2],
|
||||
cls.rebuild_timeframe_from_filename(match[2]),
|
||||
CandleType.from_string(match[3])
|
||||
) for match in _tmp if match and len(match.groups()) > 1]
|
||||
|
||||
@@ -103,9 +103,14 @@ class JsonDataHandler(IDataHandler):
|
||||
:param candle_type: Any of the enum CandleType (must match trading mode!)
|
||||
:return: DataFrame with ohlcv data, or empty DataFrame
|
||||
"""
|
||||
filename = self._pair_data_filename(self._datadir, pair, timeframe, candle_type=candle_type)
|
||||
filename = self._pair_data_filename(
|
||||
self._datadir, pair, timeframe, candle_type=candle_type)
|
||||
if not filename.exists():
|
||||
return DataFrame(columns=self._columns)
|
||||
# Fallback mode for 1M files
|
||||
filename = self._pair_data_filename(
|
||||
self._datadir, pair, timeframe, candle_type=candle_type, no_timeframe_modify=True)
|
||||
if not filename.exists():
|
||||
return DataFrame(columns=self._columns)
|
||||
try:
|
||||
pairdata = read_json(filename, orient='values')
|
||||
pairdata.columns = self._columns
|
||||
|
@@ -16,8 +16,7 @@ import arrow
|
||||
import ccxt
|
||||
import ccxt.async_support as ccxt_async
|
||||
from cachetools import TTLCache
|
||||
from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRUNCATE,
|
||||
decimal_to_precision)
|
||||
from ccxt import ROUND_DOWN, ROUND_UP, TICK_SIZE, TRUNCATE, Precise, decimal_to_precision
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BuySell,
|
||||
@@ -704,10 +703,11 @@ class Exchange:
|
||||
# counting_mode=self.precisionMode,
|
||||
# ))
|
||||
if self.precisionMode == TICK_SIZE:
|
||||
precision = self.markets[pair]['precision']['price']
|
||||
missing = price % precision
|
||||
if missing != 0:
|
||||
price = round(price - missing + precision, 10)
|
||||
precision = Precise(str(self.markets[pair]['precision']['price']))
|
||||
price_str = Precise(str(price))
|
||||
missing = price_str % precision
|
||||
if not missing == Precise("0"):
|
||||
price = round(float(str(price_str - missing + precision)), 14)
|
||||
else:
|
||||
symbol_prec = self.markets[pair]['precision']['price']
|
||||
big_price = price * pow(10, symbol_prec)
|
||||
@@ -1457,6 +1457,23 @@ class Exchange:
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
def _get_price_side(self, side: str, is_short: bool, conf_strategy: Dict) -> str:
|
||||
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)]
|
||||
return price_side
|
||||
|
||||
def get_rate(self, pair: str, refresh: bool,
|
||||
side: EntryExit, is_short: bool) -> float:
|
||||
"""
|
||||
@@ -1483,20 +1500,7 @@ 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 = self._get_price_side(side, is_short, conf_strategy)
|
||||
|
||||
price_side_word = price_side.capitalize()
|
||||
|
||||
|
@@ -542,11 +542,11 @@ class Backtesting:
|
||||
|
||||
trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60)
|
||||
try:
|
||||
closerate = self._get_close_rate(row, trade, exit_, trade_dur)
|
||||
close_rate = self._get_close_rate(row, trade, exit_, trade_dur)
|
||||
except ValueError:
|
||||
return None
|
||||
# call the custom exit price,with default value as previous closerate
|
||||
current_profit = trade.calc_profit_ratio(closerate)
|
||||
# call the custom exit price,with default value as previous close_rate
|
||||
current_profit = trade.calc_profit_ratio(close_rate)
|
||||
order_type = self.strategy.order_types['exit']
|
||||
if exit_.exit_type in (ExitType.EXIT_SIGNAL, ExitType.CUSTOM_EXIT):
|
||||
# Checks and adds an exit tag, after checking that the length of the
|
||||
@@ -560,24 +560,24 @@ class Backtesting:
|
||||
exit_reason = row[EXIT_TAG_IDX]
|
||||
# Custom exit pricing only for exit-signals
|
||||
if order_type == 'limit':
|
||||
closerate = strategy_safe_wrapper(self.strategy.custom_exit_price,
|
||||
default_retval=closerate)(
|
||||
close_rate = strategy_safe_wrapper(self.strategy.custom_exit_price,
|
||||
default_retval=close_rate)(
|
||||
pair=trade.pair, trade=trade,
|
||||
current_time=exit_candle_time,
|
||||
proposed_rate=closerate, current_profit=current_profit,
|
||||
proposed_rate=close_rate, current_profit=current_profit,
|
||||
exit_tag=exit_reason)
|
||||
# We can't place orders lower than current low.
|
||||
# freqtrade does not support this in live, and the order would fill immediately
|
||||
if trade.is_short:
|
||||
closerate = min(closerate, row[HIGH_IDX])
|
||||
close_rate = min(close_rate, row[HIGH_IDX])
|
||||
else:
|
||||
closerate = max(closerate, row[LOW_IDX])
|
||||
close_rate = max(close_rate, row[LOW_IDX])
|
||||
# Confirm trade exit:
|
||||
time_in_force = self.strategy.order_time_in_force['exit']
|
||||
|
||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
|
||||
pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount,
|
||||
rate=closerate,
|
||||
rate=close_rate,
|
||||
time_in_force=time_in_force,
|
||||
sell_reason=exit_reason, # deprecated
|
||||
exit_reason=exit_reason,
|
||||
@@ -600,12 +600,12 @@ class Backtesting:
|
||||
side=trade.exit_side,
|
||||
order_type=order_type,
|
||||
status="open",
|
||||
price=closerate,
|
||||
average=closerate,
|
||||
price=close_rate,
|
||||
average=close_rate,
|
||||
amount=trade.amount,
|
||||
filled=0,
|
||||
remaining=trade.amount,
|
||||
cost=trade.amount * closerate,
|
||||
cost=trade.amount * close_rate,
|
||||
)
|
||||
trade.orders.append(order)
|
||||
return trade
|
||||
|
@@ -1410,14 +1410,14 @@ class Telegram(RPCHandler):
|
||||
"Optionally takes a rate at which to sell "
|
||||
"(only applies to limit orders).` \n")
|
||||
message = (
|
||||
"_BotControl_\n"
|
||||
"_Bot Control_\n"
|
||||
"------------\n"
|
||||
"*/start:* `Starts the trader`\n"
|
||||
"*/stop:* Stops the trader\n"
|
||||
"*/stopbuy:* `Stops buying, but handles open trades gracefully` \n"
|
||||
"*/forceexit <trade_id>|all:* `Instantly exits the given trade or all trades, "
|
||||
"regardless of profit`\n"
|
||||
"*/fe <trade_id>|all:* `Alias to /forceexit`"
|
||||
"*/fe <trade_id>|all:* `Alias to /forceexit`\n"
|
||||
f"{force_enter_text if self._config.get('force_entry_enable', False) else ''}"
|
||||
"*/delete <trade_id>:* `Instantly delete the given trade in the database`\n"
|
||||
"*/whitelist:* `Show current whitelist` \n"
|
||||
|
Reference in New Issue
Block a user