🚀 AI 一键生成 joinquant 策略代码

如何使用 get_fundamentals API 构建高 ROE 价值投资策略?

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

问题描述

如何编写一个策略,定期筛选并买入 ROE 大于15% 且资产负债率低于50% 的股票?

解决方案

这是一个基于基本面数据的选股策略。根据您的需求,我们需要使用 JoinQuant 的 get_fundamentals 函数来查询财务数据。

策略逻辑分析

  1. 选股条件
    • ROE (净资产收益率) > 15%:这通常代表公司运用自有资本的效率较高,盈利能力强。在聚宽的 indicator 表中,字段为 roe
    • 资产负债率 < 50%:代表公司财务结构相对稳健,杠杆风险较低。计算公式为 总负债 / 总资产,对应 balance 表中的 total_liabilitytotal_assets
  2. 交易频率
    • 由于财务数据通常按季度更新,按日调仓没有必要且手续费高。我们设定为 按月调仓(例如每个月第一个交易日)。
  3. 资金管理
    • 为了避免买入过多股票导致资金过于分散,我们在满足条件的股票中,按 市值从大到小 排序,选取前 20 只进行等权重买入。
  4. 风控
    • 剔除 ST 股、停牌股和次新股(上市不满 60 天)。

策略代码实现

# -*- 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 = 20
    
    # 设定每月第一个交易日进行调仓
    run_monthly(rebalance, 1, time='09:30')

def rebalance(context):
    """
    调仓主函数
    """
    # 1. 选股
    target_list = get_stock_list(context)
    
    # 2. 交易
    trade(context, target_list)

def get_stock_list(context):
    """
    根据基本面数据筛选股票
    """
    # 获取当前时间
    date = context.previous_date
    
    # 查询财务数据
    # 筛选条件:ROE > 15 且 资产负债率 < 50% (0.5)
    # 排序:按总市值降序排列,优先买大市值的龙头
    q = query(
        valuation.code,
        valuation.market_cap
    ).filter(
        indicator.roe > 15,  # ROE大于15%
        balance.total_liability / balance.total_assets < 0.5  # 资产负债率小于50%
    ).order_by(
        valuation.market_cap.desc() # 按市值降序
    )
    
    # 获取查询结果
    df = get_fundamentals(q, date=date)
    
    if len(df) == 0:
        return []
    
    # 初步筛选出的股票列表
    initial_list = list(df['code'])
    
    # 进一步过滤:剔除ST、停牌、次新股
    final_list = filter_paused_and_st_stocks(initial_list, date)
    
    # 截取前 g.stock_num 只股票
    return final_list[:g.stock_num]

def filter_paused_and_st_stocks(stock_list, date):
    """
    过滤ST股、停牌股、上市时间不足60天的股票
    """
    current_data = get_current_data()
    filtered_list = []
    
    for stock in stock_list:
        # 过滤停牌和ST
        if current_data[stock].paused or current_data[stock].is_st:
            continue
            
        # 过滤上市时间不足60天的股票
        # 获取股票信息
        security_info = get_security_info(stock)
        if not security_info:
            continue
        
        # 计算上市天数
        days_listed = (date - security_info.start_date).days
        if days_listed < 60:
            continue
            
        filtered_list.append(stock)
        
    return filtered_list

def trade(context, target_list):
    """
    执行交易
    """
    # 获取当前持仓
    current_holdings = list(context.portfolio.positions.keys())
    
    # 1. 卖出不在目标列表中的股票
    for stock in current_holdings:
        if stock not in target_list:
            order_target_value(stock, 0)
            log.info("卖出 %s" % stock)
    
    # 如果没有目标股票,直接返回
    if len(target_list) == 0:
        return

    # 2. 买入目标列表中的股票
    # 将资金等分
    position_count = len(target_list)
    total_value = context.portfolio.total_value
    # 预留一点现金防止手续费不够,乘以0.99
    value_per_stock = total_value * 0.99 / position_count
    
    for stock in target_list:
        order_target_value(stock, value_per_stock)
        log.info("买入/调整 %s 到目标市值" % stock)

常见问题 (Q&A)

Q: 为什么使用 run_monthly 而不是 run_daily
A: 财务数据(如 ROE 和 资产负债表)是按季度发布的。每天运行筛选逻辑不仅不会获得新数据,反而会因为股价波动导致频繁调仓,增加不必要的手续费和滑点成本。按月调仓足以捕捉基本面的变化。

Q: 代码中的 indicator.roe > 15 是指 15% 吗?
A: 是的。在聚宽的数据库中,indicator.roe 的单位是百分比。所以 > 15 代表 ROE 大于 15%。

Q: 为什么要按市值排序?
A: 满足 ROE > 15% 且负债率低的公司可能很多。如果不进行排序限制数量,策略可能会买入几百只股票,导致单只股票资金过少(甚至买不够一手),或者包含很多流动性差的小盘股。按市值排序可以优先选择流动性较好的大盘蓝筹股。

Q: 如何查看回测结果?
A: 将上述代码复制到聚宽官网的“我的策略” -> “新建策略”中,点击“编译运行”即可进行回测。建议回测时间选择较长跨度(如 3-5 年)以验证策略在不同市场环境下的表现。