Files
Chan/strategies/PatternTrader.py

233 lines
8.4 KiB
Python

# --- Do not remove these libs ---
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, CategoricalParameter
from typing import Dict, List
from functools import reduce
from pandas import DataFrame
import numpy as np
import pandas as pd
# --------------------------------
# 设置pandas选项以避免FutureWarning
pd.set_option('future.no_silent_downcasting', True)
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from technical.util import resample_to_interval, resampled_merge
from freqtrade.persistence import Trade, Order
from datetime import datetime, timedelta
from typing import Optional
import logging
logger = logging.getLogger(__name__)
# freqtrade plot-dataframe --strategy PatternTrader --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_30.json --timerange=20250309-
# freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy PatternTrader --strategy-path ./user_data/Chan/strategies
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy PatternTrader --strategy-path ./user_data/Chan/strategies --timerange=20251023-
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m --pairs BTC/USDT:USDT --timerange=20250501-
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy PatternTrader --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 200 --timerange=20250201-20250401
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces buy sell roi stoploss --strategy PatternTrader --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 600 --timerange=20250201-20250401
# sudo docker compose run --rm chan_btc backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy PatternTrader --strategy-path ./user_data/Chan/strategies --timerange=20250101-
# sudo docker compose run --rm chan_btc download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101-
# sudo docker compose run --rm chan_btc trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy PatternTrader --strategy-path ./user_data/Chan/strategies
class PatternTrader(IStrategy):
"""
极简双均线策略
只使用双均线交叉作为唯一信号
"""
INTERFACE_VERSION: int = 3
# 极简参数
fast_ma: IntParameter = IntParameter(5, 15, default=8, space='buy') # 快速均线
slow_ma: IntParameter = IntParameter(20, 50, default=30, space='buy') # 慢速均线
# 添加一个简单的sell空间参数
exit_delay: IntParameter = IntParameter(1, 10, default=3, space='sell') # 出场延迟
# 时间框架
time: IntParameter = IntParameter(15, 60, default=30, space='buy')
# ROI 超参
roi_t1: IntParameter = IntParameter(10, 60, default=30, space='roi')
roi_t2: IntParameter = IntParameter(60, 240, default=120, space='roi')
roi_p1: DecimalParameter = DecimalParameter(0.02, 0.08, default=0.05, decimals=3, space='roi')
roi_p2: DecimalParameter = DecimalParameter(0.005, 0.03, default=0.01, decimals=3, space='roi')
# 合约交易参数
can_short = True
stoploss = -0.02 # 2% 止损
# 杠杆设置
lev: DecimalParameter = DecimalParameter(1.0, 3.0, default=2.0, decimals=1, space='buy')
# 运行设置
process_only_new_candles = False
startup_candle_count: int = 100
# ROI 外部覆盖
_roi_override: Optional[Dict[str, float]] = None
@property
def minimal_roi(self) -> Dict[str, float]:
"""
基于超参动态生成 ROI 梯度
"""
if self._roi_override is not None:
return self._roi_override
t1 = int(self.roi_t1.value)
t2 = int(self.roi_t2.value)
times = sorted([t1, t2])
p1 = float(self.roi_p1.value)
p2 = float(self.roi_p2.value)
profits = sorted([p1, p2], reverse=True)
return {
"0": profits[0],
str(times[0]): profits[1],
str(times[1]): 0.0,
}
@minimal_roi.setter
def minimal_roi(self, value: Dict[str, float]) -> None:
# 允许框架在解析时覆盖 ROI 设置
self._roi_override = value
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
计算技术指标(极简版)
只计算双均线
"""
res = self.get_ticker_indicator() * int(self.time.value)
dataframe_3 = resample_to_interval(dataframe, res)
# 只计算双均线
dataframe_3['fast_ma'] = ta.SMA(dataframe_3['close'], timeperiod=int(self.fast_ma.value))
dataframe_3['slow_ma'] = ta.SMA(dataframe_3['close'], timeperiod=int(self.slow_ma.value))
# 计算金叉和死叉
dataframe_3['fast_ma_cross_slow_ma'] = (dataframe_3['fast_ma'] > dataframe_3['slow_ma']) & (dataframe_3['fast_ma'].shift(1) <= dataframe_3['slow_ma'].shift(1))
dataframe_3['fast_ma_cross_slow_ma_down'] = (dataframe_3['fast_ma'] < dataframe_3['slow_ma']) & (dataframe_3['fast_ma'].shift(1) >= dataframe_3['slow_ma'].shift(1))
dataframe = resampled_merge(dataframe, dataframe_3)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
基于TA指标,填充进场趋势列(极简版)
只使用双均线交叉
"""
res = self.get_ticker_indicator() * int(self.time.value)
def _pick(df: DataFrame, name: str) -> str:
col = f"resample_{res}_{name}"
if col in df.columns:
return col
col2 = f"resample_{float(res)}_{name}"
if col2 in df.columns:
return col2
cand = [c for c in df.columns if c.endswith(f"_{name}")]
return cand[0] if len(cand) else col
fast_ma_cross_slow_ma_str = _pick(dataframe, 'fast_ma_cross_slow_ma')
fast_ma_cross_slow_ma_down_str = _pick(dataframe, 'fast_ma_cross_slow_ma_down')
# 检测多头信号:快线上穿慢线
dataframe.loc[
(
(dataframe[fast_ma_cross_slow_ma_str] == True) &
(pd.notna(dataframe[fast_ma_cross_slow_ma_str]))
),
['enter_long', 'enter_tag']] = (1, 'long_signal_simple')
# 检测空头信号:快线下穿慢线
dataframe.loc[
(
(dataframe[fast_ma_cross_slow_ma_down_str] == True) &
(pd.notna(dataframe[fast_ma_cross_slow_ma_down_str]))
),
['enter_short', 'enter_tag']] = (1, 'short_signal_simple')
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
基于TA指标,填充出场趋势列(极简版)
反向交叉出场
"""
res = self.get_ticker_indicator() * int(self.time.value)
def _pick(df: DataFrame, name: str) -> str:
col = f"resample_{res}_{name}"
if col in df.columns:
return col
col2 = f"resample_{float(res)}_{name}"
if col2 in df.columns:
return col2
cand = [c for c in df.columns if c.endswith(f"_{name}")]
return cand[0] if len(cand) else col
fast_ma_cross_slow_ma_str = _pick(dataframe, 'fast_ma_cross_slow_ma')
fast_ma_cross_slow_ma_down_str = _pick(dataframe, 'fast_ma_cross_slow_ma_down')
# 做多出场:出现死叉
dataframe.loc[
(
(dataframe[fast_ma_cross_slow_ma_down_str] == True) &
(pd.notna(dataframe[fast_ma_cross_slow_ma_down_str]))
),
['exit_long', 'exit_tag']] = (1, 'long_exit_simple')
# 做空出场:出现金叉
dataframe.loc[
(
(dataframe[fast_ma_cross_slow_ma_str] == True) &
(pd.notna(dataframe[fast_ma_cross_slow_ma_str]))
),
['exit_short', 'exit_tag']] = (1, 'short_exit_simple')
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 float(self.lev.value)
def get_ticker_indicator(self):
return int(self.timeframe[:-1])
# 简单的测试函数
def test_strategy():
"""
测试策略基本功能
"""
try:
# 创建策略实例
strategy = PatternTrader()
# 检查基本属性
print("✅ 策略实例化成功")
print(f"策略名称: {strategy.__class__.__name__}")
print(f"接口版本: {strategy.INTERFACE_VERSION}")
print(f"支持做空: {strategy.can_short}")
print(f"默认止损: {strategy.stoploss}")
# 检查参数
print("\n✅ 策略参数检查:")
print(f"布林带长度: {strategy.bb_length.value}")
print(f"杠杆: {strategy.lev.value}")
print(f"仓位比例: {strategy.position_size_pct.value}")
print("\n🎉 策略测试通过!")
return True
except Exception as e:
print(f"❌ 策略测试失败: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
test_strategy()