Warning: file_exists(): open_basedir restriction in effect. File(/www/wwwroot/com.xiximiao.oa/com.cmstop/public/www/wp-content/db.php) is not within the allowed path(s): (/www/wwwroot/com.xiximiao.oa/com.cmstop/public/www/:/tmp/:/proc/:/var/log/nginx/:/www/wwwroot/com.xiximiao.oa/com.cmstop/public/:/www/wwwroot/com.xiximiao.oa/com.cmstop/vendor/:/www/wwwroot/com.xiximiao.oa/com.cmstop/ppk/) in /www/wwwroot/com.xiximiao.oa/com.cmstop/public/www/wp-includes/load.php on line 707
2321 利用宏观经济数据的中长线策略深度研究 量化自动交易策略代码 » 轻知量化 QMT、PTrade、聚宽策略分享交流平台

2321 利用宏观经济数据的中长线策略深度研究 量化自动交易策略代码

# 风险及免责提示:该策略由聚宽用户在聚宽社区分享,仅供学习交流使用。
# 标题:一种宏观数据的中长线策略,年化15%,最大回撤9%
# 回测资金为300000

# 导入函数库
from jqdata import *                 # 从jqdata包中导入所有函数和对象
from jqdata import macro             # 导入jqdata包中的宏观数据模块,这通常包含经济指标、市场数据等宏观经济相关的数据
from decimal import Decimal          # 从decimal模块导入Decimal类,Decimal类用于精确的小数运算,避免浮点数运算中的精度问题
import numpy as np                   # 导入numpy包,这是一个广泛使用的科学计算库,提供了大量的数学函数和操作多维数组的方法
import pandas as pd                  # 导入pandas包,这是一个强大的数据处理和分析工具库,特别适用于处理表格数据
import datetime                      # 导入datetime模块,这是Python的标准库之一,用于处理日期和时间
from scipy import optimize as op     # 从scipy包中的optimize模块导入优化算法,scipy是一个开源的Python算法库和数学工具包

# 定义 initialize 函数,这个函数在策略启动时运行一次,用于初始化策略设置
def initialize(context):
    g.mode = 0      # 设置全局变量 g.mode 为 0,用于指示策略的运行模式,mode:0表示当前只买债券,1表示长线玩法(依据经济周期止盈),2表示中线玩法(ma5、ma20、ma120均线止盈)
    g.record = 0.0  # 设置全局变量 g.record 用于记录下单时沪深300的指数点位
    g.times = -1    # 记录翻倍加仓的次数,-1 代表这个经济周期中第一次购买股票类标的
    g.order_amount = 0.04     # 表示每次下单的基数,即资金的4%
    g.begin_date = '2013-01'  # 设置全局变量 g.begin_date 为 '2013-01',用于指定获取经济周期数据的起始节点
    g.stock_security = '510300.XSHG'  # 设置全局变量 g.stock_security 为 '510300.XSHG',代表沪深300ETF,用于股票类投资
    g.bond_security = '511010.XSHG'   # 设置全局变量 g.bond_security 为 '511010.XSHG',代表国债ETF,用于债券类投资
    g.over_hot = False  # 设置全局变量 g.over_hot 为 False,用于标记市场是否处于过热状态
    g.yearMa10 = 0.0  # 设置全局变量 g.yearMa10 为 0.0,用于记录沪深300跌破10年年K线时的点位
    set_benchmark('000300.XSHG')    # 设定沪深300作为基准
    # 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱,类型是股票
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    run_daily(handle, time = '14:55')    # 使用 run_daily 函数设置每天14:55运行 handle 函数,用于执行日常的交易逻辑

# 定义 handle 函数,用于执行策略的交易逻辑
def handle(context):
    pre_date = context.previous_date      # 获取前一个交易日的日期
    # 获取当前交易日的日期并转换为日期对象
    current_date = context.current_dt.strftime('%Y-%m-%d').split('-')     
    current_date = datetime.date(int(current_date[0]),int(current_date[1]),int(current_date[2]))
    diff_days = (current_date - pre_date).days  # 计算当前交易日和前一交易日之间的天数差,用于判断是否为本周的第一个交易日
    # 1.根据经济周期(使用制造业PMI的分项数据计算)来定投
    if g.mode in [0, 2]:  # 如果模式为0或2,防止误判经济周期,防止放水一波变成了复苏阶段
        current_date = current_date.strftime('%Y-%m')
    # 1.1.判断PMI的值是否符合条件(1是利用拟合函数计算斜率,2是PMI值大于等于50,3是PMI同比上涨)
        x = [0,1,2,3,4,5,6,7,8,9,10,11,12]  # 横坐标,表示月份
        y = get_PMI(current_date)  # 纵坐标,表示PMI值,获取过去十三个月PMI函数的值
        params = op.curve_fit(func,x,y)    # 使用拟合函数 func(需要定义)和x、y值计算PMI的趋势斜率
        k = 2 * params[0][0] * 12 + params[0][1]  # 计算斜率k,用来判断经济趋势
        if k > 0.0 and y[-1] >= 50.0 and y[0] <= y[-1]:     # 如果PMI的趋势是上升的,且当前PMI值大于等于50,且是同比上涨的
    # 1.2.如果PMI满足条件,计算当前供需格局(生产指数与新订单指数的百分位)
            provide_and_need = calc_provide_and_need(g.begin_date, current_date)
    # 1.3.如果供需格局处于复苏状态,则计算当前库存格局(原材料库存指数产成品库存指数的百分位)
            if provide_and_need[0] < 50.0 and provide_and_need[1] > 50.0:       # 如果供需格局显示生产指数在新订单指数之下,表示经济可能在复苏状态
                save = calc_save(g.begin_date, current_date)
    # 1.4.判断当前信息是否有效(供需格局在复苏阶段,库存格局在复苏或者过热阶段)
                if save[0] < 50.0:     # 如果库存格局在复苏或过热状态
                    g.mode = 1     # 切换到长线玩法模式
    # 1.5.如果有效则启动长线玩法,记录成交时的指数点位,且直至卖出前不再执行1.1、1.2、1.3、1.4步骤
    if g.mode == 1:    # 如果当前模式是长线玩法(模式1)
        if g.times == -1:    # 如果这是经济周期中的第一次购买
            # 记录下单时沪深300的指数点位
            g.record = float(np.array(get_bars('000300.XSHG', 1, '1d', fields = ['close'], include_now=True, df = True))[0])
        if g.bond_security in context.portfolio.positions.keys(): # 如果国债不在列表中
            order_target(g.bond_security, 0)   # 卖出所有债券
        # 获取当前价格和基金价格
        current_price = float(np.array(get_bars('000300.XSHG', 1, '1d', fields = ['close'], include_now=True, df = True))[0])
        fund_price = float(np.array(get_bars(g.stock_security, 1, '1d', fields = ['close'], include_now=True, df = True))[0])
        # 计算与记录时的指数点位的差距
        withdraw = Decimal(str(current_price)) / Decimal(str(g.record)) - Decimal('1.0')
    # 1.6.检查当前价格是否比记录价格低2%,如果是,则买入一笔沪深300ETF(依据1248定投法则)
        if withdraw < Decimal('-0.02') or g.times == -1:     # 如果当前价格比记录价格低2%,或者这是第一次购买
            g.times += 1     # 增加购买次数
            # 调用order_func函数下单购买基金
            order_func(context.portfolio.total_value, context.portfolio.available_cash, fund_price)
            # 更新记录的指数点位
            g.record = float(np.array(get_bars('000300.XSHG', 1, '1d', fields = ['close'], include_now=True, df = True))[0])
    # 1.7.指定每周第一个交易日定投一笔沪深300ETF
        else:     # 如果不是第一次购买,且当前是每周的第一个交易日
            if diff_days > 2:
                # 调用order_func函数下单购买基金
                order_func(context.portfolio.total_value, context.portfolio.available_cash, fund_price)
    # 2.根据年K图的移动平均线来定投
    if g.mode in [0, 2]:      # 如果模式是0或2,执行基于年K线的定投策略
        if g.mode == 0:       # 如果当前模式是0
    # 2.1.将月K线图转换成年K线图
            year_point = change_to_yeak_k()       # 2.1.将月K线图转换成年K线图
    # 2.2.计算年K图的移动平均线:ma10
            yearMa10 = year_move_average(year_point)    # 2.2.计算年K图的移动平均线:ma10
    # 2.3.如果跌破年K图的ma10,则记录当前ma10的价格,并且买入一个基数的沪深300ETF,尔后根据1248定投法则定投
            # 获取当前价格和基金价格
            current_price = float(np.array(get_bars('000300.XSHG', 1, '1d', fields = ['close'], include_now=True, df = True))[0])
            fund_price = float(np.array(get_bars(g.stock_security, 1, '1d', fields = ['close'], include_now=True, df = True))[0])
            if current_price < yearMa10:    # 如果当前价格跌破年K图的ma10
    # 2.4.启动中线玩法(更改mode的值),直至卖出前不再执行2.1、2.2、2.3、2.4步骤
                g.mode = 2     # 2.4.切换到中线玩法模式
                g.record = yearMa10     # 记录当前的ma10价格
                g.times += 1     # 增加购买次数
                if g.bond_security in context.portfolio.positions.keys():  
                    order_target(g.bond_security, 0)    # 卖出所有债券
                # 调用order_func函数下单购买基金
                order_func(context.portfolio.total_value, context.portfolio.available_cash, fund_price)
    # 2.5.如果当前价格比记录的价格低5%以上,则继续买入一笔沪深300ETF(依据1248定投法则买入)
        if g.mode == 2:    # 如果模式是2,执行中线玩法
            # 获取当前价格和基金价格
            current_price = float(np.array(get_bars('000300.XSHG', 1, '1d', fields = ['close'], include_now=True, df = True))[0])
            fund_price = float(np.array(get_bars(g.stock_security, 1, '1d', fields = ['close'], include_now=True, df = True))[0])
            # 计算与记录时的价格的差距
            withdraw = Decimal(str(current_price)) / Decimal(str(g.record)) - Decimal('1.0')
            if withdraw < Decimal('-0.05'):      # 如果当前价格比记录价格低5%以上
                g.times += 1   # 增加购买次数
                # 调用order_func函数下单购买基金
                order_func(context.portfolio.total_value, context.portfolio.available_cash, fund_price)
                # 更新记录的价格
                g.record = float(np.array(get_bars('000300.XSHG', 1, '1d', fields = ['close'], include_now=True, df = True))[0])
    # 2.6.指定每周第一个交易日定投一笔沪深300ETF
            else:   # 如果当前是每周的第一个交易日
                if diff_days > 2:
                    # 调用order_func函数下单购买基金
                    order_func(context.portfolio.total_value, context.portfolio.available_cash, fund_price)
    # 4.右侧买入(仅针对1和2)
    if g.mode in [1, 2]:   # 如果模式是1或2,执行右侧买入策略
        # 计算日K图的ma5、ma10、ma20、ma30
        ma5 = day_move_average(5, True)
        ma10 = day_move_average(10, True)
        ma20 = day_move_average(20, True)
        ma30 = day_move_average(30, True)
        ma30_pre = day_move_average(30, False)
    # 4.2. 如果ma5>ma10>ma20>ma30 且 当天的ma30比前一天高
        if ma5 > ma10 > ma20 > ma30 > ma30_pre:
            order_value(g.stock_security, context.portfolio.available_cash)     # 全仓买入股票类标的
    # 5.股票类标的止盈操作
    if g.stock_security in context.portfolio.positions.keys():      # 如果持有股票类标的,执行止盈操作
        sell_stock_security(current_date, context.portfolio.total_value, list(context.portfolio.positions.keys()))
    
# 以下是功能函数

# 获取过去十三个月PMI函数的值
def get_PMI(date:str) -> list:
    start_date = date      # 初始化 start_date 为传入的 date
    for i in range(13):    # 循环13次,每次循环将 start_date 减去一个月,以获取从当前日期起过去13个月的日期范围
        start_date = calc_last_month(start_date)
    # 使用 macro.run_query 执行查询,获取制造业PMI数据,查询条件是统计月份在 start_date 和 date 之间(不包括 date)
    df = macro.run_query(query(
        macro.MAC_MANUFACTURING_PMI.pmi    # 获取 PMI 值
        ).filter(
        macro.MAC_MANUFACTURING_PMI.stat_month >= start_date,  # 过滤条件:统计月份大于等于 start_date
        macro.MAC_MANUFACTURING_PMI.stat_month < date          # 过滤条件:统计月份小于 date
        ).order_by(                                            # 按统计月份升序排序
            macro.MAC_MANUFACTURING_PMI.stat_month.asc()
        ))
    return list(np.array(df['pmi']))    # 将查询结果中的 PMI 值转换为 numpy 数组,然后转换为列表并返回

# 这个函数返回生产指数和新订单指数的百分位
def calc_provide_and_need(start_date:str,end_date:str) -> tuple:
    end_date = calc_last_month(end_date)       # 调用 calc_last_month 函数计算 end_date 上一个月的月份
    # 使用 macro.run_query 执行查询,获取制造业PMI相关的生产指数和新订单指数数据
    df = macro.run_query(query(
        macro.MAC_MANUFACTURING_PMI.stat_month,  # 获取统计月份
        macro.MAC_MANUFACTURING_PMI.produce_idx,  # 获取生产指数
        macro.MAC_MANUFACTURING_PMI.new_orders_idx  # 获取新订单指数
        ).filter(  # 设置查询过滤条件
        macro.MAC_MANUFACTURING_PMI.stat_month >= start_date,  # 统计月份大于等于 start_date
        macro.MAC_MANUFACTURING_PMI.stat_month <= end_date     # 统计月份小于等于 end_date
        ).order_by(                                            # 按统计月份降序排序
        macro.MAC_MANUFACTURING_PMI.stat_month.desc()
        ))
    # 计算生产指数和新订单指数的百分位
    # 从查询结果中提取生产指数和新订单指数的值,转换为 numpy 数组
    produce_seq = np.array(df['produce_idx'])
    new_orders_seq = np.array(df['new_orders_idx'])
    # 分别计算生产指数和新订单指数的百分位
    produce_percent = percentile(produce_seq, df.iloc[0, 1])
    new_orders_percent = percentile(new_orders_seq, df.iloc[0, 2])
    # 返回一个包含生产指数和新订单指数百分位的元组
    return (produce_percent, new_orders_percent)

# 这个函数返回原材料和产成品库存指数的百分位
def calc_save(start_date:str,end_date:str) -> tuple:   # 它接收两个参数:start_date 和 end_date,这两个参数都是字符串类型,表示日期范围。
    end_date = calc_last_month(end_date)    # 调用 calc_last_month 函数计算 end_date 上一个月的月份,并将结果转换为字符串格式
    # 使用 macro.run_query 执行查询,获取制造业PMI相关的原材料库存指数和产成品库存指数数据
    df = macro.run_query(query(
        macro.MAC_MANUFACTURING_PMI.stat_month,           # 获取统计月份
        macro.MAC_MANUFACTURING_PMI.raw_material_idx,     # 获取原材料库存指数
        macro.MAC_MANUFACTURING_PMI.finished_produce_idx  # 获取产成品库存指数
        ).filter(  # 设置查询过滤条件
        macro.MAC_MANUFACTURING_PMI.stat_month >= start_date,  # 统计月份大于等于 start_date
        macro.MAC_MANUFACTURING_PMI.stat_month <= end_date     # 统计月份小于等于 end_date
        ).order_by(  # 按统计月份降序排序
        macro.MAC_MANUFACTURING_PMI.stat_month.desc()
        ))
    # 从查询结果中提取原材料库存指数和产成品库存指数的值,并将它们转换为 numpy 数组
    raw_material_seq = np.array(df['raw_material_idx'])
    finished_produce_seq = np.array(df['finished_produce_idx'])
    # 分别计算原材料库存指数和产成品库存指数的百分位
    # 这里假设 percentile 函数已经定义,它接受一个数组和一个索引位置,返回对应位置的值作为百分位
    raw_material_percent = percentile(raw_material_seq, df.iloc[0, 1])
    finished_produce_percent = percentile(finished_produce_seq, df.iloc[0, 2])
    # 返回一个包含原材料库存指数和产成品库存指数百分位的元组
    return (raw_material_percent, finished_produce_percent)

# 下单函数,total_cash表示总权益,available_cash表示可用资金,current_price表示当前价格
def order_func(total_cash:float, available_cash:float,fund_price:float):
    buy_cash = 2 ** g.times * g.order_amount * total_cash    # 计算买入金额,基于总权益、加仓次数和下单基数
    if buy_cash <= available_cash:      # 如果计算出的买入金额不超过可用资金,则按照计算金额下单买入
        order_value(g.stock_security, buy_cash)  
    else:      # 如果计算出的买入金额超过可用资金,则使用全部可用资金下单买入
        if fund_price * 100 <= available_cash:
            order_value(g.stock_security, available_cash)

关键函数解锁后查看:


# 以下是辅助函数

# 计算指定天数的日移动平均值
def day_move_average(day:int, now:bool) -> float:
    # 获取沪深300指数(000300.XSHG)过去 'day' 天的收盘价,并包括今天的值(如果 now 为 True)
    # 返回的数据是一个 DataFrame,fields 指定获取 'close' 字段
    array = np.array(get_bars('000300.XSHG', day, '1d', fields = ['close'], include_now = now, df = True))
    ma = array.mean()    # 计算获取到的收盘价数组的平均值
    return ma      # 返回计算得到的移动平均值

# 计算序列中目标值的百分位
def percentile(seq, target) -> float:
    not_less_num = len(np.extract(seq>=target,seq))    # 计算序列中大于等于目标值的元素数量
    smaller_num = len(np.extract(seq str:
    result = ''    # 初始化结果字符串
    # 解析输入日期字符串,提取年和月
    year = int(date.split('-')[0])
    month = int(date.split('-')[1])
    # 计算上一个月的月份,考虑跨年的情况
    if month - 1 < 1:
        year -= 1
        month = month + 12 - 1
    else:
        month -= 1
    # 格式化月份,确保月份是两位数字
    if month < 10:
        result = str(year) + '-0' + str(month)
    else:
        result = str(year) + '-' + str(month)
    return result    # 返回计算得到的上一个月的日期字符串

# 拟合数据的二次函数
def func(x, A, B, C):
    return A * x ** 2 + B * x + C    # 根据传入的系数 A, B, C 计算二次函数的值

2025-02-21
⚠️
本站资源大多来自网络,仅供网友学习交流,未经作者或上传书面授权,请勿作他用。
站长 vx: xiangyin615 或者 留言反馈 ,我们将尽快处理。
Notice: When you of the legal rights be violate, please stir to vx: xiangyin615
个人中心
购物车
优惠劵
搜索