添加新的策略
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
SOL5mStrategy - 基于 EMA26_EMA52_Cross 的改进版
|
||||
|
||||
核心改进(相比原版):
|
||||
★ 去掉了反向交叉退出信号(原版中这是最大亏损来源,206笔亏-3021 USDT)
|
||||
★ 加入 trailing stop 保护利润
|
||||
★ 只靠 ROI + trailing stop + 硬止损 管理退出
|
||||
|
||||
逻辑:
|
||||
- 底层使用 1m K线(由 config 中 timeframe: "1m" 控制)
|
||||
- resample 到 30m 计算 EMA26/EMA52 交叉
|
||||
- 交叉后延迟 30 根 1m K线入场(等待确认)
|
||||
- ROI 从 15% 逐步递减
|
||||
- Trailing stop:盈利 6% 后激活,回撤 3% 退出
|
||||
- 硬止损 -15%(安全网)
|
||||
|
||||
使用命令:
|
||||
freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json \
|
||||
--strategy SOL5mStrategy --strategy-path ./user_data/Chan/strategies \
|
||||
--timerange=20250301-
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from technical.util import resample_to_interval, resampled_merge
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SOL5mStrategy(IStrategy):
|
||||
INTERFACE_VERSION: int = 3
|
||||
|
||||
# === 基础配置 ===
|
||||
# 注意:实际 timeframe 由 config 文件中的 "timeframe": "1m" 控制
|
||||
# 这里不设置 timeframe,让 config 覆盖
|
||||
can_short = True
|
||||
startup_candle_count: int = 1600
|
||||
|
||||
# 硬止损 -3%(超短线合理止损,配合更严格的入场过滤)
|
||||
stoploss = -0.03
|
||||
use_custom_stoploss = False
|
||||
|
||||
# Trailing stop:盈利 3% 后激活,回撤 1.5% 退出
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.015 # 回撤 1.5% 触发退出
|
||||
trailing_stop_positive_offset = 0.03 # 盈利 3% 后才开始追踪
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
# ROI:从 6% 逐步递减(给盈利交易更多空间)
|
||||
minimal_roi = {
|
||||
"0": 0.06, # 6% 立即止盈
|
||||
"60": 0.04, # 60分钟后 4%
|
||||
"120": 0.03, # 120分钟后 3%
|
||||
"240": 0.02, # 240分钟后 2%
|
||||
"480": 0.01, # 480分钟后 1%
|
||||
"720": 0, # 720分钟后不设止盈
|
||||
}
|
||||
|
||||
order_types = {
|
||||
"entry": "market",
|
||||
"exit": "market",
|
||||
"stoploss": "market",
|
||||
"stoploss_on_exchange": False,
|
||||
}
|
||||
|
||||
# resample 时间倍数
|
||||
time15 = 15
|
||||
time30 = 30
|
||||
time60 = 60
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""在 15m / 30m / 60m 级别计算 EMA26/52 交叉信号"""
|
||||
ticker = self.get_ticker_indicator()
|
||||
|
||||
# resample 到更大时间框架
|
||||
dataframe_15m = resample_to_interval(dataframe, ticker * self.time15)
|
||||
dataframe_30m = resample_to_interval(dataframe, ticker * self.time30)
|
||||
dataframe_60m = resample_to_interval(dataframe, ticker * self.time60)
|
||||
|
||||
# 在每个时间框架上计算指标
|
||||
dataframe_15m = self.add_indicators(dataframe_15m)
|
||||
dataframe_30m = self.add_indicators(dataframe_30m)
|
||||
dataframe_60m = self.add_indicators(dataframe_60m)
|
||||
dataframe = self.add_indicators(dataframe)
|
||||
|
||||
# 合并回 1m dataframe
|
||||
dataframe = resampled_merge(dataframe, dataframe_15m)
|
||||
dataframe = resampled_merge(dataframe, dataframe_30m)
|
||||
dataframe = resampled_merge(dataframe, dataframe_60m)
|
||||
|
||||
return dataframe
|
||||
|
||||
def add_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||
"""计算 EMA26/52 及其交叉信号,以及RSI和成交量过滤"""
|
||||
dataframe["ema26"] = ta.EMA(dataframe, timeperiod=26)
|
||||
dataframe["ema52"] = ta.EMA(dataframe, timeperiod=52)
|
||||
|
||||
# RSI用于确认趋势强度
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
|
||||
# 成交量均线用于确认成交量
|
||||
dataframe["volume_mean"] = dataframe["volume"].rolling(window=20).mean()
|
||||
|
||||
# 上穿:本根 EMA26 > EMA52,上一根 EMA26 ≤ EMA52
|
||||
dataframe["ema26_cross_up_52"] = (
|
||||
(dataframe["ema26"] > dataframe["ema52"])
|
||||
& (dataframe["ema26"].shift(1) <= dataframe["ema52"].shift(1))
|
||||
)
|
||||
|
||||
# 下穿:本根 EMA26 < EMA52,上一根 EMA26 ≥ EMA52
|
||||
dataframe["ema26_cross_down_52"] = (
|
||||
(dataframe["ema26"] < dataframe["ema52"])
|
||||
& (dataframe["ema26"].shift(1) >= dataframe["ema52"].shift(1))
|
||||
)
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""30m EMA26/52 交叉入场,延迟 15 根 1m K线,加入RSI和成交量确认"""
|
||||
ticker = self.get_ticker_indicator()
|
||||
time = self.time30
|
||||
delay = 15 # 减少延迟从30到15分钟
|
||||
cross_up = f"resample_{ticker * time}_ema26_cross_up_52"
|
||||
cross_down = f"resample_{ticker * time}_ema26_cross_down_52"
|
||||
|
||||
# 获取当前时间框架的RSI和成交量
|
||||
rsi_col = "rsi"
|
||||
volume_col = "volume"
|
||||
volume_mean_col = "volume_mean"
|
||||
|
||||
# 做多:30m EMA26 上穿 EMA52 + RSI > 50(确认上涨趋势)+ 成交量确认
|
||||
dataframe.loc[
|
||||
(dataframe[cross_up].shift(delay) == True) &
|
||||
(dataframe[rsi_col] > 50) & # RSI确认上涨趋势
|
||||
(dataframe[volume_col] > dataframe[volume_mean_col]), # 成交量确认
|
||||
["enter_long", "enter_tag"],
|
||||
] = (1, "ema26x52_long")
|
||||
|
||||
# 做空:30m EMA26 下穿 EMA52 + RSI < 50(确认下跌趋势)+ 成交量确认
|
||||
dataframe.loc[
|
||||
(dataframe[cross_down].shift(delay) == True) &
|
||||
(dataframe[rsi_col] < 50) & # RSI确认下跌趋势
|
||||
(dataframe[volume_col] > dataframe[volume_mean_col]), # 成交量确认
|
||||
["enter_short", "enter_tag"],
|
||||
] = (1, "ema26x52_short")
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""不使用信号退出,完全依赖 ROI / trailing stop / 硬止损"""
|
||||
return dataframe
|
||||
|
||||
def leverage(
|
||||
self,
|
||||
pair: str,
|
||||
current_time: datetime,
|
||||
current_rate: float,
|
||||
proposed_leverage: float,
|
||||
max_leverage: float,
|
||||
entry_tag: Optional[str],
|
||||
side: str,
|
||||
**kwargs,
|
||||
) -> float:
|
||||
return 1.0
|
||||
|
||||
def get_ticker_indicator(self) -> int:
|
||||
"""获取 timeframe 的分钟数"""
|
||||
return int(self.timeframe[:-1])
|
||||
Reference in New Issue
Block a user