Merge branch 'freqtrade:develop' into develop

This commit is contained in:
Stefano Ariestasia 2023-01-09 09:26:35 +08:00 committed by GitHub
commit 8095a260b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 30 additions and 81 deletions

View File

@ -197,7 +197,7 @@ def calculate_cagr(days_passed: int, starting_balance: float, final_balance: flo
def calculate_expectancy(trades: pd.DataFrame) -> float: def calculate_expectancy(trades: pd.DataFrame) -> float:
""" """
Calculate expectancy Calculate expectancy
:param trades: DataFrame containing trades (requires columns close_date and profit_ratio) :param trades: DataFrame containing trades (requires columns close_date and profit_abs)
:return: expectancy :return: expectancy
""" """
if len(trades) == 0: if len(trades) == 0:

View File

@ -474,7 +474,7 @@ class Exchange:
try: try:
if self._api_async: if self._api_async:
self.loop.run_until_complete( self.loop.run_until_complete(
self._api_async.load_markets(reload=reload)) self._api_async.load_markets(reload=reload, params={}))
except (asyncio.TimeoutError, ccxt.BaseError) as e: except (asyncio.TimeoutError, ccxt.BaseError) as e:
logger.warning('Could not load async markets. Reason: %s', e) logger.warning('Could not load async markets. Reason: %s', e)
@ -483,7 +483,7 @@ class Exchange:
def _load_markets(self) -> None: def _load_markets(self) -> None:
""" Initialize markets both sync and async """ """ Initialize markets both sync and async """
try: try:
self._markets = self._api.load_markets() self._markets = self._api.load_markets(params={})
self._load_async_markets() self._load_async_markets()
self._last_markets_refresh = arrow.utcnow().int_timestamp self._last_markets_refresh = arrow.utcnow().int_timestamp
if self._ft_has['needs_trading_fees']: if self._ft_has['needs_trading_fees']:
@ -501,7 +501,7 @@ class Exchange:
return None return None
logger.debug("Performing scheduled market reload..") logger.debug("Performing scheduled market reload..")
try: try:
self._markets = self._api.load_markets(reload=True) self._markets = self._api.load_markets(reload=True, params={})
# Also reload async markets to avoid issues with newly listed pairs # Also reload async markets to avoid issues with newly listed pairs
self._load_async_markets(reload=True) self._load_async_markets(reload=True)
self._last_markets_refresh = arrow.utcnow().int_timestamp self._last_markets_refresh = arrow.utcnow().int_timestamp
@ -1705,7 +1705,7 @@ class Exchange:
return self._config['fee'] return self._config['fee']
# validate that markets are loaded before trying to get fee # validate that markets are loaded before trying to get fee
if self._api.markets is None or len(self._api.markets) == 0: if self._api.markets is None or len(self._api.markets) == 0:
self._api.load_markets() self._api.load_markets(params={})
return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount, return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
price=price, takerOrMaker=taker_or_maker)['rate'] price=price, takerOrMaker=taker_or_maker)['rate']

View File

@ -374,7 +374,7 @@ class FreqtradeBot(LoggingMixin):
for trade in trades: for trade in trades:
if not trade.is_open and not trade.fee_updated(trade.exit_side): if not trade.is_open and not trade.fee_updated(trade.exit_side):
# Get sell fee # Get sell fee
order = trade.select_order(trade.exit_side, False) order = trade.select_order(trade.exit_side, False, only_filled=True)
if not order: if not order:
order = trade.select_order('stoploss', False) order = trade.select_order('stoploss', False)
if order: if order:
@ -390,7 +390,7 @@ class FreqtradeBot(LoggingMixin):
for trade in trades: for trade in trades:
with self._exit_lock: with self._exit_lock:
if trade.is_open and not trade.fee_updated(trade.entry_side): if trade.is_open and not trade.fee_updated(trade.entry_side):
order = trade.select_order(trade.entry_side, False) order = trade.select_order(trade.entry_side, False, only_filled=True)
open_order = trade.select_order(trade.entry_side, True) open_order = trade.select_order(trade.entry_side, True)
if order and open_order is None: if order and open_order is None:
logger.info( logger.info(

View File

@ -5,13 +5,11 @@ This module defines the alternative HyperOptLoss class which can be used for
Hyperoptimization. Hyperoptimization.
""" """
from datetime import datetime from datetime import datetime
from math import sqrt as msqrt
from typing import Any, Dict
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config from freqtrade.constants import Config
from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.data.metrics import calculate_calmar
from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.optimize.hyperopt import IHyperOptLoss
@ -23,42 +21,15 @@ class CalmarHyperOptLoss(IHyperOptLoss):
""" """
@staticmethod @staticmethod
def hyperopt_loss_function( def hyperopt_loss_function(results: DataFrame, trade_count: int,
results: DataFrame, min_date: datetime, max_date: datetime,
trade_count: int, config: Config, *args, **kwargs) -> float:
min_date: datetime,
max_date: datetime,
config: Config,
processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any],
*args,
**kwargs
) -> float:
""" """
Objective function, returns smaller number for more optimal results. Objective function, returns smaller number for more optimal results.
Uses Calmar Ratio calculation. Uses Calmar Ratio calculation.
""" """
total_profit = backtest_stats["profit_total"] starting_balance = config['dry_run_wallet']
days_period = (max_date - min_date).days calmar_ratio = calculate_calmar(results, min_date, max_date, starting_balance)
# adding slippage of 0.1% per trade
total_profit = total_profit - 0.0005
expected_returns_mean = total_profit.sum() / days_period * 100
# calculate max drawdown
try:
_, _, _, _, _, max_drawdown = calculate_max_drawdown(
results, value_col="profit_abs"
)
except ValueError:
max_drawdown = 0
if max_drawdown != 0:
calmar_ratio = expected_returns_mean / max_drawdown * msqrt(365)
else:
# Define high (negative) calmar ratio to be clear that this is NOT optimal.
calmar_ratio = -20.0
# print(expected_returns_mean, max_drawdown, calmar_ratio) # print(expected_returns_mean, max_drawdown, calmar_ratio)
return -calmar_ratio return -calmar_ratio

View File

@ -6,9 +6,10 @@ Hyperoptimization.
""" """
from datetime import datetime from datetime import datetime
import numpy as np
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config
from freqtrade.data.metrics import calculate_sharpe
from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.optimize.hyperopt import IHyperOptLoss
@ -22,25 +23,13 @@ class SharpeHyperOptLoss(IHyperOptLoss):
@staticmethod @staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int, def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime, min_date: datetime, max_date: datetime,
*args, **kwargs) -> float: config: Config, *args, **kwargs) -> float:
""" """
Objective function, returns smaller number for more optimal results. Objective function, returns smaller number for more optimal results.
Uses Sharpe Ratio calculation. Uses Sharpe Ratio calculation.
""" """
total_profit = results["profit_ratio"] starting_balance = config['dry_run_wallet']
days_period = (max_date - min_date).days sharp_ratio = calculate_sharpe(results, min_date, max_date, starting_balance)
# adding slippage of 0.1% per trade
total_profit = total_profit - 0.0005
expected_returns_mean = total_profit.sum() / days_period
up_stdev = np.std(total_profit)
if up_stdev != 0:
sharp_ratio = expected_returns_mean / up_stdev * np.sqrt(365)
else:
# Define high (negative) sharpe ratio to be clear that this is NOT optimal.
sharp_ratio = -20.
# print(expected_returns_mean, up_stdev, sharp_ratio) # print(expected_returns_mean, up_stdev, sharp_ratio)
return -sharp_ratio return -sharp_ratio

View File

@ -6,9 +6,10 @@ Hyperoptimization.
""" """
from datetime import datetime from datetime import datetime
import numpy as np
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config
from freqtrade.data.metrics import calculate_sortino
from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.optimize.hyperopt import IHyperOptLoss
@ -22,28 +23,13 @@ class SortinoHyperOptLoss(IHyperOptLoss):
@staticmethod @staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int, def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime, min_date: datetime, max_date: datetime,
*args, **kwargs) -> float: config: Config, *args, **kwargs) -> float:
""" """
Objective function, returns smaller number for more optimal results. Objective function, returns smaller number for more optimal results.
Uses Sortino Ratio calculation. Uses Sortino Ratio calculation.
""" """
total_profit = results["profit_ratio"] starting_balance = config['dry_run_wallet']
days_period = (max_date - min_date).days sortino_ratio = calculate_sortino(results, min_date, max_date, starting_balance)
# adding slippage of 0.1% per trade
total_profit = total_profit - 0.0005
expected_returns_mean = total_profit.sum() / days_period
results['downside_returns'] = 0
results.loc[total_profit < 0, 'downside_returns'] = results['profit_ratio']
down_stdev = np.std(results['downside_returns'])
if down_stdev != 0:
sortino_ratio = expected_returns_mean / down_stdev * np.sqrt(365)
else:
# Define high (negative) sortino ratio to be clear that this is NOT optimal.
sortino_ratio = -20.
# print(expected_returns_mean, down_stdev, sortino_ratio) # print(expected_returns_mean, down_stdev, sortino_ratio)
return -sortino_ratio return -sortino_ratio

View File

@ -956,11 +956,12 @@ class LocalTrade():
return None return None
def select_order(self, order_side: Optional[str] = None, def select_order(self, order_side: Optional[str] = None,
is_open: Optional[bool] = None) -> Optional[Order]: is_open: Optional[bool] = None, only_filled: bool = False) -> Optional[Order]:
""" """
Finds latest order for this orderside and status Finds latest order for this orderside and status
:param order_side: ft_order_side of the order (either 'buy', 'sell' or 'stoploss') :param order_side: ft_order_side of the order (either 'buy', 'sell' or 'stoploss')
:param is_open: Only search for open orders? :param is_open: Only search for open orders?
:param only_filled: Only search for Filled orders (only valid with is_open=False).
:return: latest Order object if it exists, else None :return: latest Order object if it exists, else None
""" """
orders = self.orders orders = self.orders
@ -968,6 +969,8 @@ class LocalTrade():
orders = [o for o in orders if o.ft_order_side == order_side] orders = [o for o in orders if o.ft_order_side == order_side]
if is_open is not None: if is_open is not None:
orders = [o for o in orders if o.ft_is_open == is_open] orders = [o for o in orders if o.ft_is_open == is_open]
if is_open is False and only_filled:
orders = [o for o in orders if o.filled and o.status in NON_OPEN_EXCHANGE_STATES]
if len(orders) > 0: if len(orders) > 0:
return orders[-1] return orders[-1]
else: else:

View File

@ -2,7 +2,7 @@ numpy==1.24.1
pandas==1.5.2 pandas==1.5.2
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==2.4.60 ccxt==2.5.46
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==38.0.1; platform_machine == 'armv7l' cryptography==38.0.1; platform_machine == 'armv7l'
cryptography==38.0.4; platform_machine != 'armv7l' cryptography==38.0.4; platform_machine != 'armv7l'

View File

@ -48,8 +48,8 @@ def hyperopt_results():
return pd.DataFrame( return pd.DataFrame(
{ {
'pair': ['ETH/USDT', 'ETH/USDT', 'ETH/USDT', 'ETH/USDT'], 'pair': ['ETH/USDT', 'ETH/USDT', 'ETH/USDT', 'ETH/USDT'],
'profit_ratio': [-0.1, 0.2, -0.1, 0.3], 'profit_ratio': [-0.1, 0.2, -0.12, 0.3],
'profit_abs': [-0.2, 0.4, -0.2, 0.6], 'profit_abs': [-0.2, 0.4, -0.21, 0.6],
'trade_duration': [10, 30, 10, 10], 'trade_duration': [10, 30, 10, 10],
'amount': [0.1, 0.1, 0.1, 0.1], 'amount': [0.1, 0.1, 0.1, 0.1],
'exit_reason': [ExitType.STOP_LOSS, ExitType.ROI, ExitType.STOP_LOSS, ExitType.ROI], 'exit_reason': [ExitType.STOP_LOSS, ExitType.ROI, ExitType.STOP_LOSS, ExitType.ROI],