大家好,今天给大家分享一套可直接运行的Python大类资产轮动量化策略,聚焦医疗、白酒、黄金三大主流赛道,整合了数据获取、趋势预测、仓位管理、回测验证全流程,适合量化新手入门实战,也可根据自身需求灵活调整参数和标的。

策略核心逻辑:通过爬取基金历史净值数据,用随机森林回归预测未来30天趋势,结合最大回撤控制仓位,同时支持每月轮动回测,兼顾稳健性和收益性,全程无冗余代码,注释清晰,复制即可运行。

一、策略整体框架

整个策略分为5大核心模块,流程清晰,层层递进,新手也能快速看懂:

  1. 配置模块:定义初始资金、风险控制、预测参数、文件路径和基金标的,可灵活修改;

  2. 工具函数:日志输出功能,记录策略运行过程和关键信息,方便排查问题;

  3. 数据获取模块:爬取天天基金网的基金净值数据,保存历史数据到本地,支持批量获取;

  4. 预测与仓位管理模块:用随机森林构建预测模型,根据预测趋势(上涨/震荡/下跌)分配仓位,控制风险;

  5. 回测模块:基于backtrader实现每月轮动回测,输出回测报告(总收益、交易次数等),验证策略有效性;

  6. 主程序:支持三种运行模式(实时执行、回测、定时任务),适配不同使用场景。

二、完整可运行代码(无BUG版)

代码已做过多次测试,解决了数据爬取失败、回测报错、仓位计算异常等问题,注释详细,关键步骤都有说明,直接复制到PyCharm即可运行(需安装对应依赖)。

import pandas as pd
import numpy as np
import os
import re
import requests
import schedule
import time
import backtrader as bt
from datetime import datetime
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import MinMaxScaler
import argparse

# ======================== 配置 ========================
TOTAL_MONEY = 10000.0
MAX_DRAWDOWN = 0.05
TREND_DAYS = 60
FUTURE_DAYS = 30
PREDICT_THRESHOLD_UP = 0.002
PREDICT_THRESHOLD_DOWN = -0.008

CSV_PATH = "大类资产轮动记录_稳定版.csv"
HISTORY_DATA_PATH = "fund_history_data.csv"
LOG_PATH = "strategy_log.txt"

FUND_CODES = {
    "医疗": "012635",
    "白酒": "012414",
    "黄金": "518880"
}

# ======================== 工具函数 ========================
def log_info(msg):
    log_msg = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
    print(log_msg)
    try:
        with open(LOG_PATH, "a", encoding="utf-8") as f:
            f.write(log_msg + "\n")
    except:
        pass

# ======================== 数据获取 ========================
def get_fund_data(fund_code, days=60):
    try:
        url = f"https://fund.eastmoney.com/pingzhongdata/{fund_code}.js"
        headers = {"User-Agent": "Mozilla/5.0"}
        r = requests.get(url, headers=headers, timeout=10)
        txt = r.text
        net_pattern = re.compile(r'Data_netWorthTrend = \[(.*?)\];', re.S)
        match = net_pattern.search(txt)
        if not match:
            return {"ok": False}
        data_str = match.group(1)
        date_values = re.findall(r'"x":(\d+),"y":(\d+\.\d+)', data_str)
        df = pd.DataFrame(date_values, columns=["timestamp", "net"])
        df["timestamp"] = pd.to_datetime(df["timestamp"], unit='ms')
        df["net"] = pd.to_numeric(df["net"])
        df = df.dropna().sort_values("timestamp").tail(days)
        return {"ok": True, "history": np.array(df["net"]), "history_df": df}
    except:
        return {"ok": False}

def save_fund_history():
    log_info("📥 生成历史数据...")
    all_data = []
    for name, code in FUND_CODES.items():
        d = get_fund_data(code, 365)
        if d["ok"]:
            df = d["history_df"].copy()
            df["fund_name"] = name
            all_data.append(df)
    if all_data:
        full = pd.concat(all_data)
        full.to_csv(HISTORY_DATA_PATH, index=False, encoding="utf-8-sig")
        return True
    return False

# ======================== 预测 ========================
def create_seq_dataset(arr, step_in, step_out):
    X, y = [], []
    for i in range(len(arr) - step_in - step_out + 1):
        X.append(arr[i:i + step_in, 0])
        y.append(np.mean(arr[i + step_in:i + step_in + step_out, 0]))
    return np.array(X), np.array(y)

def predict_future_trend(history):
    data = history.reshape(-1, 1)
    scaler = MinMaxScaler((0, 1))
    scaled = scaler.fit_transform(data)
    step_in = 10
    X, y = create_seq_dataset(scaled, step_in, FUTURE_DAYS)
    if len(X) < 5:
        return data[-1, 0], data[-1, 0]
    model = RandomForestRegressor(n_estimators=50, random_state=42)
    model.fit(X, y)
    last_seq = scaled[-step_in:].reshape(1, -1)
    pred_scaled = model.predict(last_seq)
    pred = scaler.inverse_transform(pred_scaled.reshape(-1, 1))[0, 0]
    return pred, data[-1, 0]

def calc_position(history, name=""):
    max_risk = TOTAL_MONEY * MAX_DRAWDOWN
    pred, curr = predict_future_trend(history)
    change = (pred - curr) / curr if curr != 0 else 0
    if change > PREDICT_THRESHOLD_UP:
        pos = max_risk
        note = f"未来30天上涨 +{change:.2%} → 持仓"
    elif change > PREDICT_THRESHOLD_DOWN:
        pos = max_risk * 0.4
        note = f"未来30天震荡 {change:.2%} → 轻仓"
    else:
        pos = 0.0
        note = f"未来30天下跌 {change:.2%} → 空仓"
    log_info(f"📈 {name} 仓位={pos:.0f}元|{note}")
    return round(pos, 2)

# ======================== 回测(每月轮动 · 无BUG版)========================
class FundRotationStrategy(bt.Strategy):
    params = (("trend_days", 60), ("rebalance_day", 2))
    def __init__(self):
        self.funds = list(FUND_CODES.keys())
        self.last_month = None
        self.trade_count = 0

    def next(self):
        d = self.datas[0].datetime.date(0)
        if self.last_month == d.month: return
        if d.day < self.params.rebalance_day: return
        self.last_month = d.month

        best_idx = 0
        best_ret = -999
        for i, data in enumerate(self.datas):
            if len(data) < self.params.trend_days: continue
            ret = data.close[0] / data.close[-self.params.trend_days] - 1
            if ret > best_ret:
                best_ret = ret
                best_idx = i

        for i, data in enumerate(self.datas):
            self.order_target_value(data, self.broker.getvalue() * 0.95 if i == best_idx else 0)
        self.trade_count += 1

def run_backtest():
    log_info("🚀 每月轮动回测(无BUG版)")
    if not os.path.exists(HISTORY_DATA_PATH):
        save_fund_history()
    df = pd.read_csv(HISTORY_DATA_PATH, encoding="utf-8-sig")
    df["timestamp"] = pd.to_datetime(df["timestamp"])

    cerebro = bt.Cerebro()
    cerebro.broker.setcash(TOTAL_MONEY)
    cerebro.broker.setcommission(0.001)

    for name in FUND_CODES.keys():
        sub = df[df["fund_name"] == name].copy()
        sub = sub.rename(columns={"timestamp":"datetime","net":"close"}).set_index("datetime")
        sub["open"] = sub["high"] = sub["low"] = sub["close"]
        sub["volume"] = 1
        feed = bt.feeds.PandasData(dataname=sub, name=name)
        cerebro.adddata(feed)

    cerebro.addstrategy(FundRotationStrategy)
    cerebro.addanalyzer(bt.analyzers.Returns, _name="returns")
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name="dd")

    results = cerebro.run()
    final = cerebro.broker.getvalue()
    total_ret = (final - TOTAL_MONEY) / TOTAL_MONEY
    trade_count = results[0].trade_count

    log_info("="*60)
    log_info("📊 每月轮动 · 真实回测")
    log_info(f"总收益:{total_ret:.2%}")
    log_info(f"交易次数:{trade_count}")
    log_info(f"最终资产:{final:.2f} 元")
    log_info("="*60)

    report = pd.DataFrame({
        "指标": ["总收益", "交易次数", "初始资金", "最终资产"],
        "数值": [f"{total_ret:.2%}", trade_count, TOTAL_MONEY, round(final,2)]
    })
    report.to_excel("量化回测报告.xlsx", index=False)
    log_info("✅ Excel 报告已生成")

# ======================== 主程序 ========================
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--mode", default="run", choices=["run","backtest","schedule"])
    args = parser.parse_args()

    if args.mode == "backtest":
        run_backtest()
        return

    if args.mode == "run":
        log_info("📊 执行策略")
        positions = {}
        for name, code in FUND_CODES.items():
            data = get_fund_data(code, TREND_DAYS)
            if data["ok"]:
                positions[name] = calc_position(data["history"], name)
            else:
                positions[name] = 0
        total = sum(positions.values())
        log_info(f"✅ 总持仓:{total:.0f} 元|余额:{TOTAL_MONEY-total:.0f} 元")

if __name__ == "__main__":
    main()

三、依赖库安装(必看)

运行代码前,需先安装以下依赖库(复制命令到终端执行),版本无严格要求,兼容最新版即可:

pip install pandas numpy requests schedule backtrader scikit-learn openpyxl

说明:openpyxl用于生成Excel回测报告,缺少会导致报告生成失败;其他库是策略核心依赖,不可缺少。

四、使用教程(新手友好)

1. 运行模式说明(3种模式,按需选择)

  • 实时执行模式(默认):直接运行代码,自动爬取最新基金数据,计算当前各基金仓位,输出总持仓和余额;

  • 回测模式:在终端执行命令 python 文件名.py --mode backtest,自动生成历史数据,执行每月轮动回测,输出回测结果和Excel报告;

  • 定时任务模式(可扩展):代码中已导入schedule库,可自行添加定时逻辑(如每天9:30执行策略),实现自动化运行。

2. 参数修改指南(灵活适配自身需求)

配置模块的参数可自由修改,核心参数说明如下:

  • TOTAL_MONEY:初始资金,默认10000元,可改为任意金额;

  • MAX_DRAWDOWN:最大回撤,默认5%,风险承受能力高可调大(如0.1),保守可调小(如0.03);

  • FUTURE_DAYS:预测周期,默认30天,可改为15天、60天;

  • FUND_CODES:基金标的,可新增/替换,比如添加新能源、债券基金,格式保持一致即可;

  • rebalance_day:回测调仓日,默认每月2号,可改为每月10号、15号(避免月初数据不全)。

3. 常见问题排查

  • 问题1:数据获取失败:检查基金代码是否正确(可在天天基金网查询),网络是否正常,User-Agent是否更新;

  • 问题2:回测报错“no data”:历史数据未生成成功,删除HISTORY_DATA_PATH对应的文件,重新运行回测模式;

  • 问题3:Excel报告生成失败:未安装openpyxl,执行 pip install openpyxl 即可;

  • 问题4:预测结果偏差大:可调整随机森林的n_estimators参数(如改为100),或调整step_in输入序列长度。

五、策略优化方向(进阶)

该策略为基础版,适合新手入门,进阶玩家可从以下方向优化,提升策略性能:

  1. 标的优化:新增更多大类资产(如债券、新能源、指数基金),分散风险;

  2. 模型优化:替换预测模型(如LSTM、XGBoost),对比不同模型的预测效果;

  3. 仓位优化:引入更多指标(如MACD、RSI),结合多指标判断仓位,而非单一预测趋势;

  4. 定时任务:添加schedule定时逻辑,每天自动执行策略,发送仓位提醒(如邮件、企业微信);

  5. 风险控制:新增止损、止盈逻辑,进一步降低回撤风险。

六、总结

这套策略整合了数据爬取、量化预测、回测验证全流程,代码无BUG,可直接运行,适合量化新手实战练习。核心优势是兼顾稳健性和实用性,通过最大回撤控制风险,每月轮动策略避免频繁交易,适合长期持有。

大家可以根据自身的风险承受能力和投资需求,修改参数、替换标的,逐步优化策略。如果运行过程中遇到问题,欢迎在评论区留言,我会及时回复~

最后,提醒大家:量化策略仅为辅助工具,市场有风险,投资需谨慎,实际操作请结合自身判断!

更多推荐