增加选股策略

This commit is contained in:
oficcejo
2025-12-12 19:46:38 +08:00
parent 595278100f
commit ad5865d3b4
25 changed files with 4818 additions and 20 deletions
+460
View File
@@ -0,0 +1,460 @@
'''
均线回归策略 V3.0 进攻版
优化重点:提高Beta,增强进攻性,减少过度保守
核心改进:
1. 放宽市场择时,只在极端熊市降仓
2. 增加持仓数量,提高资金利用率
3. 放宽买入条件,增加交易机会
4. 优化止盈,让利润奔跑
5. 增加突破买入模式
'''
import jqdata
import numpy as np
## 初始化函数
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式
set_option('use_real_price', True)
# 设定成交量比例
set_option('order_volume_ratio', 1)
# 设置交易手续费
set_order_cost(OrderCost(open_tax=0, close_tax=0.001,
open_commission=0.0003, close_commission=0.0003,
close_today_commission=0, min_commission=5), type='stock')
# ========== 策略参数(进攻版)==========
g.stocknum = 8 # 持仓数量增加到8只
g.max_position_ratio = 0.98 # 最大仓位98%
# 均线参数
g.ma_short = 20 # 短期均线
g.ma_mid = 60 # 中期均线
g.ma_long = 120 # 长期均线
# 回调买入参数(放宽条件)
g.pullback_ratio = 0.035 # 放宽到3.5%
g.break_tolerance = 0.015 # 允许跌破1.5%
# 止盈止损参数(让利润奔跑)
g.base_take_profit = 0.20 # 止盈提高到20%
g.base_stop_loss = 0.06 # 止损放宽到6%
g.trailing_stop = 0.08 # 回撤容忍8%
# 市值筛选(亿)- 扩大范围
g.min_market_cap = 20
g.max_market_cap = 1000
# 行业分散参数
g.max_industry_stocks = 3 # 同行业最多3只
# 市场状态
g.market_state = 'NORMAL'
g.position_ratio = 1.0
# 记录
g.highest_profit = {}
g.stock_volatility = {}
g.hold_days = {} # 持仓天数
# 定时任务
run_daily(market_analysis, '09:31')
run_daily(morning_check, '09:35')
run_daily(check_positions, '14:00') # 只检查一次
run_daily(afternoon_trade, '14:50')
## 市场环境分析(放宽版)
def market_analysis(context):
"""
只在极端情况下降低仓位,大部分时间保持高仓位
"""
index_code = '000300.XSHG'
try:
df = attribute_history(index_code, 130, '1d', ['close'], skip_paused=True)
if len(df) < 120:
g.market_state = 'NORMAL'
g.position_ratio = 1.0
return
close = df['close'].values
current = close[-1]
ma20 = np.mean(close[-20:])
ma60 = np.mean(close[-60:])
ma120 = np.mean(close[-120:])
change_5d = (close[-1] - close[-6]) / close[-6]
change_10d = (close[-1] - close[-11]) / close[-11]
# 只在极端熊市才降仓(条件更严格)
if current < ma120 and ma20 < ma60 < ma120 and change_10d < -0.08:
# 极端熊市:指数跌破120日均线,均线空头,10日跌幅超8%
g.market_state = 'BEAR'
g.position_ratio = 0.5
log.info("【市场】极端熊市,仓位50%%")
elif change_5d < -0.08:
# 短期暴跌超8%:暂时减仓
g.market_state = 'BEAR'
g.position_ratio = 0.6
log.info("【市场】短期暴跌,仓位60%%")
elif current > ma20 and ma20 > ma60:
# 趋势向上:满仓
g.market_state = 'BULL'
g.position_ratio = 1.0
log.info("【市场】趋势向上,满仓")
else:
# 默认高仓位运行
g.market_state = 'NORMAL'
g.position_ratio = 0.9
log.info("【市场】正常状态,仓位90%%")
except Exception as e:
g.market_state = 'NORMAL'
g.position_ratio = 0.9
## 早盘检查
def morning_check(context):
g.buy_list = check_stocks(context)
log.info("【选股】%d 只,市场: %s,仓位: %.0f%%" %
(len(g.buy_list), g.market_state, g.position_ratio * 100))
## 检查持仓 - 优化版(让利润奔跑)
def check_positions(context):
if len(context.portfolio.positions) == 0:
return
for stock in list(context.portfolio.positions.keys()):
position = context.portfolio.positions[stock]
if position.closeable_amount <= 0:
continue
cost = position.avg_cost
current_price = position.price
if cost <= 0:
continue
profit_ratio = (current_price - cost) / cost
# 更新持仓天数
g.hold_days[stock] = g.hold_days.get(stock, 0) + 1
# 更新最高盈利
if stock not in g.highest_profit:
g.highest_profit[stock] = profit_ratio
else:
g.highest_profit[stock] = max(g.highest_profit[stock], profit_ratio)
highest = g.highest_profit[stock]
hold_days = g.hold_days.get(stock, 0)
# 动态止损(根据波动率和持仓时间)
volatility = g.stock_volatility.get(stock, 0.025)
# 持仓时间越长,止损越宽松
time_factor = min(1 + hold_days * 0.01, 1.5)
dynamic_stop_loss = min(g.base_stop_loss * time_factor, 0.12)
# === 止损逻辑 ===
if profit_ratio < -dynamic_stop_loss:
log.info("【止损】%s 亏损 %.2f%%" % (stock, profit_ratio * 100))
order_target(stock, 0)
clean_stock_data(stock)
continue
# === 均线破位(只在亏损时卖)===
if profit_ratio < 0 and check_sell_signal(stock):
log.info("【均线破位】%s 破位且亏损,卖出" % stock)
order_target(stock, 0)
clean_stock_data(stock)
continue
# === 移动止盈(让利润奔跑)===
if highest >= g.base_take_profit:
drawdown = highest - profit_ratio
# 盈利越多,允许回撤越大(最多允许回撤15%)
allowed_drawdown = min(g.trailing_stop + highest * 0.4, 0.18)
if drawdown >= allowed_drawdown:
log.info("【移动止盈】%s 最高 %.2f%%,回撤 %.2f%%" %
(stock, highest * 100, drawdown * 100))
order_target(stock, 0)
clean_stock_data(stock)
continue
# === 分批止盈(保守止盈,只卖小部分)===
if profit_ratio >= g.base_take_profit * 2:
# 盈利超40%,卖出30%锁定利润
sell_amount = int(position.closeable_amount * 0.3 / 100) * 100
if sell_amount >= 100:
log.info("【大幅止盈】%s 盈利 %.2f%%,卖出30%%" % (stock, profit_ratio * 100))
order(stock, -sell_amount)
## 清理股票数据
def clean_stock_data(stock):
for d in [g.highest_profit, g.stock_volatility, g.hold_days]:
if stock in d:
del d[stock]
## 尾盘交易
def afternoon_trade(context):
buy_stocks(context)
## 买入函数(积极版)
def buy_stocks(context):
if not hasattr(g, 'buy_list') or not g.buy_list:
return
position_count = len(context.portfolio.positions)
adjusted_stocknum = int(g.stocknum * g.position_ratio)
if adjusted_stocknum <= 0 or position_count >= adjusted_stocknum:
return
# 计算资金分配
available_cash = context.portfolio.available_cash * g.max_position_ratio
buy_count = min(adjusted_stocknum - position_count, len(g.buy_list))
if buy_count <= 0 or available_cash < 10000:
return
cash_per_stock = available_cash / buy_count
held_industries = get_held_industries(context)
bought = 0
for stock in g.buy_list:
if bought >= buy_count:
break
if stock in context.portfolio.positions:
continue
# 行业分散
stock_industry = get_stock_industry(stock)
if stock_industry and held_industries.get(stock_industry, 0) >= g.max_industry_stocks:
continue
# 简化买入条件检查
if check_buy_signal(stock):
order_value(stock, cash_per_stock)
log.info("【买入】%s" % stock)
g.highest_profit[stock] = 0
g.stock_volatility[stock] = 0.025
g.hold_days[stock] = 0
if stock_industry:
held_industries[stock_industry] = held_industries.get(stock_industry, 0) + 1
bought += 1
## 获取已持仓的行业分布
def get_held_industries(context):
industries = {}
for stock in context.portfolio.positions.keys():
ind = get_stock_industry(stock)
if ind:
industries[ind] = industries.get(ind, 0) + 1
return industries
## 获取股票所属行业
def get_stock_industry(stock):
try:
ind_dict = get_industry(stock)
if ind_dict and stock in ind_dict:
# 获取申万一级行业
for ind_code, ind_info in ind_dict[stock].items():
if ind_code.startswith('sw_l1'):
return ind_info.get('industry_name', None)
return None
except:
return None
## 选股函数(进攻版)
def check_stocks(context):
# 第一步:基础筛选(放宽条件)
q = query(
valuation.code,
valuation.market_cap
).filter(
valuation.market_cap.between(g.min_market_cap, g.max_market_cap)
).order_by(
valuation.market_cap.asc()
).limit(500) # 扩大候选池
df = get_fundamentals(q)
if df.empty:
return []
stock_list = list(df['code'])
stock_list = filter_basic(stock_list)
# 均线信号筛选
candidates = []
for stock in stock_list[:200]: # 检查前200只
if check_buy_signal(stock):
score = calculate_trend_score(stock)
candidates.append((stock, score))
# 按评分排序
candidates.sort(key=lambda x: x[1], reverse=True)
buy_list = [c[0] for c in candidates[:g.stocknum * 3]]
return buy_list
## 计算趋势强度评分
def calculate_trend_score(stock):
"""
评分因素:
1. 均线发散程度
2. 价格距离MA20的位置
3. 成交量配合
"""
try:
df = attribute_history(stock, 130, '1d', ['close', 'volume'], skip_paused=True)
if len(df) < 120:
return 0
close = df['close'].values
volume = df['volume'].values
ma20 = np.mean(close[-20:])
ma60 = np.mean(close[-60:])
ma120 = np.mean(close[-120:])
current = close[-1]
score = 0
# 均线发散程度(20分)
spread = (ma20 - ma120) / ma120
score += min(spread * 100, 20)
# 价格位置(30分)- 越接近MA20越好
distance = abs(current - ma20) / ma20
score += max(0, 30 - distance * 500)
# 成交量(20分)- 回调缩量为佳
vol_ma5 = np.mean(volume[-5:])
vol_ma20 = np.mean(volume[-20:])
if vol_ma5 < vol_ma20 * 0.8:
score += 20 # 缩量回调
elif vol_ma5 < vol_ma20:
score += 10
# 趋势持续性(30分)
ma20_slope = (ma20 - np.mean(close[-25:-5])) / np.mean(close[-25:-5])
if ma20_slope > 0:
score += min(ma20_slope * 300, 30)
return score
except:
return 0
## 基础过滤
def filter_basic(stock_list):
if not stock_list:
return []
current_data = get_current_data()
filtered = []
for stock in stock_list:
# 过滤停牌
if current_data[stock].paused:
continue
# 过滤ST
if current_data[stock].is_st:
continue
if 'ST' in current_data[stock].name or '*' in current_data[stock].name:
continue
# 过滤科创板、北交所
if stock.startswith('688') or stock.startswith('8') or stock.startswith('4'):
continue
# 过滤涨跌停
if current_data[stock].last_price >= current_data[stock].high_limit:
continue
if current_data[stock].last_price <= current_data[stock].low_limit:
continue
filtered.append(stock)
return filtered
## 检查买入信号(放宽版)
def check_buy_signal(stock):
"""
放宽买入条件,增加交易机会
"""
try:
df = attribute_history(stock, g.ma_long + 10, '1d', ['close'], skip_paused=True)
if len(df) < g.ma_long:
return False
close = df['close'].values
current_price = close[-1]
# 计算均线
ma20 = np.mean(close[-g.ma_short:])
ma60 = np.mean(close[-g.ma_mid:])
ma120 = np.mean(close[-g.ma_long:])
# 条件1:均线多头排列(核心条件)
if not (ma20 > ma60 > ma120):
return False
# 条件2MA20向上
ma20_5d_ago = np.mean(close[-g.ma_short-5:-5])
if ma20 <= ma20_5d_ago:
return False
# 条件3:价格在MA20附近(放宽范围)
distance = (current_price - ma20) / ma20
# 允许在MA20上方5%以内,或跌破2%以内
if distance > 0.05 or distance < -0.02:
return False
# 条件4:价格不能离MA60太远
if current_price > ma60 * 1.30:
return False
return True
except:
return False
## 检查卖出信号(均线破位)
def check_sell_signal(stock):
"""
卖出条件:
1. 价格跌破MA60
2. MA20下穿MA60(死叉)
3. 价格跌破MA20且持续3天
"""
try:
df = attribute_history(stock, g.ma_mid + 5, '1d', ['close'], skip_paused=True)
if len(df) < g.ma_mid:
return False
close = df['close'].values
current_price = close[-1]
ma20 = np.mean(close[-g.ma_short:])
ma60 = np.mean(close[-g.ma_mid:])
# 价格跌破MA60超过2%
if current_price < ma60 * 0.98:
return True
# MA20下穿MA60(死叉)
ma20_yesterday = np.mean(close[-g.ma_short-1:-1])
ma60_yesterday = np.mean(close[-g.ma_mid-1:-1])
if ma20_yesterday > ma60_yesterday and ma20 < ma60:
return True
# 连续3天收盘在MA20下方
ma20_3d = [np.mean(close[-g.ma_short-i:-i]) if i > 0 else ma20 for i in range(3)]
below_ma20_count = sum(1 for i in range(3) if close[-1-i] < ma20_3d[i] * 0.99)
if below_ma20_count >= 3:
return True
return False
except:
return False