返回文章列表
May 17, 2025
5 分钟阅读

基于Avellaneda-Stoikov模型的加密货币对做市商算法构建指南

做市商
加密货币
Avellaneda-Stoikov
算法交易
强化学习
PPO
DeFi

大家好!今天我将带你一步步构建一个适用于USD+/wETH和USD+/cbbtc等加密货币对的做市商算法。我们将采用Avellaneda-Stoikov(A-S)模型,并结合强化学习(PPO)算法实现动态价差优化。听起来很复杂?别担心,我会拆解成易懂的步骤,即使是初学者也能上手。

什么是做市商?为什么需要做市商?

做市商是一种策略,交易者同时挂出买单和卖单,通过买卖价差(spread)获利。在DeFi领域,做市商为市场提供流动性,减少其他参与者的滑点。

想象你是市场上的小贩,总是愿意以略低于市场价买入、略高于市场价卖出。你的利润就是买卖价差。但有个问题:如果价格突然单边波动,你可能会积累过多库存,或者反过来,手里什么都没有。

Avellaneda-Stoikov模型:让数学服务于交易

A-S模型是一种确定做市最优价格的数学方法。它的最大优势在于,不仅考虑当前市场价格,还结合了你的持仓(库存)、市场波动率和风险偏好。

核心公式如下:

δ_a = S_t + (1/γ) * ln(1 + γ/k) + q_t * σ² * T
δ_b = S_t - (1/γ) * ln(1 + γ/k) - q_t * σ² * T

其中:

  • δ_aδ_b 分别为卖出价和买入价
  • S_t 当前市场价格
  • γ 风险参数(越大价差越宽)
  • k 市场订单到达率
  • q_t 当前库存
  • σ 波动率
  • T 时间窗口

链上交易的特殊性

将算法迁移到链上,会遇到额外挑战:

  1. 延迟——区块链交易非即时,订单成交前价格可能已变
  2. Gas费——每笔交易都要支付网络手续费
  3. AMM/PMM机制——流动性池机制与传统交易所有所不同

下面看看如何在算法中考虑这些因素。

步骤1:环境搭建与数据采集

首先需要搭建环境以获取市场数据。我们用Binance API获取当前价格和订单簿深度。

std::tuple MarketMaker::get_binance_data(const std::string& pair) {
    // 实际代码中这里会请求Binance API
    // 返回:mid_price, bid, ask, bid_volume, ask_volume
    double mid_price = 2000.0;
    double bid = mid_price - 1.0;
    double ask = mid_price + 1.0;
    double bid_volume = 10.0;
    double ask_volume = 8.0;
    return {mid_price, bid, ask, bid_volume, ask_volume};
}

还需要链上指标,如gas费和网络延迟:

std::pair MarketMaker::get_onchain_metrics() {
    // 实际代码中这里会请求以太坊节点
    // 返回:gas_price (wei), latency (秒)
    return {50e9, 12.0};
}

步骤2:实现基础A-S模型

现在实现A-S模型的价差计算:

std::pair MarketMaker::calculate_spreads(double S_t, double sigma, double k, double q_t) {
    // Avellaneda-Stoikov公式
    double spread_term = (1.0 / gamma_) * log(1.0 + gamma_ / k);
    double inventory_term = q_t * sigma * sigma * T_;
    
    double delta_a = S_t + spread_term + inventory_term;  // 卖出价
    double delta_b = S_t - spread_term - inventory_term;  // 买入价
    
    return {delta_a, delta_b};
}

注意inventory_term。如果你库存为正(手里有很多币),卖出价会降低,买入价会更低,以鼓励卖出、抑制买入。反之亦然。

步骤3:适配链上交易特性

现在要考虑区块链的特殊性。先看延迟:

double MarketMaker::adjust_price_with_latency(double S_t, double sigma, double latency) {
    // 用延迟模拟价格的随机变化
    double latency_adjustment = utils::normal_dist(0.0, sigma * std::sqrt(latency));
    return S_t + latency_adjustment;
}

这里用的是随机游走模型:波动率越大、延迟越长,订单成交前价格变化越大。

再看gas费:

double MarketMaker::calculate_gas_cost(double gas_price, double trade_size) {
    const double GAS_LIMIT_PER_ORDER = 100000;  // 每单大致gas消耗
    return (gas_price * GAS_LIMIT_PER_ORDER * trade_size) / 1e18;  // wei转ETH
}

最后,适配PMM池的价差:

std::pair MarketMaker::adjust_spreads_for_pmm(double S_t, double delta_a, double delta_b, double pool_depth) {
    // 简化PMM模型:根据池深调整价差
    const double MIN_POOL_DEPTH = 10.0;
    double depth_factor = std::max(pool_depth, MIN_POOL_DEPTH) / MIN_POOL_DEPTH;
    
    // 池深越大,价差越小
    double spread_reduction = 1.0 / std::sqrt(depth_factor);
    double mid_price = (delta_a + delta_b) / 2;
    double new_delta_a = mid_price + (delta_a - mid_price) * spread_reduction;
    double new_delta_b = mid_price - (mid_price - delta_b) * spread_reduction;
    
    return {new_delta_a, new_delta_b};
}

步骤4:库存管理

为了跟踪和管理库存,创建一个简单的类:

class InventoryManager {
public:
    InventoryManager() : inventory_(0.0) {}
    
    void update_inventory(double size, bool is_buy) {
        inventory_ += is_buy ? size : -size;
    }
    
    double get_inventory() const {
        return inventory_;
    }
    
private:
    double inventory_;
};

步骤5:整合为完整算法

现在将所有组件整合为一个做市商算法:

void MarketMaker::step(double S_t, double sigma, double k, double latency, double gas_cost, double trade_size) {
    // 获取当前库存
    double current_inventory = inventory_.get_inventory();
    
    // 根据当前市场和库存计算价差
    auto [delta_a, delta_b] = calculate_spreads(S_t, sigma, k, current_inventory);
    auto [adjusted_delta_a, adjusted_delta_b] = adjust_spreads_for_onchain(S_t, delta_a, delta_b, latency, sigma, gas_cost, trade_size);
    
    // 生成独立市场价格
    double market_price = S_t + utils::normal_dist(0.0, sigma);
    
    // 判断是否成交
    bool is_buy = (market_price = adjusted_delta_a);
    
    // 执行交易并更新库存
    if (is_buy) {
        inventory_.update_inventory(trade_size, true);
        std::cout  reset();
    
    // 执行动作,获取新状态、奖励和done标志
    std::tuple, double, bool> step(const std::array& action);
    
private:
    // 获取当前环境状态
    std::vector get_state() const;
    
    MarketMaker& mm_;
    double current_inventory_;
    double current_profit_;
    int current_step_;
    int max_steps_;
    
    // 当前市场参数
    double mid_price_;
    double sigma_;
    double latency_;
    double pool_depth_;
    
    std::mt19937 rng_;
};

环境状态包括当前价格、库存、波动率、网络延迟和池深。动作为买卖价差和数量。

现在实现奖励函数:

double reward = profit_term - inventory_risk - gas_cost;

其中:

  • profit_term 交易利润
  • inventory_risk 大库存惩罚(风险)
  • gas_cost gas消耗

最后,训练PPO智能体:

void PPOTrainer::train(int episodes) {
    for (int ep = 0; ep  states;
        std::vector actions;
        std::vector rewards;
        
        while (true) {
            // 策略网络输出动作
            auto action_probs = policy_net_->forward(torch::tensor(state));
            auto action = action_probs.multinomial(1);
            
            // 环境执行一步
            auto [next_state, reward, done] = env_.step(action);
            
            // 保存转移
            states.push_back(torch::tensor(state));
            actions.push_back(action);
            rewards.push_back(reward);
            
            if (done) break;
            state = next_state;
        }
        
        // PPO策略更新
        update_policy(states, actions, rewards);
    }
}

步骤7:测试与可视化

为了测试算法,创建一个简单的仿真:

int main() {
    // 任务要求T=300秒
    MarketMaker mm(0.1, 300.0);

    // 波动率历史数据仿真
    std::vector prices = {2000.0};
    double S_t = 2000.0;
    double trade_size = 1.0;
    double initial_sigma = 0.05;  // 5%波动率

    for (int i = 0; i < 300; ++i) {
        std::cout << "Step " << i + 1 << ": ";

        // 获取数据(模拟)
        auto [mid_price, bid_ask] = mm.get_binance_data("USD+/wETH");
        auto [gas_cost, latency] = mm.get_onchain_metrics();
        
        // 随机价格波动,模拟真实市场
        S_t = mid_price + utils::normal_dist(0.0, mid_price * 0.01);

        // 计算波动率
        double sigma = mm.calculate_volatility(prices, 5);
        if (sigma < 0.01) sigma = initial_sigma;

        // 订单到达率(模拟)
        double k = 5.0;

        mm.step(S_t, sigma, k, latency, gas_cost, trade_size);

        // 更新下一步价格
        S_t += utils::normal_dist(0.0, S_t * 0.02);
        prices.push_back(S_t);
    }

    return 0;
}

后续展望

算法已完成,但还有很多优化空间:

  1. 接入真实API:用真实Binance API和以太坊节点替换模拟
  2. 改进波动率模型:用GARCH等高级模型
  3. 扩展PPO:增加更多状态和动作参数
  4. 优化gas:最小化gas消耗的策略
  5. 多币种策略:扩展到多交易对

结论

我们构建了一个兼顾链上特性、结合A-S模型与现代RL方法的做市商算法。这种方法能适应市场变化,在控制风险的同时最大化收益。

当然,真实交易中还有许多额外因素需考虑,但本算法为后续开发打下了坚实基础。请记住:算法交易不仅要有数学,更要有充分测试、监控和持续优化。

希望本文能帮助你理解做市商原理,并激励你开发自己的算法。祝交易顺利!

引文

@software{soloviov2025marketmakingavellanedastoikov,
  author = {Soloviov, Eugen},
  title = {基于Avellaneda-Stoikov模型的加密货币对做市商算法构建指南},
  year = {2025},
  url = {https://marketmaker.cc/zh/blog/post/market-making-avellaneda-stoikov},
  version = {0.1.0},
  description = {一步步教你如何用Avellaneda-Stoikov模型和PPO算法为USD+/wETH和USD+/cbbtc等加密货币对构建做市商算法。涵盖链上交易特性、库存管理、强化学习训练等。}
}

MarketMaker.cc Team

量化研究与策略

在 Telegram 中讨论