# fund_rotation **Repository Path**: ren-liangwei/fund_rotation ## Basic Information - **Project Name**: fund_rotation - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-23 - **Last Updated**: 2026-05-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Fund Rotation — 量化行业轮动策略框架 基于**相对强弱动量**与**市场状态择时**的量化策略回测与模拟交易系统。支持**基金**与**股票**双资产池,内置**数据源抽象**、**前复权计算**、**四大解耦组件**、**信号日志审计**及**参数遍历回测**,方便快速验证与迭代交易思路。 --- # 目录 - [策略原理](#策略原理) - [系统架构](#系统架构) - [核心模块说明](#核心模块说明) - [目录结构](#目录结构) - [快速开始](#快速开始) - [使用指南](#使用指南) - [扩展开发指南](#扩展开发指南) - [数据库表结构](#数据库表结构) - [常见问题](#常见问题) - [重要变更记录](#重要变更记录) --- # 策略原理 > 天之道,损有余而补不足。 ——《道德经》 本策略遵循“**先富带动后富**”的哲学思想,通过动态的仓位再平衡,将资金从过气龙头撤出,重新分配给蓄势待发的潜力行业。 - **动量效应捕捉**:计算所有标的相对于基准指数(沪深300)的**超额收益率**,选取表现最强的若干只标的集中持有。 - **市场状态择时**:通过**市场温度计**判断当前市场环境(进攻/防御),在熊市中降低仓位规避风险,在趋势市中重仓出击。 - **仓位再平衡**:定期(如每季度)进行调仓,采用**二八原则**将大部分资金分配给领跑者,少量资金布控于后方,形成“先富带动后富”的动态均衡。 --- # 系统架构 Config (JSON) │ ├─ DataProvider (Abstract) ← 行情/分红/拆分数据源 │ └─ MyDataProvider (新浪) │ ├─ MarketRegime (Abstract) ← 市场温度计:判断进攻/防御 │ ├─ MAAndPrevCloseRegime │ └─ Breakout20DayHighRegime │ ├─ StockSelector (Abstract) ← 选股器:决定买什么 │ └─ ExcessReturnSelector │ ├─ PositionSizer (Abstract) ← 仓位分配器:决定买多少 │ └─ DefaultPositionSizer │ └─ SignalLogger (Optional) ← 记录决策明细 工作流程: 1. **SignalEngine** 在调仓日按序调用各组件。 2. **MarketRegime** 判断市场状态。 3. 若进攻,**StockSelector** 输出入选标的及超额收益。 4. **PositionSizer** 根据策略参数计算每只标的的目标权重。 5. **SignalLogger** 可选地将排名、收益率、权重等明细写入日志表,供审计。 回测模块 (`BacktestEngine`) 独立于虚拟交易,通过 `TRUNCATE` 隔离回测信号,确保不同参数组合的结果不会交叉污染。 --- # 核心模块说明 ## 第一、数据源 (`data_interface.py` / `data_fetcher.py` / `my_data_provider`) - **抽象接口**:`BaseDataProvider` 定义了获取指数日线、基金/股票日线、分红、拆分、配股等方法。 - **默认实现**:`MyDataProvider`(位于 `my_data_provider/my_fetcher.py`)**完全基于新浪原生接口**,不依赖任何第三方金融库。 - **复权引擎**:`DataFetcher.calc_adj_close()` 综合处理**现金分红、份额拆分、送股、转增、配股**五类除权事件,输出前复权价格。 - **可扩展性**:只需继承 `BaseDataProvider`,在 `config.json` 中更改 `custom_module` 和 `custom_class` 即可切换数据源。 ## 第二、解耦四大组件 #### 1. MarketRegime — “何时买” - **基类**:`fund_rotation/market_regime.py:MarketRegime` - **已实现**: - `MAAndPrevCloseRegime`:经典双条件(站上均线且指数不创新低) - `Breakout20DayHighRegime`:通道突破(指数突破过去20日最高价) - **扩展**:继承基类,实现 `is_attack()` 方法即可。 #### 2. StockSelector — “买什么” - **基类**:`fund_rotation/stock_selector.py:StockSelector` - **已实现**: - `ExcessReturnSelector`:过去 N 日相对超额收益率排序,取前 `top_n` 只 - **扩展**:继承基类,实现 `select()` 方法,返回入选标的及超额收益率。 #### 3. PositionSizer — “买多少” - **基类**:`fund_rotation/position_sizer.py:PositionSizer` - **已实现**: - `DefaultPositionSizer`:二八法则等权分配。进攻时前 `top_n` 只平分 `top_weight` 比例,其余平分剩余;防御时总仓位降至 `defense_weight`。 - **扩展**:继承基类,实现 `allocate()` 方法,可实现递减权重(如5:2:1)等自定义规则。 #### 4. SignalLogger — “记录决策” - **类**:`fund_rotation/signal_logger.py:SignalLogger` - **作用**:将每次调仓的每只标的的排名、相对收益率、权重写入 `backtest_signal_detail` 表(或实盘 `signal_detail`)。 - **开关**:`config.json` 中 `enable_detail_log` 控制。默认关闭,不影响回测性能。 信号引擎 (`signal_engine.py`) - 负责调度上述四大组件,生成标准信号字典。 - 支持 `backtest_mode`:回测模式下信号写入 `backtest_signals` 表,并从内存维护 `prev_idx`,完全不依赖历史信号表;实盘模式则使用 `signals` 表。 ## 第三、回测引擎 (`backtest_engine.py`) - 在内存中模拟账户、每日逐日盯市、计算权益曲线。 - 每次回测启动时自动 `TRUNCATE` 回测专用表,保障数据隔离。 - 支持单次回测与参数网格扫描(`run_backtest.py`)。 ## 第四、模拟交易 (`trade_executor.py` / `rebalance.py`) - 用于实盘(或虚拟盘)调仓指导。 - `rebalance.py` 生成调仓指令邮件,不直接写入成交记录,避免收盘价滞后问题。 --- # 目录结构 fund_rotation/ │ ├── fund_rotation/ # 核心包 │ ├── analyzer.py # 分析工具(权益曲线、单基盈亏) │ ├── backtest_engine.py # 回测引擎 │ ├── config_loader.py # 配置加载 │ ├── data_fetcher.py # 数据采集与复权 │ ├── data_interface.py # 数据源抽象接口 │ ├── db_manager.py # 数据库管理 │ ├── market_regime.py # 市场状态判断(新) │ ├── stock_selector.py # 个股选择器(新) │ ├── position_sizer.py # 仓位分配器(新) │ ├── signal_logger.py # 信号日志记录(新) │ ├── signal_engine.py # 信号引擎 │ ├── portfolio.py # 持仓管理(部分功能被回测引擎替代) │ ├── trade_executor.py # 虚拟交易与邮件 │ └── init.py │ ├── my_data_provider/ # 自定义数据源 │ ├── my_fetcher.py # 新浪原生接口实现 │ └── init.py │ ├── config.json # 主配置文件 ├── db_schema.sql # 数据库建表语句 ├── daily_update.py # 每日数据更新 ├── run_backtest.py # 统一回测入口 ├── rebalance.py # 调仓信号生成(面向实盘) ├── test_my_fetcher.py # 数据源测试脚本 ├── requirements.txt └── README.md **已弃用/辅助文件**: - `backtest.py` / `param_sweep.py`:早期单次回测和参数扫描脚本,功能已整合进 `run_backtest.py`,可保留作为参考。 - `rebalance.py`:原用于虚拟成交并更新持仓快照,现改为指令输出模式。 --- # 快速开始 1. **安装依赖** ```bash pip install -r requirements.txt ``` ​ 2.**初始化数据库** ```sql mysql -u root -p < db_schema.sql ``` ​ 3.**修改配置文件 config.json** ​ 填入数据库连接信息、邮箱(可选)、策略参数、基金池或股票池。 4.**拉取历史数据** ```python python daily_update.py ``` ​ 5.**运行回测** ```shell # 单次回测(使用 config.json 参数) python run_backtest.py --mode single # 参数扫描(ma 40~300,步长10) python run_backtest.py --mode sweep --min 40 --max 300 --step 10 ``` # 回测 ## 单次回测(使用 config.json 参数) ```python python run_backtest.py --mode single ``` ## 参数扫描(ma 40~300,步长10) ```python python run_backtest.py --mode sweep --min 40 --max 300 --step 10 ``` ### 1.使用指南 **回测模式切换** run_backtest.py 支持两种模式: **single**:快速验证当前参数组合,生成权益曲线图及绩效指标(年化收益、最大回撤、夏普比率)。 **sweep**:遍历 ma_days 和 lookback_days 组合,输出 param_sweep_results.csv 及每组参数的权益图,用于寻找最优参数。 常用命令示例: ```shell # 小范围快速测试 python run_backtest.py --mode sweep --min 60 --max 120 --step 20 # 精细扫描 python run_backtest.py --mode sweep --min 80 --max 100 --step 5 --out fine_result.csv ``` ### 2.小范围快速测试 ``` python run_backtest.py --mode sweep --min 60 --max 120 --step 20 ``` ### 3.精细扫描 ``` python run_backtest.py --mode sweep --min 80 --max 100 --step 5 --out fine_result.csv ``` ### 4.模拟交易指导 ​ 执行 python rebalance.py 会在终端和邮件中输出待执行交易清单,包含基金代码、买卖方向、参考净值、预估金额。 交易者可在次日盘中择机执行,避免收盘价滞后假设。 ### 5.激活调仓明细日志 在 config.json 中将 enable_detail_log 设为 true,并确保数据库已创建 backtest_signal_detail 表。运行回测后,可通过 SQL 审查每只标的的排名和权重: ```SQL SELECT bsd.* FROM backtest_signal_detail bsd JOIN backtest_signals bss ON bss.id = bsd.signal_id WHERE bss.is_attack = 1; ``` # 扩展开发指南 得益于解耦设计,替换或新增策略组件非常简单。以下提供三个典型场景的 Demo。 1. ## 新增市场状态判断(MarketRegime) 需求:当沪深300的10日均线上穿30日均线时进攻。 步骤: 在 fund_rotation/market_regime.py 中添加新类: python ```python class SMACrossoverRegime(MarketRegime): def __init__(self, db, config): super().__init__(db, config) self.short = config['strategy'].get('short_ma', 10) self.long = config['strategy'].get('long_ma', 30) ``` ```python def is_attack(self, trade_date, prev_idx=None): close = self._get_close(trade_date) short_ma = self._calc_ma(trade_date, self.short) long_ma = self._calc_ma(trade_date, self.long) return short_ma > long_ma ``` 在文件底部的工厂函数 create_market_regime 中增加分支: ```python if name == 'SMACrossover': return SMACrossoverRegime(db, config) ``` 在 config.json 中修改: ```json "market_regime": "SMACrossover", "short_ma": 10, "long_ma": 30 ``` 2. ## 新增选股器(StockSelector) 需求:按过去20日日均成交量排序,选取前5名。步骤: ​ 在 fund_rotation/stock_selector.py 中添加: python ```python class VolumeSelector(StockSelector): def select(self, trade_date): lookback = self.strategy['lookback_days'] # 计算每只标的的过去N日平均成交量(需要数据库中有 volume 字段) volumes = {} for fund in self.funds: code = fund['code'] avg_vol = self._get_avg_volume(code, trade_date, lookback) volumes[code] = avg_vol sorted_funds = sorted(volumes.items(), key=lambda x: x[1], reverse=True) selected = [f[0] for f in sorted_funds[:self.strategy['top_n']]] return {'selected': selected, 'excess_returns': {}} # 可按需传递其他信息 ``` ​ 在工厂函数中注册,然后在配置中将 stock_selector 改为 Volume 即可。 3. ## 新增仓位分配器(PositionSizer) 需求:进攻时采用递减权重(5:2:1 分配给前3名)。步骤: ​ 在 fund_rotation/position_sizer.py 中添加: ```python class DecrementalSizer(PositionSizer): def allocate(self, selected_funds, is_attack, excess_returns=None): weights = {} if is_attack: ratios = [5, 2, 1][:len(selected_funds)] total_ratio = sum(ratios) top_weight = self.strategy['top_weight'] for i, code in enumerate(selected_funds): weights[code] = ratios[i] / total_ratio * top_weight # 剩余基金平分剩余权重 other_codes = [f['code'] for f in self.funds if f['code'] not in selected_funds] other_weight = (1 - top_weight) / len(other_codes) for code in other_codes: weights[code] = other_weight else: defense_total = self.strategy['defense_weight'] per_fund = defense_total / len(self.funds) for fund in self.funds: weights[fund['code']] = per_fund return weights ``` 注册后,配置 "position_sizer": "Decremental" 即生效。 # 数据库表结构 ## 行情数据表 daily_prices 存储指数和所有标的的日线数据(含前复权价)。 | 字段 | 类型 | 说明 | | ---------------------- | ------------- | ------------------- | | symbol | VARCHAR(20) | 代码(如 sh600519) | | trade_date | DATE | 交易日 | | open, high, low, close | DECIMAL(10,4) | OHLC | | volume | BIGINT | 成交量 | | adj_close | DECIMAL(10,4) | 前复权收盘价 | ## 除权事件表 | 表名 | 用途 | 关键字段 | | ------------- | ------------- | ----------------------------------- | | dividends | 现金分红 | ex_date, dividend_per_share | | splits | 份额拆分/折算 | split_date, split_ratio | | bonus_shares | 送股/转增 | ex_date, bonus_type, ratio | | rights_issues | 配股 | ex_date, rights_ratio, rights_price | ## 信号日志表 | 表名 | 用途 | 说明 | | ---------------- | -------------------- | ------------------------------------------ | | signals | 实盘/模拟交易信号 | 被 `TradeExecutor` 读取 | | backtest_signals | 回测专用信号 | 结构与 `signals` 一致,每次回测前 TRUNCATE | | signal_detail | 实盘信号明细(可选) | 记录排名、超额收益、权重 | # 常见问题 Q: 为什么回测结果在不同参数下完全一样? A: 请检查 backtest_engine.py 是否在每次运行前清空了 backtest_signals,以及是否使用了 backtest_mode=True 避免读取数据库中的旧信号。 Q: 如何切换基金/股票池? A: 修改 config.json 中的 funds 数组,并调整 security_type(fund 或 stock)。首次切换后需重新运行 daily_update.py 拉取数据。 Q: 分红、配股数据如何更新? A: daily_update.py 会自动抓取。对于股票,会请求新浪分红页面;对于基金,会调用新浪分红接口。 重要变更记录 v2.1 (2026-05) 彻底解耦:市场状态、选股、仓位分配、日志四大组件独立。 双表隔离:回测与实盘信号物理分离,支持 TRUNCATE 清理。 信号明细日志:支持可开关的调仓审计。 统一回测入口:run_backtest.py 取代分散脚本。 v2.0 (2026-04) 数据源抽象层,可切换新浪原生/自定义数据。 完整复权引擎(分红/拆分/配股)。 参数网格扫描与权益曲线绘制。 v1.0 (2026-03) 初始版本:基金行业轮动基本逻辑,AKShare 数据支持。