# quantlab-php —— PHP 版量化策略 / 信号引擎
PHP 侧是与 `quantlab/`(Python 版)**同一套因子与偏好映射规则**的轻量实现,负责两件事:
1. **策略生成**:把「风险 + 周期 + 因子权重」等偏好参数翻译成完整策略规格(JSON);
2. **截面选股**:读取日线数据,算出最新一期的因子得分,给出下个周期的推荐持仓。
它**没有回测能力**(回测在 Python 侧 `run.py`),定位是「可挂到现有 nginx + PHP-FPM 后端的信号/接口层」,
输出的配置 JSON 与 Python 侧**可互换**。
> ⚠️ 免责声明:输出为量化信号,仅供学习与研究,不构成投资建议。市场有风险,投资需谨慎。
---
## 一、环境要求
| 项目 | 要求 | 说明 |
|---|---|---|
| PHP | **7.3+**(7.3.4 实测通过) | 无任何外部依赖、无需 Composer |
| PHP 扩展 | `json`(内置)、`curl`(抓取兜底用) | `mbstring` 非必需;`curl` 缺失时会走 `file_get_contents` 抓取 |
| 数据缓存 | 复用 `../quantlab/data/kline/*.csv` | 由 Python 侧 `run.py` / `recommend.py` 下载;也可由 PHP 自行抓取 |
| 操作系统 | Linux / Windows / macOS | 纯 CLI + 标准 PHP-FPM,无平台绑定 |
**验证环境是否就绪:**
```bash
php -v # 确认 >= 7.3
php -m | grep -E "json|curl" # 确认 json / curl 扩展
```
---
## 二、目录结构
```
quantlab-php/
├── src/
│ ├── StrategyGenerator.php # 偏好参数 → 策略规格(与 Python 同一套映射)
│ └── FactorEngine.php # 因子计算 + 截面 Z-score + 选股
├── cli.php # 命令行入口(生成配置 + 最新信号)
├── api.php # HTTP API 入口(可挂 nginx + PHP-FPM)
└── README.md # 本文档
```
---
## 三、快速启动(命令行)
### 1. 准备数据
PHP 默认读取 `../quantlab/data/universe_sample.csv`(股票池)与
`../quantlab/data/kline/*.csv`(日线缓存)。二者由 Python 侧首次运行自动生成:
```bash
# 回到项目根目录,先用 Python 下载一次数据(约 120 只股票,几分钟)
python recommend.py --period 月
```
> 如果不想先跑 Python,PHP 的 `FactorEngine::fetchKline()` 会在缓存缺失时自行抓取腾讯日线接口,
> 但首次全池抓取较慢,且受 WAF 限流影响,**推荐先由 Python 侧准备缓存**。
### 2. 运行 CLI
```bash
# 默认:平衡 / 波段
php quantlab-php/cli.php
# 指定偏好
php quantlab-php/cli.php --risk 稳健 --horizon 波段
php quantlab-php/cli.php --risk 进取 --horizon 月 --style momentum=0.5,low_vol=0.2,trend=0.3
php quantlab-php/cli.php --risk 保守 --horizon 季 --positions 20 --cash 0.15
```
输出是一段 JSON,关键字段:
| 字段 | 含义 |
|---|---|
| `as_of` | 信号日(全池最新共同交易日) |
| `universe_size` | 有效股票池数量 |
| `eligible` | 通过 200 日线趋势过滤后的候选数 |
| `config` | 完整策略规格(因子权重 / 回看窗口 / 风控 / 成本) |
| `holdings` | 推荐持仓:`code`(6 位)、`name`、`weight`(权重) |
| `scores` | 各票综合得分(降序) |
| `elapsed_ms` | 耗时(毫秒) |
---
## 四、CLI 完整参数
| 选项 | 取值 | 默认 | 说明 |
|---|---|---|---|
| `--risk` | 保守/稳健/平衡/进取/激进 | 平衡 | 风险偏好:决定目标波动率、回撤熔断、持仓数、单票上限、低波倾斜 |
| `--horizon` | 短线/波段/中线/长线/半年,或 周/双周/月/季/半年 | 波段 | 持有周期:决定调仓频率、动量/趋势回看窗口、反转权重 |
| `--style` | `因子=权重,因子=权重` | 默认权重 | 覆盖因子权重(自动归一化),因子名见下表 |
| `--positions` | 整数 | 风险档默认 | 覆盖最大持仓数 |
| `--rebalance` | 交易日数 | 周期档默认 | 覆盖调仓间隔 |
| `--cash` | 0~1 小数 | 风险档默认 | 覆盖现金缓冲比例 |
| `--date` | YYYY-MM-DD | 最新交易日 | 指定信号日(回看截面) |
| `--code-file` | 文件路径 | 默认股票池 | 覆盖股票池 CSV 路径 |
| `--data-dir` | 目录路径 | 默认缓存目录 | 覆盖日线缓存目录 |
| `--no-trend-filter` | 开关 | 关闭 | 关闭「低于 200 日线不入选」过滤 |
**周期别名**(`--horizon` 可直接用周/双周/月/季/半年):
| 别名 | 内部映射 | 交易日 | 动量窗口 | 趋势窗口 | 反转权重 |
|---|---|---|---|---|---|
| 周 | 短线 | 5 | 20 | 20 | 20% |
| 双周 | 波段 | 10 | 60 | 60 | 10% |
| 月 | 中线 | 20 | 120 | 120 | 0 |
| 季 | 长线 | 60 | 180 | 200 | 0 |
| 半年 | 半年 | 120 | 250 | 250 | 0 |
> 注意:`年` 别名仅 Python 侧 `recommend.py`/`run.py` 支持;PHP 侧请直接使用 `半年` 或通过
> `--rebalance 250 --style ...` 手动指定 250 日调仓。
**可用的因子名**(`--style` 中):
`momentum`(动量)、`quality_momentum`(质量动量)、`low_vol`(低波)、`trend`(趋势)、
`reversal`(短期反转)、`high_52w`(52 周高点)、`liquidity`(流动性)。
---
## 五、HTTP API(挂 nginx + PHP-FPM)
### 1. 接口定义
`GET /api.php`,参数与 CLI 一一对应,通过 query string 传入:
```
GET /quantlab-php/api.php?risk=稳健&horizon=波段&style=momentum=0.4,low_vol=0.3,trend=0.3&positions=12
```
| 参数 | 说明 |
|---|---|
| `risk` | 风险偏好(保守/稳健/平衡/进取/激进) |
| `horizon` | 持有周期(短线/波段/中线/长线/半年,或 周/双周/月/季/半年) |
| `style` | 因子权重覆盖,逗号分隔的 `因子=权重` |
| `positions` | 最大持仓数 |
| `rebalance` | 调仓间隔(交易日) |
| `date` | 指定信号日 YYYY-MM-DD |
| `no_trend_filter` | 任意值即关闭 200 日线过滤 |
返回:`Content-Type: application/json; charset=utf-8`,结构与 CLI 一致
(`api.php` 的 `holdings` 不含 `name` 字段,其余相同)。
### 2. nginx 部署步骤
以将 `quantlab-php/` 放到站点根 `D:/srv/www/quant/` 为例(假设已有 PHP-FPM):
**第 1 步:放置代码与数据**
```bash
# 把 quantlab-php/ 整个目录复制到站点根
cp -r quantlab-php/ /srv/www/quant/
# 确保数据缓存可读(PHP 读取 Python 侧下载的缓存)
# 或直接连同 quantlab/data/ 一起复制过去
```
**第 2 步:配置 nginx location**
```nginx
server {
listen 80;
server_name quant.example.com;
root /srv/www/quant/quantlab-php;
index api.php;
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000; # 你的 PHP-FPM 监听地址
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
}
}
```
> `api.php` 内 `require __DIR__ . '/src/...'` 使用绝对路径,`$document_root` 指向 `quantlab-php/`
> 即可正确解析。若 `root` 指向 `quant/` 目录,请用 `quantlab-php/api.php` 访问。
**第 3 步:重载并验证**
```bash
nginx -s reload
# 本机测试
curl "http://127.0.0.1/quantlab-php/api.php?risk=稳健&horizon=月"
```
返回 JSON 中 `engine` 字段为 `php`,`as_of` 为最新交易日即部署成功。
### 3. 数据目录的读写权限
- 若 PHP 需要**自行抓取**日线(缓存缺失时),`src/FactorEngine.php` 中
`fetchKline($code, $startDate, $cacheDir)` 会写缓存文件,需确保 PHP-FPM 进程对
`../quantlab/data/kline/` 有**写权限**;
- 若缓存已由 Python 侧准备好,PHP 只读即可,无需写权限。
---
## 六、与 Python 版的一致性
| 环节 | Python | PHP | 说明 |
|---|---|---|---|
| 偏好 → 策略映射 | `quantlab/strategy_generator.py` | `StrategyGenerator.php` | 同一套风险/周期定义域与权重推导 |
| 因子计算 | `quantlab/factors.py` | `FactorEngine::computeFactors()` | 相同因子定义、去极值、截面 Z-score |
| 选股排序 | `quantlab/backtest.py` | `FactorEngine::selectAt()` | 相同综合得分 = Σ(因子权重 × 截面Z值) |
| 配置 JSON | 落盘 `out/*.json` | `config` 字段 | 结构一致,可互换 |
| 回测 | ✅ `run.py` | ❌ 不支持 | 回测仅 Python 侧 |
**已做跨语言一致性验证**:PHP `cli.php --horizon 月` 与 Python `recommend.py --period 月`
推荐 Top6 一致。
---
## 七、常见问题
**Q:报「风险偏好必须为 …」或「持有周期必须为 …」?**
`--risk`/`--horizon` 取值超出定义域,或存在中文编码问题。确认参数用 UTF-8 中文,且取值在
上文表格范围内。
**Q:报「未找到股票池文件」?**
默认路径 `../quantlab/data/universe_sample.csv` 不存在。先用 Python 跑一次 `recommend.py`
生成,或用 `--code-file` 指定自定义 CSV(格式:首行 `code,name`,之后每行 `6位代码,名称`)。
**Q:输出 `holdings` 为空或 `eligible` 为 0?**
大概率是 `--no-trend-filter` 未加且当前全池低于 200 日线(熊市),或缓存数据过期。
先确认缓存日期是否为最新交易日,必要时先用 Python 刷新数据。
**Q:`api.php` 返回空 / 500?**
查看 PHP-FPM 错误日志;常见原因是数据缓存目录不可读、`curl` 扩展缺失导致抓取失败。
---
## 八、免责声明
本工具输出的所有策略与信号均为基于历史价量数据的量化排序结果,仅供研究与学习,
**不构成任何投资建议**。历史表现不代表未来收益;样本池为静态大市值列表,存在幸存者偏差。
市场有风险,投资需谨慎,任何投资决策请结合自身风险承受能力独立判断,必要时咨询持牌专业机构。