一、套利原理
. Z. v8 Z% o. z/ s& h, t+ G8 U/ A
! U% p W) w/ ]. {1 L. `! `- M以下就是汇总一下,以数字货币为例,举例BTC币本位(币安目前U本位交割合约除了BTC没有其他品种),蝶式套利涉及三个合约,BTC永续、BTC当季、BTC次季三个合约,计算价差公式为:价差=次季+永续-2*当季,当价差偏低时进行价差做多,则永续做多1份、次季做多1份,当季做空2份,当价差偏高时进行价差做空,则永续做空1份、次季做空1份,当季做多2份,以下为举例
+ @8 b7 H- Z( N( r$ S0 V/ I1 `EOS | 永续价格 | 次季价格 | 当季价格 | 价差 | 盈利 | 当前价格(参考做多价差阈值为1, 做空价差阈值为-1) | 7 | 9 | 8 | 0 | 偏移参考价差进行做多,当价差平多时 | 8 | 10 | 8.5 | 1 | 1 | 偏移参考价差进行做空,当价差平空时 | 6 | 8 | 7.5 | -1 | 1 | 根据上面例子来看当价差不断向外扩偏移不回归的时候,可将价差加仓变成对应的网格加仓,我自己目前是将价差求一个EMA价差,然后设置阈值,通过当前价差与EMA价差的差值跟阈值进行比较判断是否价差开仓。1 |7 K% a& X) O) u" |( b7 s1 K& a
EOS/方向 | EMA价差 | 价差 | 阈值 | 单仓份数 | 总份数 | 价差做空 | 10 | 14 | 2 | 2 | 8 | 价差做多 | 10 | 8 | 2 | 1 | 4 | 根据上表看出开仓总份数是单个的4倍,主要是永续、次季各一份,当季占两份,阈值设置可自行调整,自己设置的阈值公式为=手续费x合约均价x16,当手续费设置过小时阈值也小
$ \5 O [7 J( T( S8 ] `二、伪代码流程
' O. E8 ]' \4 j5 o% S
; c9 F* \% E0 l: ?伪代码的原因是由于没有自己使用ccxt比较抱歉,目前自己的该程序是订阅websocket 1分钟以及ticker通道,当1分钟数据推送的时候进行一些条件判断,使用rest api直接定时运行即可,以下内容我是将自己代码主程序改成伪代码形式$ `- t0 V" [* V$ ]( i' x% l6 F
# 永续 次季 当季
9 s: C: ^5 M7 uSWAP_time = 永续时间 # 通过获取最后一根K线判断
# O* y$ y- q/ K1 S4 w% u0 QNQ_time = 次季时间 # 通过获取最后一根K线判断
# N) I3 W( E+ y. ~3 k, qCQ_time = 当季时间 # 通过获取最后一根K线判断9 k6 ]" x. T5 \3 q; C( e3 {) A1 z
if (pd.to_datetime(SWAP_time) == pd.to_datetime(CQ_time)) and (pd.to_datetime(SWAP_time) == pd.to_datetime(NQ_time)):8 Z! O( C# y i3 y1 F/ n0 Q
# 永续仓位& h* g6 _" s; ?. E& x3 _) ]- D8 J4 I
swap_pos = 永续合约仓位方向: d! ^ P7 `) L; S$ x' R
# 次季仓位/ r# `- t/ F5 R6 @
nq_pos = 次季合约仓位方向
: r3 p$ R% ~: r* I1 }& v # 当季仓位# ^! { ]0 x$ J1 _2 z
cq_pos = 当季合约仓位方向1 Z6 v1 P9 |8 e G6 U
now_diff_price = 永续价格 + 次季价格 - 2 * 当季价格 # 通过ticker可得6 J7 e' o N# i5 Z4 `% I- Z
if swap_pos == 0 and nq_pos == 0 and cq_pos == 0: # 当不持仓" I. Q. |& s7 l$ \
# 当三者时间相对时进行判断
2 ]9 J, J6 \' P9 H g" k# P$ F if self.last_diff_price is None: # 当价差不存在时进行价差计算 永续+次季 - 2 * 当季8 f& U( Z0 Y/ {1 N0 D
diff_price = (self.records[self.symbols[0]]['close'] + self.records[self.symbols[1]]['close']) - 2 * \
' h0 f5 y: D) x3 R self.records[self.symbols[2]]['close']
5 d" N4 U) o1 W& X0 z* u, a # 进行价差平均2 c( n/ }' z) {: U, F- n
diff_price_mean = diff_price.ewm(self.windows, adjust=False).mean() # 求价差EMA6 Z |( s& a; W& t5 o/ h+ ?- m7 |
self.last_diff_price = diff_price_mean.iloc[-1] # 记录当前价差平均
$ [0 x" A0 n8 C avg_price = 三个合约均价
. l9 @& f+ W3 h# h self.diff_threshold = self.fee * avg_price * 16 # 计算阈值% j6 H% [0 A0 r( `. h, i6 R
* F1 h) y" M3 _! ^
amount = int((now_diff_price - self.last_diff_price) / self.diff_threshold) # 计算份数
5 ~; V8 N- Q0 Z) \ if amount >= 1: # 当大于一份进行做空7 D% p: G: ^4 \" c
contract_size = 合约面值大小 # BTC 100U 其余差不多都是10U4 j0 x, {% R1 D
balance = 对应的币本位的数量 * 对应币的价格 # 换算USDT7 i3 J r7 C# x8 A# Q1 G( f) f
# 计算下单份数8 W: L1 c$ ^! E1 n! Y+ h
contract_num = int(balance / (4 * contract_size)) # 可开张数4 Q: c/ `; e' G( y) N+ J1 D
# 计算永续实际开单进行做空
1 M3 f" [7 F6 b0 G; x; B" u; N price = 永续ticker价格 需要进行精度处理
3 k* m( L9 K Z6 z0 N # 永续做空 下单量为 contract_num * amount
' Z3 c8 {2 @2 W; b; f 做空下单函数2 r- Q9 c4 r* A# l! m
0 X3 m( O0 A. _6 ]' l+ N3 A # 计算次季实际开单进行做空9 t0 ?) z! w% j: ~+ X4 G
price = 次季ticker价格 需要进行精度处理, G' ?- K% B/ Q% }
# 次季做空 下单量为 contract_num * amount. j6 g6 O. G& F
做空下单函数% i; O+ D" s7 m6 V9 M
3 ]- Q% q5 o2 e% j4 \) i
# 计算当季实际开单进行做多 2份
; ^6 x3 c4 C( |- K' a* k; J4 H price = 当季ticker价格 需要进行精度处理 O4 `* {+ c7 s4 N( j J% s
# 当季做多 下单量为 contract_num * amount * 2
& T9 F* A: O- t 做多下单函数' ?% V! z2 x1 f' Y5 [$ V' l
self.pos_diff_price = None
1 i `% e4 S0 x( T- Z; A msg = f"价差套利进行做空,当前价差为:{now_diff_price}, 价差均值为:{self.last_diff_price}, 开仓份数:{amount}, 价差阈值:{self.diff_threshold}"
6 l$ `$ M/ x$ u logger.info(msg, caller=self)6 H1 _4 f! J$ H7 _
elif amount <= -1: # 做多) `9 Q; y% D. |+ R
contract_size = 合约面值大小 # BTC 100U 其余差不多都是10U* ~/ t" S) `' p# i6 c
balance = 对应的币本位的数量 * 对应币的价格 # 换算USDT" s+ P4 G0 e; p% b! u$ v+ v
# 计算下单份数
9 B% B) x0 Q/ {+ _ contract_num = int(balance / (4 * contract_size)) # 可开张数
$ [/ i) I4 K7 ~$ |% h% m # 计算永续实际开单进行做多) m- u% p; M, ?" a
price = 永续ticker价格 需要进行精度处理
% Q% w' w1 \. O4 k' y: B # 永续做多 下单量为 contract_num * amount$ e) O4 O- f1 f* {# _1 ?
做多下单函数
. m$ b: ~; R' G4 P5 n$ k d' ~" _; I7 D6 s( T' A
# 计算次季实际开单进行做多
7 N. w3 ?3 _0 l price = 次季ticker价格 需要进行精度处理2 v" N; i3 Z. P2 @2 m& C4 Z
# 次季做多 下单量为 contract_num * amount& u6 R" Z8 O, b" _. y' @
做多下单函数
( w3 w- I7 \. J: y) q$ m7 p3 f% a8 o
# 计算当季实际开单进行做空 2份
% ~) x, P1 p! x price = 当季ticker价格 需要进行精度处理
0 [' u% z m3 n+ e, a2 Q$ c # 当季做空 下单量为 contract_num * amount * 26 q4 t2 X5 t' j% I, v/ w. R4 W
做空下单函数
" D0 n) `0 {2 A/ h& Q- k0 G& `+ Q self.pos_diff_price = None+ p# _ V a. b$ e" C" \5 K( q
msg = f"价差套利进行做多,当前价差为:{now_diff_price}, 价差均值为:{self.last_diff_price}, 开仓份数:{amount}, 价差阈值:{self.diff_threshold}"
$ z, J4 A# {" t2 W) m- c# | logger.info(msg, caller=self)
! g. G7 P! K, `; _3 y& d( r; K self.add_pos_count = 0
5 f/ n& l5 p2 p2 t' Z8 u; E, H msg = f'当前仓位为空仓,当前价差为:{now_diff_price}, 价差均值为:{self.last_diff_price}, 是否加仓:{amount}, 价差阈值:{self.diff_threshold}': {$ z) `, @) i3 v5 \1 f
logger.info(msg, caller=self)
& U6 j+ K4 ^, S# L) |6 l+ u8 a elif swap_pos == 1 and nq_pos == 1 and cq_pos == -1: # 套利做多情况
+ B- J& T& X5 q& e7 r2 v0 S T- R if self.pos_diff_price is None: # 当持仓实际价差不存在时
+ q/ T/ r: u0 i0 y! Z9 G # 永续仓位均价
9 y1 P& z! K/ {5 V5 W: x; Q! \0 R swap_price = 永续仓位持仓均价" m; n& [) X3 d- o7 z" p
# 次季仓位均价
+ ^3 E3 n: p$ g nq_price = 次季仓位持仓均价
+ R8 R- `: s8 r0 t( \ # 当季仓位均价
. Z x0 B3 ^# o cq_price = 当季仓位持仓均价9 \' T/ J) W, U& [- T, @/ ?
self.pos_diff_price = swap_price + nq_price - 2 * cq_price, s- f2 e& ]% i. I6 F0 L' y$ L- f/ s& K
: C8 d9 O; i0 ~# P5 @9 T9 q6 u amount = int((now_diff_price - self.pos_diff_price) / self.diff_threshold) # 计算当前价差与持仓价差 判断是否加仓平仓- |) h+ _1 T1 H
if amount >= 1: # 套利平仓平多
! m4 p0 u2 ?: [, O" E) L' P9 S # 计算永续实际开单进行平多5 |6 I! h- Y2 B5 c0 p' @
quantity_swap = 永续持仓量# {* X2 x$ y- a
price = 永续ticker价格 需要进行精度处理
`8 k9 a" W* H0 U0 k# o6 a # 永续平多 平仓量quantity_swap
- ~9 k4 o% r5 w3 V 平多下单函数0 a+ f- L' Z( u* W
) c- i' }$ q/ s # 计算次季实际开单进行平多1 `& | j' a$ ]) |' z$ Z
quantity_nq = 次季持仓量4 R% B/ E! |6 z4 i2 [+ o. u
price = 次季ticker价格 需要进行精度处理: z/ {0 h# ? x! J5 H
平多下单函数
' T9 }$ X# u) B6 b
/ C- a% m1 [) ]& Q5 ~$ h1 N # 计算当季实际开单进行平空' G! b" g; q& q( P3 F) i
quantity_cq = 当季持仓量
9 s9 J0 |. K- y# s' w price = 当季ticker价格 需要进行精度处理3 M! Y$ K6 N) w" C. u
平空下单函数* X* l; W+ L* a& J+ I% Y& [; J
self.last_diff_price = None
4 r- j. |7 J3 y msg = f"价差套利进行平空,当前价差为:{now_diff_price}, 仓位价差为:{self.pos_diff_price}, 开仓份数:{amount}, 价差阈值:{self.diff_threshold}"7 q, b, C4 ~( H& P) R8 k2 F
logger.info(msg, caller=self)
9 U9 Q( V: ^2 E# ~ elif amount <= -1: # 加仓做多
/ ?9 {$ J+ r/ i7 [3 l3 ^ if self.add_pos_count < 2: # 设置加仓次 设置可加仓几次 若加仓次数太多会导致爆仓& z9 L$ g% X. D: ~
contract_size = 合约面值大小 # BTC 100U 其余差不多都是10U0 Y0 e# ?* e* M8 g
balance = 对应的币本位的数量 * 对应币的价格 # 换算USDT
, m% q. [1 i2 ^ # 计算下单份数
# n7 R, h- ^4 Z# Z! h% a contract_num = int(balance / (4 * contract_size)) # 可开张数
# ^9 c( E1 \. N4 e6 ]; } # 计算永续实际开单进行做多
! c4 y& I% y3 b2 Z- ] price = 永续ticker价格 需要进行精度处理7 o8 L9 @- J8 `
- N/ k' n# M& J/ r* V% ^6 A # 永续做多 下单量为 contract_num * amount
* z" \$ V0 j9 R 做多下单函数
! }8 F; R! A& ^& I& S2 B, V7 d
- J I! z7 a, p+ x # 计算次季实际开单进行做多! X D3 d6 n, Y' G5 T
price = 次季ticker价格 需要进行精度处理2 P, Y; V9 o+ s4 V. g' Y6 B4 I! \# E* J
& s+ d- v$ i3 m9 X/ k' ~' \: v
# 次季做多 下单量为 contract_num * amount
3 u/ S. i! O# i4 Q7 i 做多下单函数; a2 S. B+ \: Z% k8 c
5 V+ L6 r4 m3 {0 H' Z1 T. J* M- o # 计算当季实际开单进行做空 2份% G: ~' @, e, d4 Z
price = 当季ticker价格 需要进行精度处理
; ~% a1 i3 u/ ^7 X& {8 h5 }" `) B( i # 当季做空 下单量为 contract_num * amount * 24 e& f# r( I/ |6 P) G, u7 h H
做空下单函数) ? {0 ~+ \- U' w
self.add_pos_count += 1
& t' h. x& \1 L8 l" Q msg = f'套利价差做多进行加仓,当前次数为{self.add_pos_count}'
" _7 E/ I. C9 D. G' ` logger.info(msg, caller=self)
( Q6 K3 `. K9 }) S# K+ q, | msg = f'当前仓位为做多仓位,当前价差为:{now_diff_price}, 仓位价差为:{self.pos_diff_price}, 是否加仓:{amount}, 价差阈值:{self.diff_threshold}'" ^ g! O9 |8 S' @. F, \/ N& Q" J
logger.info(msg, caller=self)
3 S7 P1 H, p1 Q; f' f+ K( B7 _8 V! y
! B1 `5 I) Y* p8 D( S+ l elif swap_pos == -1 and nq_pos == -1 and cq_pos == 1: # 套利做空情况
& \& g* R! u" i: W0 @# j5 { if self.pos_diff_price is None: # 当持仓实际价差不存在时( E( q/ `8 S6 w" t, o
# 永续仓位均价
' p8 H+ T" V" I7 D9 ` swap_price = self.symbol_position_order_info.loc[condition_swap, "持仓均价"].iloc[0]
5 C8 ]1 z. d# K/ I) ^) K5 ^/ ] # 次季仓位均价8 |8 \* B- n; W% l4 m# B5 r9 `
nq_price = self.symbol_position_order_info.loc[condition_nq, "持仓均价"].iloc[0]
; h3 t( f1 L( G& a. K- u/ q m # 当季仓位均价' x% b1 G* V9 c7 n% J5 {! x/ f; B
cq_price = self.symbol_position_order_info.loc[condition_cq, "持仓均价"].iloc[0]5 i- A2 Q" ?, H
self.pos_diff_price = swap_price + nq_price - 2 * cq_price
* o6 Q/ l2 r( u8 S0 x' s4 q
* a4 f# A/ A: D2 g+ h amount = int((now_diff_price - self.pos_diff_price) / self.diff_threshold)' a5 Z2 v( s# i9 ~. l, y
if amount <= -1: # 套利平仓平空4 E# l& u: O- O5 X
# 计算永续实际开单进行平空做多 R5 ^/ c8 g1 n) {' C
quantity_swap = 永续持仓量) ~* x6 `( H4 H
! Q( g, ~; Q# I' c( T: z
price = 永续ticker价格 需要进行精度处理
! [6 W! t* y5 J, Y 平空下单函数
$ A" ~4 D# N/ |- D1 P* R
$ M2 a4 g; ]9 g( v # 计算次季实际开单进行平空做多0 G* n/ R, i3 R. Z# T. ]2 }
quantity_nq = 次季持仓量
/ D! X' ~" Q7 y5 \0 ]* F1 U price = 次季ticker价格 需要进行精度处理) g! U4 f. X; M. Y+ L: B" J
平空下单函数
1 p7 i# ^2 b. j6 b2 A5 y7 V9 I" J+ k. N# N. r6 l) [
# 计算当季实际开单进行平多做空8 M( _; {; ?; k9 g3 N2 i
quantity_cq = 当季持仓量 q& D9 K! E% L: \0 v+ k
price = 当季ticker价格 需要进行精度处理; _0 y; c% T$ s% w; O- |
平多下单函数9 H. e- Y) B$ |
self.last_diff_price = None
. y4 Q9 x& i/ H2 x msg = f"价差套利进行平多,当前价差为:{now_diff_price}, 仓位价差为:{self.pos_diff_price}, 开仓份数:{amount}, 价差阈值:{self.diff_threshold}"3 Q1 f7 b, c; a$ s2 o k8 N
logger.info(msg, caller=self)
& @/ U5 }5 i* t0 c/ P+ A, a elif amount <= 1: # 加仓做空
' t6 r9 Z4 r$ i5 l7 w3 C if self.add_pos_count < 2: # 设置加仓次
/ J" d D& t/ O+ O7 N6 Z contract_size = 合约面值大小 # BTC 100U 其余差不多都是10U
! `0 \$ f2 j# I$ `1 _ balance = 对应的币本位的数量 * 对应币的价格 # 换算USDT! C) F4 T- B" n7 m/ I) ?, S" q! O
# 计算下单份数
+ }( @0 N4 o! X8 P7 [7 m9 W contract_num = int(balance / (4 * contract_size)) # 可开张数
. @5 v) F" ~, r # 计算永续实际开单进行做空* Z+ H9 d. z0 _9 V9 `) w* ]
price = 永续ticker价格0 Z3 j" \# X0 t6 x- F
需要进行精度处理
0 k; x$ g' h2 {( q- i) d2 _. h. p # 永续做空 下单量为 contract_num * amount! |* q% E# n( B- V, t) A! h. ?/ i \
做空下单函数8 Q/ z# U" o# t7 J$ G
; a3 r6 z6 R3 M6 l- d+ }% g # 计算次季实际开单进行做空
7 T& B8 R4 Q/ ]- q- C e" Y$ V price = 次季ticker价格
7 S O+ |. W) Q! D- G/ q 需要进行精度处理
* g2 x; h9 y, v7 Y # 次季做空 下单量为 contract_num * amount
; P: P8 h7 x/ z6 }# u% ` 做空下单函数& X/ u& W+ z* I& \5 H* R( _: w
8 ?# e* Z% _( r6 f z6 t6 u; L # 计算当季实际开单进行做多 2份
/ t2 Q- M: Z \: e* s price = 当季ticker价格
% O/ \8 T6 _& G4 M: Y! v! m& J0 l 需要进行精度处理8 f1 w! Q. t* Z: Y6 K2 Z! M
# 当季做多 下单量为 contract_num * amount * 2
3 v0 C! e& Y/ l2 I- F+ Q: A 做多下单函数
5 R# l j+ S7 d9 z$ j: D2 s self.add_pos_count += 10 N+ J d5 m; u! F$ c
msg = f'套利价差做空进行加仓,当前次数为{self.add_pos_count}' y8 U& |# w( \, V# n
logger.info(msg, caller=self)
1 s" R1 B# _3 p& ^2 \% x4 _ msg = f'当前仓位为做空仓位,当前价差为:{now_diff_price}, 仓位价差为:{self.pos_diff_price}, 是否加仓:{amount}, 价差阈值:{self.diff_threshold}'
" k6 N5 ^1 ?/ e3 \2 C8 E4 i logger.info(msg, caller=self)
( }. d1 n7 r! W" z; f8 }# A else:
# \7 \& U( ]& x) ~* j9 w5 T( {6 O msg = '当前持仓有问题,请赶快检查'% L! t" @5 [6 M- g8 e, a% V8 O1 T+ [- `% t8 l
仓位异常发送钉钉消息 由于下单都是挂单模式 可能会短暂的仓位不平衡情况
7 P8 V6 o( d; f- [! J. \三、注意事项! I w6 c$ J* \' i4 P! i4 p
4 l2 U9 Z; t \' P) Z1.在币安交易所由于除了BTC有交割合约,其余品种没有交割合约,此策略是在币本位上面运行,目前没有采取对冲模型,纯套利赚币,在OKEX上面则可以直接使用U本位交割合约。1 z+ X1 U* n5 k3 ~2 S
2.选择品种最好采用流动性好的品种,币本位挂单手续费是万1,吃单是万5,若使用吃单模式价差阈值则会变大,造成开仓信号会减少很多,目前在ETH合约进行操作,之前设置手续费为万5时一天都没有交易信号,目前是设置为万2,一天还有几次成交机会2 G& [6 O& q; I& O$ `
3.加仓次数不易太多,目前采用的是第一次开仓时1倍杠杆满仓模式,当后面加仓的时候就是变相使用加杠杆, v# K" v. I( O/ ?
4.挂单模式需要专门一个订单检查模块对订单不断进行检测进行撤单重新挂单处理7 H: l# J' R% D% Y/ P
5.持仓信息单位需要在PC端设置为张数,目前仓位检测只是方向检测,如果需要更准确的检测最好是永续仓位+次季仓位=当季仓位: ^ u3 `. O6 I% ?
四、总结5 l" n' z7 V9 U! K" {1 D3 O
) M4 G* X& }/ G! I% d, {
刚开始是想通过套利进行刷手续费,但是就目前策略运行来看,频率比较低(ETH每天2-3次机会与吃资金费用差不多),可能选择其他波动大的品种机会会比较多一点,希望有尝试的老板提出其他波动好的品种进行尝试,本次伪代码形式是第一次这样操作,若有不明白以及有误的地方希望有老板指出. u8 a9 _# T) k) ] I/ o3 u
代码附件:- ] |7 ]0 t7 f h
2 ~+ C+ L, D, \8 u
|