simple_strategies.py
"""
QTrading 简单策略模块
"""
import pandas as pd
import numpy as np
from strategy.base_strategy import BaseStrategy
from utils.common import normalize_score
class MA20Strategy(BaseStrategy):
"""MA20 策略(简单均线策略)"""
name: str = "MA20Strategy"
description: str = "基于 20 日均线的简单策略"
def __init__(self, adjust_method: str = "qfq"):
super().__init__(adjust_method)
self.params = {
'ma_period': 20,
'trend_threshold': 0.02,
'volatility_threshold': 0.03
}
def score(self, data: pd.DataFrame) -> float:
"""计算股票得分(0~100)"""
if data.empty:
return 0.0
# 计算 20 日均线
ma20 = self.calculate_ma(data, period=self.params['ma_period'])
if len(ma20) < self.params['ma_period']:
return 0.0
# 获取最新价格和均线值
current_price = data['close'].iloc[-1]
latest_ma = ma20.iloc[-1]
# 趋势评分(价格在均线上方得分高)
trend_score = 100 if current_price > latest_ma else 0
if abs(current_price - latest_ma) / latest_ma < self.params['trend_threshold']:
trend_score = 50
# 波动率评分(波动率越低得分越高)
volatility = self.calculate_price_volatility(data, period=20)
volatility_score = max(0, 100 - volatility * 10)
# 成交量评分
volume_momentum = self.calculate_volume_momentum(data, period=20)
volume_score = normalize_score(volume_momentum, -100, 100)
# 综合评分
total_score = (trend_score * 0.6 + volatility_score * 0.2 + volume_score * 0.2)
return max(0, min(100, round(total_score, 2)))
class RSIStrategy(BaseStrategy):
"""RSI 策略(相对强弱指标策略)"""
name: str = "RSIStrategy"
description: str = "基于 RSI 指标的策略"
def __init__(self, adjust_method: str = "qfq"):
super().__init__(adjust_method)
self.params = {
'rsi_period': 14,
'oversold_threshold': 30,
'overbought_threshold': 70,
'trend_period': 20
}
def score(self, data: pd.DataFrame) -> float:
"""计算股票得分(0~100)"""
if data.empty:
return 0.0
# 计算 RSI
rsi = self.calculate_rsi(data, period=self.params['rsi_period'])
if len(rsi) < self.params['rsi_period'] + 1:
return 0.0
current_rsi = rsi.iloc[-1]
# RSI 评分(中间区域得分高)
if current_rsi < self.params['oversold_threshold']:
rsi_score = 30 # 超卖区域得分较低
elif current_rsi > self.params['overbought_threshold']:
rsi_score = 40 # 超买区域得分较低
else:
# 中间区域得分最高
distance = min(abs(current_rsi - 50), 20)
rsi_score = 100 - (distance / 20) * 60
# 趋势评分
ma20 = self.calculate_ma(data, period=self.params['trend_period'])
current_price = data['close'].iloc[-1]
latest_ma = ma20.iloc[-1]
trend_score = 100 if current_price > latest_ma else 20
# 波动率评分
volatility = self.calculate_price_volatility(data, period=20)
volatility_score = max(0, 100 - volatility * 10)
# 综合评分
total_score = (rsi_score * 0.5 + trend_score * 0.3 + volatility_score * 0.2)
return max(0, min(100, round(total_score, 2)))
class MACDStrategy(BaseStrategy):
"""MACD 策略"""
name: str = "MACDStrategy"
description: str = "基于 MACD 指标的策略"
def __init__(self, adjust_method: str = "qfq"):
super().__init__(adjust_method)
self.params = {
'fast_period': 12,
'slow_period': 26,
'signal_period': 9,
'trend_period': 20
}
def score(self, data: pd.DataFrame) -> float:
"""计算股票得分(0~100)"""
if data.empty:
return 0.0
# 计算 MACD
macd_result = self.calculate_macd(
data,
self.params['fast_period'],
self.params['slow_period'],
self.params['signal_period']
)
if len(macd_result['macd']) < self.params['slow_period'] + 1:
return 0.0
current_diff = macd_result['diff'].iloc[-1]
current_dea = macd_result['dea'].iloc[-1]
current_macd = macd_result['macd'].iloc[-1]
# MACD 评分
macd_score = 0
if current_diff > current_dea and current_macd > 0:
macd_score = 100 # 金叉且MACD柱为正
elif current_diff > current_dea and current_macd < 0:
macd_score = 70 # 金叉但MACD柱为负
elif current_diff < current_dea and current_macd > 0:
macd_score = 30 # 死叉但MACD柱为正
else:
macd_score = 0 # 死叉且MACD柱为负
# 趋势评分
ma20 = self.calculate_ma(data, period=self.params['trend_period'])
current_price = data['close'].iloc[-1]
latest_ma = ma20.iloc[-1]
trend_score = 100 if current_price > latest_ma else 20
# 成交量评分
volume_momentum = self.calculate_volume_momentum(data, period=20)
volume_score = normalize_score(volume_momentum, -100, 100)
# 综合评分
total_score = (macd_score * 0.5 + trend_score * 0.3 + volume_score * 0.2)
return max(0, min(100, round(total_score, 2)))
class BollingerBandStrategy(BaseStrategy):
"""布林带策略"""
name: str = "BollingerBandStrategy"
description: str = "基于布林带指标的策略"
def __init__(self, adjust_method: str = "qfq"):
super().__init__(adjust_method)
self.params = {
'bb_period': 20,
'bb_std': 2,
'trend_period': 50
}
def score(self, data: pd.DataFrame) -> float:
"""计算股票得分(0~100)"""
if data.empty:
return 0.0
# 计算布林带
bb_result = self.calculate_bollinger_bands(
data,
period=self.params['bb_period'],
num_std=self.params['bb_std']
)
if len(bb_result['middle']) < self.params['bb_period']:
return 0.0
current_price = data['close'].iloc[-1]
upper_band = bb_result['upper'].iloc[-1]
middle_band = bb_result['middle'].iloc[-1]
lower_band = bb_result['lower'].iloc[-1]
# 布林带位置评分
if current_price > upper_band:
bb_score = 40 # 上轨上方(超买)
elif current_price < lower_band:
bb_score = 60 # 下轨下方(超卖)
elif current_price > middle_band:
bb_score = 80 # 中轨上方(上升趋势)
else:
bb_score = 20 # 中轨下方(下降趋势)
# 布林带宽度评分(波动率)
bb_width = (upper_band - lower_band) / middle_band
width_score = max(0, 100 - bb_width * 1000)
# 趋势评分
ma50 = self.calculate_ma(data, period=self.params['trend_period'])
latest_ma50 = ma50.iloc[-1]
trend_score = 100 if current_price > latest_ma50 else 40
# 综合评分
total_score = (bb_score * 0.4 + width_score * 0.3 + trend_score * 0.3)
return max(0, min(100, round(total_score, 2)))
class MomentumStrategy(BaseStrategy):
"""动量策略"""
name: str = "MomentumStrategy"
description: str = "基于价格动量的策略"
def __init__(self, adjust_method: str = "qfq"):
super().__init__(adjust_method)
self.params = {
'momentum_period': 20,
'volatility_period': 20,
'correlation_period': 20
}
def score(self, data: pd.DataFrame) -> float:
"""计算股票得分(0~100)"""
if data.empty:
return 0.0
# 价格动量评分
price_momentum = self.calculate_price_momentum(
data, period=self.params['momentum_period']
)
momentum_score = normalize_score(price_momentum, -50, 50)
# 波动率评分(波动率越低得分越高)
volatility = self.calculate_price_volatility(
data, period=self.params['volatility_period']
)
volatility_score = max(0, 100 - volatility * 10)
# 价格-成交量相关性评分
correlation = self.calculate_price_volume_correlation(
data, period=self.params['correlation_period']
)
correlation_score = normalize_score(correlation, -1, 1)
# 综合评分
total_score = (momentum_score * 0.5 + volatility_score * 0.3 + correlation_score * 0.2)
return max(0, min(100, round(total_score, 2)))