← 기사 목록으로
May 17, 2025
5분 소요

Avellaneda-Stoikov 모델을 사용한 암호화폐 페어 마켓 메이킹 알고리즘 구축

Avellaneda-Stoikov 모델을 사용한 암호화폐 페어 마켓 메이킹 알고리즘 구축
#market making
#cryptocurrency
#Avellaneda-Stoikov
#algorithmic trading
#reinforcement learning
#PPO
#DeFi

안녕하세요! 오늘은 USD+/wETH와 USD+/cbbtc 암호화폐 페어를 위한 마켓 메이킹 알고리즘 구축 방법을 보여드리겠습니다. Avellaneda-Stoikov(A-S) 모델을 사용하고 동적 스프레드 최적화를 위해 강화 학습(PPO) 알고리즘으로 강화할 것입니다. 복잡하게 들리나요? 걱정하지 마세요, 초보 개발자도 따라할 수 있도록 모든 것을 명확한 단계로 분해하겠습니다.

마켓 메이킹 시각화 마켓 메이킹의 본질 시각화: 최적의 시장 유동성을 합성하기 위해 공정 가격 주변에서 매수/매도 주문을 지속적으로 균형 조절.

마켓 메이킹이란 무엇이며 왜 필요한가

마켓 메이킹은 트레이더가 자산의 매수 및 매도 주문을 동시에 배치하여 스프레드(가격 차이)로 수익을 얻는 전략입니다. 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는 ask 가격과 bid 가격
  • S_t는 현재 시장 가격
  • γ는 위험 매개변수(높을수록 스프레드가 넓음)
  • k는 주문 도착률
  • q_t는 현재 재고
  • σ는 변동성
  • T는 시간 수평선

온체인 거래의 특성

알고리즘을 온체인으로 이동하면 추가적인 도전이 발생합니다:

  1. 지연 – 블록체인의 트랜잭션은 즉각적이지 않으며, 주문이 실행되기 전에 가격이 변할 수 있음
  2. 가스 비용 – 모든 트랜잭션에 네트워크 수수료 필요
  3. AMM/PMM 특성 – 유동성 풀 메커니즘이 전통적 거래소와 다름

이러한 요소를 알고리즘에 어떻게 반영하는지 살펴봅시다.

1단계: 환경 설정 및 데이터 수집

먼저 시장 데이터를 가져오기 위한 환경을 설정해야 합니다. Binance API를 사용하여 현재 가격과 주문장 깊이를 가져옵니다.

std::tuple MarketMaker::get_binance_data(const std::string& pair) {
    // In real code, this would be a request to the Binance API
    // Returns: 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};
}

또한 가스 비용과 네트워크 지연 같은 온체인 메트릭도 필요합니다:

std::pair MarketMaker::get_onchain_metrics() {
    // In real code, this would be a request to an Ethereum node
    // Returns: gas_price (wei), latency (seconds)
    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 formula
    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;  // Ask price
    double delta_b = S_t - spread_term - inventory_term;  // Bid price

    return {delta_a, delta_b};
}

inventory_term에 주목하세요. 양의 재고(자산이 많음)가 있으면, ask 가격이 내려가고 bid 가격은 더 내려가서 매도를 촉진하고 매수를 제한합니다. 음의 재고는 반대입니다.

3단계: 온체인 거래를 위한 모델 적응

블록체인 특성을 고려해야 합니다. 먼저 지연부터:

double MarketMaker::adjust_price_with_latency(double S_t, double sigma, double latency) {
    // Simulate random price change due to latency
    double latency_adjustment = utils::normal_dist(0.0, sigma * std::sqrt(latency));
    return S_t + latency_adjustment;
}

여기서 랜덤 워크 모델을 사용합니다: 변동성이 높고 지연이 길수록, 주문이 실행되기 전에 가격이 더 많이 변할 수 있습니다.

이제 가스 비용을 고려합시다:

double MarketMaker::calculate_gas_cost(double gas_price, double trade_size) {
    const double GAS_LIMIT_PER_ORDER = 100000;  // Approximate value per order
    return (gas_price * GAS_LIMIT_PER_ORDER * trade_size) / 1e18;  // Convert wei to ETH
}

마지막으로 PMM 풀 특성에 맞게 스프레드를 적응시킵시다:

std::pair MarketMaker::adjust_spreads_for_pmm(double S_t, double delta_a, double delta_b, double pool_depth) {
    // Simplified PMM model: adjust spreads based on pool depth
    const double MIN_POOL_DEPTH = 10.0;
    double depth_factor = std::max(pool_depth, MIN_POOL_DEPTH) / MIN_POOL_DEPTH;

    // Reduce spreads with greater 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) {
    // Get current inventory
    double current_inventory = inventory_.get_inventory();

    // Calculate spreads based on current market conditions and 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);

    // Generate independent market price
    double market_price = S_t + utils::normal_dist(0.0, sigma);

    // Determine if trades should occur based on market price and spreads
    bool is_buy = (market_price = adjusted_delta_a);

    // Execute trades and update inventory
    if (is_buy) {
        inventory_.update_inventory(trade_size, true);
        std::cout  reset();

    // Take action and get new state, reward, and done flag
    std::tuple, double, bool> step(const std::array& action);

private:
    // Get current environment state
    std::vector get_state() const;

    MarketMaker& mm_;
    double current_inventory_;
    double current_profit_;
    int current_step_;
    int max_steps_;

    // Current market parameters
    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는 소비된 가스

마지막으로 PPO 에이전트를 훈련합시다:

void PPOTrainer::train(int episodes) {
    for (int ep = 0; ep  states;
        std::vector actions;
        std::vector rewards;

        while (true) {
            // Get action from policy
            auto action_probs = policy_net_->forward(torch::tensor(state));
            auto action = action_probs.multinomial(1);

            // Take a step in the environment
            auto [next_state, reward, done] = env_.step(action);

            // Save transition
            states.push_back(torch::tensor(state));
            actions.push_back(action);
            rewards.push_back(reward);

            if (done) break;
            state = next_state;
        }

        // Update PPO policy
        update_policy(states, actions, rewards);
    }
}

스프레드 최적화를 위한 PPO 강화 학습 에이전트 실행 중인 강화 학습: PPO 에이전트가 복잡한 시장 상태를 처리하여 최대 기대 보상을 위해 매수/매도 스프레드를 동적으로 최적화.

7단계: 테스트 및 시각화

알고리즘을 테스트하기 위한 간단한 시뮬레이션을 만듭시다:

int main() {
    // Use T = 300 seconds as specified in the task
    MarketMaker mm(0.1, 300.0);

    // Simulate historical data for volatility
    std::vector prices = {2000.0};
    double S_t = 2000.0;
    double trade_size = 1.0;
    double initial_sigma = 0.05;  // 5% volatility

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

        // Get data (stubs)
        auto [mid_price, bid_ask] = mm.get_binance_data("USD+/wETH");
        auto [gas_cost, latency] = mm.get_onchain_metrics();

        // Add random price movement to simulate a real market
        S_t = mid_price + utils::normal_dist(0.0, mid_price * 0.01);

        // Calculate volatility
        double sigma = mm.calculate_volatility(prices, 5);
        if (sigma < 0.01) sigma = initial_sigma;

        // Order arrival rate (stub)
        double k = 5.0;

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

        // Update price for next step
        S_t += utils::normal_dist(0.0, S_t * 0.02);
        prices.push_back(S_t);
    }

    return 0;
}

다음 단계

마켓 메이킹 알고리즘이 완성되었지만, 개선할 방법이 많습니다:

  1. 실제 API 연결: 스텁을 실제 Binance API와 Ethereum 노드 요청으로 대체
  2. 변동성 모델 개선: GARCH 또는 기타 고급 모델 사용
  3. PPO 확장: 상태와 액션에 더 많은 매개변수 추가
  4. 가스 최적화: 가스 비용 최소화 전략
  5. 멀티 자산 전략: 여러 페어로 동시 확장

결론

온체인 거래 특성을 고려하고 고전적인 A-S 모델과 최신 RL 방법을 모두 사용하는 마켓 메이킹 알고리즘을 구축했습니다. 이 접근법은 변화하는 시장 조건에 적응하고 리스크를 통제하면서 이익을 극대화할 수 있게 합니다.

물론 실제 거래에서는 고려해야 할 추가 요소가 많지만, 이 알고리즘은 추가 개발을 위한 견고한 기반을 제공합니다. 알고리즘 거래에서는 수학뿐만 아니라 철저한 테스트, 모니터링, 지속적인 최적화도 중요하다는 것을 기억하세요.

이 기사가 마켓 메이킹의 원리를 더 잘 이해하고 자신만의 알고리즘을 만드는 데 영감을 주었기를 바랍니다. 거래에 행운을 빕니다!

Citation

@software{soloviov2025marketmakingavellanedastoikov,
  author = {Soloviov, Eugen},
  title = {Building a Market Making Algorithm for Crypto Pairs Using the Avellaneda-Stoikov Model},
  year = {2025},
  url = {https://marketmaker.cc/en/blog/post/market-making-avellaneda-stoikov},
  version = {0.1.0},
  description = {A step-by-step guide to building a market making algorithm for USD+/wETH and USD+/cbbtc pairs using the Avellaneda-Stoikov model and PPO. Onchain trading features, inventory management, RL training.}
}
blog.disclaimer

MarketMaker.cc Team

퀀트 리서치 및 전략

Telegram에서 토론하기
Newsletter

시장에서 앞서 나가세요

뉴스레터를 구독하여 독점적인 AI 트레이딩 통찰력, 시장 분석 및 플랫폼 업데이트를 받아보세요.

귀하의 개인정보를 존중합니다. 언제든지 구독을 취소할 수 있습니다.