Merge branch 'develop' into feat/freqai

This commit is contained in:
Matthias
2022-07-24 16:18:58 +02:00
27 changed files with 346 additions and 121 deletions

View File

@@ -29,7 +29,7 @@ ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_pos
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
"position_stacking", "use_max_market_positions",
"enable_protections", "dry_run_wallet",
"enable_protections", "dry_run_wallet", "timeframe_detail",
"epochs", "spaces", "print_all",
"print_colorized", "print_json", "hyperopt_jobs",
"hyperopt_random_state", "hyperopt_min_trades",

View File

@@ -591,3 +591,4 @@ TradeList = List[List]
LongShort = Literal['long', 'short']
EntryExit = Literal['entry', 'exit']
BuySell = Literal['buy', 'sell']
MakerTaker = Literal['maker', 'taker']

View File

@@ -20,7 +20,7 @@ from ccxt import ROUND_DOWN, ROUND_UP, TICK_SIZE, TRUNCATE, Precise, decimal_to_
from pandas import DataFrame
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BuySell,
EntryExit, ListPairsWithTimeframes, PairWithTimeframe)
EntryExit, ListPairsWithTimeframes, MakerTaker, PairWithTimeframe)
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
@@ -88,7 +88,8 @@ class Exchange:
# TradingMode.SPOT always supported and not required in this list
]
def __init__(self, config: Dict[str, Any], validate: bool = True, freqai: bool = False) -> None:
def __init__(self, config: Dict[str, Any], validate: bool = True,
load_leverage_tiers: bool = False) -> None:
"""
Initializes this module with the given config,
it does basic validation whether the specified exchange and pairs are valid.
@@ -186,7 +187,7 @@ class Exchange:
self.markets_refresh_interval: int = exchange_config.get(
"markets_refresh_interval", 60) * 60
if self.trading_mode != TradingMode.SPOT and freqai is False:
if self.trading_mode != TradingMode.SPOT and load_leverage_tiers:
self.fill_leverage_tiers()
self.additional_exchange_init()
@@ -850,20 +851,27 @@ class Exchange:
'filled': _amount,
'cost': (dry_order['amount'] * average) / leverage
})
dry_order = self.add_dry_order_fee(pair, dry_order)
# market orders will always incurr taker fees
dry_order = self.add_dry_order_fee(pair, dry_order, 'taker')
dry_order = self.check_dry_limit_order_filled(dry_order)
dry_order = self.check_dry_limit_order_filled(dry_order, immediate=True)
self._dry_run_open_orders[dry_order["id"]] = dry_order
# Copy order and close it - so the returned order is open unless it's a market order
return dry_order
def add_dry_order_fee(self, pair: str, dry_order: Dict[str, Any]) -> Dict[str, Any]:
def add_dry_order_fee(
self,
pair: str,
dry_order: Dict[str, Any],
taker_or_maker: MakerTaker,
) -> Dict[str, Any]:
fee = self.get_fee(pair, taker_or_maker=taker_or_maker)
dry_order.update({
'fee': {
'currency': self.get_pair_quote_currency(pair),
'cost': dry_order['cost'] * self.get_fee(pair),
'rate': self.get_fee(pair)
'cost': dry_order['cost'] * fee,
'rate': fee
}
})
return dry_order
@@ -929,7 +937,8 @@ class Exchange:
pass
return False
def check_dry_limit_order_filled(self, order: Dict[str, Any]) -> Dict[str, Any]:
def check_dry_limit_order_filled(
self, order: Dict[str, Any], immediate: bool = False) -> Dict[str, Any]:
"""
Check dry-run limit order fill and update fee (if it filled).
"""
@@ -943,7 +952,12 @@ class Exchange:
'filled': order['amount'],
'remaining': 0,
})
self.add_dry_order_fee(pair, order)
self.add_dry_order_fee(
pair,
order,
'taker' if immediate else 'maker',
)
return order
@@ -1601,7 +1615,7 @@ class Exchange:
@retrier
def get_fee(self, symbol: str, type: str = '', side: str = '', amount: float = 1,
price: float = 1, taker_or_maker: str = 'maker') -> float:
price: float = 1, taker_or_maker: MakerTaker = 'maker') -> float:
try:
if self._config['dry_run'] and self._config.get('fee', None) is not None:
return self._config['fee']

View File

@@ -923,7 +923,7 @@ class FreqaiDataKitchen:
and training the model.
"""
exchange = ExchangeResolver.load_exchange(
self.config["exchange"]["name"], self.config, validate=False, freqai=True
self.config["exchange"]["name"], self.config, validate=False, load_leverage_tiers=False
)
new_pairs_days = int((timerange.stopts - timerange.startts) / SECONDS_IN_DAY)

View File

@@ -65,7 +65,8 @@ class FreqtradeBot(LoggingMixin):
# Check config consistency here since strategies can set certain options
validate_config_consistency(config)
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
self.exchange = ExchangeResolver.load_exchange(
self.config['exchange']['name'], self.config, load_leverage_tiers=True)
init_db(self.config['db_url'])

View File

@@ -84,7 +84,8 @@ class Backtesting:
self.processed_dfs: Dict[str, Dict] = {}
self._exchange_name = self.config['exchange']['name']
self.exchange = ExchangeResolver.load_exchange(self._exchange_name, self.config)
self.exchange = ExchangeResolver.load_exchange(
self._exchange_name, self.config, load_leverage_tiers=True)
self.dataprovider = DataProvider(self.config, self.exchange)
if self.config.get('strategy_list'):

View File

@@ -255,18 +255,18 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
"""
# Trades can be empty
if trades is not None and len(trades) > 0:
# Create description for sell summarizing the trade
# Create description for exit summarizing the trade
trades['desc'] = trades.apply(
lambda row: f"{row['profit_ratio']:.2%}, " +
(f"{row['enter_tag']}, " if row['enter_tag'] is not None else "") +
f"{row['exit_reason']}, " +
f"{row['trade_duration']} min",
axis=1)
trade_buys = go.Scatter(
trade_entries = go.Scatter(
x=trades["open_date"],
y=trades["open_rate"],
mode='markers',
name='Trade buy',
name='Trade entry',
text=trades["desc"],
marker=dict(
symbol='circle-open',
@@ -277,12 +277,12 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
)
)
trade_sells = go.Scatter(
trade_exits = go.Scatter(
x=trades.loc[trades['profit_ratio'] > 0, "close_date"],
y=trades.loc[trades['profit_ratio'] > 0, "close_rate"],
text=trades.loc[trades['profit_ratio'] > 0, "desc"],
mode='markers',
name='Sell - Profit',
name='Exit - Profit',
marker=dict(
symbol='square-open',
size=11,
@@ -290,12 +290,12 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
color='green'
)
)
trade_sells_loss = go.Scatter(
trade_exits_loss = go.Scatter(
x=trades.loc[trades['profit_ratio'] <= 0, "close_date"],
y=trades.loc[trades['profit_ratio'] <= 0, "close_rate"],
text=trades.loc[trades['profit_ratio'] <= 0, "desc"],
mode='markers',
name='Sell - Loss',
name='Exit - Loss',
marker=dict(
symbol='square-open',
size=11,
@@ -303,9 +303,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
color='red'
)
)
fig.add_trace(trade_buys, 1, 1)
fig.add_trace(trade_sells, 1, 1)
fig.add_trace(trade_sells_loss, 1, 1)
fig.add_trace(trade_entries, 1, 1)
fig.add_trace(trade_exits, 1, 1)
fig.add_trace(trade_exits_loss, 1, 1)
else:
logger.warning("No trades found.")
return fig
@@ -444,7 +444,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
Generate the graph from the data generated by Backtesting or from DB
Volume will always be ploted in row2, so Row 1 and 3 are to our disposal for custom indicators
:param pair: Pair to Display on the graph
:param data: OHLCV DataFrame containing indicators and buy/sell signals
:param data: OHLCV DataFrame containing indicators and entry/exit signals
:param trades: All trades created
:param indicators1: List containing Main plot indicators
:param indicators2: List containing Sub plot indicators

View File

@@ -19,7 +19,7 @@ class ExchangeResolver(IResolver):
@staticmethod
def load_exchange(exchange_name: str, config: dict, validate: bool = True,
freqai: bool = False) -> Exchange:
load_leverage_tiers: bool = False) -> Exchange:
"""
Load the custom class from config parameter
:param exchange_name: name of the Exchange to load
@@ -30,10 +30,13 @@ class ExchangeResolver(IResolver):
exchange_name = exchange_name.title()
exchange = None
try:
exchange = ExchangeResolver._load_exchange(exchange_name,
kwargs={'config': config,
'validate': validate,
'freqai': freqai})
exchange = ExchangeResolver._load_exchange(
exchange_name,
kwargs={
'config': config,
'validate': validate,
'load_leverage_tiers': load_leverage_tiers}
)
except ImportError:
logger.info(
f"No {exchange_name} specific subclass found. Using the generic class instead.")

View File

@@ -37,7 +37,7 @@ def get_exchange(config=Depends(get_config)):
if not ApiServer._exchange:
from freqtrade.resolvers import ExchangeResolver
ApiServer._exchange = ExchangeResolver.load_exchange(
config['exchange']['name'], config)
config['exchange']['name'], config, load_leverage_tiers=False)
return ApiServer._exchange