Avellaneda-Stoikov做市策略¶
理论背景
Avellaneda-Stoikov模型是由Marco Avellaneda和Sasha Stoikov在2008年提出的经典做市商策略模型。该模型通过数学优化方法,帮助做市商在风险控制和利润最大化之间找到平衡。
核心问题
做市商面临的核心问题:
- 报价决策:如何设定买卖报价以吸引交易对手?
- 库存管理:如何控制库存风险,避免过度持仓?
- 利润优化:如何在给定风险水平下最大化期望利润?
模型假设
- 价格遵循随机游走过程(几何布朗运动)
- 订单到达率与价格敏感度相关
- 做市商具有风险厌恶偏好(用风险厌恶系数γ表示)
- 在有限时间周期T内运作
数学模型
1. 保留价格(Reservation Price)
保留价格是做市商愿意进行交易的最优价格,考虑了库存成本:
符号说明:
r: 保留价格S(t): 当前中间价(Mid Price)q: 当前库存(Inventory),正数表示多头,负数表示空头γ: 风险厌恶系数(Risk Aversion Parameter),通常在0.01-1.0之间σ: 价格波动率(Volatility)T: 时间周期(Time Horizon),策略运行时间(秒)
物理含义:
- 如果
q > 0(持有多头),保留价格 < 中间价,倾向于卖出降低风险 - 如果
q < 0(持有空头),保留价格 > 中间价,倾向于买入降低风险 - 库存越大,价格调整幅度越大
2. 最优价差(Optimal Spread)
最优价差是在给定风险厌恶水平下,最大化期望利润的价差:
符号说明:
δ: 最优价差(Optimal Spread)κ: 价格敏感度参数(Price Sensitivity),通常在1.0-2.0之间- 其他符号同上
价差组成部分:
- 风险补偿项:
γ * σ² * T- 补偿持有库存的风险 - 流动性溢价:
(2/γ) * ln(1 + γ/κ)- 反映市场流动性对价差的影响
3. 最优报价(Optimal Quotes)
基于保留价格和最优价差,计算买卖报价:
报价逻辑:
- 买价低于保留价格,卖价高于保留价格
- 价差均匀分布在保留价格两侧
- 确保买卖报价围绕保留价格对称
4. 波动率估计
历史波动率(Historical Volatility)
使用价格历史的标准差:
Parkinson波动率(Parkinson Volatility)
使用高低价数据估计,更高效:
优势:
- 只需要高低价数据,不需要频率很高的价格采样
- 对波动率的估计更准确
- 计算效率高
核心算法
流程
1. 初始化策略参数(γ, κ, T, σ, q)
2. 订阅订单簿数据流
3. 对每个订单簿更新:
a. 更新中间价 S(t)
b. 更新波动率 σ(使用价格历史)
c. 计算保留价格 r = S - q*γ*σ²*T
d. 计算最优价差 δ* = γ*σ²*T + (2/γ)*ln(1+γ/κ)
e. 应用价差限制(min_spread ≤ δ* ≤ max_spread)
f. 计算买卖报价:
- bid = r - δ*/2 - inventory_penalty
- ask = r + δ*/2 - inventory_penalty
g. 根据库存调整订单大小
h. 提交/更新订单
4. 对每个成交事件:
a. 更新库存 q
b. 应用库存限制
c. 记录统计信息
库存管理
-
库存限制:
-
最大库存:
|q| ≤ max_inventory -
库存惩罚:根据库存偏离调整报价
-
库存惩罚公式:
- 订单大小调整:
核心数据结构
策略配置(ASConfig)
struct ASConfig {
std::string instrument_id; // 交易品种ID,如"BTCUSDT"
double risk_aversion; // 风险厌恶系数 γ (0.01-1.0)
double order_arrival_rate; // 订单到达率 λ (50-200)
double price_sensitivity; // 价格敏感度 κ (1.0-2.0)
double time_horizon; // 时间周期 T (60-600秒)
double base_order_size; // 基础订单大小
double max_position_size; // 最大持仓
double max_inventory; // 最大库存偏离
size_t volatility_window; // 波动率计算窗口
bool use_parkinson; // 是否使用Parkinson波动率
double inventory_penalty_factor; // 库存惩罚因子
double max_spread_bps; // 最大价差(基点)
double min_spread_bps; // 最小价差(基点)
};
订单簿快照(OrderBookSnapshot)
struct alignas(64) OrderBookSnapshot {
double best_bid; // 最佳买价
double best_ask; // 最佳卖价
double bid_volume; // 买一量
double ask_volume; // 卖一量
int64_t timestamp_ns; // 纳秒时间戳
// 计算中间价
inline double mid_price() const noexcept {
return (best_bid + best_ask) * 0.5;
}
// 计算价差
inline double spread() const noexcept { return best_ask - best_bid; }
};
报价更新(QuoteUpdate)
struct alignas(64) QuoteUpdate {
double bid_price; // 买价
double ask_price; // 卖价
double bid_size; // 买单大小
double ask_size; // 卖单大小
double spread; // 价差
double reservation_price; // 保留价格
};
关键代码分析
1. 保留价格计算
代码位置:include/avellaneda_stoikov.hpp - calculate_quotes()
inline QuoteUpdate AvellanedaStoikov::calculate_quotes(
int64_t timestamp_ns) noexcept {
const double mid = mid_price_; // 当前中间价
const double sigma = volatility_; // 当前波动率
const double gamma = config_.risk_aversion; // 风险厌恶系数 γ
const double q = inventory_; // 当前库存 q
const double time_remaining = config_.time_horizon; // 时间周期 T
// 计算保留价格(Reservation Price)
// r = S - q * γ * σ² * T
const double reservation_price =
mid - q * gamma * sigma * sigma * time_remaining;
关键点:
- 使用
inline关键字内联函数,减少函数调用开销 noexcept确保函数不抛出异常,允许编译器优化- 库存
q的符号决定价格调整方向
2. 最优价差计算
// 计算最优价差(Optimal Spread)
// δ* = γ * σ² * T + (2/γ) * ln(1 + γ/κ)
const double kappa = config_.price_sensitivity; // 价格敏感度 κ
const double spread_base = gamma * sigma * sigma * time_remaining;
const double spread_adjustment = (2.0 / gamma) * std::log(1.0 + gamma / kappa);
double optimal_spread = spread_base + spread_adjustment;
数学分解:
spread_base:风险补偿项γσ²Tspread_adjustment:流动性溢价项(2/γ)ln(1+γ/κ)- 两部分相加得到最优价差
3. 价差限制
// 应用价差限制(防止价差过大或过小)
const double min_spread = mid * config_.min_spread_bps / 10000.0;
const double max_spread = mid * config_.max_spread_bps / 10000.0;
optimal_spread = std::clamp(optimal_spread, min_spread, max_spread);
为什么需要限制:
- 价差过小:可能无法覆盖交易成本
- 价差过大:可能无法吸引交易对手
- 使用基点(bps,1 bps = 0.01%)便于配置
4. 买卖报价计算
// 计算买卖报价
// bid* = r - δ*/2, ask* = r + δ*/2
const double half_spread = optimal_spread * 0.5;
double bid_price = reservation_price - half_spread;
double ask_price = reservation_price + half_spread;
// 库存惩罚调整
// 进一步根据库存偏离调整报价
const double inventory_penalty = q * config_.inventory_penalty_factor * sigma;
bid_price -= inventory_penalty; // 库存为正时,降低买价
ask_price -= inventory_penalty; // 库存为正时,降低卖价
库存惩罚逻辑:
- 如果
q > 0(持有多头):inventory_penalty > 0 bid_price降低 → 更难买入ask_price降低 → 更容易卖出- 目标是减少多头库存
- 如果
q < 0(持有空头):inventory_penalty < 0 bid_price提高 → 更容易买入ask_price提高 → 更难卖出- 目标是减少空头库存
5. 订单大小调整
// 订单大小调整(根据库存偏离)
// size = base_size * (1 - |q| / max_inventory)
const double size_adjustment =
1.0 - std::min(std::abs(q) / config_.max_inventory, 1.0);
const double order_size = config_.base_order_size * size_adjustment;
调整逻辑:
- 库存为0时:
size_adjustment = 1.0,使用完整订单大小 - 库存接近最大值时:
size_adjustment → 0,减小订单大小 - 库存达到最大值时:
size_adjustment = 0,停止下单
6. 波动率更新
Parkinson波动率计算:
double PriceHistory::calculate_parkinson_volatility() const noexcept {
if (count_ < 2) return 0.0;
size_t n = std::min(count_, window_size_);
double sum = 0.0;
for (size_t i = 0; i < n; ++i) {
const auto& [high, low] = high_low_[i];
if (high > 0.0 && low > 0.0 && low > 0.0) {
double hl_ratio = std::log(high / low); // ln(H/L)
sum += hl_ratio * hl_ratio; // (ln(H/L))²
}
}
// Parkinson估计器: σ² = (1/(4*ln(2))) * mean((ln(H/L))²)
constexpr double parkinson_factor = 1.0 / (4.0 * std::log(2.0));
return std::sqrt(parkinson_factor * (sum / n));
}
Parkinson因子:
1/(4*ln(2)) ≈ 0.361- 理论推导得到的系数- 假设价格遵循几何布朗运动
- 比简单标准差估计更准确
7. 库存管理
inline void AvellanedaStoikov::on_fill(bool is_buy, double quantity) noexcept {
if (is_buy) {
inventory_ += quantity; // 买入增加库存
} else {
inventory_ -= quantity; // 卖出减少库存
}
// 限制库存范围 [-max_inventory, +max_inventory]
inventory_ =
std::clamp(inventory_, -config_.max_inventory, config_.max_inventory);
// 更新统计信息(原子操作,线程安全)
stats_.current_inventory.store(inventory_, std::memory_order_relaxed);
stats_.inventory_adjustments.fetch_add(1, std::memory_order_relaxed);
}
关键点:
- 使用
std::clamp确保库存不超出限制 - 原子操作确保多线程安全
memory_order_relaxed最小化同步开销
参数调优指南
核心参数
| 参数 | 符号 | 典型值 | 影响 |
|---|---|---|---|
| 风险厌恶系数 | γ | 0.05-0.2 | 越大,价差越大,风险越低 |
| 价格敏感度 | κ | 1.0-2.0 | 越大,价差越大,成交概率越低 |
| 时间周期 | T | 60-600秒 | 越大,价差越大,风险补偿越多 |
| 波动率窗口 | N | 50-200 | 越大,波动率估计越平滑但延迟越大 |
调优策略
-
高波动市场:
-
增加
γ(提高风险厌恶) - 增加
T(更长的时间周期) -
减小
base_order_size(减小订单大小) -
低波动市场:
-
减小
γ(降低风险厌恶) - 减小
T(更短的时间周期) -
增加
base_order_size(增大订单大小) -
高流动性市场:
-
减小
κ(提高价格敏感度) -
减小
min_spread_bps(允许更小价差) -
低流动性市场:
-
增加
κ(降低价格敏感度) - 增加
min_spread_bps(确保足够价差)
核心代码文件
- 核心策略:
include/avellaneda_stoikov.hpp
#pragma once
#include <atomic>
#include <cstdint>
#include <deque>
#include <memory>
#include "config.hpp"
#include "orderbook.hpp"
namespace avellaneda_stoikov {
// 报价更新结果
struct alignas(64) QuoteUpdate {
double bid_price;
double ask_price;
double bid_size;
double ask_size;
double spread;
double reservation_price;
};
// 策略统计信息
struct alignas(64) StrategyStats {
std::atomic<uint64_t> quote_updates{0};
std::atomic<uint64_t> orderbook_updates{0};
std::atomic<uint64_t> inventory_adjustments{0};
std::atomic<double> current_inventory{0.0};
std::atomic<double> current_mid_price{0.0};
std::atomic<double> current_volatility{0.0};
};
// 高性能Avellaneda-Stoikov策略实现
// 使用缓存行对齐避免false sharing
class alignas(128) AvellanedaStoikov {
public:
explicit AvellanedaStoikov(const ASConfig& config);
~AvellanedaStoikov() = default;
// 禁止拷贝和移动(高性能场景通常使用单例或引用)
AvellanedaStoikov(const AvellanedaStoikov&) = delete;
AvellanedaStoikov& operator=(const AvellanedaStoikov&) = delete;
AvellanedaStoikov(AvellanedaStoikov&&) = delete;
AvellanedaStoikov& operator=(AvellanedaStoikov&&) = delete;
// 处理订单簿更新 - 热路径,必须内联优化
[[nodiscard]] inline QuoteUpdate on_orderbook_update(
const OrderBookSnapshot& snapshot) noexcept;
// 处理成交事件
inline void on_fill(bool is_buy, double quantity) noexcept;
// 获取当前统计信息
[[nodiscard]] inline const StrategyStats& get_stats() const noexcept {
return stats_;
}
// 重置库存
inline void reset_inventory() noexcept {
inventory_ = 0.0;
stats_.current_inventory.store(0.0, std::memory_order_relaxed);
}
private:
// 计算报价 - 核心算法,内联优化
[[nodiscard]] inline QuoteUpdate calculate_quotes(
int64_t timestamp_ns) noexcept;
// 更新波动率
inline void update_volatility(const OrderBookSnapshot& snapshot) noexcept;
ASConfig config_;
// 关键数据使用缓存对齐,避免false sharing
alignas(64) double mid_price_;
alignas(64) double volatility_;
alignas(64) double inventory_;
// 价格历史用于波动率计算
std::unique_ptr<PriceHistory> price_history_;
// 统计信息
StrategyStats stats_;
int64_t last_update_ns_;
};
// 内联实现
inline QuoteUpdate AvellanedaStoikov::on_orderbook_update(
const OrderBookSnapshot& snapshot) noexcept {
stats_.orderbook_updates.fetch_add(1, std::memory_order_relaxed);
// 更新中间价
mid_price_ = snapshot.mid_price();
stats_.current_mid_price.store(mid_price_, std::memory_order_relaxed);
// 更新波动率
update_volatility(snapshot);
// 计算新报价
auto quote = calculate_quotes(snapshot.timestamp_ns);
stats_.quote_updates.fetch_add(1, std::memory_order_relaxed);
return quote;
}
inline void AvellanedaStoikov::on_fill(bool is_buy, double quantity) noexcept {
if (is_buy) {
inventory_ += quantity;
} else {
inventory_ -= quantity;
}
// 限制库存范围
inventory_ =
std::clamp(inventory_, -config_.max_inventory, config_.max_inventory);
stats_.current_inventory.store(inventory_, std::memory_order_relaxed);
stats_.inventory_adjustments.fetch_add(1, std::memory_order_relaxed);
}
inline QuoteUpdate AvellanedaStoikov::calculate_quotes(
int64_t timestamp_ns) noexcept {
const double mid = mid_price_;
const double sigma = volatility_;
const double gamma = config_.risk_aversion;
const double q = inventory_;
const double time_remaining = config_.time_horizon;
// 计算保留价格(Reservation Price)
// r = S - q * γ * σ² * T
const double reservation_price =
mid - q * gamma * sigma * sigma * time_remaining;
// 计算最优价差(Optimal Spread)
// δ = γ * σ² * T + (2/γ) * ln(1 + γ/κ)
const double kappa = config_.price_sensitivity;
const double spread_base = gamma * sigma * sigma * time_remaining;
const double spread_adjustment =
(2.0 / gamma) * std::log(1.0 + gamma / kappa);
double optimal_spread = spread_base + spread_adjustment;
// 应用价差限制
const double min_spread = mid * config_.min_spread_bps / 10000.0;
const double max_spread = mid * config_.max_spread_bps / 10000.0;
optimal_spread = std::clamp(optimal_spread, min_spread, max_spread);
// 计算买卖报价
const double half_spread = optimal_spread * 0.5;
double bid_price = reservation_price - half_spread;
double ask_price = reservation_price + half_spread;
// 库存惩罚调整
const double inventory_penalty = q * config_.inventory_penalty_factor * sigma;
bid_price -= inventory_penalty;
ask_price -= inventory_penalty;
// 订单大小调整(根据库存偏离)
const double size_adjustment =
1.0 - std::min(std::abs(q) / config_.max_inventory, 1.0);
const double order_size = config_.base_order_size * size_adjustment;
return QuoteUpdate{.bid_price = bid_price,
.ask_price = ask_price,
.bid_size = order_size,
.ask_size = order_size,
.spread = optimal_spread,
.reservation_price = reservation_price};
}
inline void AvellanedaStoikov::update_volatility(
const OrderBookSnapshot& snapshot) noexcept {
price_history_->add_price(snapshot.mid_price());
if (config_.use_parkinson) {
volatility_ = price_history_->calculate_parkinson_volatility();
} else {
volatility_ = price_history_->calculate_volatility();
}
stats_.current_volatility.store(volatility_, std::memory_order_relaxed);
}
} // namespace avellaneda_stoikov
- 实现文件:
src/avellaneda_stoikov.cpp
#include <algorithm>
#include <cmath>
#include "avellaneda_stoikov.hpp"
namespace avellaneda_stoikov {
AvellanedaStoikov::AvellanedaStoikov(const ASConfig& config)
: config_(config),
mid_price_(0.0),
volatility_(0.0),
inventory_(0.0),
price_history_(std::make_unique<PriceHistory>(config.volatility_window)),
last_update_ns_(0) {
// 初始化统计信息
stats_.quote_updates.store(0, std::memory_order_relaxed);
stats_.orderbook_updates.store(0, std::memory_order_relaxed);
stats_.inventory_adjustments.store(0, std::memory_order_relaxed);
stats_.current_inventory.store(0.0, std::memory_order_relaxed);
stats_.current_mid_price.store(0.0, std::memory_order_relaxed);
stats_.current_volatility.store(0.0, std::memory_order_relaxed);
}
} // namespace avellaneda_stoikov
- 配置结构:
include/config.hpp
#pragma once
#include <cstdint>
#include <string>
namespace avellaneda_stoikov {
// 缓存行大小对齐(64字节,某些系统128字节)
constexpr size_t CACHE_LINE_SIZE = 64;
// 策略配置结构体
struct ASConfig {
std::string instrument_id; // 交易品种ID
double risk_aversion; // 风险厌恶系数 γ (0.01 - 1.0)
double order_arrival_rate; // 订单到达率 λ (50 - 200)
double price_sensitivity; // 价格敏感度 κ (1.0 - 2.0)
double time_horizon; // 策略运行周期 T (60 - 600秒)
double base_order_size; // 基础订单大小
double max_position_size; // 最大持仓
double max_inventory; // 最大库存偏离
size_t volatility_window; // 波动率窗口大小
bool use_parkinson; // 是否使用Parkinson波动率
double inventory_penalty_factor; // 库存惩罚因子
double max_spread_bps; // 最大价差(基点)
double min_spread_bps; // 最小价差(基点)
// 默认构造函数
ASConfig()
: instrument_id("BTCUSDT"),
risk_aversion(0.1),
order_arrival_rate(100.0),
price_sensitivity(1.5),
time_horizon(300.0),
base_order_size(0.001),
max_position_size(0.1),
max_inventory(0.05),
volatility_window(100),
use_parkinson(true),
inventory_penalty_factor(0.5),
max_spread_bps(50.0),
min_spread_bps(5.0) {}
};
} // namespace avellaneda_stoikov
- 订单簿处理:
include/orderbook.hpp
#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
namespace avellaneda_stoikov {
// 订单簿快照 - 高性能结构,缓存对齐
struct alignas(64) OrderBookSnapshot {
double best_bid;
double best_ask;
double bid_volume;
double ask_volume;
int64_t timestamp_ns;
// 计算中间价
[[nodiscard]] inline double mid_price() const noexcept {
return (best_bid + best_ask) * 0.5;
}
// 计算价差
[[nodiscard]] inline double spread() const noexcept {
return best_ask - best_bid;
}
// 计算价差百分比(基点)
[[nodiscard]] inline double spread_bps() const noexcept {
double mid = mid_price();
return mid > 0.0 ? (spread() / mid) * 10000.0 : 0.0;
}
};
// 价格历史记录 - 用于计算波动率
class PriceHistory {
public:
explicit PriceHistory(size_t window_size)
: window_size_(window_size),
prices_(window_size),
high_low_(window_size),
count_(0) {}
// 添加价格更新
inline void add_price(double price, double high = 0.0,
double low = 0.0) noexcept {
size_t idx = count_ % window_size_;
prices_[idx] = price;
if (high > 0.0 && low > 0.0) {
high_low_[idx] = {high, low};
}
++count_;
}
// 计算历史波动率(标准差)
[[nodiscard]] double calculate_volatility() const noexcept {
if (count_ < 2) return 0.0;
size_t n = std::min(count_, window_size_);
double sum = 0.0;
double sum_sq = 0.0;
for (size_t i = 0; i < n; ++i) {
sum += prices_[i];
sum_sq += prices_[i] * prices_[i];
}
double mean = sum / n;
double variance = (sum_sq / n) - (mean * mean);
return std::sqrt(std::max(0.0, variance));
}
// 计算Parkinson波动率(使用高低价)
[[nodiscard]] double calculate_parkinson_volatility() const noexcept {
if (count_ < 2) return 0.0;
size_t n = std::min(count_, window_size_);
double sum = 0.0;
for (size_t i = 0; i < n; ++i) {
const auto& [high, low] = high_low_[i];
if (high > 0.0 && low > 0.0 && low > 0.0) {
double hl_ratio = std::log(high / low);
sum += hl_ratio * hl_ratio;
}
}
// Parkinson估计器: σ² = (1/(4*ln(2))) * mean((ln(H/L))²)
constexpr double parkinson_factor = 1.0 / (4.0 * std::log(2.0));
return std::sqrt(parkinson_factor * (sum / n));
}
[[nodiscard]] size_t count() const noexcept { return count_; }
private:
size_t window_size_;
std::vector<double> prices_;
std::vector<std::pair<double, double>> high_low_;
size_t count_;
};
} // namespace avellaneda_stoikov