做市商策略

ZaynPei Lv6

做市商策略 (Market Maker Strategy)是一个典型的高频交易策略,其核心目标不是预测市场的长期方向,而是通过为市场提供流动性,从买卖价差(Bid-Ask Spread)中赚取微小但频繁的利润。

  • 核心职能: 一个健康的金融市场需要随时都有人愿意买,也随时有人愿意卖。做市商就是这样的参与者,他们主动、持续地向市场报出自己的买入价(Bid)和卖出价(Ask)。

  • 盈利模式: 做市商的利润来源,正是其报出的卖出价与买入价之间的差额,即买卖价差 (Bid-Ask Spread)。假如做市商以6344的价格卖出的同时,希望能以6333的价格买入,如果双边都成交,他就赚取了11个点的价差。这是一个积少成多的过程。

核心风险:库存风险与逆向选择

做市商并非稳赚不赔,它面临着巨大的风险,其中最核心的是:

库存风险 (Inventory Risk):

  • 场景: 当市场出现单边下跌行情时,做市商的买单会不断被“聪明”的交易者成交,导致做市商积累了大量亏损的多头头寸(库存)。

  • 场景: 当市场单边上涨时,做市商的卖单会不断被成交,导致其积累大量亏损的空头头寸。

  • 本质: 做市商的目标是维持一个中性的、零风险的库存。一旦库存失衡,做市商就从一个流动性提供者,被迫变成了一个承担方向性风险的投机者。

逆向选择 (Adverse Selection):

  • 场景: 拥有信息优势或速度优势的交易者,总是在对做市商最不利的时候来与你交易。例如,当他们知道价格即将上涨时,他们会迅速吃掉你的卖单。

  • 本质: 做市商永远在和比自己“更快”或“更聪明”的对手方博弈,这要求做市商必须拥有极低延迟的交易系统和复杂的报价模型来保护自己。

策略逻辑

我们来介绍一个最简化、最基础的被动做市商模型,它的逻辑非常纯粹:

第一步:数据源: 订阅Tick级别的数据。这是最高频率的数据,包含了每一次最优买卖价(盘口)的变动。做市商策略必须工作在这样的高频数据上。

第二步:定义交易行为

  • 开多仓: 在当前的买一价 (Best Bid Price) 挂一个限价买单。

  • 开空仓: 在当前的卖一价 (Best Ask Price) 挂一个限价卖单。

  • 平多仓: 在当前的卖一价 (Best Ask Price) 挂一个限价卖单。

  • 平空仓: 在当前的买一价 (Best Bid Price) 挂一个限价买单。

第三步:执行循环

策略的目标是始终尝试在市场上同时持有一个多头仓位一个空头仓位(或者说,始终挂着一个买单和一个卖单)。

  • 如果没有多仓,就去挂买单。如果有了多仓,就去挂卖单尝试平仓。

  • 如果没有空仓,就去挂卖单。如果有了空仓,就去挂买单尝试平仓。

  • 只要买单成交后,对应的平仓卖单也能成交,一个完整的“低买高卖”循环就完成了,策略赚取了一个买卖价差。

策略代码

下面是一个非常简化的代码示例,展示了做市商策略的核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *
'''
本策略通过不断对CZCE.CF801进行:
买(卖)一价现价单开多(空)仓和卖(买)一价平多(空)仓来做市, 并以此赚取差价
回测数据为:CZCE.CF801的tick数据
回测时间为:2017-09-29 11:25:00到2017-09-29 11:30:00
需要特别注意的是:本平台对于回测对限价单固定完全成交, 本例子仅供参考.
目前只支持最近三个月的tick数据,回测时间和标的需要修改
'''
def init(context): # context 是框架提供的对象, 它在策略初始化时由 gm.api 自动传入 init 函数。可以把它理解为“全局变量对象”,策略运行期间的各种信息都可以放在里面。
# 订阅CZCE.CF801的tick数据
context.symbol = 'CZCE.CF801' # symbol为订阅的交易标的
subscribe(symbols=context.symbol, frequency='tick') # subscribe函数用于订阅行情数据, symbols为订阅的标的, frequency为数据频率, 此时订阅的是tick数据
# Tick = 最小价格变动数据, 每当市场上有新的成交或者盘口变化,就会产生一个 tick 数据。(盘口”是指市场上当前的买卖挂单情况,也就是 买卖双方的报价和数量信息)

def on_tick(context, tick): # on_tick函数是策略的核心函数, 每当有新的tick数据到达时, 框架会自动调用这个函数, 并把最新的tick数据传入tick参数
# 获取最新的盘口数据
quotes = tick['quotes'][0] # tick结构见下文, 通过调用这一行代码可以获取当前的盘口快照(买一到买五, 卖一到卖五的价格和手数)

# 获取持有的多仓, context.account() 返回 当前策略绑定的交易账户对象, position() 方法用于查询持仓, side=PositionSide_Long 表示查询多仓
position_long = context.account().position(symbol=context.symbol, side=PositionSide_Long)
# 获取持有的空仓
position_short = context.account().position(symbol=context.symbol, side=PositionSide_Short)

# 没有仓位则双向开限价单
# 若有仓位则限价单平仓
if not position_long:
# 获取买一价
price = quotes['bid_p']
print('买一价为: ', price)
# 下单买入1手多仓, order_target_volume函数用于下单, symbol为交易标的, volume为目标持仓量(调整仓位到目标手数), price为限价单价格, order_type为订单类型(限价单或市价单), position_side为持仓方向(多仓或空仓)
order_target_volume(symbol=context.symbol, volume=1, price=price, order_type=OrderType_Limit,
position_side=PositionSide_Long)
print('CZCE.CF801开限价单多仓1手')
else:
# 获取卖一价
price = quotes['ask_p']
print('卖一价为: ', price)
# 下一个卖单就可以把多仓平掉
order_target_volume(symbol=context.symbol, volume=0, price=price, order_type=OrderType_Limit,
position_side=PositionSide_Long)
print('CZCE.CF801平限价单多仓1手')
if not position_short:
# 获取卖一价
price = quotes['ask_p']
print('卖一价为: ', price)
order_target_volume(symbol=context.symbol, volume=1, price=price, order_type=OrderType_Limit,
position_side=PositionSide_Short)
print('CZCE.CF801卖一价开限价单空仓')
else:
# 获取买一价
price = quotes['bid_p']
print('买一价为: ', price)
order_target_volume(symbol=context.symbol, volume=0, price=price, order_type=OrderType_Limit,
position_side=PositionSide_Short)
print('CZCE.CF801买一价平限价单空仓')
if __name__ == '__main__':
'''
strategy_id策略ID,由系统生成
filename文件名,请与本文件名保持一致
mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
token绑定计算机的ID,可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_initial_cash回测初始资金
backtest_commission_ratio回测佣金比例
backtest_slippage_ratio回测滑点比例
backtest_transaction_ratio回测成交比例
'''

# 启动策略的回测或实盘运行, 启动后框架会自动调用 init(context) 初始化策略, 在每个回测时间点调用 on_tick 或 on_bar记录账户资金变化、持仓、盈亏等, 最终输出回测报告
run(strategy_id='strategy_id',
filename='main.py',
mode=MODE_BACKTEST,
token='token_id',
backtest_start_time='2017-09-29 11:25:00',
backtest_end_time='2017-09-29 11:30:00',
backtest_adjust=ADJUST_PREV,
backtest_initial_cash=500000,
backtest_commission_ratio=0.00006,
backtest_slippage_ratio=0.0001,
backtest_transaction_ratio=0.5)

部分代码解释:

  1. tick 是一个字典(dict), 通常包含以下主要字段:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    tick = {
    "symbol": "CZCE.CF801", # 合约代码
    "time": "2025-10-17 13:30:01.123", # tick 时间
    "last": 1234.5, # 最新成交价
    "volume": 100, # 最新成交量(通常是单笔成交量)
    "quotes": [ # 当前盘口快照
    {
    "bid": [1234.0, 1233.5, 1233.0, ...], # 买一到买五价格
    "bid_volume": [10, 15, 20, ...], # 买一到买五手数
    "ask": [1235.0, 1235.5, 1236.0, ...],# 卖一到卖五价格
    "ask_volume": [5, 10, 8, ...] # 卖一到卖五手数
    }
    ]
    }

需要注意的是,本演示策略只是用作示例,在现实中,以买一和卖一挂单不一定会成交,实际收益率也达不到示例水平。做市商策略对交易系统的延迟要求极高,且需要非常复杂的风险控制和资金管理逻辑,建议有一定量化交易经验的用户再进行尝试。