update
This commit is contained in:
parent
06fb2e8ead
commit
e485c4b119
@ -64,14 +64,7 @@
|
||||
},
|
||||
"use_exit_signal": true,
|
||||
"exit_profit_only": false,
|
||||
"ignore_roi_if_entry_signal": false,
|
||||
"stoploss": -0.05,
|
||||
"minimal_roi": {
|
||||
"0": 0.06,
|
||||
"30": 0.04,
|
||||
"60": 0.02,
|
||||
"120": 0
|
||||
},
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": 150,
|
||||
"max_open_trades": 4,
|
||||
|
||||
@ -4,12 +4,12 @@
|
||||
"margin_mode": "isolated",
|
||||
"max_open_trades": 5,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": 100,
|
||||
"stake_amount": 75,
|
||||
"tradable_balance_ratio": 1,
|
||||
"fiat_display_currency": "USD",
|
||||
"dry_run": true,
|
||||
"timeframe": "3m",
|
||||
"dry_run_wallet": 1000,
|
||||
"dry_run_wallet": 2000,
|
||||
"cancel_open_orders_on_exit": true,
|
||||
"stoploss": -0.05,
|
||||
"max_entry_position_adjustment": 3,
|
||||
|
||||
44
freqtrade/templates/aa.json
Normal file
44
freqtrade/templates/aa.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"strategy_name": "FreqaiPrimer",
|
||||
"params": {
|
||||
"stoploss": {
|
||||
"stoploss": -0.05
|
||||
},
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 5
|
||||
},
|
||||
"buy": {
|
||||
"ADD_POSITION_THRESHOLD": -0.046,
|
||||
"BUY_THRESHOLD_MAX": -0.01,
|
||||
"BUY_THRESHOLD_MIN": -0.026,
|
||||
"COOLDOWN_PERIOD_MINUTES": 9,
|
||||
"MAX_ENTRY_POSITION_ADJUSTMENT": 3
|
||||
},
|
||||
"sell": {
|
||||
"EXIT_POSITION_RATIO": 0.572,
|
||||
"HOLD_TIME_EXTENSION": 1.975,
|
||||
"HOLD_TIME_REDUCTION": 0.832,
|
||||
"MAX_HOLD_TIME_MINUTES": 101,
|
||||
"SELL_THRESHOLD_MAX": 0.085,
|
||||
"SELL_THRESHOLD_MIN": 0.007,
|
||||
"SLOPE_THRESHOLD": 0.0,
|
||||
"TRAILING_STOP_DISTANCE": 0.005,
|
||||
"TRAILING_STOP_START": 0.048
|
||||
},
|
||||
"protection": {},
|
||||
"roi": {
|
||||
"0": 0.128,
|
||||
"12": 0.038,
|
||||
"36": 0.023,
|
||||
"53": 0
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.09,
|
||||
"trailing_stop_positive_offset": 0.16499999999999998,
|
||||
"trailing_only_offset_is_reached": false
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2025-06-27 03:30:20.329593+00:00"
|
||||
}
|
||||
727
freqtrade/templates/aa.py
Normal file
727
freqtrade/templates/aa.py
Normal file
@ -0,0 +1,727 @@
|
||||
import logging
|
||||
import numpy as np
|
||||
import datetime
|
||||
import os
|
||||
import json
|
||||
import glob
|
||||
from functools import reduce
|
||||
from freqtrade.persistence import Trade
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
import pandas as pd
|
||||
from typing import Dict
|
||||
from freqtrade.strategy import (DecimalParameter, IStrategy, IntParameter)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class FreqaiPrimer(IStrategy):
|
||||
"""
|
||||
优化后的 FreqAI 动态阈值交易策略
|
||||
- 主时间框架:3m
|
||||
- FreqAI 时间框架:3m, 15m, 1h
|
||||
- 动态最大持仓时间(30-360 分钟),基于市场趋势和利润率
|
||||
- 改进趋势判定,移除模拟 4h K线,使用 1h 线性回归和 3m 短期趋势
|
||||
- 新增 MACD 和 Stochastic 指标,增强特征工程
|
||||
- 修改:使用 FreqAI 优化 stake_amount
|
||||
"""
|
||||
|
||||
# --- 🧪 Hyperopt Parameters ---
|
||||
ROI_T0 = DecimalParameter(0.01, 0.05, default=0.02, space='roi', optimize=True)
|
||||
ROI_T1 = DecimalParameter(0.005, 0.02, default=0.01, space='roi', optimize=True)
|
||||
ROI_T2 = DecimalParameter(0.0, 0.01, default=0.0, space='roi', optimize=True)
|
||||
|
||||
TRAILING_STOP_START = DecimalParameter(0.01, 0.05, default=0.03, space='sell', optimize=True)
|
||||
TRAILING_STOP_DISTANCE = DecimalParameter(0.005, 0.02, default=0.01, space='sell', optimize=True)
|
||||
|
||||
BUY_THRESHOLD_MIN = DecimalParameter(-0.1, -0.01, default=-0.05, space='buy', optimize=True)
|
||||
BUY_THRESHOLD_MAX = DecimalParameter(-0.02, -0.001, default=-0.005, space='buy', optimize=True)
|
||||
SELL_THRESHOLD_MIN = DecimalParameter(0.001, 0.02, default=0.005, space='sell', optimize=True)
|
||||
SELL_THRESHOLD_MAX = DecimalParameter(0.02, 0.1, default=0.05, space='sell', optimize=True)
|
||||
|
||||
ADD_POSITION_THRESHOLD = DecimalParameter(-0.05, -0.01, default=-0.02, space='buy', optimize=True)
|
||||
EXIT_POSITION_RATIO = DecimalParameter(0.2, 0.7, default=0.5, space='sell', optimize=True)
|
||||
COOLDOWN_PERIOD_MINUTES = IntParameter(1, 10, default=5, space='buy', optimize=True)
|
||||
MAX_ENTRY_POSITION_ADJUSTMENT = IntParameter(1, 3, default=2, space='buy', optimize=True)
|
||||
|
||||
# 新增:持仓时间和趋势斜率参数
|
||||
MAX_HOLD_TIME_MINUTES = IntParameter(30, 360, default=120, space='sell', optimize=True)
|
||||
HOLD_TIME_EXTENSION = DecimalParameter(1.0, 2.0, default=1.5, space='sell', optimize=True) # 牛市延长因子
|
||||
HOLD_TIME_REDUCTION = DecimalParameter(0.5, 1.0, default=0.7, space='sell', optimize=True) # 熊市缩短因子
|
||||
SLOPE_THRESHOLD = DecimalParameter(0.00005, 0.001, default=0.0001, space='sell', optimize=True) # 趋势斜率阈值
|
||||
|
||||
# --- 🛠️ 固定配置参数 ---
|
||||
minimal_roi = {
|
||||
"0": ROI_T0.value,
|
||||
"30": ROI_T1.value,
|
||||
"60": ROI_T2.value
|
||||
}
|
||||
|
||||
stoploss = -0.015
|
||||
timeframe = "3m"
|
||||
use_custom_stoploss = True
|
||||
position_adjustment_enable = True
|
||||
startup_candle_count = 200
|
||||
|
||||
plot_config = {
|
||||
"main_plot": {
|
||||
"ema200": {"color": "blue"},
|
||||
"bb_upperband": {"color": "gray"},
|
||||
"bb_lowerband": {"color": "gray"},
|
||||
"bb_middleband": {"color": "gray"}
|
||||
},
|
||||
"subplots": {
|
||||
"Signals": {
|
||||
"enter_long": {"color": "green"},
|
||||
"exit_long": {"color": "red"}
|
||||
},
|
||||
"Price-Value Divergence": {
|
||||
"&-price_value_divergence": {"color": "purple"}
|
||||
},
|
||||
"Volume Z-Score": {
|
||||
"volume_z_score": {"color": "orange"}
|
||||
},
|
||||
"RSI": {
|
||||
"rsi": {"color": "cyan"}
|
||||
},
|
||||
"MACD": {
|
||||
"macd": {"color": "blue"},
|
||||
"macdsignal": {"color": "orange"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freqai_info = {
|
||||
"identifier": "test58",
|
||||
"model": "LightGBMRegressor",
|
||||
"feature_parameters": {
|
||||
"include_timeframes": ["3m", "15m", "1h"],
|
||||
"label_period_candles": 12,
|
||||
"include_shifted_candles": 3,
|
||||
"include_corr_pairs": [],
|
||||
"include_returned_api_pairs": False
|
||||
},
|
||||
"data_split_parameters": {
|
||||
"test_size": 0.2,
|
||||
"shuffle": False
|
||||
},
|
||||
"model_training_parameters": {
|
||||
"n_estimators": 200,
|
||||
"learning_rate": 0.05,
|
||||
"num_leaves": 31,
|
||||
"verbose": -1
|
||||
},
|
||||
"fit_live_predictions_candles": 100,
|
||||
"live_retrain_candles": 100,
|
||||
"target_parameters": { # 新增:支持多目标回归
|
||||
"targets": ["&-price_value_divergence", "&-stake_amount"]
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, config: dict, *args, **kwargs):
|
||||
super().__init__(config, *args, **kwargs)
|
||||
logger.debug("✅ 策略已初始化,日志级别设置为 DEBUG")
|
||||
self.trailing_stop_enabled = False
|
||||
self.pair_stats = {}
|
||||
self.stats_logged = False
|
||||
self.fit_live_predictions_candles = self.freqai_info.get("fit_live_predictions_candles", 100)
|
||||
self.last_entry_time = {}
|
||||
self.initial_stake = {} # 新增:记录每笔交易的首次入场金额
|
||||
|
||||
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
|
||||
pair = metadata.get('pair', 'Unknown')
|
||||
|
||||
# 现有指标
|
||||
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
|
||||
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
|
||||
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
|
||||
|
||||
real = ta.TYPPRICE(dataframe)
|
||||
upperband, middleband, lowerband = ta.BBANDS(real, timeperiod=period, nbdevup=2.0, nbdevdn=2.0)
|
||||
dataframe["bb_lowerband-period"] = lowerband
|
||||
dataframe["bb_upperband-period"] = upperband
|
||||
dataframe["bb_middleband-period"] = middleband
|
||||
dataframe["%-bb_width-period"] = (dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]) / dataframe["bb_middleband-period"]
|
||||
|
||||
dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period)
|
||||
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
|
||||
dataframe["%-relative_volume-period"] = dataframe["volume"] / dataframe["volume"].rolling(period).mean()
|
||||
|
||||
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
|
||||
dataframe["%-price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
|
||||
|
||||
# 新增指标
|
||||
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
|
||||
dataframe["%-macd-period"] = macd['macd']
|
||||
dataframe["%-macdsignal-period"] = macd['macdsignal']
|
||||
dataframe["%-macdhist-period"] = macd['macdhist']
|
||||
|
||||
stoch = ta.STOCH(dataframe, fastk_period=14, slowk_period=3, slowd_period=3)
|
||||
dataframe["%-slowk-period"] = stoch['slowk']
|
||||
dataframe["%-slowd-period"] = stoch['slowd']
|
||||
|
||||
columns_to_clean = [
|
||||
"%-rsi-period", "%-mfi-period", "%-sma-period", "%-ema-period", "%-adx-period",
|
||||
"bb_lowerband-period", "bb_middleband-period", "bb_upperband-period",
|
||||
"%-bb_width-period", "%-relative_volume-period", "%-price_value_divergence",
|
||||
"%-macd-period", "%-macdsignal-period", "%-macdhist-period",
|
||||
"%-slowk-period", "%-slowd-period"
|
||||
]
|
||||
for col in columns_to_clean:
|
||||
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
|
||||
logger.debug(f"[{pair}] 特征工程完成,列:{list(dataframe.columns)}")
|
||||
return dataframe
|
||||
|
||||
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
||||
pair = metadata.get('pair', 'Unknown')
|
||||
if len(dataframe) < 200:
|
||||
logger.warning(f"[{pair}] 数据量不足({len(dataframe)}根K线),需要至少200根K线进行训练")
|
||||
return dataframe
|
||||
|
||||
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
|
||||
dataframe["&-price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
|
||||
|
||||
dataframe["volume_mean_20"] = dataframe["volume"].rolling(20).mean()
|
||||
dataframe["volume_std_20"] = dataframe["volume"].rolling(20).std()
|
||||
dataframe["volume_z_score"] = (dataframe["volume"] - dataframe["volume_mean_20"]) / dataframe["volume_std_20"]
|
||||
|
||||
# 新增:计算 FreqAI 目标 stake_amount
|
||||
# 基于市场趋势、价格波动性和仓位调整逻辑动态计算
|
||||
market_trend = self.get_market_trend(dataframe=dataframe, metadata=metadata)
|
||||
base_stake = 60 # 默认基础下单金额
|
||||
stake_multiplier = 1.0
|
||||
if market_trend == 'bull':
|
||||
stake_multiplier = 1.2 # 牛市增加下单金额
|
||||
elif market_trend == 'bear':
|
||||
stake_multiplier = 0.8 # 熊市减少下单金额
|
||||
|
||||
# 计算价格波动性(基于近期收盘价的标准差)
|
||||
price_volatility = dataframe["close"].pct_change().rolling(20).std().iloc[-1]
|
||||
if np.isnan(price_volatility):
|
||||
price_volatility = 0.01 # 防止除零
|
||||
volatility_factor = 1.0 + min(price_volatility * 10, 0.5) # 波动性调整因子,限制最大增幅
|
||||
|
||||
# 计算 &-stake_amount
|
||||
dataframe["&-stake_amount"] = base_stake * stake_multiplier * volatility_factor
|
||||
# 限制 stake_amount 在合理范围内(例如 7 到 200)
|
||||
dataframe["&-stake_amount"] = dataframe["&-stake_amount"].clip(lower=7, upper=200)
|
||||
|
||||
# 数据清理
|
||||
dataframe["&-price_value_divergence"] = dataframe["&-price_value_divergence"].replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
dataframe["volume_z_score"] = dataframe["volume_z_score"].replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
dataframe["&-stake_amount"] = dataframe["&-stake_amount"].replace([np.inf, -np.inf], 0).ffill().fillna(base_stake)
|
||||
|
||||
logger.debug(f"[{pair}] 设置 FreqAI 目标 - "
|
||||
f"&-price_value_divergence={dataframe['&-price_value_divergence'].iloc[-1]:.6f}, "
|
||||
f"&-stake_amount={dataframe['&-stake_amount'].iloc[-1]:.2f}, "
|
||||
f"market_trend={market_trend}, volatility_factor={volatility_factor:.2f}")
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
pair = metadata.get('pair', 'Unknown')
|
||||
dataframe = self.freqai.start(dataframe, metadata, self)
|
||||
logger.info(f"[{pair}] 当前可用列(调用FreqAI前):{list(dataframe.columns)}")
|
||||
|
||||
# 基本指标
|
||||
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
|
||||
dataframe["price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
|
||||
|
||||
if not hasattr(self, 'freqai') or self.freqai is None:
|
||||
logger.error(f"[{pair}] FreqAI 未初始化,请确保回测命令中启用了 --freqai")
|
||||
dataframe["&-price_value_divergence"] = dataframe["price_value_divergence"]
|
||||
dataframe["&-stake_amount"] = 60 # 回退默认值
|
||||
else:
|
||||
logger.debug(f"self.freqai 类型:{type(self.freqai)}")
|
||||
|
||||
if "&-price_value_divergence" not in dataframe.columns:
|
||||
logger.warning(f"[{pair}] 回归模型未生成 &-price_value_divergence,回退到规则计算")
|
||||
dataframe["&-price_value_divergence"] = dataframe["price_value_divergence"]
|
||||
if "&-stake_amount" not in dataframe.columns:
|
||||
logger.warning(f"[{pair}] 回归模型未生成 &-stake_amount,回退到默认值 60")
|
||||
dataframe["&-stake_amount"] = 60
|
||||
|
||||
upperband, middleband, lowerband = ta.BBANDS(dataframe["close"], timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
|
||||
dataframe["bb_upperband"] = upperband
|
||||
dataframe["bb_middleband"] = middleband
|
||||
dataframe["bb_lowerband"] = lowerband
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
dataframe["volume_mean_20"] = dataframe["volume"].rolling(20).mean()
|
||||
dataframe["volume_std_20"] = dataframe["volume"].rolling(20).std()
|
||||
dataframe["volume_z_score"] = (dataframe["volume"] - dataframe["volume_mean_20"]) / dataframe["volume_std_20"]
|
||||
|
||||
# 新增 MACD 和 Stochastic
|
||||
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
|
||||
dataframe["macd"] = macd['macd']
|
||||
dataframe["macdsignal"] = macd['macdsignal']
|
||||
dataframe["macdhist"] = macd['macdhist']
|
||||
|
||||
stoch = ta.STOCH(dataframe, fastk_period=14, slowk_period=3, slowd_period=3)
|
||||
dataframe["slowk"] = stoch['slowk']
|
||||
dataframe["slowd"] = stoch['slowd']
|
||||
|
||||
# 数据清理
|
||||
for col in ["ema200", "bb_upperband", "bb_middleband", "bb_lowerband", "rsi", "volume_z_score",
|
||||
"&-price_value_divergence", "price_value_divergence", "macd", "macdsignal", "macdhist",
|
||||
"slowk", "slowd", "&-stake_amount"]:
|
||||
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
|
||||
logger.debug(f"[{pair}] 最新数据 - close:{dataframe['close'].iloc[-1]:.6f}, "
|
||||
f"rsi:{dataframe['rsi'].iloc[-1]:.2f}, "
|
||||
f"&-price_value_divergence:{dataframe['&-price_value_divergence'].iloc[-1]:.6f}, "
|
||||
f"volume_z_score:{dataframe['volume_z_score'].iloc[-1]:.2f}, "
|
||||
f"bb_lowerband:{dataframe['bb_lowerband'].iloc[-1]:.6f}, "
|
||||
f"macd:{dataframe['macd'].iloc[-1]:.6f}, "
|
||||
f"slowk:{dataframe['slowk'].iloc[-1]:.2f}, "
|
||||
f"&-stake_amount:{dataframe['&-stake_amount'].iloc[-1]:.2f}")
|
||||
|
||||
# 获取 labels_mean 和 labels_std
|
||||
labels_mean = None
|
||||
labels_std = None
|
||||
try:
|
||||
model_base_dir = os.path.join(self.config["user_data_dir"], "models", self.freqai_info["identifier"])
|
||||
pair_base = pair.split('/')[0] if '/' in pair else pair
|
||||
sub_dirs = glob.glob(os.path.join(model_base_dir, f"sub-train-{pair_base}_*"))
|
||||
|
||||
if not sub_dirs:
|
||||
logger.warning(f"[{pair}] 未找到任何子目录:{model_base_dir}/sub-train-{pair_base}_*")
|
||||
else:
|
||||
latest_sub_dir = max(sub_dirs, key=lambda x: int(x.split('_')[-1]))
|
||||
pair_base_lower = pair_base.lower()
|
||||
timestamp = latest_sub_dir.split('_')[-1]
|
||||
metadata_file = os.path.join(latest_sub_dir, f"cb_{pair_base_lower}_{timestamp}_metadata.json")
|
||||
|
||||
if os.path.exists(metadata_file):
|
||||
with open(metadata_file, "r") as f:
|
||||
metadata_json = json.load(f)
|
||||
labels_mean = metadata_json["labels_mean"]
|
||||
labels_std = metadata_json["labels_std"]
|
||||
logger.info(f"[{pair}] 从最新子目录 {latest_sub_dir} 读取 labels_mean:{labels_mean}, labels_std:{labels_std}")
|
||||
else:
|
||||
logger.warning(f"[{pair}] 最新的 metadata.json 文件 {metadata_file} 不存在")
|
||||
except Exception as e:
|
||||
logger.warning(f"[{pair}] 无法从子目录读取 labels_mean 和 labels_std:{e},重新计算")
|
||||
|
||||
if labels_mean is None or labels_std is None:
|
||||
logger.warning(f"[{pair}] 无法获取 labels_mean 和 labels_std,重新计算")
|
||||
dataframe["&-price_value_divergence_actual"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"]
|
||||
dataframe["&-price_value_divergence_actual"] = dataframe["&-price_value_divergence_actual"].replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
recent_data = dataframe["&-price_value_divergence_actual"].tail(self.fit_live_predictions_candles)
|
||||
labels_mean = {'&-price_value_divergence': recent_data.mean(), '&-stake_amount': 60}
|
||||
labels_std = {'&-price_value_divergence': recent_data.std(), '&-stake_amount': 10}
|
||||
if np.isnan(labels_std['&-price_value_divergence']) or labels_std['&-price_value_divergence'] == 0:
|
||||
labels_std['&-price_value_divergence'] = 0.01
|
||||
logger.warning(f"[{pair}] &-price_value_divergence labels_std 计算异常,使用默认值 0.01")
|
||||
if np.isnan(labels_std['&-stake_amount']) or labels_std['&-stake_amount'] == 0:
|
||||
labels_std['&-stake_amount'] = 10
|
||||
logger.warning(f"[{pair}] &-stake_amount labels_std 计算异常,使用默认值 10")
|
||||
|
||||
# 动态调整买卖阈值
|
||||
market_trend = self.get_market_trend(dataframe=dataframe, metadata=metadata)
|
||||
k_buy = 1.0
|
||||
k_sell = 1.2
|
||||
if market_trend == 'bull':
|
||||
k_buy = 0.8
|
||||
k_sell = 1.0
|
||||
elif market_trend == 'bear':
|
||||
k_buy = 1.2
|
||||
k_sell = 1.5
|
||||
else:
|
||||
k_buy = 1.0
|
||||
k_sell = 1.2
|
||||
|
||||
if labels_mean['&-price_value_divergence'] > 0.015:
|
||||
k_sell += 0.5
|
||||
|
||||
self.buy_threshold = labels_mean['&-price_value_divergence'] - k_buy * labels_std['&-price_value_divergence']
|
||||
self.sell_threshold = labels_mean['&-price_value_divergence'] + k_sell * labels_std['&-price_value_divergence']
|
||||
|
||||
self.buy_threshold = max(self.buy_threshold, self.BUY_THRESHOLD_MIN.value)
|
||||
self.buy_threshold = min(self.buy_threshold, self.BUY_THRESHOLD_MAX.value)
|
||||
self.sell_threshold = min(self.sell_threshold, self.SELL_THRESHOLD_MAX.value)
|
||||
self.sell_threshold = max(self.sell_threshold, self.SELL_THRESHOLD_MIN.value)
|
||||
|
||||
logger.info(f"[{pair}] 市场趋势:{market_trend}, labels_mean:{labels_mean}, labels_std:{labels_std}")
|
||||
logger.info(f"[{pair}] k_buy:{k_buy:.2f}, k_sell:{k_sell:.2f}")
|
||||
logger.info(f"[{pair}] 动态买入阈值:{self.buy_threshold:.4f}, 卖出阈值:{self.sell_threshold:.4f}")
|
||||
|
||||
if not self.stats_logged:
|
||||
logger.info("===== 所有币对的 labels_mean 和 labels_std 汇总 =====")
|
||||
for p, stats in self.pair_stats.items():
|
||||
logger.info(f"[{p}] labels_mean:{stats['labels_mean']}, labels_std:{stats['labels_std']}")
|
||||
logger.info("==============================================")
|
||||
self.stats_logged = True
|
||||
|
||||
return dataframe
|
||||
|
||||
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
|
||||
return {
|
||||
"0": params.get("roi_t0", self.ROI_T0.value),
|
||||
"30": params.get("roi_t1", self.ROI_T1.value),
|
||||
"60": params.get("roi_t2", self.ROI_T2.value)
|
||||
}
|
||||
|
||||
def roi_space(self):
|
||||
return [self.ROI_T0, self.ROI_T1, self.ROI_T2]
|
||||
|
||||
def trailing_space(self):
|
||||
return [self.TRAILING_STOP_START, self.TRAILING_STOP_DISTANCE]
|
||||
|
||||
def leverage_space(self):
|
||||
return [
|
||||
self.ADD_POSITION_THRESHOLD,
|
||||
self.EXIT_POSITION_RATIO,
|
||||
self.COOLDOWN_PERIOD_MINUTES,
|
||||
self.MAX_ENTRY_POSITION_ADJUSTMENT
|
||||
]
|
||||
|
||||
def buy_space(self):
|
||||
return [
|
||||
self.BUY_THRESHOLD_MIN,
|
||||
self.BUY_THRESHOLD_MAX,
|
||||
self.ADD_POSITION_THRESHOLD,
|
||||
self.COOLDOWN_PERIOD_MINUTES,
|
||||
self.MAX_ENTRY_POSITION_ADJUSTMENT
|
||||
]
|
||||
|
||||
def sell_space(self):
|
||||
return [
|
||||
self.SELL_THRESHOLD_MIN,
|
||||
self.SELL_THRESHOLD_MAX,
|
||||
self.EXIT_POSITION_RATIO,
|
||||
self.MAX_HOLD_TIME_MINUTES,
|
||||
self.HOLD_TIME_EXTENSION,
|
||||
self.HOLD_TIME_REDUCTION,
|
||||
self.SLOPE_THRESHOLD
|
||||
]
|
||||
|
||||
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||
proposed_stake: float, min_stake: float | None, max_stake: float,
|
||||
leverage: float, entry_tag: str | None, side: str, **kwargs) -> float:
|
||||
dataframe = self.dp.get_pair_dataframe(pair, self.timeframe)
|
||||
dataframe = self.populate_indicators(dataframe, {'pair': pair})
|
||||
stake_amount = dataframe["&-stake_amount"].iloc[-1] if "&-stake_amount" in dataframe.columns else 60
|
||||
stake_amount = max(min(stake_amount, max_stake), min_stake or 7) # 确保在 min_stake 和 max_stake 范围内
|
||||
logger.debug(f"[{pair}] 使用 FreqAI 预测的 stake_amount: {stake_amount:.2f}")
|
||||
return stake_amount
|
||||
|
||||
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
||||
current_rate: float, current_profit: float,
|
||||
min_stake: float | None, max_stake: float,
|
||||
current_entry_rate: float, current_exit_rate: float,
|
||||
current_entry_profit: float, current_exit_profit: float,
|
||||
**kwargs) -> float | None | tuple[float | None, str | None]:
|
||||
"""
|
||||
动态调整仓位:支持加仓、减仓、追踪止损和最大持仓时间限制
|
||||
- 加仓逻辑:第一次加仓为首次入场金额的2倍,第二次4倍,第三次8倍
|
||||
- 增强日志,记录首次入场金额和加仓计算
|
||||
"""
|
||||
pair = trade.pair
|
||||
dataframe = self.dp.get_pair_dataframe(pair, self.timeframe)
|
||||
dataframe = self.populate_indicators(dataframe, {'pair': pair})
|
||||
stake_amount_value = dataframe["&-stake_amount"].iloc[-1] if "&-stake_amount" in dataframe.columns else 60
|
||||
initial_stake_amount = self.initial_stake.get(pair, stake_amount_value)
|
||||
market_trend = self.get_market_trend(dataframe=dataframe, metadata={'pair': pair})
|
||||
hold_time = (current_time - trade.open_date_utc).total_seconds() / 60
|
||||
profit_ratio = (current_rate - trade.open_rate) / trade.open_rate
|
||||
|
||||
# 获取首次入场金额
|
||||
initial_stake_amount = self.initial_stake.get(pair, stake_amount_value)
|
||||
logger.debug(f"{pair} 首次入场金额: {initial_stake_amount:.2f}, 当前持仓金额: {trade.stake_amount:.2f}, "
|
||||
f"加仓次数: {trade.nr_of_successful_entries - 1}")
|
||||
|
||||
# 动态最大持仓时间
|
||||
max_hold_time = self.MAX_HOLD_TIME_MINUTES.value
|
||||
if market_trend == 'bull':
|
||||
max_hold_time *= self.HOLD_TIME_EXTENSION.value
|
||||
elif market_trend == 'bear':
|
||||
max_hold_time *= self.HOLD_TIME_REDUCTION.value
|
||||
if profit_ratio > 0.02:
|
||||
max_hold_time *= 1.2
|
||||
elif profit_ratio < -0.02:
|
||||
max_hold_time *= 0.8
|
||||
max_hold_time = int(max_hold_time)
|
||||
|
||||
# 加仓逻辑
|
||||
max_entry_adjustments = self.MAX_ENTRY_POSITION_ADJUSTMENT.value
|
||||
if trade.nr_of_successful_entries <= max_entry_adjustments + 1:
|
||||
add_position_threshold = self.ADD_POSITION_THRESHOLD.value
|
||||
if profit_ratio <= add_position_threshold and hold_time > 5 and market_trend in ['bear', 'sideways']:
|
||||
# 根据加仓次数设置倍数
|
||||
add_count = trade.nr_of_successful_entries - 1 # 当前加仓次数(0, 1, 2)
|
||||
if add_count < 3: # 限制最多3次加仓
|
||||
multiplier = 2 ** (add_count + 1) # 2^1=2, 2^2=4, 2^3=8
|
||||
add_amount = initial_stake_amount * multiplier
|
||||
logger.debug(f"{pair} 加仓计算: 第 {add_count + 1} 次加仓,倍数={multiplier}, "
|
||||
f"金额 = {initial_stake_amount:.2f} * {multiplier} = {add_amount:.2f}")
|
||||
|
||||
# 检查最小和最大下单金额
|
||||
if min_stake is not None and add_amount < min_stake:
|
||||
logger.warning(f"{pair} 加仓金额 {add_amount:.2f} 低于最小下单金额 {min_stake:.2f},取消加仓")
|
||||
return (None, f"Add amount {add_amount:.2f} below min_stake {min_stake:.2f}")
|
||||
if add_amount > max_stake:
|
||||
logger.warning(f"{pair} 加仓金额 {add_amount:.2f} 超出最大可用金额 {max_stake:.2f},调整为 {max_stake:.2f}")
|
||||
add_amount = max_stake
|
||||
logger.info(f"{pair} 价格下跌 {profit_ratio*100:.2f}%,触发第 {add_count + 1} 次加仓 {add_amount:.2f}")
|
||||
return (add_amount, f"Price dropped {profit_ratio*100:.2f}%, add {add_amount:.2f}")
|
||||
|
||||
# 减仓逻辑
|
||||
exit_position_ratio = self.EXIT_POSITION_RATIO.value
|
||||
if profit_ratio >= 0.03:
|
||||
if market_trend == 'bull':
|
||||
reduce_amount = -exit_position_ratio * 0.6 * trade.stake_amount
|
||||
logger.info(f"{pair} 牛市,利润 {profit_ratio*100:.2f}%,减仓 {abs(reduce_amount):.2f}")
|
||||
return (reduce_amount, f"Bull market, profit {profit_ratio*100:.2f}%")
|
||||
else:
|
||||
reduce_amount = -exit_position_ratio * trade.stake_amount
|
||||
logger.info(f"{pair} 利润 {profit_ratio*100:.2f}%,减仓 {abs(reduce_amount):.2f}")
|
||||
return (reduce_amount, f"Profit {profit_ratio*100:.2f}%")
|
||||
elif profit_ratio >= 0.05:
|
||||
reduce_amount = -exit_position_ratio * 1.4 * trade.stake_amount
|
||||
logger.info(f"{pair} 利润 {profit_ratio*100:.2f}%,减仓 {abs(reduce_amount):.2f}")
|
||||
return (reduce_amount, f"Profit {profit_ratio*100:.2f}%")
|
||||
|
||||
# 追踪止损逻辑
|
||||
trailing_stop_start = self.TRAILING_STOP_START.value
|
||||
trailing_stop_distance = self.TRAILING_STOP_DISTANCE.value
|
||||
if market_trend == 'bull':
|
||||
trailing_stop_distance *= 1.5
|
||||
trailing_stop_start *= 1.2
|
||||
elif market_trend == 'bear':
|
||||
trailing_stop_distance *= 0.7
|
||||
trailing_stop_start *= 0.8
|
||||
|
||||
if profit_ratio >= trailing_stop_start and not self.trailing_stop_enabled:
|
||||
self.trailing_stop_enabled = True
|
||||
trade.adjust_min_max_rates(current_rate, current_rate)
|
||||
logger.info(f"{pair} 价格上涨超过 {trailing_stop_start*100:.1f}%,启动追踪止损")
|
||||
return None
|
||||
|
||||
if self.trailing_stop_enabled:
|
||||
max_rate = trade.max_rate or current_rate
|
||||
trailing_stop_price = max_rate * (1 - trailing_stop_distance)
|
||||
if current_rate < trailing_stop_price:
|
||||
logger.info(f"{pair} 价格回落至 {trailing_stop_price:.6f},触发全部卖出")
|
||||
return (-trade.stake_amount, f"Trailing stop at {trailing_stop_price:.6f}")
|
||||
trade.adjust_min_max_rates(current_rate, trade.min_rate)
|
||||
return None
|
||||
|
||||
# 最大持仓时间限制
|
||||
if hold_time > max_hold_time:
|
||||
logger.info(f"{pair} {market_trend} 市场,持仓时间超过 {max_hold_time} 分钟,强制清仓")
|
||||
return (-trade.stake_amount, f"Max hold time ({max_hold_time} min) exceeded")
|
||||
|
||||
return None
|
||||
|
||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||
time_in_force: str, current_time: datetime, **kwargs) -> bool:
|
||||
dataframe = self.dp.get_pair_dataframe(pair, self.timeframe)
|
||||
dataframe = self.populate_indicators(dataframe, {'pair': pair})
|
||||
stake_amount_value = dataframe["&-stake_amount"].iloc[-1] if "&-stake_amount" in dataframe.columns else 60
|
||||
self.initial_stake[pair] = stake_amount_value
|
||||
market_trend = self.get_market_trend(dataframe=DataFrame(), metadata={'pair': pair})
|
||||
cooldown_period_minutes = self.COOLDOWN_PERIOD_MINUTES.value if market_trend == 'bull' else self.COOLDOWN_PERIOD_MINUTES.value // 2
|
||||
|
||||
if pair in self.last_entry_time:
|
||||
last_time = self.last_entry_time[pair]
|
||||
if (current_time - last_time).total_seconds() < cooldown_period_minutes * 60:
|
||||
logger.info(f"[{pair}] 冷却期内({cooldown_period_minutes} 分钟),跳过本次入场")
|
||||
return False
|
||||
|
||||
self.initial_stake[pair] = stake_amount_value
|
||||
self.last_entry_time[pair] = current_time
|
||||
self.trailing_stop_enabled = False
|
||||
logger.info(f"[{pair}] 确认入场,价格:{rate:.6f}, 首次入场金额:{self.initial_stake[pair]:.2f}")
|
||||
return True
|
||||
|
||||
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
|
||||
rate: float, time_in_force: str, exit_reason: str,
|
||||
current_time: datetime, **kwargs) -> bool:
|
||||
adjusted_rate = rate * (1 + 0.00125)
|
||||
logger.info(f"[{pair}] 退出交易,原因:{exit_reason}, 原始利润:{trade.calc_profit_ratio(rate):.2%}, "
|
||||
f"调整后卖出价:{adjusted_rate:.6f}")
|
||||
# 新增:清理 initial_stake 记录
|
||||
if pair in self.initial_stake:
|
||||
del self.initial_stake[pair]
|
||||
return True
|
||||
|
||||
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
|
||||
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||
adjusted_rate = proposed_rate * (1 - 0.00125)
|
||||
logger.debug(f"[{pair}] 自定义买入价:{adjusted_rate:.6f}(原价:{proposed_rate:.6f})")
|
||||
return adjusted_rate
|
||||
|
||||
def custom_exit_price(self, pair: str, trade: Trade,
|
||||
current_time: datetime, proposed_rate: float,
|
||||
current_profit: float, exit_tag: str | None, **kwargs) -> float:
|
||||
adjusted_rate = proposed_rate * (1 + 0.00125)
|
||||
logger.debug(f"[{pair}] 自定义卖出价:{adjusted_rate:.6f}(原价:{proposed_rate:.6f})")
|
||||
return adjusted_rate
|
||||
|
||||
def get_market_trend(self, dataframe: DataFrame = None, metadata: dict = None) -> str:
|
||||
"""
|
||||
判断市场趋势,基于1h或15m数据分析简单趋势
|
||||
- 确保ma120列始终生成,修复'ma120'错误
|
||||
- 添加详细日志,调试数据状态
|
||||
- 简化逻辑,降低数据要求
|
||||
"""
|
||||
pair = metadata.get('pair', 'Unknown')
|
||||
try:
|
||||
# 获取1h数据
|
||||
df = self.dp.get_pair_dataframe(pair=pair, timeframe='1h')
|
||||
timeframe = '1h'
|
||||
logger.debug(f"{pair} 1h DataFrame 初始状态: shape={df.shape if df is not None else 'None'}, "
|
||||
f"columns={df.columns.tolist() if df is not None else 'None'}, "
|
||||
f"dtypes={df.dtypes if df is not None else 'None'}")
|
||||
|
||||
# 检查1h数据有效性
|
||||
if df is None or df.empty or 'close' not in df.columns:
|
||||
logger.warning(f"{pair} 1h 数据不可用或缺少 'close' 列,尝试15m数据")
|
||||
df = self.dp.get_pair_dataframe(pair=pair, timeframe='15m')
|
||||
timeframe = '15m'
|
||||
logger.debug(f"{pair} 15m DataFrame 状态: shape={df.shape if df is not None else 'None'}, "
|
||||
f"columns={df.columns.tolist() if df is not None else 'None'}, "
|
||||
f"dtypes={df.dtypes if df is not None else 'None'}")
|
||||
if df is None or df.empty or 'close' not in df.columns:
|
||||
logger.error(f"{pair} 15m 数据也不可用,返回默认趋势:震荡市")
|
||||
return "sideways"
|
||||
|
||||
# 验证数据长度
|
||||
available_candles = len(df)
|
||||
logger.debug(f"{pair} 数据长度: {available_candles} 根K线")
|
||||
if available_candles < 3:
|
||||
logger.warning(f"{pair} 数据不足({available_candles}根K线,需至少3根),返回默认趋势:震荡市")
|
||||
return "sideways"
|
||||
|
||||
# 确保close列为数值型
|
||||
df['close'] = pd.to_numeric(df['close'], errors='coerce')
|
||||
if df['close'].isna().all():
|
||||
logger.error(f"{pair} 'close' 列全为NaN或无效,返回默认趋势:震荡市")
|
||||
return "sideways"
|
||||
logger.debug(f"{pair} close列样本: {df['close'].tail(3).tolist()}")
|
||||
|
||||
# 计算ma120
|
||||
sma_period = min(available_candles, 120 if timeframe == '1h' else 480)
|
||||
if sma_period < 3:
|
||||
sma_period = 3
|
||||
logger.warning(f"{pair} 数据量不足,使用最小SMA周期:{sma_period}")
|
||||
|
||||
try:
|
||||
df['ma120'] = df['close'].rolling(window=sma_period, min_periods=1).mean()
|
||||
except Exception as e:
|
||||
logger.error(f"{pair} rolling mean 计算失败:{e},使用close列作为回退")
|
||||
df['ma120'] = df['close'].copy()
|
||||
|
||||
# 验证ma120列
|
||||
if 'ma120' not in df.columns or df['ma120'].isna().all():
|
||||
logger.error(f"{pair} ma120 列生成失败,使用close列作为最终回退")
|
||||
df['ma120'] = df['close'].copy()
|
||||
logger.debug(f"{pair} ma120 计算后: exists={'ma120' in df.columns}, "
|
||||
f"has_NaN={df['ma120'].isna().any()}, sample={df['ma120'].tail(3).tolist()}")
|
||||
|
||||
# 提取最近数据
|
||||
last_n = df.tail(3 if timeframe == '1h' else 12) # 3根1h或12根15m ≈ 3小时
|
||||
if len(last_n) < 3:
|
||||
logger.warning(f"{pair} 最近数据不足({len(last_n)}根K线),返回默认趋势:震荡市")
|
||||
return "sideways"
|
||||
logger.debug(f"{pair} last_n 状态: shape={last_n.shape}, columns={last_n.columns.tolist()}")
|
||||
|
||||
# 计算斜率
|
||||
try:
|
||||
x = np.arange(len(last_n))
|
||||
price_slope, _ = np.polyfit(x, last_n['close'].fillna(last_n['close'].mean()), 1)
|
||||
ma_slope, _ = np.polyfit(x, last_n['ma120'].fillna(last_n['ma120'].mean()), 1)
|
||||
except Exception as e:
|
||||
logger.error(f"{pair} 斜率计算失败:{e},返回默认趋势:震荡市")
|
||||
return "sideways"
|
||||
|
||||
current_price = last_n['close'].iloc[-1]
|
||||
current_ma120 = last_n['ma120'].iloc[-1]
|
||||
price_above_ma = current_price > current_ma120
|
||||
|
||||
logger.debug(f"{pair} 趋势计算: price_slope={price_slope:.6f}, ma_slope={ma_slope:.6f}, "
|
||||
f"price_above_ma={price_above_ma}, current_price={current_price:.6f}, "
|
||||
f"current_ma120={current_ma120:.6f}")
|
||||
|
||||
# 趋势判定
|
||||
if price_slope > self.SLOPE_THRESHOLD.value and ma_slope > self.SLOPE_THRESHOLD.value and price_above_ma:
|
||||
logger.info(f"{pair} 检测到牛市")
|
||||
return "bull"
|
||||
elif price_slope < -self.SLOPE_THRESHOLD.value and ma_slope < -self.SLOPE_THRESHOLD.value and not price_above_ma:
|
||||
logger.info(f"{pair} 检测到熊市")
|
||||
return "bear"
|
||||
else:
|
||||
logger.info(f"{pair} 检测到震荡市")
|
||||
return "sideways"
|
||||
except Exception as e:
|
||||
logger.error(f"{pair} 获取市场趋势失败:{e}")
|
||||
return "sideways"
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
生成买入信号,放松条件以增加交易机会
|
||||
- 移除 MACD 条件
|
||||
- 放宽 RSI 阈值 (< 60)
|
||||
- 放宽 Volume Z-Score (> 0.5)
|
||||
- 详细日志记录每个条件的值
|
||||
"""
|
||||
pair = metadata.get('pair', 'Unknown')
|
||||
conditions = []
|
||||
|
||||
if "&-price_value_divergence" in dataframe.columns:
|
||||
cond1 = (dataframe["&-price_value_divergence"] < self.buy_threshold)
|
||||
cond2 = (dataframe["volume_z_score"] > 0.5) # 放宽到 0.5
|
||||
cond3 = (dataframe["rsi"] < 60) # 放宽到 60
|
||||
cond4 = (dataframe["close"] <= dataframe["bb_lowerband"])
|
||||
buy_condition = cond1 & cond2 & cond3 & cond4
|
||||
conditions.append(buy_condition)
|
||||
|
||||
# 详细日志
|
||||
divergence_value = dataframe['&-price_value_divergence'].iloc[-1] if not dataframe['&-price_value_divergence'].isna().all() else np.nan
|
||||
logger.debug(f"[{pair}] 买入条件检查 - "
|
||||
f"&-price_value_divergence={divergence_value:.6f} < {self.buy_threshold:.6f}: {cond1.iloc[-1]}, "
|
||||
f"volume_z_score={dataframe['volume_z_score'].iloc[-1]:.2f} > 0.5: {cond2.iloc[-1]}, "
|
||||
f"rsi={dataframe['rsi'].iloc[-1]:.2f} < 60: {cond3.iloc[-1]}, "
|
||||
f"close={dataframe['close'].iloc[-1]:.6f} <= bb_lowerband={dataframe['bb_lowerband'].iloc[-1]:.6f}: {cond4.iloc[-1]}")
|
||||
else:
|
||||
logger.warning(f"[{pair}] ⚠️ &-price_value_divergence 列缺失,跳过买入信号生成")
|
||||
|
||||
if len(conditions) > 0:
|
||||
combined_condition = reduce(lambda x, y: x & y, conditions)
|
||||
if combined_condition.any():
|
||||
dataframe.loc[combined_condition, 'enter_long'] = 1
|
||||
logger.info(f"[{pair}] 入场信号触发,条件满足")
|
||||
else:
|
||||
logger.debug(f"[{pair}] 买入条件均不满足,未触发入场信号")
|
||||
else:
|
||||
logger.debug(f"[{pair}] 无有效买入条件")
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
生成卖出信号
|
||||
- 放宽条件,移除 MACD 和 Stochastic
|
||||
"""
|
||||
pair = metadata.get('pair', 'Unknown')
|
||||
conditions = []
|
||||
|
||||
if "&-price_value_divergence" in dataframe.columns:
|
||||
cond1 = (dataframe["&-price_value_divergence"] > self.sell_threshold)
|
||||
cond2 = (dataframe["rsi"] > 75)
|
||||
sell_condition = cond1 | cond2
|
||||
conditions.append(sell_condition)
|
||||
|
||||
logger.debug(f"[{pair}] 卖出条件检查 - "
|
||||
f"&-price_value_divergence={dataframe['&-price_value_divergence'].iloc[-1]:.6f} > {self.sell_threshold:.6f}: {cond1.iloc[-1]}, "
|
||||
f"rsi={dataframe['rsi'].iloc[-1]:.2f} > 75: {cond2.iloc[-1]}")
|
||||
else:
|
||||
logger.warning(f"[{pair}] ⚠️ &-price_value_divergence 列缺失,跳过卖出信号生成")
|
||||
|
||||
if len(conditions) > 0:
|
||||
dataframe.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1
|
||||
logger.debug(f"[{pair}] 出场信号触发,条件满足")
|
||||
else:
|
||||
logger.debug(f"[{pair}] 无有效卖出条件")
|
||||
|
||||
return dataframe
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"strategy_name": "FreqaiPrimer",
|
||||
"params": {
|
||||
"roi": {},
|
||||
"stoploss": {
|
||||
"stoploss": -0.05
|
||||
},
|
||||
@ -8,33 +9,27 @@
|
||||
"max_open_trades": 5
|
||||
},
|
||||
"buy": {
|
||||
"ADD_POSITION_THRESHOLD": -0.045,
|
||||
"BUY_THRESHOLD_MAX": -0.006,
|
||||
"BUY_THRESHOLD_MIN": -0.1,
|
||||
"COOLDOWN_PERIOD_MINUTES": 2,
|
||||
"MAX_ENTRY_POSITION_ADJUSTMENT": 3
|
||||
"ADD_POSITION_THRESHOLD": -0.01,
|
||||
"BUY_THRESHOLD_MAX": -0.014,
|
||||
"BUY_THRESHOLD_MIN": -0.019,
|
||||
"COOLDOWN_PERIOD_MINUTES": 1,
|
||||
"MAX_ENTRY_POSITION_ADJUSTMENT": 1
|
||||
},
|
||||
"sell": {
|
||||
"EXIT_POSITION_RATIO": 0.305,
|
||||
"SELL_THRESHOLD_MAX": 0.053,
|
||||
"SELL_THRESHOLD_MIN": 0.004,
|
||||
"TRAILING_STOP_DISTANCE": 0.008,
|
||||
"TRAILING_STOP_START": 0.039
|
||||
"EXIT_POSITION_RATIO": 0.412,
|
||||
"SELL_THRESHOLD_MAX": 0.082,
|
||||
"SELL_THRESHOLD_MIN": 0.008,
|
||||
"TRAILING_STOP_DISTANCE": 0.005,
|
||||
"TRAILING_STOP_START": 0.02
|
||||
},
|
||||
"protection": {},
|
||||
"roi": {
|
||||
"0": 0.044,
|
||||
"7": 0.034999999999999996,
|
||||
"14": 0.009,
|
||||
"68": 0
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.292,
|
||||
"trailing_stop_positive_offset": 0.361,
|
||||
"trailing_only_offset_is_reached": false
|
||||
"trailing_stop_positive": 0.253,
|
||||
"trailing_stop_positive_offset": 0.313,
|
||||
"trailing_only_offset_is_reached": true
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2025-06-25 02:38:38.192887+00:00"
|
||||
}
|
||||
"export_time": "2025-06-27 07:47:09.914912+00:00"
|
||||
}
|
||||
@ -19,10 +19,6 @@ class FreqaiPrimer(IStrategy):
|
||||
"""
|
||||
|
||||
# --- 🧪 Hyperopt Parameters ---
|
||||
ROI_T0 = DecimalParameter(0.01, 0.05, default=0.02, space='roi', optimize=True)
|
||||
ROI_T1 = DecimalParameter(0.005, 0.02, default=0.01, space='roi', optimize=True)
|
||||
ROI_T2 = DecimalParameter(0.0, 0.01, default=0.0, space='roi', optimize=True)
|
||||
|
||||
TRAILING_STOP_START = DecimalParameter(0.01, 0.05, default=0.03, space='sell', optimize=True)
|
||||
TRAILING_STOP_DISTANCE = DecimalParameter(0.005, 0.02, default=0.01, space='sell', optimize=True)
|
||||
|
||||
@ -38,12 +34,6 @@ class FreqaiPrimer(IStrategy):
|
||||
MAX_ENTRY_POSITION_ADJUSTMENT = IntParameter(1, 3, default=2, space='buy', optimize=True)
|
||||
|
||||
# --- 🛠️ 固定配置参数 ---
|
||||
minimal_roi = {
|
||||
"0": ROI_T0.value,
|
||||
"30": ROI_T1.value,
|
||||
"60": ROI_T2.value
|
||||
}
|
||||
|
||||
stoploss = -0.015
|
||||
timeframe = "3m"
|
||||
use_custom_stoploss = True
|
||||
@ -158,7 +148,6 @@ class FreqaiPrimer(IStrategy):
|
||||
|
||||
return dataframe
|
||||
|
||||
# 在 populate_indicators 中修改市场趋势相关逻辑
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
pair = metadata.get('pair', 'Unknown')
|
||||
logger.info(f"[{pair}] 当前可用列(调用FreqAI前):{list(dataframe.columns)}")
|
||||
@ -266,21 +255,6 @@ class FreqaiPrimer(IStrategy):
|
||||
|
||||
return dataframe
|
||||
|
||||
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
|
||||
roi_table = {
|
||||
"0": params.get("roi_t0", self.ROI_T0.value),
|
||||
"30": params.get("roi_t1", self.ROI_T1.value),
|
||||
"60": params.get("roi_t2", self.ROI_T2.value),
|
||||
}
|
||||
return roi_table
|
||||
|
||||
def roi_space(self):
|
||||
return [
|
||||
DecimalParameter(0.01, 0.05, name="roi_t0"),
|
||||
DecimalParameter(0.005, 0.02, name="roi_t1"),
|
||||
DecimalParameter(0.0, 0.01, name="roi_t2")
|
||||
]
|
||||
|
||||
def trailing_space(self):
|
||||
return [
|
||||
DecimalParameter(0.01, 0.05, name="trailing_stop_start"),
|
||||
@ -399,17 +373,6 @@ class FreqaiPrimer(IStrategy):
|
||||
logger.debug(f"{pair} 首次入场金额: {initial_stake_amount:.2f}, 当前持仓金额: {trade.stake_amount:.2f}, "
|
||||
f"加仓次数: {trade.nr_of_successful_entries - 1}")
|
||||
|
||||
# 动态最大持仓时间
|
||||
# max_hold_time = self.MAX_HOLD_TIME_MINUTES.value
|
||||
# # 线性映射持仓时间因子
|
||||
# hold_factor = self.HOLD_TIME_REDUCTION.value + (self.HOLD_TIME_EXTENSION.value - self.HOLD_TIME_REDUCTION.value) * (trend_score / 100)
|
||||
# max_hold_time *= hold_factor
|
||||
# if profit_ratio > 0.02:
|
||||
# max_hold_time *= 1.2
|
||||
# elif profit_ratio < -0.02:
|
||||
# max_hold_time *= 0.8
|
||||
# max_hold_time = int(max_hold_time)
|
||||
|
||||
# 加仓逻辑
|
||||
max_entry_adjustments = self.MAX_ENTRY_POSITION_ADJUSTMENT.value
|
||||
if trade.nr_of_successful_entries <= max_entry_adjustments + 1:
|
||||
@ -478,11 +441,6 @@ class FreqaiPrimer(IStrategy):
|
||||
trade.adjust_min_max_rates(current_rate, trade.min_rate)
|
||||
return None
|
||||
|
||||
# 最大持仓时间限制
|
||||
# if hold_time > max_hold_time:
|
||||
# logger.info(f"{pair} 趋势值 {trend_score:.2f},持仓时间超过 {max_hold_time} 分钟,强制清仓")
|
||||
# return (-trade.stake_amount, f"Max hold time ({max_hold_time} min) exceeded")
|
||||
|
||||
return None
|
||||
|
||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||
|
||||
@ -65,7 +65,7 @@ echo "docker-compose run --rm freqtrade hyperopt \
|
||||
--timerange ${START_DATE}-${END_DATE} \
|
||||
-e 100 \
|
||||
--hyperopt-loss ShortTradeDurHyperOptLoss \
|
||||
--spaces buy sell roi trailing \
|
||||
--spaces buy sell trailing \
|
||||
--fee 0.0016"
|
||||
docker-compose run --rm freqtrade hyperopt \
|
||||
--logfile /freqtrade/user_data/logs/freqtrade.log \
|
||||
@ -77,7 +77,7 @@ docker-compose run --rm freqtrade hyperopt \
|
||||
--timerange ${START_DATE}-${END_DATE} \
|
||||
-e 100 \
|
||||
--hyperopt-loss SharpeHyperOptLoss \
|
||||
--spaces buy sell roi trailing \
|
||||
--spaces buy sell trailing \
|
||||
--fee 0.0016
|
||||
|
||||
#>output.log 2>&1
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user