205 lines
6.7 KiB
Python
205 lines
6.7 KiB
Python
"""
|
||
纯随机规则策略 (PureRandomRuleStrategy)
|
||
|
||
完全不看 K 线、不看指标、不看量价、不看趋势、不看形态的纯规则交易系统。
|
||
|
||
规则:
|
||
- 固定时间周期开仓(例如:每 4 小时一单)
|
||
- 方向随机多空,不做任何行情判断
|
||
- 每次只开1个方向,不对冲
|
||
- 固定止盈:2%
|
||
- 固定止损:1%
|
||
- 到价立即平仓,不移动、不修改
|
||
- 单笔仓位:总资金的 5%
|
||
- 单笔最大风险:总资金的 0.05%
|
||
- 连续止损 3 次,当天停止交易
|
||
- 总持仓不超过 20%
|
||
|
||
使用命令:
|
||
freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json --strategy PureRandomRuleStrategy --strategy-path ./user_data/Chan/strategies --timerange=20260101-
|
||
|
||
实盘命令:
|
||
freqtrade trade -c ./user_data/Chan/config/Chan.json \
|
||
--strategy PureRandomRuleStrategy --strategy-path ./user_data/Chan/strategies
|
||
"""
|
||
|
||
import logging
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
import random
|
||
import pandas as pd
|
||
import talib.abstract as ta
|
||
from pandas import DataFrame
|
||
from freqtrade.strategy import IStrategy
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class PureRandomRuleStrategy(IStrategy):
|
||
"""
|
||
纯随机规则策略
|
||
|
||
核心特点:
|
||
1. 不看任何行情数据
|
||
2. 固定时间开仓(可配置间隔)
|
||
3. 随机选择多空方向
|
||
4. 固定止盈止损
|
||
5. 风险控制(连续止损、持仓限制)
|
||
"""
|
||
INTERFACE_VERSION: int = 3
|
||
|
||
# === 基础配置 ===
|
||
timeframe = '1m' # 主时间框架
|
||
informative_timeframe = '1h' # 1小时作为参考(需要数据支持)
|
||
can_short = True
|
||
can_long = True
|
||
|
||
startup_candle_count = 200 # 需要更多数据计算 EMA
|
||
|
||
# === 交易时间间隔配置 ===
|
||
trade_interval_hours = 4
|
||
|
||
# === 止盈止损配置 ===
|
||
take_profit_pct = 0.024
|
||
stop_loss_pct = 0.01
|
||
|
||
# === 仓位配置 ===
|
||
entry_percent = 0.05
|
||
max_position_pct = 0.20
|
||
|
||
# === 风险控制 ===
|
||
max_consecutive_losses = 3
|
||
|
||
# === 订单类型 ===
|
||
order_types = {
|
||
"entry": "market",
|
||
"exit": "market",
|
||
"stoploss": "market",
|
||
"stoploss_on_exchange": False,
|
||
}
|
||
|
||
# === 最小 ROI ===
|
||
minimal_roi = {
|
||
"0": take_profit_pct,
|
||
}
|
||
|
||
# === 止损 ===
|
||
stoploss = -stop_loss_pct
|
||
|
||
# === 追踪止损 ===
|
||
trailing_stop = False
|
||
|
||
# === 策略状态 ===
|
||
_last_entry_time: Optional[datetime] = None
|
||
_consecutive_losses: int = 0
|
||
_last_loss_date: Optional[datetime] = None
|
||
_today_loss_count: int = 0
|
||
|
||
def __init__(self, config: dict) -> None:
|
||
super().__init__(config)
|
||
random.seed()
|
||
|
||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||
"""
|
||
计算 1h EMA26 和波动幅度
|
||
使用 resampled_merge 合并 1h 数据
|
||
"""
|
||
from technical.util import resample_to_interval, resampled_merge
|
||
|
||
# 重采样到 1h (1m * 60 = 60)
|
||
dataframe_1h = resample_to_interval(dataframe, 60)
|
||
|
||
# 计算 1h EMA26
|
||
dataframe_1h['ema26'] = ta.EMA(dataframe_1h, timeperiod=26)
|
||
|
||
# 计算 1h 波动幅度: (high - low) / open * 100%
|
||
dataframe_1h['volatility'] = (dataframe_1h['high'] - dataframe_1h['low']) / dataframe_1h['open']
|
||
|
||
# 合并到主 dataframe
|
||
# 列名格式: resample_60_ema26, resample_60_volatility
|
||
dataframe = resampled_merge(dataframe, dataframe_1h)
|
||
|
||
return dataframe
|
||
|
||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||
"""
|
||
入场逻辑:固定时间 + 1h EMA26方向过滤 + 波动过滤 + 时间过滤
|
||
|
||
规则:
|
||
1. 1h EMA26 上方 -> 只做多
|
||
2. 1h EMA26 下方 -> 只做空
|
||
3. 固定时间间隔开仓(4小时)
|
||
4. 1h 波动幅度 > 0.5% 且 < 5%
|
||
5. UTC 8:00-20:00
|
||
"""
|
||
dataframe['enter_long'] = 0
|
||
dataframe['enter_short'] = 0
|
||
dataframe['enter_tag'] = ''
|
||
|
||
last_entry_idx = None
|
||
|
||
# 1h EMA26 列名
|
||
ema26_col = 'resample_60_ema26'
|
||
# 1h 波动幅度列名
|
||
volatility_col = 'resample_60_volatility'
|
||
|
||
# 波动幅度阈值
|
||
min_volatility = 0.005 # 0.5%
|
||
max_volatility = 0.05 # 5%
|
||
|
||
for i in range(len(dataframe)):
|
||
current_time = dataframe['date'].iloc[i]
|
||
current_price = dataframe['close'].iloc[i]
|
||
|
||
# 使用 resample 后的 EMA26 列
|
||
ema26_1h = dataframe[ema26_col].iloc[i]
|
||
# 波动幅度
|
||
volatility = dataframe[volatility_col].iloc[i]
|
||
|
||
# 跳过没有 EMA 数据的情况
|
||
if pd.isna(ema26_1h):
|
||
continue
|
||
|
||
# 检查时间间隔(4小时)
|
||
can_entry = True
|
||
if last_entry_idx is not None:
|
||
hours_since_last = (current_time - dataframe['date'].iloc[last_entry_idx]).total_seconds() / 3600
|
||
if hours_since_last < self.trade_interval_hours:
|
||
can_entry = False
|
||
|
||
# 检查当天连续止损
|
||
if self._today_loss_count >= self.max_consecutive_losses:
|
||
can_entry = False
|
||
|
||
# 检查波动幅度(>0.5% 且 <5%)
|
||
if not pd.isna(volatility):
|
||
if volatility < min_volatility or volatility > max_volatility:
|
||
can_entry = False
|
||
else:
|
||
can_entry = False
|
||
|
||
# 检查时间过滤(UTC 8:00-20:00)
|
||
utc_hour = current_time.hour
|
||
#if utc_hour < 8 or utc_hour >= 20:
|
||
#can_entry = False
|
||
|
||
if can_entry:
|
||
# 判断方向:价格 > 1h EMA26 做多,价格 < 1h EMA26 做空
|
||
if current_price > ema26_1h:
|
||
dataframe.loc[dataframe.index[i], 'enter_long'] = 1
|
||
dataframe.loc[dataframe.index[i], 'enter_tag'] = 'long_above_ema'
|
||
elif current_price < ema26_1h:
|
||
dataframe.loc[dataframe.index[i], 'enter_short'] = 1
|
||
dataframe.loc[dataframe.index[i], 'enter_tag'] = 'short_below_ema'
|
||
|
||
if dataframe.loc[dataframe.index[i], 'enter_long'] == 1 or dataframe.loc[dataframe.index[i], 'enter_short'] == 1:
|
||
last_entry_idx = i
|
||
self._last_entry_time = current_time
|
||
|
||
return dataframe
|
||
|
||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||
dataframe['exit_long'] = 0
|
||
dataframe['exit_short'] = 0
|
||
return dataframe
|