Merge branch 'freqtrade:develop' into develop
This commit is contained in:
commit
e5e63d5bee
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -425,7 +425,7 @@ jobs:
|
|||||||
python setup.py sdist bdist_wheel
|
python setup.py sdist bdist_wheel
|
||||||
|
|
||||||
- name: Publish to PyPI (Test)
|
- name: Publish to PyPI (Test)
|
||||||
uses: pypa/gh-action-pypi-publish@v1.8.4
|
uses: pypa/gh-action-pypi-publish@v1.8.5
|
||||||
if: (github.event_name == 'release')
|
if: (github.event_name == 'release')
|
||||||
with:
|
with:
|
||||||
user: __token__
|
user: __token__
|
||||||
@ -433,7 +433,7 @@ jobs:
|
|||||||
repository_url: https://test.pypi.org/legacy/
|
repository_url: https://test.pypi.org/legacy/
|
||||||
|
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@v1.8.4
|
uses: pypa/gh-action-pypi-publish@v1.8.5
|
||||||
if: (github.event_name == 'release')
|
if: (github.event_name == 'release')
|
||||||
with:
|
with:
|
||||||
user: __token__
|
user: __token__
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.10.10-slim-bullseye as base
|
FROM python:3.10.11-slim-bullseye as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
|
@ -274,19 +274,20 @@ A backtesting result will look like that:
|
|||||||
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
|
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
|
||||||
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
|
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
|
||||||
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
||||||
========================================================= EXIT REASON STATS ==========================================================
|
|
||||||
| Exit Reason | Exits | Wins | Draws | Losses |
|
|
||||||
|:-------------------|--------:|------:|-------:|--------:|
|
|
||||||
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
|
||||||
| stop_loss | 166 | 0 | 0 | 166 |
|
|
||||||
| exit_signal | 56 | 36 | 0 | 20 |
|
|
||||||
| force_exit | 2 | 0 | 0 | 2 |
|
|
||||||
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
||||||
| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
||||||
|:---------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
|:---------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
||||||
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
|
||||||
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
|
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
|
||||||
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
|
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
|
||||||
|
==================== EXIT REASON STATS ====================
|
||||||
|
| Exit Reason | Exits | Wins | Draws | Losses |
|
||||||
|
|:-------------------|--------:|------:|-------:|--------:|
|
||||||
|
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
||||||
|
| stop_loss | 166 | 0 | 0 | 166 |
|
||||||
|
| exit_signal | 56 | 36 | 0 | 20 |
|
||||||
|
| force_exit | 2 | 0 | 0 | 2 |
|
||||||
|
|
||||||
================== SUMMARY METRICS ==================
|
================== SUMMARY METRICS ==================
|
||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
|-----------------------------+---------------------|
|
|-----------------------------+---------------------|
|
||||||
|
@ -180,7 +180,7 @@ As you begin to modify the strategy and the prediction model, you will quickly r
|
|||||||
|
|
||||||
# you can use feature values from dataframe
|
# you can use feature values from dataframe
|
||||||
# Assumes the shifted RSI indicator has been generated in the strategy.
|
# Assumes the shifted RSI indicator has been generated in the strategy.
|
||||||
rsi_now = self.raw_features[f"%-rsi-period-10_shift-1_{pair}_"
|
rsi_now = self.raw_features[f"%-rsi-period_10_shift-1_{pair}_"
|
||||||
f"{self.config['timeframe']}"].iloc[self._current_tick]
|
f"{self.config['timeframe']}"].iloc[self._current_tick]
|
||||||
|
|
||||||
# reward agent for entering trades
|
# reward agent for entering trades
|
||||||
|
@ -2,5 +2,5 @@ markdown==3.3.7
|
|||||||
mkdocs==1.4.2
|
mkdocs==1.4.2
|
||||||
mkdocs-material==9.1.5
|
mkdocs-material==9.1.5
|
||||||
mdx_truly_sane_lists==1.3
|
mdx_truly_sane_lists==1.3
|
||||||
pymdown-extensions==9.10
|
pymdown-extensions==9.11
|
||||||
jinja2==3.1.2
|
jinja2==3.1.2
|
||||||
|
@ -9,9 +9,6 @@ This same command can also be used to update freqUI, should there be a new relea
|
|||||||
|
|
||||||
Once the bot is started in trade / dry-run mode (with `freqtrade trade`) - the UI will be available under the configured port below (usually `http://127.0.0.1:8080`).
|
Once the bot is started in trade / dry-run mode (with `freqtrade trade`) - the UI will be available under the configured port below (usually `http://127.0.0.1:8080`).
|
||||||
|
|
||||||
!!! info "Alpha release"
|
|
||||||
FreqUI is still considered an alpha release - if you encounter bugs or inconsistencies please open a [FreqUI issue](https://github.com/freqtrade/frequi/issues/new/choose).
|
|
||||||
|
|
||||||
!!! Note "developers"
|
!!! Note "developers"
|
||||||
Developers should not use this method, but instead use the method described in the [freqUI repository](https://github.com/freqtrade/frequi) to get the source-code of freqUI.
|
Developers should not use this method, but instead use the method described in the [freqUI repository](https://github.com/freqtrade/frequi) to get the source-code of freqUI.
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ class Exchange:
|
|||||||
# or by specifying them in the configuration.
|
# or by specifying them in the configuration.
|
||||||
_ft_has_default: Dict = {
|
_ft_has_default: Dict = {
|
||||||
"stoploss_on_exchange": False,
|
"stoploss_on_exchange": False,
|
||||||
|
"stop_price_param": "stopPrice",
|
||||||
"order_time_in_force": ["GTC"],
|
"order_time_in_force": ["GTC"],
|
||||||
"ohlcv_params": {},
|
"ohlcv_params": {},
|
||||||
"ohlcv_candle_limit": 500,
|
"ohlcv_candle_limit": 500,
|
||||||
@ -1115,11 +1116,11 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
if not self._ft_has.get('stoploss_on_exchange'):
|
if not self._ft_has.get('stoploss_on_exchange'):
|
||||||
raise OperationalException(f"stoploss is not implemented for {self.name}.")
|
raise OperationalException(f"stoploss is not implemented for {self.name}.")
|
||||||
|
price_param = self._ft_has['stop_price_param']
|
||||||
return (
|
return (
|
||||||
order.get('stopPrice', None) is None
|
order.get(price_param, None) is None
|
||||||
or ((side == "sell" and stop_loss > float(order['stopPrice'])) or
|
or ((side == "sell" and stop_loss > float(order[price_param])) or
|
||||||
(side == "buy" and stop_loss < float(order['stopPrice'])))
|
(side == "buy" and stop_loss < float(order[price_param])))
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_stop_order_type(self, user_order_type) -> Tuple[str, str]:
|
def _get_stop_order_type(self, user_order_type) -> Tuple[str, str]:
|
||||||
@ -1159,8 +1160,8 @@ class Exchange:
|
|||||||
|
|
||||||
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict:
|
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict:
|
||||||
params = self._params.copy()
|
params = self._params.copy()
|
||||||
# Verify if stopPrice works for your exchange!
|
# Verify if stopPrice works for your exchange, else configure stop_price_param
|
||||||
params.update({'stopPrice': stop_price})
|
params.update({self._ft_has['stop_price_param']: stop_price})
|
||||||
return params
|
return params
|
||||||
|
|
||||||
@retrier(retries=0)
|
@retrier(retries=0)
|
||||||
|
@ -28,6 +28,7 @@ class Okx(Exchange):
|
|||||||
"funding_fee_timeframe": "8h",
|
"funding_fee_timeframe": "8h",
|
||||||
"stoploss_order_types": {"limit": "limit"},
|
"stoploss_order_types": {"limit": "limit"},
|
||||||
"stoploss_on_exchange": True,
|
"stoploss_on_exchange": True,
|
||||||
|
"stop_price_param": "stopLossPrice",
|
||||||
}
|
}
|
||||||
_ft_has_futures: Dict = {
|
_ft_has_futures: Dict = {
|
||||||
"tickers_have_quoteVolume": False,
|
"tickers_have_quoteVolume": False,
|
||||||
@ -162,29 +163,12 @@ class Okx(Exchange):
|
|||||||
return pair_tiers[-1]['maxNotional'] / leverage
|
return pair_tiers[-1]['maxNotional'] / leverage
|
||||||
|
|
||||||
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict:
|
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict:
|
||||||
|
params = super()._get_stop_params(side, ordertype, stop_price)
|
||||||
params = self._params.copy()
|
|
||||||
# Verify if stopPrice works for your exchange!
|
|
||||||
params.update({'stopLossPrice': stop_price})
|
|
||||||
|
|
||||||
if self.trading_mode == TradingMode.FUTURES and self.margin_mode:
|
if self.trading_mode == TradingMode.FUTURES and self.margin_mode:
|
||||||
params['tdMode'] = self.margin_mode.value
|
params['tdMode'] = self.margin_mode.value
|
||||||
params['posSide'] = self._get_posSide(side, True)
|
params['posSide'] = self._get_posSide(side, True)
|
||||||
return params
|
return params
|
||||||
|
|
||||||
def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
|
|
||||||
"""
|
|
||||||
OKX uses non-default stoploss price naming.
|
|
||||||
"""
|
|
||||||
if not self._ft_has.get('stoploss_on_exchange'):
|
|
||||||
raise OperationalException(f"stoploss is not implemented for {self.name}.")
|
|
||||||
|
|
||||||
return (
|
|
||||||
order.get('stopLossPrice', None) is None
|
|
||||||
or ((side == "sell" and stop_loss > float(order['stopLossPrice'])) or
|
|
||||||
(side == "buy" and stop_loss < float(order['stopLossPrice'])))
|
|
||||||
)
|
|
||||||
|
|
||||||
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
|
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
|
||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
return self.fetch_dry_run_order(order_id)
|
return self.fetch_dry_run_order(order_id)
|
||||||
|
@ -1291,7 +1291,7 @@ class FreqaiDataKitchen:
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def use_strategy_to_populate_indicators(
|
def use_strategy_to_populate_indicators( # noqa: C901
|
||||||
self,
|
self,
|
||||||
strategy: IStrategy,
|
strategy: IStrategy,
|
||||||
corr_dataframes: dict = {},
|
corr_dataframes: dict = {},
|
||||||
@ -1362,12 +1362,12 @@ class FreqaiDataKitchen:
|
|||||||
dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy,
|
dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy,
|
||||||
corr_dataframes, base_dataframes, True)
|
corr_dataframes, base_dataframes, True)
|
||||||
|
|
||||||
dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata)
|
if self.live:
|
||||||
|
dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata)
|
||||||
|
dataframe = self.remove_special_chars_from_feature_names(dataframe)
|
||||||
|
|
||||||
self.get_unique_classes_from_labels(dataframe)
|
self.get_unique_classes_from_labels(dataframe)
|
||||||
|
|
||||||
dataframe = self.remove_special_chars_from_feature_names(dataframe)
|
|
||||||
|
|
||||||
if self.config.get('reduce_df_footprint', False):
|
if self.config.get('reduce_df_footprint', False):
|
||||||
dataframe = reduce_dataframe_footprint(dataframe)
|
dataframe = reduce_dataframe_footprint(dataframe)
|
||||||
|
|
||||||
|
@ -306,7 +306,7 @@ class IFreqaiModel(ABC):
|
|||||||
if check_features:
|
if check_features:
|
||||||
self.dd.load_metadata(dk)
|
self.dd.load_metadata(dk)
|
||||||
dataframe_dummy_features = self.dk.use_strategy_to_populate_indicators(
|
dataframe_dummy_features = self.dk.use_strategy_to_populate_indicators(
|
||||||
strategy, prediction_dataframe=dataframe.tail(1), pair=metadata["pair"]
|
strategy, prediction_dataframe=dataframe.tail(1), pair=pair
|
||||||
)
|
)
|
||||||
dk.find_features(dataframe_dummy_features)
|
dk.find_features(dataframe_dummy_features)
|
||||||
self.check_if_feature_list_matches_strategy(dk)
|
self.check_if_feature_list_matches_strategy(dk)
|
||||||
@ -316,7 +316,7 @@ class IFreqaiModel(ABC):
|
|||||||
else:
|
else:
|
||||||
if populate_indicators:
|
if populate_indicators:
|
||||||
dataframe = self.dk.use_strategy_to_populate_indicators(
|
dataframe = self.dk.use_strategy_to_populate_indicators(
|
||||||
strategy, prediction_dataframe=dataframe, pair=metadata["pair"]
|
strategy, prediction_dataframe=dataframe, pair=pair
|
||||||
)
|
)
|
||||||
populate_indicators = False
|
populate_indicators = False
|
||||||
|
|
||||||
@ -332,6 +332,10 @@ class IFreqaiModel(ABC):
|
|||||||
dataframe_train = dk.slice_dataframe(tr_train, dataframe_base_train)
|
dataframe_train = dk.slice_dataframe(tr_train, dataframe_base_train)
|
||||||
dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe_base_backtest)
|
dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe_base_backtest)
|
||||||
|
|
||||||
|
dataframe_train = dk.remove_special_chars_from_feature_names(dataframe_train)
|
||||||
|
dataframe_backtest = dk.remove_special_chars_from_feature_names(dataframe_backtest)
|
||||||
|
dk.get_unique_classes_from_labels(dataframe_train)
|
||||||
|
|
||||||
if not self.model_exists(dk):
|
if not self.model_exists(dk):
|
||||||
dk.find_features(dataframe_train)
|
dk.find_features(dataframe_train)
|
||||||
dk.find_labels(dataframe_train)
|
dk.find_labels(dataframe_train)
|
||||||
|
@ -1483,8 +1483,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
order = self.exchange.cancel_order_with_result(order['id'], trade.pair,
|
order = self.exchange.cancel_order_with_result(
|
||||||
trade.amount)
|
order['id'], trade.pair, trade.amount)
|
||||||
except InvalidOrderException:
|
except InvalidOrderException:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
f"Could not cancel {trade.exit_side} order {trade.open_order_id}")
|
f"Could not cancel {trade.exit_side} order {trade.open_order_id}")
|
||||||
@ -1496,17 +1496,18 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# Order might be filled above in odd timing issues.
|
# Order might be filled above in odd timing issues.
|
||||||
if order.get('status') in ('canceled', 'cancelled'):
|
if order.get('status') in ('canceled', 'cancelled'):
|
||||||
trade.exit_reason = None
|
trade.exit_reason = None
|
||||||
|
trade.open_order_id = None
|
||||||
else:
|
else:
|
||||||
trade.exit_reason = exit_reason_prev
|
trade.exit_reason = exit_reason_prev
|
||||||
cancelled = True
|
cancelled = True
|
||||||
else:
|
else:
|
||||||
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
|
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
|
||||||
trade.exit_reason = None
|
trade.exit_reason = None
|
||||||
|
trade.open_order_id = None
|
||||||
|
|
||||||
self.update_trade_state(trade, trade.open_order_id, order)
|
self.update_trade_state(trade, trade.open_order_id, order)
|
||||||
|
|
||||||
logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.')
|
logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.')
|
||||||
trade.open_order_id = None
|
|
||||||
trade.close_rate = None
|
trade.close_rate = None
|
||||||
trade.close_rate_requested = None
|
trade.close_rate_requested = None
|
||||||
|
|
||||||
@ -1786,9 +1787,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if not stoploss_order:
|
if not stoploss_order:
|
||||||
logger.info(f'Found open order for {trade}')
|
logger.info(f'Found open order for {trade}')
|
||||||
try:
|
try:
|
||||||
order = action_order or self.exchange.fetch_order_or_stoploss_order(order_id,
|
order = action_order or self.exchange.fetch_order_or_stoploss_order(
|
||||||
trade.pair,
|
order_id, trade.pair, stoploss_order)
|
||||||
stoploss_order)
|
|
||||||
except InvalidOrderException as exception:
|
except InvalidOrderException as exception:
|
||||||
logger.warning('Unable to fetch order %s: %s', order_id, exception)
|
logger.warning('Unable to fetch order %s: %s', order_id, exception)
|
||||||
return False
|
return False
|
||||||
|
@ -23,6 +23,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
NON_OPT_PARAM_APPENDIX = " # value loaded from strategy"
|
NON_OPT_PARAM_APPENDIX = " # value loaded from strategy"
|
||||||
|
|
||||||
|
HYPER_PARAMS_FILE_FORMAT = rapidjson.NM_NATIVE | rapidjson.NM_NAN
|
||||||
|
|
||||||
|
|
||||||
def hyperopt_serializer(x):
|
def hyperopt_serializer(x):
|
||||||
if isinstance(x, np.integer):
|
if isinstance(x, np.integer):
|
||||||
@ -76,9 +78,18 @@ class HyperoptTools():
|
|||||||
with filename.open('w') as f:
|
with filename.open('w') as f:
|
||||||
rapidjson.dump(final_params, f, indent=2,
|
rapidjson.dump(final_params, f, indent=2,
|
||||||
default=hyperopt_serializer,
|
default=hyperopt_serializer,
|
||||||
number_mode=rapidjson.NM_NATIVE | rapidjson.NM_NAN
|
number_mode=HYPER_PARAMS_FILE_FORMAT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load_params(filename: Path) -> Dict:
|
||||||
|
"""
|
||||||
|
Load parameters from file
|
||||||
|
"""
|
||||||
|
with filename.open('r') as f:
|
||||||
|
params = rapidjson.load(f, number_mode=HYPER_PARAMS_FILE_FORMAT)
|
||||||
|
return params
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def try_export_params(config: Config, strategy_name: str, params: Dict):
|
def try_export_params(config: Config, strategy_name: str, params: Dict):
|
||||||
if params.get(FTHYPT_FILEVERSION, 1) >= 2 and not config.get('disableparamexport', False):
|
if params.get(FTHYPT_FILEVERSION, 1) >= 2 and not config.get('disableparamexport', False):
|
||||||
@ -189,7 +200,7 @@ class HyperoptTools():
|
|||||||
for s in ['buy', 'sell', 'protection',
|
for s in ['buy', 'sell', 'protection',
|
||||||
'roi', 'stoploss', 'trailing', 'max_open_trades']:
|
'roi', 'stoploss', 'trailing', 'max_open_trades']:
|
||||||
HyperoptTools._params_update_for_json(result_dict, params, non_optimized, s)
|
HyperoptTools._params_update_for_json(result_dict, params, non_optimized, s)
|
||||||
print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE))
|
print(rapidjson.dumps(result_dict, default=str, number_mode=HYPER_PARAMS_FILE_FORMAT))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
HyperoptTools._params_pretty_print(params, 'buy', "Buy hyperspace params:",
|
HyperoptTools._params_pretty_print(params, 'buy', "Buy hyperspace params:",
|
||||||
|
@ -865,6 +865,11 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
|
|||||||
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
|
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
|
table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency)
|
||||||
|
if isinstance(table, str) and len(table) > 0:
|
||||||
|
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
|
||||||
|
print(table)
|
||||||
|
|
||||||
if (results.get('results_per_enter_tag') is not None
|
if (results.get('results_per_enter_tag') is not None
|
||||||
or results.get('results_per_buy_tag') is not None):
|
or results.get('results_per_buy_tag') is not None):
|
||||||
# results_per_buy_tag is deprecated and should be removed 2 versions after short golive.
|
# results_per_buy_tag is deprecated and should be removed 2 versions after short golive.
|
||||||
@ -884,11 +889,6 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
|
|||||||
print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))
|
print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency)
|
|
||||||
if isinstance(table, str) and len(table) > 0:
|
|
||||||
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
|
|
||||||
print(table)
|
|
||||||
|
|
||||||
for period in backtest_breakdown:
|
for period in backtest_breakdown:
|
||||||
days_breakdown_stats = generate_periodic_breakdown_stats(
|
days_breakdown_stats = generate_periodic_breakdown_stats(
|
||||||
trade_list=results['trades'], period=period)
|
trade_list=results['trades'], period=period)
|
||||||
@ -917,11 +917,11 @@ def show_backtest_results(config: Config, backtest_stats: Dict):
|
|||||||
strategy, results, stake_currency,
|
strategy, results, stake_currency,
|
||||||
config.get('backtest_breakdown', []))
|
config.get('backtest_breakdown', []))
|
||||||
|
|
||||||
if len(backtest_stats['strategy']) > 1:
|
if len(backtest_stats['strategy']) > 0:
|
||||||
# Print Strategy summary table
|
# Print Strategy summary table
|
||||||
|
|
||||||
table = text_table_strategy(backtest_stats['strategy_comparison'], stake_currency)
|
table = text_table_strategy(backtest_stats['strategy_comparison'], stake_currency)
|
||||||
print(f"{results['backtest_start']} -> {results['backtest_end']} |"
|
print(f"Backtested {results['backtest_start']} -> {results['backtest_end']} |"
|
||||||
f" Max open trades : {results['max_open_trades']}")
|
f" Max open trades : {results['max_open_trades']}")
|
||||||
print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '='))
|
print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
@ -55,7 +55,7 @@ class UvicornServer(uvicorn.Server):
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def run_in_thread(self):
|
def run_in_thread(self):
|
||||||
self.thread = threading.Thread(target=self.run)
|
self.thread = threading.Thread(target=self.run, name='FTUvicorn')
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
while not self.started:
|
while not self.started:
|
||||||
time.sleep(1e-3)
|
time.sleep(1e-3)
|
||||||
|
@ -8,7 +8,7 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union
|
|||||||
|
|
||||||
from freqtrade.constants import Config
|
from freqtrade.constants import Config
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.misc import deep_merge_dicts, json_load
|
from freqtrade.misc import deep_merge_dicts
|
||||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||||
from freqtrade.strategy.parameters import BaseParameter
|
from freqtrade.strategy.parameters import BaseParameter
|
||||||
|
|
||||||
@ -124,8 +124,7 @@ class HyperStrategyMixin:
|
|||||||
if filename.is_file():
|
if filename.is_file():
|
||||||
logger.info(f"Loading parameters from file {filename}")
|
logger.info(f"Loading parameters from file {filename}")
|
||||||
try:
|
try:
|
||||||
with filename.open('r') as f:
|
params = HyperoptTools.load_params(filename)
|
||||||
params = json_load(f)
|
|
||||||
if params.get('strategy_name') != self.__class__.__name__:
|
if params.get('strategy_name') != self.__class__.__name__:
|
||||||
raise OperationalException('Invalid parameter file provided.')
|
raise OperationalException('Invalid parameter file provided.')
|
||||||
return params
|
return params
|
||||||
|
@ -10,7 +10,7 @@ coveralls==3.3.1
|
|||||||
ruff==0.0.260
|
ruff==0.0.260
|
||||||
mypy==1.1.1
|
mypy==1.1.1
|
||||||
pre-commit==3.2.1
|
pre-commit==3.2.1
|
||||||
pytest==7.2.2
|
pytest==7.3.0
|
||||||
pytest-asyncio==0.21.0
|
pytest-asyncio==0.21.0
|
||||||
pytest-cov==4.0.0
|
pytest-cov==4.0.0
|
||||||
pytest-mock==3.10.0
|
pytest-mock==3.10.0
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
scipy==1.10.1
|
scipy==1.10.1
|
||||||
scikit-learn==1.1.3
|
scikit-learn==1.1.3
|
||||||
scikit-optimize==0.9.0
|
scikit-optimize==0.9.0
|
||||||
filelock==3.10.6
|
filelock==3.11.0
|
||||||
progressbar2==4.2.0
|
progressbar2==4.2.0
|
||||||
|
@ -2,7 +2,7 @@ numpy==1.24.2
|
|||||||
pandas==1.5.3
|
pandas==1.5.3
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==3.0.50
|
ccxt==3.0.58
|
||||||
cryptography==40.0.1
|
cryptography==40.0.1
|
||||||
aiohttp==3.8.4
|
aiohttp==3.8.4
|
||||||
SQLAlchemy==2.0.8
|
SQLAlchemy==2.0.8
|
||||||
|
2
setup.py
2
setup.py
@ -59,7 +59,7 @@ setup(
|
|||||||
install_requires=[
|
install_requires=[
|
||||||
# from requirements.txt
|
# from requirements.txt
|
||||||
'ccxt>=2.6.26',
|
'ccxt>=2.6.26',
|
||||||
'SQLAlchemy',
|
'SQLAlchemy>=2.0.6',
|
||||||
'python-telegram-bot>=13.4',
|
'python-telegram-bot>=13.4',
|
||||||
'arrow>=0.17.0',
|
'arrow>=0.17.0',
|
||||||
'cachetools',
|
'cachetools',
|
||||||
|
@ -119,6 +119,7 @@ def make_unfiltered_dataframe(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
freqai.dk.pair = "ADA/BTC"
|
freqai.dk.pair = "ADA/BTC"
|
||||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
||||||
@ -152,6 +153,7 @@ def make_data_dictionary(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
freqai.dk.pair = "ADA/BTC"
|
freqai.dk.pair = "ADA/BTC"
|
||||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
||||||
|
@ -19,6 +19,7 @@ def test_update_historic_data(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||||
|
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
@ -41,6 +42,7 @@ def test_load_all_pairs_histories(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
@ -60,6 +62,7 @@ def test_get_base_and_corr_dataframes(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
||||||
@ -87,6 +90,7 @@ def test_use_strategy_to_populate_indicators(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
||||||
@ -103,8 +107,9 @@ def test_get_timerange_from_live_historic_predictions(mocker, freqai_conf):
|
|||||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = False
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = False
|
||||||
timerange = TimeRange.parse_timerange("20180126-20180130")
|
timerange = TimeRange.parse_timerange("20180126-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
sub_timerange = TimeRange.parse_timerange("20180128-20180130")
|
sub_timerange = TimeRange.parse_timerange("20180128-20180130")
|
||||||
|
@ -180,6 +180,7 @@ def test_get_full_model_path(mocker, freqai_conf, model):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
|||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.can_short = can_short
|
freqai.can_short = can_short
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
freqai.dk.set_paths('ADA/BTC', 10000)
|
freqai.dk.set_paths('ADA/BTC', 10000)
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
@ -135,6 +136,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
@ -178,6 +180,7 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
@ -371,6 +374,9 @@ def test_backtesting_fit_live_predictions(mocker, freqai_conf, caplog):
|
|||||||
sub_timerange = TimeRange.parse_timerange("20180129-20180130")
|
sub_timerange = TimeRange.parse_timerange("20180129-20180130")
|
||||||
corr_df, base_df = freqai.dd.get_base_and_corr_dataframes(sub_timerange, "LTC/BTC", freqai.dk)
|
corr_df, base_df = freqai.dd.get_base_and_corr_dataframes(sub_timerange, "LTC/BTC", freqai.dk)
|
||||||
df = freqai.dk.use_strategy_to_populate_indicators(strategy, corr_df, base_df, "LTC/BTC")
|
df = freqai.dk.use_strategy_to_populate_indicators(strategy, corr_df, base_df, "LTC/BTC")
|
||||||
|
df = strategy.set_freqai_targets(df.copy(), metadata={"pair": "LTC/BTC"})
|
||||||
|
df = freqai.dk.remove_special_chars_from_feature_names(df)
|
||||||
|
freqai.dk.get_unique_classes_from_labels(df)
|
||||||
freqai.dk.pair = "ADA/BTC"
|
freqai.dk.pair = "ADA/BTC"
|
||||||
freqai.dk.full_df = df.fillna(0)
|
freqai.dk.full_df = df.fillna(0)
|
||||||
freqai.dk.full_df
|
freqai.dk.full_df
|
||||||
@ -394,6 +400,7 @@ def test_principal_component_analysis(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
@ -425,10 +432,12 @@ def test_plot_feature_importance(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
freqai.dd.pair_dict = MagicMock()
|
freqai.dd.pair_dict = {"ADA/BTC": {"model_filename": "fake_name",
|
||||||
|
"trained_timestamp": 1, "data_path": "", "extras": {}}}
|
||||||
|
|
||||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
new_timerange = TimeRange.parse_timerange("20180120-20180130")
|
new_timerange = TimeRange.parse_timerange("20180120-20180130")
|
||||||
|
@ -986,7 +986,8 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
|
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
|
||||||
|
return_value=expected_result)
|
||||||
PairLocks.timeframe = default_conf['timeframe']
|
PairLocks.timeframe = default_conf['timeframe']
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
assert strategy.stoploss == -0.05
|
assert strategy.stoploss == -0.05
|
||||||
@ -1005,11 +1006,13 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
|
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
|
||||||
|
return_value=expected_result)
|
||||||
with pytest.raises(OperationalException, match="Invalid parameter file provided."):
|
with pytest.raises(OperationalException, match="Invalid parameter file provided."):
|
||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
mocker.patch('freqtrade.strategy.hyper.json_load', MagicMock(side_effect=ValueError()))
|
mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
|
||||||
|
MagicMock(side_effect=ValueError()))
|
||||||
|
|
||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
assert log_has("Invalid parameter file format.", caplog)
|
assert log_has("Invalid parameter file format.", caplog)
|
||||||
|
@ -2955,6 +2955,9 @@ def test_manage_open_orders_exit_usercustom(
|
|||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
assert freqtrade.strategy.check_exit_timeout.call_count == 1
|
assert freqtrade.strategy.check_exit_timeout.call_count == 1
|
||||||
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
assert freqtrade.strategy.check_entry_timeout.call_count == 0
|
||||||
|
trade = Trade.session.scalars(select(Trade)).first()
|
||||||
|
# cancelling didn't succeed - order-id remains open.
|
||||||
|
assert trade.open_order_id is not None
|
||||||
|
|
||||||
# 2nd canceled trade - Fail execute exit
|
# 2nd canceled trade - Fail execute exit
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
@ -3465,6 +3468,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
|
|||||||
|
|
||||||
# TODO: should not be magicmock
|
# TODO: should not be magicmock
|
||||||
trade = MagicMock()
|
trade = MagicMock()
|
||||||
|
trade.open_order_id = '125'
|
||||||
reason = CANCEL_REASON['TIMEOUT']
|
reason = CANCEL_REASON['TIMEOUT']
|
||||||
order = {'remaining': 1,
|
order = {'remaining': 1,
|
||||||
'id': '125',
|
'id': '125',
|
||||||
@ -3472,6 +3476,10 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
|
|||||||
'status': "open"}
|
'status': "open"}
|
||||||
assert not freqtrade.handle_cancel_exit(trade, order, reason)
|
assert not freqtrade.handle_cancel_exit(trade, order, reason)
|
||||||
|
|
||||||
|
# mocker.patch(f'{EXMS}.cancel_order_with_result', return_value=order)
|
||||||
|
# assert not freqtrade.handle_cancel_exit(trade, order, reason)
|
||||||
|
# assert trade.open_order_id == '125'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_short, open_rate, amt", [
|
@pytest.mark.parametrize("is_short, open_rate, amt", [
|
||||||
(False, 2.0, 30.0),
|
(False, 2.0, 30.0),
|
||||||
|
Loading…
Reference in New Issue
Block a user