跳转至

Avellaneda-Stoikov做市策略

理论背景

Avellaneda-Stoikov模型是由Marco Avellaneda和Sasha Stoikov在2008年提出的经典做市商策略模型。该模型通过数学优化方法,帮助做市商在风险控制和利润最大化之间找到平衡。

核心问题

做市商面临的核心问题:

  1. 报价决策:如何设定买卖报价以吸引交易对手?
  2. 库存管理:如何控制库存风险,避免过度持仓?
  3. 利润优化:如何在给定风险水平下最大化期望利润?

模型假设

  • 价格遵循随机游走过程(几何布朗运动)
  • 订单到达率与价格敏感度相关
  • 做市商具有风险厌恶偏好(用风险厌恶系数γ表示)
  • 在有限时间周期T内运作

数学模型

1. 保留价格(Reservation Price)

保留价格是做市商愿意进行交易的最优价格,考虑了库存成本:

r(t,q) = S(t) - q * γ * σ² * T

符号说明:

  • 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)

最优价差是在给定风险厌恶水平下,最大化期望利润的价差:

δ = γ * σ² * T + (2/γ) * ln(1 + γ/κ)

符号说明:

  • δ: 最优价差(Optimal Spread)
  • κ: 价格敏感度参数(Price Sensitivity),通常在1.0-2.0之间
  • 其他符号同上

价差组成部分:

  1. 风险补偿项:γ * σ² * T - 补偿持有库存的风险
  2. 流动性溢价:(2/γ) * ln(1 + γ/κ) - 反映市场流动性对价差的影响

3. 最优报价(Optimal Quotes)

基于保留价格和最优价差,计算买卖报价:

bid = r - δ*/2
ask = r + δ*/2

报价逻辑:

  • 买价低于保留价格,卖价高于保留价格
  • 价差均匀分布在保留价格两侧
  • 确保买卖报价围绕保留价格对称

4. 波动率估计

历史波动率(Historical Volatility)

使用价格历史的标准差:

σ² = Var[ln(S(t+Δt)/S(t))]

Parkinson波动率(Parkinson Volatility)

使用高低价数据估计,更高效:

σ² = (1/(4*ln(2))) * E[(ln(H/L))²]

优势:

  • 只需要高低价数据,不需要频率很高的价格采样
  • 对波动率的估计更准确
  • 计算效率高

核心算法

流程

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. 记录统计信息

库存管理

  1. 库存限制:

  2. 最大库存:|q| ≤ max_inventory

  3. 库存惩罚:根据库存偏离调整报价

  4. 库存惩罚公式:

penalty = q * penalty_factor * σ
bid_adjusted = bid - penalty
ask_adjusted = ask - penalty
  1. 订单大小调整:
size_adjustment = 1.0 - min(|q| / max_inventory, 1.0)
order_size = base_size * size_adjustment

核心数据结构

策略配置(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:风险补偿项 γσ²T
  • spread_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 越大,波动率估计越平滑但延迟越大

调优策略

  1. 高波动市场:

  2. 增加 γ(提高风险厌恶)

  3. 增加 T(更长的时间周期)
  4. 减小 base_order_size(减小订单大小)

  5. 低波动市场:

  6. 减小 γ(降低风险厌恶)

  7. 减小 T(更短的时间周期)
  8. 增加 base_order_size(增大订单大小)

  9. 高流动性市场:

  10. 减小 κ(提高价格敏感度)

  11. 减小 min_spread_bps(允许更小价差)

  12. 低流动性市场:

  13. 增加 κ(降低价格敏感度)

  14. 增加 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