🚀 AI 一键生成 joinquant 策略代码

JoinQuant 实战:如何构建一个结合财务指标与动量因子的多因子选股模型?

joinquant | 发布时间: 2025/12/9
以下内容由 EasyQuant 生成。

问题描述

如何编写一个选股策略,既考虑公司的财务状况,又结合近期的股价表现?

解决方案

编写一个结合**财务状况(基本面)近期股价表现(技术面)**的选股策略,通常被称为“基本面+技术面”双驱动策略。

策略设计思路

为了实现这一目标,我们可以设计如下逻辑:

  1. 股票池范围:以沪深300指数成分股为基础,确保流动性和公司规模。
  2. 财务筛选(基本面)
    • 盈利能力:净资产收益率(ROE) > 8%,筛选赚钱能力强的公司。
    • 估值安全:市盈率(PE)在 0 到 50 之间,剔除亏损股和极度高估的股票。
    • 排序:按 ROE 从高到低排序,初选前 50 只。
  3. 股价表现筛选(技术面)
    • 趋势判断:当前股价 > 60日均线(MA60),确保处于上升或多头趋势中。
  4. 交易逻辑
    • 调仓频率:按月调仓。
    • 买入:买入同时满足上述条件的股票,最多持有 10 只,等权重分配。
    • 卖出:不在新一期选股列表中的股票全部卖出。

策略代码实现

以下是基于聚宽(JoinQuant)API 的完整策略代码:

# -*- coding: utf-8 -*-
from jqdata import *

def initialize(context):
    """
    初始化函数,设定基准、手续费、滑点等
    """
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 设定股票交易手续费:买入万分之三,卖出万分之三加千分之一印花税
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 设定全局变量
    g.stock_num = 10  # 最大持仓数量
    
    # 设定按月运行,每月第一个交易日进行调仓
    run_monthly(trade, monthday=1, time='09:30')

def trade(context):
    """
    交易函数,包含选股和调仓逻辑
    """
    # 1. 获取初始股票池(沪深300)
    initial_pool = get_index_stocks('000300.XSHG')
    
    # 2. 基本面筛选(财务状况)
    # 查询条件:PE在0-50之间,ROE大于8%
    # 排序:按ROE从高到低排序
    q = query(
        valuation.code,
        valuation.pe_ratio,
        indicator.roe
    ).filter(
        valuation.code.in_(initial_pool),
        valuation.pe_ratio > 0,
        valuation.pe_ratio < 50,
        indicator.roe > 8
    ).order_by(
        indicator.roe.desc()
    ).limit(50) # 初选前50只
    
    df_fund = get_fundamentals(q, date=context.previous_date)
    if len(df_fund) == 0:
        return
    fundamental_list = list(df_fund['code'])
    
    # 3. 技术面筛选(近期股价表现)
    # 逻辑:当前价格站上60日均线
    final_buy_list = []
    
    for stock in fundamental_list:
        # 获取过去60天的收盘价数据
        # 注意:为了计算MA60,我们需要取60天的数据
        h_data = attribute_history(stock, 60, '1d', ['close'], skip_paused=True)
        
        # 如果数据不足60天(如新股),跳过
        if len(h_data) < 60:
            continue
            
        close_price = h_data['close'][-1] # 当前收盘价(前一分钟/前一天)
        ma60 = h_data['close'].mean()     # 60日均线
        
        # 如果股价在60日均线之上,加入最终买入列表
        if close_price > ma60:
            final_buy_list.append(stock)
        
        # 如果选够了目标数量,停止筛选
        if len(final_buy_list) >= g.stock_num:
            break
            
    # 4. 执行调仓操作
    # 获取当前持仓
    current_holdings = list(context.portfolio.positions.keys())
    
    # 卖出不在目标列表中的股票
    for stock in current_holdings:
        if stock not in final_buy_list:
            order_target_value(stock, 0)
            log.info("卖出 %s" % stock)
            
    # 买入目标列表中的股票
    if len(final_buy_list) > 0:
        # 等权重分配资金
        # 注意:这里简单处理,用总资产除以目标数量。实际交易中可能需要考虑保留现金等。
        value_per_stock = context.portfolio.total_value / g.stock_num
        
        for stock in final_buy_list:
            order_target_value(stock, value_per_stock)
            log.info("买入/调整 %s" % stock)

def after_code_changed(context):
    """
    模拟交易更换代码后运行函数
    """
    # 可以在这里重置参数或更新设置
    pass

代码逻辑详解

  1. initialize (初始化):

    • 设置了基准为沪深300指数,以便对比策略表现。
    • 设置了真实价格模式(动态复权),这是回测准确性的关键。
    • 使用 run_monthly 设定策略为月度调仓,避免频繁交易产生过高手续费。
  2. trade (核心逻辑):

    • 基本面筛选:使用 get_fundamentals 函数查询财务数据。
      • valuation.pe_ratio:市盈率,用于过滤高估值泡沫股。
      • indicator.roe:净资产收益率,衡量公司利用股东资本赚钱的效率,是巴菲特最看重的指标之一。
    • 技术面筛选:使用 attribute_history 获取历史行情。
      • 计算 ma60 (60日均线),这是股市的“生命线”。股价在均线之上通常意味着中期趋势向好。
    • 资金管理:采用简单的等权重模型,将资金平均分配给选出的股票。
  3. 风险控制:

    • 通过限制 g.stock_num = 10,实现分散投资,避免单只股票暴雷导致账户重创。
    • 通过 PE 和 ROE 的双重过滤,避开了垃圾股和过热股。

常见问题 Q&A

Q: 为什么要结合基本面和技术面?
A: 仅看基本面(财务)容易买入“价值陷阱”,即公司很好但股价一直不涨甚至阴跌;仅看技术面(股价)容易买入“垃圾概念股”,一旦炒作结束会面临暴跌。结合两者可以在公司质地优良的基础上,选择市场认可(趋势向上)的时机介入。

Q: 如何调整策略的激进程度?
A:

  • 更稳健:可以降低 PE 的上限(如改为 0-30),提高 ROE 的下限(如 >15%),或者选择更大的均线周期(如 MA120)。
  • 更激进:可以放宽 PE 限制,加入成长因子(如净利润增长率 > 30%),或者使用更短期的均线(如 MA20)来捕捉短期爆发力。

Q: 策略中的 order_target_value 是什么意思?
A: 这是一个非常实用的下单函数。它的意思是“将某只股票的持仓调整到指定金额”。如果当前没持仓,它会买入;如果持仓不足,它会补仓;如果持仓过多,它会卖出部分。这自动帮我们完成了调仓和再平衡的操作。