763 lines
30 KiB
Markdown
763 lines
30 KiB
Markdown
# QSForex 详细说明文档
|
||
|
||
---
|
||
|
||
## 一、项目简介
|
||
|
||
**QSForex** 是一个开源的、基于事件驱动架构的外汇(Forex)量化交易系统,支持**回测**和**实盘交易**。项目由 QuantStart.com 创始人 Michael Halls-Moore 于 2015 年开发,采用 MIT 开源许可证,目前处于 "alpha" 阶段。
|
||
|
||
### 核心特性
|
||
|
||
| 特性 | 说明 |
|
||
|------|------|
|
||
| 事件驱动架构 | 回测与实盘使用同一套事件接口,策略无需修改即可从回测切换到实盘 |
|
||
| Tick 级精度 | 支持逐 tick 级别的多日、多货币对回测 |
|
||
| 交易成本 | 回测中默认包含点差(Spread)成本 |
|
||
| 多货币对 | 支持同时交易多个货币对 |
|
||
| 实盘交易 | 通过 OANDA Brokerage API 进行实盘交易 |
|
||
| 绩效分析 | 支持权益曲线、收益率、最大回撤等指标计算与可视化 |
|
||
| 单元测试 | 核心计算逻辑有完整的单元测试覆盖 |
|
||
|
||
---
|
||
|
||
## 二、项目目录结构
|
||
|
||
```
|
||
qsforex/
|
||
├── __init__.py # 顶层包入口
|
||
├── settings.py # 全局配置(OANDA 凭证、数据路径、杠杆等)
|
||
├── logging.conf # 日志配置
|
||
├── requirements.txt # Python 依赖包列表
|
||
│
|
||
├── event/ # ====== 事件系统(核心通信机制) ======
|
||
│ ├── __init__.py
|
||
│ └── event.py # 定义 TickEvent / SignalEvent / OrderEvent
|
||
│
|
||
├── data/ # ====== 数据层 ======
|
||
│ ├── __init__.py
|
||
│ ├── price.py # 历史 CSV 数据读取器(回测用)
|
||
│ └── streaming.py # OANDA Streaming API 实时数据流(实盘用)
|
||
│
|
||
├── strategy/ # ====== 策略层 ======
|
||
│ ├── __init__.py
|
||
│ └── strategy.py # TestStrategy + MovingAverageCrossStrategy
|
||
│
|
||
├── portfolio/ # ====== 组合与仓位管理 ======
|
||
│ ├── __init__.py
|
||
│ ├── portfolio.py # 资金管理、风险控制、信号执行
|
||
│ ├── position.py # 单个仓位的 P&L 计算
|
||
│ ├── portfolio_test.py # 组合模块单元测试
|
||
│ └── position_test.py # 仓位模块单元测试
|
||
│
|
||
├── execution/ # ====== 执行层 ======
|
||
│ ├── __init__.py
|
||
│ └── execution.py # SimulatedExecution + OANDAExecutionHandler
|
||
│
|
||
├── backtest/ # ====== 回测引擎 ======
|
||
│ ├── __init__.py
|
||
│ ├── backtest.py # Backtest 主循环
|
||
│ └── output.py # 可视化输出(权益曲线、回撤图)
|
||
│
|
||
├── performance/ # ====== 绩效计算 ======
|
||
│ ├── __init__.py
|
||
│ └── performance.py # 最大回撤(MDD)计算
|
||
│
|
||
├── trading/ # ====== 实盘交易 ======
|
||
│ ├── __init__.py
|
||
│ └── trading.py # 多线程实时交易循环
|
||
│
|
||
├── examples/ # ====== 示例 ======
|
||
│ ├── __init__.py
|
||
│ └── mac.py # 移动平均线交叉策略回测示例
|
||
│
|
||
└── scripts/ # ====== 辅助脚本 ======
|
||
├── generate_simulated_pair.py # 生成模拟外汇 tick 数据
|
||
└── test_performance.py # 绩效计算调试脚本
|
||
```
|
||
|
||
---
|
||
|
||
## 三、事件驱动架构详解
|
||
|
||
### 3.1 概述
|
||
|
||
整个系统围绕一个**事件队列(Event Queue)** 运转。事件是系统中各组件之间通信的唯一方式。这种设计使得各模块高度解耦,策略代码在回测和实盘之间无需修改即可复用。
|
||
|
||
### 3.2 事件类型
|
||
|
||
系统定义了三种事件,按处理顺序依次流转:
|
||
|
||
```
|
||
数据源 → TickEvent → 策略 → SignalEvent → 组合 → OrderEvent → 执行引擎
|
||
```
|
||
|
||
#### TickEvent(行情事件)
|
||
|
||
由数据层产生,包含市场报价信息。
|
||
|
||
```python
|
||
class TickEvent(Event):
|
||
def __init__(self, instrument, time, bid, ask):
|
||
self.type = 'TICK'
|
||
self.instrument = instrument # 货币对,如 "GBPUSD"
|
||
self.time = time # 时间戳
|
||
self.bid = bid # 买价
|
||
self.ask = ask # 卖价
|
||
```
|
||
|
||
#### SignalEvent(信号事件)
|
||
|
||
由策略层产生,表示一个交易决策。
|
||
|
||
```python
|
||
class SignalEvent(Event):
|
||
def __init__(self, instrument, order_type, side, time):
|
||
self.type = 'SIGNAL'
|
||
self.instrument = instrument # 货币对
|
||
self.order_type = order_type # 订单类型(当前仅支持 "market")
|
||
self.side = side # 方向:"buy" 或 "sell"
|
||
self.time = time # 产生信号的 tick 时间
|
||
```
|
||
|
||
#### OrderEvent(订单事件)
|
||
|
||
由组合层产生,表示一个需要发送到经纪商的实际订单。
|
||
|
||
```python
|
||
class OrderEvent(Event):
|
||
def __init__(self, instrument, units, order_type, side):
|
||
self.type = 'ORDER'
|
||
self.instrument = instrument # 货币对
|
||
self.units = units # 交易数量
|
||
self.order_type = order_type # 订单类型
|
||
self.side = side # 方向
|
||
```
|
||
|
||
### 3.3 事件流向图
|
||
|
||
```
|
||
┌──────────────┐
|
||
│ 数据源 │
|
||
│ (CSV/OANDA) │
|
||
└──────┬───────┘
|
||
│ TickEvent
|
||
▼
|
||
┌───────────────────────┐
|
||
│ 事件队列 (Queue) │
|
||
└───────┬───────────────┘
|
||
│
|
||
┌──────────────┼──────────────┐
|
||
▼ ▼ ▼
|
||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||
│ 策略 │ │ 组合 │ │ 执行 │
|
||
│ (Strategy)│ │(Portfolio)│ │(Execution)│
|
||
└─────┬────┘ └─────┬────┘ └──────────┘
|
||
│ │
|
||
│ SignalEvent │ OrderEvent
|
||
└─────────────┘
|
||
(放回事件队列)
|
||
```
|
||
|
||
---
|
||
|
||
## 四、各模块详细说明
|
||
|
||
### 4.1 数据层(data/)
|
||
|
||
#### 4.1.1 PriceHandler(抽象基类)
|
||
|
||
[price.py](file:///c:/Users/Administrator/Desktop/qsforex/data/price.py#L15-L76)
|
||
|
||
所有数据处理器继承自 `PriceHandler`,提供以下核心功能:
|
||
|
||
- **`_set_up_prices_dict()`**:构建价格字典,同时维护正向和反向汇率对。例如,当交易 `GBPUSD` 时,系统自动计算并维护 `USDGBP` 的 bid/ask,用于跨货币对的盈亏换算。
|
||
- **`invert_prices()`**:将 bid/ask 价格取倒数,生成反向汇率对。
|
||
|
||
#### 4.1.2 HistoricCSVPriceHandler(历史数据处理器)
|
||
|
||
[price.py](file:///c:/Users/Administrator/Desktop/qsforex/data/price.py#L77-L202)
|
||
|
||
用于回测场景,从 CSV 文件读取历史 tick 数据。
|
||
|
||
**数据格式**:兼容 DukasCopy Historical Data Feed 格式,文件名格式为 `{货币对}_{YYYYMMDD}.csv`,例如 `GBPUSD_20140112.csv`。
|
||
|
||
CSV 文件内部格式:
|
||
```
|
||
Time,Ask,Bid,AskVolume,BidVolume
|
||
12.01.2014 22:00:10.123,1.50200,1.50180,1.50,1.50
|
||
```
|
||
|
||
**处理流程**:
|
||
1. 扫描 `CSV_DATA_DIR` 目录下所有符合格式的 CSV 文件
|
||
2. 提取所有日期,去重排序
|
||
3. 按天加载数据,使用 Pandas 将多个货币对的数据按时间合并排序
|
||
4. `stream_next_tick()` 方法每次被调用时,按时间顺序输出下一个 tick
|
||
5. 每输出一个 tick,同时更新正向和反向汇率对的价格
|
||
|
||
#### 4.1.3 StreamingForexPrices(实时数据流处理器)
|
||
|
||
[streaming.py](file:///c:/Users/Administrator/Desktop/qsforex/data/streaming.py#L16-L91)
|
||
|
||
用于实盘交易场景,通过 OANDA 的 Streaming API 获取实时价格。
|
||
|
||
**处理流程**:
|
||
1. 建立到 OANDA streaming 端点的 HTTPS 连接
|
||
2. 使用 `requests.Session` 的流式传输持续接收数据
|
||
3. 逐行解析 JSON 格式的 tick 数据
|
||
4. 生成 `TickEvent` 放入事件队列
|
||
5. 同步更新正向和反向汇率对价格
|
||
|
||
---
|
||
|
||
### 4.2 策略层(strategy/)
|
||
|
||
[strategy.py](file:///c:/Users/Administrator/Desktop/qsforex/strategy/strategy.py)
|
||
|
||
策略层负责从 `TickEvent` 中分析市场数据,产生交易信号。所有策略必须实现 `calculate_signals(event)` 方法。
|
||
|
||
#### 4.2.1 TestStrategy(测试策略)
|
||
|
||
简单的测试策略,每 5 个 tick 交替买入/卖出。设计目的是持续"穿越点差",因此必然亏损,仅用于验证系统运行是否正常。
|
||
|
||
```
|
||
逻辑:
|
||
tick 0-4: 无操作
|
||
tick 5: BUY → 持仓
|
||
tick 10: SELL → 平仓
|
||
tick 15: BUY → 持仓
|
||
...循环
|
||
```
|
||
|
||
#### 4.2.2 MovingAverageCrossStrategy(双均线交叉策略)
|
||
|
||
经典的移动平均线交叉策略,仅做多。
|
||
|
||
**参数**:
|
||
| 参数 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `short_window` | 500 | 短期 SMA 窗口(tick 数) |
|
||
| `long_window` | 2000 | 长期 SMA 窗口(tick 数) |
|
||
|
||
**信号逻辑**:
|
||
- 短期 SMA 上穿长期 SMA 且未持仓 → 产生 `BUY` 信号(开多仓)
|
||
- 短期 SMA 下穿长期 SMA 且已持仓 → 产生 `SELL` 信号(平多仓)
|
||
|
||
**性能优化**:使用**滚动 SMA 算法**,避免每次重新计算完整均线:
|
||
```python
|
||
def calc_rolling_sma(self, sma_m_1, window, price):
|
||
return ((sma_m_1 * (window - 1)) + price) / window
|
||
```
|
||
该算法将时间复杂度从 O(n) 降为 O(1)。
|
||
|
||
**多货币对支持**:通过 `pairs_dict` 为每个货币对维护独立的 tick 计数、持仓状态和 SMA 值。
|
||
|
||
---
|
||
|
||
### 4.3 组合与仓位管理(portfolio/)
|
||
|
||
#### 4.3.1 Position(仓位类)
|
||
|
||
[position.py](file:///c:/Users/Administrator/Desktop/qsforex/portfolio/position.py#L5-L108)
|
||
|
||
管理单个货币对的交易仓位,支持多空双向。
|
||
|
||
**核心属性**:
|
||
| 属性 | 说明 |
|
||
|------|------|
|
||
| `home_currency` | 账户计价货币(如 GBP) |
|
||
| `position_type` | 仓位方向:`"long"` 或 `"short"` |
|
||
| `currency_pair` | 交易货币对(如 EURUSD) |
|
||
| `units` | 持仓数量 |
|
||
| `avg_price` | 平均持仓成本 |
|
||
| `cur_price` | 当前市场价格 |
|
||
| `profit_base` | 以计价货币计算的浮动盈亏 |
|
||
| `profit_perc` | 浮动盈亏百分比 |
|
||
|
||
**关键方法**:
|
||
| 方法 | 说明 |
|
||
|------|------|
|
||
| `calculate_pips()` | 计算当前盈亏点数 |
|
||
| `calculate_profit_base()` | 计算以账户货币计价的盈亏金额 |
|
||
| `update_position_price()` | 更新当前价格并重新计算盈亏 |
|
||
| `add_units(units)` | 加仓,重新计算平均成本 |
|
||
| `remove_units(units)` | 减仓,返回已实现盈亏 |
|
||
| `close_position()` | 平仓,返回已实现盈亏 |
|
||
|
||
**跨货币对换算**:对于 EUR/USD 仓位,账户以 GBP 计价时的盈亏计算:
|
||
```
|
||
Profit_GBP = Pips × USD/GBP_Rate × Units
|
||
```
|
||
|
||
#### 4.3.2 Portfolio(组合类)
|
||
|
||
[portfolio.py](file:///c:/Users/Administrator/Desktop/qsforex/portfolio/portfolio.py#L20-L193)
|
||
|
||
管理整个交易组合,负责资金管理、风险控制和信号执行。
|
||
|
||
**核心属性**:
|
||
| 属性 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `equity` | 100,000.00 | 账户总权益 |
|
||
| `balance` | = equity | 当前余额 |
|
||
| `leverage` | 20 | 杠杆倍数 |
|
||
| `risk_per_trade` | 0.02 (2%) | 单笔交易风险比例 |
|
||
| `trade_units` | equity × 0.02 | 单笔交易头寸规模 |
|
||
|
||
**关键方法**:
|
||
| 方法 | 说明 |
|
||
|------|------|
|
||
| `calc_risk_position_size()` | 计算基于风险的仓位大小 |
|
||
| `add_new_position()` | 创建新仓位 |
|
||
| `add_position_units()` | 对已有仓位加仓 |
|
||
| `remove_position_units()` | 对已有仓位减仓 |
|
||
| `close_position()` | 平掉整个仓位 |
|
||
| `update_portfolio(tick_event)` | 更新所有持仓的浮动盈亏 |
|
||
| `execute_signal(signal_event)` | 执行交易信号,生成 OrderEvent |
|
||
| `output_results()` | 输出回测结果到 CSV |
|
||
| `create_equity_file()` | 创建回测结果 CSV 文件 |
|
||
|
||
**信号执行逻辑** [execute_signal](file:///c:/Users/Administrator/Desktop/qsforex/portfolio/portfolio.py#L135-L192):
|
||
|
||
| 信号方向 | 当前仓位 | 动作 |
|
||
|---------|---------|------|
|
||
| `buy` | 无仓位 | 创建多头仓位 |
|
||
| `buy` | 有多头仓位 | 加仓 |
|
||
| `buy` | 有空头仓位 | 若 units 相等则平仓 |
|
||
| `sell` | 无仓位 | 创建空头仓位 |
|
||
| `sell` | 有多头仓位 | 若 units 相等则平仓 |
|
||
| `sell` | 有空头仓位 | 加仓 |
|
||
|
||
---
|
||
|
||
### 4.4 执行层(execution/)
|
||
|
||
[execution.py](file:///c:/Users/Administrator/Desktop/qsforex/execution/execution.py)
|
||
|
||
#### 4.4.1 ExecutionHandler(抽象基类)
|
||
|
||
定义了 `execute_order()` 接口,所有执行处理器必须实现此方法。
|
||
|
||
#### 4.4.2 SimulatedExecution(模拟执行)
|
||
|
||
回测模式下的执行器,`execute_order()` 方法为空操作。实际的成交处理由 `Portfolio` 对象直接完成。
|
||
|
||
#### 4.4.3 OANDAExecutionHandler(OANDA 执行器)
|
||
|
||
实盘模式下的执行器,通过 OANDA REST API 发送订单。
|
||
|
||
**下单流程**:
|
||
1. 建立到 OANDA API 域名的 HTTPS 连接
|
||
2. 构造 HTTP POST 请求,包含认证信息和订单参数
|
||
3. 发送到 `/v1/accounts/{account_id}/orders` 端点
|
||
4. 记录 API 响应日志
|
||
|
||
---
|
||
|
||
### 4.5 回测引擎(backtest/)
|
||
|
||
#### 4.5.1 Backtest 类
|
||
|
||
[backtest.py](file:///c:/Users/Administrator/Desktop/qsforex/backtest/backtest.py#L12-L82)
|
||
|
||
回测引擎的核心类,将所有组件组装在一起运行回测。
|
||
|
||
**初始化参数**:
|
||
| 参数 | 说明 |
|
||
|------|------|
|
||
| `pairs` | 交易货币对列表 |
|
||
| `data_handler` | 数据处理器类(如 `HistoricCSVPriceHandler`) |
|
||
| `strategy` | 策略类(如 `MovingAverageCrossStrategy`) |
|
||
| `strategy_params` | 策略参数字典 |
|
||
| `portfolio` | 组合管理类 |
|
||
| `execution` | 执行处理器类 |
|
||
| `equity` | 初始资金 |
|
||
| `heartbeat` | 事件循环心跳间隔(秒) |
|
||
| `max_iters` | 最大迭代次数 |
|
||
|
||
**回测主循环** `_run_backtest()`:
|
||
```python
|
||
while iters < max_iters and ticker.continue_backtest:
|
||
try:
|
||
event = events.get(False) # 非阻塞获取事件
|
||
except queue.Empty:
|
||
ticker.stream_next_tick() # 没有事件则推进下一个 tick
|
||
else:
|
||
if event.type == 'TICK':
|
||
strategy.calculate_signals(event) # 策略分析
|
||
portfolio.update_portfolio(event) # 更新盈亏
|
||
elif event.type == 'SIGNAL':
|
||
portfolio.execute_signal(event) # 执行信号
|
||
elif event.type == 'ORDER':
|
||
execution.execute_order(event) # 执行订单
|
||
time.sleep(heartbeat)
|
||
iters += 1
|
||
```
|
||
|
||
**关键设计**:当事件队列为空时,主动从数据源获取下一个 tick 并放入队列,而非等待。这种"拉取"模式确保回测的确定性和可复现性。
|
||
|
||
#### 4.5.2 可视化输出
|
||
|
||
[output.py](file:///c:/Users/Administrator/Desktop/qsforex/backtest/output.py)
|
||
|
||
读取 `equity.csv` 文件,使用 Matplotlib + Seaborn 绘制三张图表:
|
||
|
||
1. **权益曲线**(上图):组合价值随时间变化
|
||
2. **期间收益率**(中图):每个 tick 的收益率
|
||
3. **回撤曲线**(下图):从历史高点的回撤幅度
|
||
|
||
---
|
||
|
||
### 4.6 绩效计算(performance/)
|
||
|
||
[performance.py](file:///c:/Users/Administrator/Desktop/qsforex/performance/performance.py)
|
||
|
||
`create_drawdowns(pnl)` 函数:
|
||
|
||
- 计算权益曲线的**峰值到谷值的最大回撤(Maximum Drawdown, MDD)**
|
||
- 计算**回撤持续时间**
|
||
- 返回 `(drawdown_series, max_drawdown, max_duration)`
|
||
|
||
---
|
||
|
||
### 4.7 实盘交易(trading/)
|
||
|
||
[trading.py](file:///c:/Users/Administrator/Desktop/qsforex/trading/trading.py)
|
||
|
||
实盘交易的入口,使用**双线程**架构:
|
||
|
||
```
|
||
线程 1(价格流): StreamingForexPrices.stream_to_queue()
|
||
└── 持续从 OANDA 接收实时价格,生成 TickEvent 放入队列
|
||
|
||
线程 2(交易循环): trade()
|
||
└── 从队列获取事件 → 策略 → 组合 → 执行
|
||
```
|
||
|
||
---
|
||
|
||
## 五、配置文件说明
|
||
|
||
### 5.1 settings.py
|
||
|
||
```python
|
||
# OANDA 环境配置
|
||
ENVIRONMENTS = {
|
||
"streaming": {
|
||
"real": "stream-fxtrade.oanda.com",
|
||
"practice": "stream-fxpractice.oanda.com",
|
||
"sandbox": "stream-sandbox.oanda.com"
|
||
},
|
||
"api": {
|
||
"real": "api-fxtrade.oanda.com",
|
||
"practice": "api-fxpractice.oanda.com",
|
||
"sandbox": "api-sandbox.oanda.com"
|
||
}
|
||
}
|
||
|
||
# 数据目录和输出目录(从环境变量读取)
|
||
CSV_DATA_DIR = os.environ.get('QSFOREX_CSV_DATA_DIR', None)
|
||
OUTPUT_RESULTS_DIR = os.environ.get('QSFOREX_OUTPUT_RESULTS_DIR', None)
|
||
|
||
# 交易环境选择
|
||
DOMAIN = "practice" # "practice" / "real" / "sandbox"
|
||
|
||
# OANDA 认证
|
||
ACCESS_TOKEN = os.environ.get('OANDA_API_ACCESS_TOKEN', None)
|
||
ACCOUNT_ID = os.environ.get('OANDA_API_ACCOUNT_ID', None)
|
||
|
||
# 账户设置
|
||
BASE_CURRENCY = "GBP" # 账户基础货币
|
||
EQUITY = Decimal("100000.00") # 初始资金
|
||
```
|
||
|
||
### 5.2 logging.conf
|
||
|
||
[logging.conf](file:///c:/Users/Administrator/Desktop/qsforex/logging.conf)
|
||
|
||
配置了 root logger 和 `qsforex.trading.trading` logger,均输出到控制台,级别为 DEBUG。
|
||
|
||
---
|
||
|
||
## 六、使用流程
|
||
|
||
### 6.1 回测流程
|
||
|
||
```
|
||
步骤 1: 生成模拟数据
|
||
└── python scripts/generate_simulated_pair.py GBPUSD
|
||
|
||
步骤 2: 运行回测
|
||
└── python examples/mac.py
|
||
|
||
步骤 3: 查看结果
|
||
└── python backtest/output.py
|
||
```
|
||
|
||
### 6.2 实盘交易流程
|
||
|
||
```
|
||
步骤 1: 配置 OANDA 凭证(settings.py)
|
||
步骤 2: 编写策略(继承策略基类)
|
||
步骤 3: 启动交易
|
||
└── python trading/trading.py
|
||
```
|
||
|
||
---
|
||
|
||
## 七、技术栈
|
||
|
||
| 类别 | 依赖包 | 版本 |
|
||
|------|--------|------|
|
||
| 数值计算 | numpy | 1.9.2 |
|
||
| 科学计算 | scipy | 0.15.1 |
|
||
| 数据处理 | pandas | 0.16.1 |
|
||
| 可视化 | matplotlib | 1.4.3 |
|
||
| 统计可视化 | seaborn | 0.5.1 |
|
||
| 机器学习 | scikit-learn | 0.16.1 |
|
||
| HTTP 请求 | requests | 2.7.0 |
|
||
| 测试框架 | nose | 1.3.6 |
|
||
| Mock | mock | 1.0.1 |
|
||
| 交互环境 | ipython | 3.1.0 |
|
||
|
||
---
|
||
|
||
## 八、已知问题与改进建议
|
||
|
||
### 8.1 兼容性问题
|
||
|
||
| 问题 | 严重程度 | 说明 |
|
||
|------|---------|------|
|
||
| Python 2.7 语法 | 高 | 使用 `from __future__`、`try: import Queue`、`__metaclass__` 等旧式写法,无法在 Python 3 上直接运行 |
|
||
| 依赖版本过旧 | 高 | pandas 0.16、numpy 1.9 发布于 2015 年,存在安全漏洞 |
|
||
| OANDA API 版本 | 高 | 使用 OANDA v1 API,该版本已被废弃,需迁移到 v3 REST API |
|
||
|
||
### 8.2 代码缺陷
|
||
|
||
| 问题 | 位置 | 说明 |
|
||
|------|------|------|
|
||
| 缺少 `self.` 前缀 | [portfolio.py L165](file:///c:/Users/Administrator/Desktop/qsforex/portfolio/portfolio.py#L165) | `add_position_units` 应为 `self.add_position_units`,会导致 `NameError` |
|
||
| 无 setup.py | 根目录 | 缺少标准的 Python 包安装配置,无法通过 `pip install` 安装 |
|
||
| 硬编码 | [generate_simulated_pair.py](file:///c:/Users/Administrator/Desktop/qsforex/scripts/generate_simulated_pair.py) | 模拟数据生成的日期、初始价格等参数硬编码 |
|
||
|
||
### 8.3 改进建议
|
||
|
||
1. **Python 3 迁移**:将所有语法升级到 Python 3.6+,移除 `from __future__` 导入
|
||
2. **依赖更新**:升级到最新版本的 pandas、numpy 等核心依赖
|
||
3. **API 升级**:将 OANDA API 从 v1 迁移到 v3
|
||
4. **包管理现代化**:添加 `setup.py` 或 `pyproject.toml`
|
||
5. **Bug 修复**:修复 portfolio.py 中缺失 `self.` 的问题
|
||
6. **配置外部化**:将模拟数据生成参数从硬编码改为配置文件或命令行参数
|
||
|
||
---
|
||
|
||
## 九、扩展指南
|
||
|
||
### 9.1 编写自定义策略
|
||
|
||
只需继承或模仿现有策略,实现 `calculate_signals(event)` 方法:
|
||
|
||
```python
|
||
class MyCustomStrategy(object):
|
||
def __init__(self, pairs, events):
|
||
self.pairs = pairs
|
||
self.events = events
|
||
# 初始化你的策略状态
|
||
|
||
def calculate_signals(self, event):
|
||
if event.type == 'TICK':
|
||
# 你的策略逻辑
|
||
# 当满足条件时,生成 SignalEvent
|
||
signal = SignalEvent(pair, "market", "buy", event.time)
|
||
self.events.put(signal)
|
||
```
|
||
|
||
然后在回测或实盘入口中替换策略类即可。
|
||
|
||
### 9.2 添加新的数据源
|
||
|
||
继承 `PriceHandler` 基类,实现数据获取和 `stream_next_tick()` 方法。
|
||
|
||
---
|
||
|
||
## 十、许可证
|
||
|
||
MIT License - Copyright (c) 2015 Michael Halls-Moore
|
||
|
||
---
|
||
|
||
> **风险提示**:外汇保证金交易具有高风险,可能不适合所有投资者。过去的表现不代表未来的结果。高杠杆可能对您不利也可能对您有利。在决定投资外汇之前,您应仔细考虑您的投资目标、经验水平和风险承受能力。
|
||
|
||
---
|
||
|
||
## 十一、可复用模块分析 —— 多项目整合视角
|
||
|
||
> 本章节专门为**综合多个项目构建自有交易系统**的开发者编写,帮助你快速识别 QSForex 中最值得借鉴和复用的模块,以及它在多项目组合中的定位。
|
||
|
||
### 11.1 本项目在量化交易系统中的定位
|
||
|
||
QSForex 的核心价值在于**轻量级、教学友好、架构清晰**。它不是一个功能完备的生产级系统,而是一个**架构原型**——用最少的代码完整展示了事件驱动交易系统的骨架。在多项目整合中,QSForex 适合作为:
|
||
|
||
| 角色 | 说明 |
|
||
|------|------|
|
||
| **架构参考蓝图** | 事件驱动模式是量化交易系统的主流范式,本项目的实现简洁易懂 |
|
||
| **快速原型验证** | 当你有一个新策略想法时,可以最快速度搭出回测环境 |
|
||
| **教学/入门材料** | 代码量小、注释清晰,适合团队新成员快速理解量化系统全貌 |
|
||
|
||
---
|
||
|
||
### 11.2 模块可复用性矩阵
|
||
|
||
以下按照**可复用价值从高到低**排列,并标注每个模块在多项目整合中的建议处理方式。
|
||
|
||
#### ⭐⭐⭐⭐⭐ 强烈推荐复用/借鉴
|
||
|
||
| 模块 | 核心价值 | 可复用部分 | 跨市场适用性 |
|
||
|------|---------|-----------|-------------|
|
||
| **事件驱动架构** | ⭐⭐⭐⭐⭐ | 整个事件队列 + 事件类型设计 | 股票/期货/加密货币均可 |
|
||
| **回测引擎** | ⭐⭐⭐⭐⭐ | `Backtest._run_backtest()` 主循环 | 通用,只需替换数据源 |
|
||
| **仓位管理** | ⭐⭐⭐⭐⭐ | `Position` 类的盈亏计算逻辑 | 外汇特有,但多空双向模式通用 |
|
||
| **滚动 SMA 算法** | ⭐⭐⭐⭐ | `calc_rolling_sma()` 高效增量计算 | 所有时间序列策略通用 |
|
||
|
||
#### 详细分析
|
||
|
||
**① 事件驱动架构(最核心的复用价值)**
|
||
|
||
```
|
||
事件类型设计 → 事件队列 → 事件分发循环
|
||
```
|
||
|
||
这是 QSForex 的灵魂。无论你最终使用什么市场、什么语言、什么数据库,这个**三层解耦**的模式是量化交易系统的事实标准:
|
||
|
||
```
|
||
数据层 ──TickEvent──→ 策略层 ──SignalEvent──→ 组合层 ──OrderEvent──→ 执行层
|
||
↑ ↑ ↑ ↑
|
||
可替换 可替换 可替换 可替换
|
||
```
|
||
|
||
**迁移建议**:
|
||
- 保留事件类型定义(TickEvent / SignalEvent / OrderEvent),按需扩展(如增加 `FillEvent`、`RiskEvent`)
|
||
- 保留事件队列 + `while` 循环分发的模式
|
||
- 替换各层具体实现即可适配任何市场
|
||
|
||
**② 回测引擎主循环**
|
||
|
||
[backtest.py](file:///c:/Users/Administrator/Desktop/qsforex/backtest/backtest.py#L41-L64) 中的 `_run_backtest()` 方法是经典的回测主循环模式:
|
||
|
||
```python
|
||
while iters < max_iters and ticker.continue_backtest:
|
||
try:
|
||
event = events.get(False) # 非阻塞取事件
|
||
except queue.Empty:
|
||
ticker.stream_next_tick() # 无事件时推进数据
|
||
else:
|
||
if event.type == 'TICK':
|
||
strategy.calculate_signals(event)
|
||
portfolio.update_portfolio(event)
|
||
elif event.type == 'SIGNAL':
|
||
portfolio.execute_signal(event)
|
||
elif event.type == 'ORDER':
|
||
execution.execute_order(event)
|
||
```
|
||
|
||
**迁移建议**:
|
||
- 这个循环结构可以直接移植到任何市场(股票、期货、加密货币)
|
||
- 只需实现对应市场的 `PriceHandler` 子类即可
|
||
- 可以扩展为支持 `BarEvent`(K线事件)以适配日线级别策略
|
||
|
||
**③ 仓位盈亏计算**
|
||
|
||
[position.py](file:///c:/Users/Administrator/Desktop/qsforex/portfolio/position.py) 中的 `Position` 类实现了完整的:
|
||
- 多空双向持仓
|
||
- 平均成本计算(加仓/减仓时重新计算)
|
||
- 已实现/未实现盈亏分离
|
||
- 跨货币换算
|
||
|
||
**迁移建议**:
|
||
- 外汇特有的汇率换算部分可以剥离,核心的 `avg_price` 加权平均算法是通用的
|
||
- 期货/股票场景下,需增加保证金计算、合约乘数等字段
|
||
- 加密货币场景几乎可以直接复用
|
||
|
||
**④ 滚动 SMA 增量算法**
|
||
|
||
[strategy.py](file:///c:/Users/Administrator/Desktop/qsforex/strategy/strategy.py#L69-L70) 中的高效均线计算:
|
||
|
||
```python
|
||
def calc_rolling_sma(self, sma_m_1, window, price):
|
||
return ((sma_m_1 * (window - 1)) + price) / window
|
||
```
|
||
|
||
这个算法将 SMA 计算从 O(n) 优化到 O(1),在高频回测中效果显著。
|
||
|
||
---
|
||
|
||
#### ⭐⭐⭐ 可借鉴思路,需重新实现
|
||
|
||
| 模块 | 核心价值 | 需改造的原因 |
|
||
|------|---------|-------------|
|
||
| **数据层** | CSV 多文件按天加载、多货币对按时间归并 | 格式绑定 DukasCopy,且 Python 2.7 的 pandas 语法已过时 |
|
||
| **信号执行逻辑** | buy/sell 与多空仓位的状态机 | 逻辑有 Bug(缺少 `self.`),且不支持部分平仓 |
|
||
| **绩效计算** | 最大回撤计算 | 实现过于简单,现代系统需要 Sharpe/Sortino/Calmar 等 |
|
||
| **可视化输出** | 三图布局(权益+收益+回撤) | matplotlib 版本过旧,可用 plotly/bokeh 替换 |
|
||
|
||
---
|
||
|
||
#### ⭐⭐ 参考价值有限,建议替换
|
||
|
||
| 模块 | 原因 |
|
||
|------|------|
|
||
| **OANDA 执行器** | 使用已废弃的 OANDA v1 API,必须用 v3 重写或换经纪商 |
|
||
| **实时数据流** | 同上,API 版本过时 |
|
||
| **模拟数据生成** | 仅生成随机游走数据,无市场微观结构,建议用专业回测数据源 |
|
||
| **日志配置** | 可用 structlog 或 loguru 替代 |
|
||
|
||
---
|
||
|
||
### 11.3 多项目整合的推荐架构
|
||
|
||
如果你正在综合多个项目构建自己的交易系统,推荐的分层架构如下:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 策略层 (Strategy) │
|
||
│ 从 QSForex 借鉴:事件接口 + calculate_signals │
|
||
│ 从其他项目借鉴:因子计算、ML模型、信号组合 │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ 组合层 (Portfolio) │
|
||
│ 从 QSForex 借鉴:多空仓位管理 + 风险头寸计算 │
|
||
│ 从其他项目借鉴:凯利公式、VaR、组合优化 │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ 执行层 (Execution) │
|
||
│ 从 QSForex 借鉴:SimulatedExecution(回测模式) │
|
||
│ 从其他项目借鉴:多经纪商适配、订单路由、智能下单 │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ 数据层 (Data) │
|
||
│ 从 QSForex 借鉴:多标的按时间归并的思想 │
|
||
│ 从其他项目借鉴:实时数据库、数据清洗、复权处理 │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ 事件总线 (Event Bus) │
|
||
│ 从 QSForex 借鉴:Events Queue + 事件类型体系 ★★★ │
|
||
│ 从其他项目借鉴:Redis/Kafka 分布式事件总线 │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### 11.4 QSForex 在多项目组合中的角色总结
|
||
|
||
| 维度 | QSForex 的定位 |
|
||
|------|---------------|
|
||
| **架构教学** | 🥇 最佳入门教材——事件驱动模式的最简实现 |
|
||
| **回测引擎** | 🥈 可作为快速原型验证工具 |
|
||
| **策略开发** | 🥈 内置策略简单,但策略接口设计值得参考 |
|
||
| **生产就绪度** | ❌ 不适合直接用于生产环境 |
|
||
| **跨市场支持** | ❌ 仅外汇,需大量改造才能支持股票/期货 |
|
||
| **代码质量** | ⚠️ 有已知 Bug,需要修复后再复用 |
|
||
|
||
**推荐的复用策略**:
|
||
|
||
1. **提取事件驱动骨架**:将 `event/` + `backtest/` 的核心循环提取为通用框架
|
||
2. **修复并提取仓位管理**:修复 `portfolio.py` 的 Bug,抽象出通用的 `Position` 基类
|
||
3. **保留策略接口规范**:统一 `calculate_signals(event)` 签名作为所有策略的入口规范
|
||
4. **替换其他所有模块**:数据源、执行器、绩效分析、可视化全部用现代方案重新实现
|
||
|
||
> **一句话总结**:QSForex 的**事件驱动架构 + 回测主循环 + 仓位管理**是最值得带走的核心资产,其余模块建议用其他项目的更成熟实现替代。 |