股票筛选策略 - 专注于寻找高波动、适合做T的股票
Timeframe
5m
Direction
Long Only
Stoploss
-3.0%
Trailing Stop
Yes
ROI
0m: 5.0%, 15m: 3.0%, 30m: 2.0%, 60m: 1.0%
Interface Version
3
Startup Candles
N/A
Indicators
5
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
股票筛选策略 - 寻找适合做T的高波动股票
筛选条件:
1. 日内波动幅度>2%
2. 有明确趋势(60分钟MACD多头)
3. 成交量活跃(放量特征明显)
4. RSI在合理区间(不在极端位置)
筛选出的股票特征类似sh600629:
- 平均日内波动2-3%
- 频繁的高抛低吸机会
- 放量上涨概率大
"""
from freqtrade.strategy import IStrategy, informative
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
class StockScreeningStrategy(IStrategy):
"""
股票筛选策略 - 专注于寻找高波动、适合做T的股票
"""
INTERFACE_VERSION = 3
# ROI设置 - 快进快出
minimal_roi = {
"0": 0.05, # 5%立即获利
"15": 0.03, # 15分钟后3%
"30": 0.02, # 30分钟后2%
"60": 0.01 # 1小时后1%
}
# 止损设置
stoploss = -0.03
# 追踪止损
trailing_stop = True
trailing_stop_positive = 0.015
trailing_stop_positive_offset = 0.025
trailing_only_offset_is_reached = True
# 时间框架
timeframe = '5m'
# 启动模式
process_only_new_candles = True
use_exit_signal = True
exit_profit_only = False
# 筛选参数(基于sh600629分析)
min_volatility = 1.5 # 最小日内波动幅度1.5%
ideal_volatility = 2.0 # 理想波动幅度2%+
max_volatility = 6.0 # 最大波动幅度6%(避免过度投机)
min_volume_ratio = 1.3 # 最小放量比例
rsi_low = 25 # RSI下限
rsi_high = 75 # RSI上限
atr_min = 0.1 # 最小ATR(波动率)
atr_max = 1.0 # 最大ATR
@informative('1h')
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""60分钟时间框架 - 判断大趋势"""
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# 均线系统
dataframe['ma5'] = ta.SMA(dataframe, timeperiod=5)
dataframe['ma10'] = ta.SMA(dataframe, timeperiod=10)
dataframe['ma20'] = ta.SMA(dataframe, timeperiod=20)
# RSI
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# ATR
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
# 近期涨跌幅(20周期)
dataframe['price_change_20'] = (dataframe['close'] / dataframe['close'].shift(20) - 1) * 100
return dataframe
@informative('15m')
def populate_indicators_15m(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""15分钟时间框架"""
# RSI
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# 布林带
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=20, stds=2)
dataframe['bb_lower'] = bollinger['lower']
dataframe['bb_middle'] = bollinger['mid']
dataframe['bb_upper'] = bollinger['upper']
dataframe['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / dataframe['bb_middle'] * 100
# 成交量
dataframe['volume_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_ma20']
# 统计15分钟内波动幅度>2%的次数(过去20根)
dataframe['intraday_range'] = (dataframe['high'] - dataframe['low']) / dataframe['low'] * 100
dataframe['high_volatility_count'] = (dataframe['intraday_range'] > 2).rolling(20).sum()
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""5分钟主时间框架"""
# RSI
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# 布林带
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=20, stds=2)
dataframe['bb_lower'] = bollinger['lower']
dataframe['bb_middle'] = bollinger['mid']
dataframe['bb_upper'] = bollinger['upper']
dataframe['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / dataframe['bb_middle'] * 100
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# 均线
dataframe['ma5'] = ta.SMA(dataframe, timeperiod=5)
dataframe['ma10'] = ta.SMA(dataframe, timeperiod=10)
dataframe['ma20'] = ta.SMA(dataframe, timeperiod=20)
# ATR
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
# 成交量
dataframe['volume_ma20'] = ta.SMA(dataframe['volume'], timeperiod=20)
dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_ma20']
# 日内波动幅度
dataframe['intraday_range'] = (dataframe['high'] - dataframe['low']) / dataframe['low'] * 100
# 统计过去20根K线的波动特征
dataframe['avg_volatility_20'] = dataframe['intraday_range'].rolling(20).mean()
dataframe['high_volatility_count'] = (dataframe['intraday_range'] > self.ideal_volatility).rolling(20).sum()
# 放量统计
dataframe['volume_up_count'] = (
(dataframe['volume_ratio'] > self.min_volume_ratio) &
(dataframe['close'] > dataframe['open'])
).rolling(20).sum()
dataframe['volume_down_count'] = (
(dataframe['volume_ratio'] > self.min_volume_ratio) &
(dataframe['close'] < dataframe['open'])
).rolling(20).sum()
# 价格变化
dataframe['price_change'] = dataframe['close'].pct_change() * 100
# MACD金叉
dataframe['macd_cross_up'] = qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])
# 筛选评分系统
dataframe['screening_score'] = 0
# 评分1: 波动率合适 (0-3分)
dataframe.loc[
(dataframe['avg_volatility_20'] >= self.min_volatility) &
(dataframe['avg_volatility_20'] <= self.max_volatility),
'screening_score'
] += 1
dataframe.loc[
(dataframe['avg_volatility_20'] >= self.ideal_volatility) &
(dataframe['avg_volatility_20'] <= 4),
'screening_score'
] += 2 # 理想波动额外2分
# 评分2: 频繁的高波动 (0-2分)
dataframe.loc[
dataframe['high_volatility_count'] > 10,
'screening_score'
] += 2
# 评分3: 放量上涨>下跌 (0-2分)
dataframe.loc[
dataframe['volume_up_count'] > dataframe['volume_down_count'],
'screening_score'
] += 2
# 评分4: 60分钟趋势向上 (0-2分)
dataframe.loc[
(dataframe['macd_1h'] > dataframe['macdsignal_1h']) &
(dataframe['ma5_1h'] > dataframe['ma20_1h']),
'screening_score'
] += 2
# 评分5: ATR在合理区间 (0-1分)
dataframe.loc[
(dataframe['atr_1h'] >= self.atr_min) &
(dataframe['atr_1h'] <= self.atr_max),
'screening_score'
] += 1
# 评分6: 布林带宽度合适(适合做T)(0-1分)
dataframe.loc[
dataframe['bb_width_15m'] > 3, # 布林带宽度>3%
'screening_score'
] += 1
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
入场条件 - 筛选合格股票后的入场时机
"""
conditions = []
# === 核心筛选条件:评分>=7分的股票 ===
stock_qualified = (dataframe['screening_score'] >= 7)
# === 入场时机1: 低吸 ===
buy_dip = (
(dataframe['rsi'] < 35) & # RSI超卖
(dataframe['close'] <= dataframe['bb_lower']) & # 触及布林下轨
(dataframe['volume_ratio'] > self.min_volume_ratio) # 放量
)
# === 入场时机2: 突破 ===
breakout = (
(dataframe['macd_cross_up']) & # MACD金叉
(dataframe['close'] > dataframe['ma20']) & # 突破MA20
(dataframe['volume_ratio'] > 1.5) & # 强放量
(dataframe['price_change'] > 0.5) # 上涨
)
# === 入场时机3: 趋势跟随 ===
trend_follow = (
(dataframe['ma5'] > dataframe['ma10']) & # 均线多头
(dataframe['ma10'] > dataframe['ma20']) &
(dataframe['rsi'] > 50) & (dataframe['rsi'] < 65) & # RSI健康
(dataframe['macd'] > dataframe['macdsignal']) & # MACD多头
(dataframe['volume_ratio'] > 1.2) # 适度放量
)
# 综合条件:必须是合格股票 + 入场时机
conditions.append(
stock_qualified &
(buy_dip | breakout | trend_follow)
)
if conditions:
dataframe.loc[
conditions[0],
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
出场条件 - 高抛
"""
conditions = []
# === 高抛条件 ===
sell_high = (
(dataframe['rsi'] > 70) & # RSI超买
(dataframe['close'] >= dataframe['bb_upper']) & # 触及布林上轨
(dataframe['close'] > dataframe['ma5'] * 1.02) # 远离MA5
)
# === 趋势转弱 ===
trend_weak = (
(dataframe['ma5'] < dataframe['ma10']) & # 均线死叉
(dataframe['macd'] < dataframe['macdsignal']) & # MACD死叉
(dataframe['volume_ratio'] > 1.3) # 放量下跌
)
# === 股票不再符合筛选条件 ===
not_qualified = (dataframe['screening_score'] < 5)
conditions.append(sell_high | trend_weak | not_qualified)
if conditions:
dataframe.loc[
conditions[0],
'exit_long'] = 1
return dataframe
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time, entry_tag, **kwargs) -> bool:
"""
最终确认 - 确保股票符合筛选标准
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 1:
return False
last_candle = dataframe.iloc[-1]
# 检查筛选评分
if last_candle['screening_score'] < 7:
return False
# 检查波动率
if last_candle['avg_volatility_20'] < self.min_volatility:
return False
# 检查60分钟趋势
if last_candle['macd_1h'] <= last_candle['macdsignal_1h']:
return False
# 检查放量特征
if last_candle['volume_up_count'] <= last_candle['volume_down_count']:
return False
return True
def custom_stake_amount(self, pair: str, current_time, current_rate: float,
proposed_stake: float, min_stake: float, max_stake: float,
entry_tag, **kwargs) -> float:
"""
根据筛选评分调整仓位
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 1:
return proposed_stake
last_candle = dataframe.iloc[-1]
# 评分越高,仓位越大
score = last_candle['screening_score']
if score >= 10:
return max_stake * 0.8 # 高分股票80%仓位
elif score >= 8:
return max_stake * 0.5 # 中高分50%仓位
elif score >= 7:
return max_stake * 0.3 # 及格分30%仓位
else:
return min_stake # 最小仓位