Paired trading

来源:https://uqer.io/community/share/54895a8df9f06c31c3950ca0

配对交易

策略思路

寻找走势相关且股价相近的一对股票,根据其价格变动买卖

策略实现

历史前五日的Pearson相关系数若大于给定的阈值则触发买卖操作

  1. from scipy.stats.stats import pearsonr
  2. start = datetime(2013, 1, 1)
  3. end = datetime(2014, 12, 1)
  4. benchmark = 'HS300'
  5. universe = ['000559.XSHE', '600126.XSHG']
  6. capital_base = 1e6
  7. corlen = 5
  8. def initialize(account):
  9. add_history('hist', corlen)
  10. account.cutoff = 0.9
  11. account.prev_prc1 = 0
  12. account.prev_prc2 = 0
  13. account.prev_prcb = 0
  14. def handle_data(account, data):
  15. stk1 = universe[0]
  16. stk2 = universe[1]
  17. prc1 = data[stk1]['closePrice']
  18. prc2 = data[stk2]['closePrice']
  19. prcb = data['HS300']['return']
  20. px1 = account.hist[stk1]['closePrice'].values
  21. px2 = account.hist[stk2]['closePrice'].values
  22. pxb = account.hist['HS300']['return'].values
  23. corval, pval = pearsonr(px1, px2)
  24. mov1, mov2 = adj(prc1, prc2, prcb, account.prev_prc1, account.prev_prc2, account.prev_prcb)
  25. amount =1e4 / prc2
  26. if (mov1 > 0) and (abs(corval) > account.cutoff):
  27. order(stk2, amount)
  28. elif (mov1 < 0) and (abs(corval) > account.cutoff):
  29. if (account.position.stkpos.get(stk2, 0) > amount):
  30. order(stk2, -amount)
  31. else:
  32. order_to(stk2, 0)
  33. amount =1e4 / prc1
  34. if (mov2 > 0) and (abs(corval) > account.cutoff):
  35. order(stk1, amount)
  36. elif (mov2 < 0) and (abs(corval) > account.cutoff):
  37. if (account.position.stkpos.get(stk1, 0) > amount):
  38. order(stk1, -amount)
  39. else:
  40. order_to(stk1, 0)
  41. account.prev_prc1 = prc1
  42. account.prev_prc2 = prc2
  43. account.prev_prcb = prcb
  44. def dmv(curr, prev):
  45. delta = curr / prev - 1
  46. return delta
  47. def adj(x, y, base, prev_x, prev_y, prev_base):
  48. dhs = dmv(base, prev_base)
  49. dx = dmv(x, prev_x) - dhs
  50. dy = dmv(y, prev_y) - dhs
  51. return (dx, dy)

Paired trading - 图1

  1. min(bt.cash)
  2. 232096.85369499651
  1. import pandas as pd
  2. import numpy as np
  3. from datetime import datetime
  4. import quartz
  5. import quartz.backtest as qb
  6. import quartz.performance as qp
  7. from quartz.api import *
  8. from scipy.stats.stats import pearsonr
  9. start = datetime(2013, 1, 1) # 回测起始时间
  10. end = datetime(2014, 12, 1) # 回测结束时间
  11. benchmark = 'HS300' # 使用沪深 300 作为参考标准
  12. capital_base = 1e6 # 起始资金
  13. corlen = 5
  14. def initialize(account): # 初始化虚拟账户状态
  15. add_history('hist', corlen)
  16. account.cutoff = 0.9
  17. account.prev_prc1 = 0
  18. account.prev_prc2 = 0
  19. account.prev_prcb = 0
  20. def handle_data(account, data): # 每个交易日的买入卖出指令
  21. stk1 = universe[0]
  22. stk2 = universe[1]
  23. prc1 = data[stk1]['closePrice']
  24. prc2 = data[stk2]['closePrice']
  25. prcb = data['HS300']['return']
  26. px1 = account.hist[stk1]['closePrice'].values
  27. px2 = account.hist[stk2]['closePrice'].values
  28. pxb = account.hist['HS300']['return'].values
  29. corval, pval = pearsonr(px1, px2)
  30. mov1, mov2 = adj(prc1, prc2, prcb, account.prev_prc1, account.prev_prc2, account.prev_prcb)
  31. #amount = int( 0.08 * capital_base / prc2)
  32. amount =1e4 / prc2
  33. if (mov1 > 0) and (abs(corval) > account.cutoff):
  34. order(stk2, amount)
  35. elif (mov1 < 0) and (abs(corval) > account.cutoff):
  36. if (account.position.stkpos.get(stk2, 0) > amount):
  37. order(stk2, -amount)
  38. else:
  39. order_to(stk2, 0)
  40. #amount = int(0.08 * capital_base / prc1)
  41. amount =1e4 / prc1
  42. if (mov2 > 0) and (abs(corval) > account.cutoff):
  43. order(stk1, amount)
  44. elif (mov2 < 0) and (abs(corval) > account.cutoff):
  45. if (account.position.stkpos.get(stk1, 0) > amount):
  46. order(stk1, -amount)
  47. else:
  48. order_to(stk1, 0)
  49. account.prev_prc1 = prc1
  50. account.prev_prc2 = prc2
  51. account.prev_prcb = prcb
  52. def dmv(curr, prev):
  53. delta = curr / prev - 1
  54. return delta
  55. def adj(x, y, base, prev_x, prev_y, prev_base):
  56. dhs = dmv(base, prev_base)
  57. dx = dmv(x, prev_x) - dhs
  58. dy = dmv(y, prev_y) - dhs
  59. return (dx, dy)
  60. pool_raw = pd.read_csv("po.pair.2012.csv")
  61. pool = []
  62. for i in range(len(pool_raw)):
  63. s1, s2 = pool_raw.loc[i].tolist()
  64. if [s2, s1] not in pool:
  65. pool.append([s1, s2])
  66. outfile = []
  67. for i, universe in enumerate(pool):
  68. print i
  69. try:
  70. bt = qb.backtest(start, end, benchmark, universe, capital_base, initialize = initialize, handle_data = handle_data)
  71. perf = qp.perf_parse(bt)
  72. outfile.append(universe + [perf["annualized_return"], perf["sharpe"]])
  73. except:
  74. pass
  75. keys = ['stock1', 'stock2', 'annualized_return', 'sharpe']
  76. outdict = {}
  77. outfile = zip(*sorted(outfile, key=lambda x:x[2], reverse=True))
  78. for i,k in enumerate(keys):
  79. outdict[k] = outfile[i]
  80. outdict = pd.DataFrame(outdict).loc[:, keys]
  81. outdict
  82. ['000066.XSHE', '000707.XSHE']
  83. ['000066.XSHE', '600117.XSHG']
  84. ['000066.XSHE', '600126.XSHG']
  85. ['000066.XSHE', '600819.XSHG']
  86. ['000089.XSHE', '600035.XSHG']
  87. ['000089.XSHE', '600037.XSHG']
  88. ['000089.XSHE', '600595.XSHG']
  89. ['000159.XSHE', '000967.XSHE']
  90. ['000159.XSHE', '600595.XSHG']
  91. ['000417.XSHE', '000541.XSHE']
  92. ['000417.XSHE', '000685.XSHE']
  93. ['000417.XSHE', '600875.XSHG']
  94. ['000425.XSHE', '000528.XSHE']
  95. ['000507.XSHE', '600391.XSHG']
  96. ['000541.XSHE', '000987.XSHE']
  97. ['000541.XSHE', '600330.XSHG']
  98. ['000541.XSHE', '600883.XSHG']
  99. ['000554.XSHE', '000707.XSHE']
  100. ['000559.XSHE', '600026.XSHG']
  101. ['000559.XSHE', '600126.XSHG']
  102. ['000559.XSHE', '600477.XSHG']
  103. ['000559.XSHE', '600581.XSHG']
  104. ['000559.XSHE', '601666.XSHG']
  105. ['000635.XSHE', '000707.XSHE']
  106. ['000635.XSHE', '600068.XSHG']
  107. ['000635.XSHE', '600117.XSHG']
  108. ['000635.XSHE', '600188.XSHG']
  109. ['000635.XSHE', '600295.XSHG']
  110. ['000635.XSHE', '600550.XSHG']
  111. ['000635.XSHE', '600819.XSHG']
  112. ['000635.XSHE', '601168.XSHG']
  113. ['000635.XSHE', '601233.XSHG']
  114. ['000650.XSHE', '600261.XSHG']
  115. ['000683.XSHE', '000936.XSHE']
  116. ['000683.XSHE', '600595.XSHG']
  117. ['000685.XSHE', '000988.XSHE']
  118. ['000685.XSHE', '601101.XSHG']
  119. ['000698.XSHE', '000949.XSHE']
  120. ['000707.XSHE', '000911.XSHE']
  121. ['000707.XSHE', '000969.XSHE']
  122. ['000707.XSHE', '000987.XSHE']
  123. ['000707.XSHE', '600117.XSHG']
  124. ['000707.XSHE', '600295.XSHG']
  125. ['000707.XSHE', '600550.XSHG']
  126. ['000707.XSHE', '600831.XSHG']
  127. ['000707.XSHE', '601168.XSHG']
  128. ['000707.XSHE', '601233.XSHG']
  129. ['000708.XSHE', '600327.XSHG']
  130. ['000709.XSHE', '601107.XSHG']
  131. ['000709.XSHE', '601618.XSHG']
  132. ['000717.XSHE', '600282.XSHG']
  133. ['000717.XSHE', '600307.XSHG']
  134. ['000717.XSHE', '600808.XSHG']
  135. ['000761.XSHE', '600320.XSHG']
  136. ['000761.XSHE', '600548.XSHG']
  137. ['000822.XSHE', '600117.XSHG']
  138. ['000830.XSHE', '600068.XSHG']
  139. ['000830.XSHE', '600320.XSHG']
  140. ['000830.XSHE', '600550.XSHG']
  141. ['000877.XSHE', '601519.XSHG']
  142. ['000898.XSHE', '600022.XSHG']
  143. ['000898.XSHE', '600808.XSHG']
  144. ['000911.XSHE', '600550.XSHG']
  145. ['000916.XSHE', '600033.XSHG']
  146. ['000916.XSHE', '600035.XSHG']
  147. ['000916.XSHE', '600126.XSHG']
  148. ['000930.XSHE', '600026.XSHG']
  149. ['000932.XSHE', '600569.XSHG']
  150. ['000933.XSHE', '600348.XSHG']
  151. ['000933.XSHE', '600595.XSHG']
  152. ['000936.XSHE', '600477.XSHG']
  153. ['000937.XSHE', '600348.XSHG']
  154. ['000937.XSHE', '600508.XSHG']
  155. ['000937.XSHE', '600997.XSHG']
  156. ['000937.XSHE', '601001.XSHG']
  157. ['000939.XSHE', '600819.XSHG']
  158. ['000967.XSHE', '600879.XSHG']
  159. ['000969.XSHE', '600831.XSHG']
  160. ['000973.XSHE', '600460.XSHG']
  161. ['000987.XSHE', '600636.XSHG']
  162. ['000987.XSHE', '600827.XSHG']
  163. ['000987.XSHE', '601001.XSHG']
  164. ['600008.XSHG', '600035.XSHG']
  165. ['600012.XSHG', '600428.XSHG']
  166. ['600020.XSHG', '600033.XSHG']
  167. ['600020.XSHG', '600035.XSHG']
  168. ['600026.XSHG', '600068.XSHG']
  169. ['600026.XSHG', '600089.XSHG']
  170. ['600026.XSHG', '600126.XSHG']
  171. ['600026.XSHG', '600307.XSHG']
  172. ['600026.XSHG', '600331.XSHG']
  173. ['600026.XSHG', '600375.XSHG']
  174. ['600026.XSHG', '600581.XSHG']
  175. ['600026.XSHG', '600963.XSHG']
  176. ['600026.XSHG', '601666.XSHG']
  177. ['600026.XSHG', '601898.XSHG']
  178. ['600033.XSHG', '600035.XSHG']
  179. ['600035.XSHG', '600126.XSHG']
  180. ['600035.XSHG', '600269.XSHG']
  181. ['600035.XSHG', '600307.XSHG']
  182. ['600035.XSHG', '600586.XSHG']
  183. ['600037.XSHG', '600327.XSHG']
  184. ['600068.XSHG', '600126.XSHG']
  185. ['600068.XSHG', '600269.XSHG']
  186. ['600068.XSHG', '600320.XSHG']
  187. ['600068.XSHG', '600550.XSHG']
  188. ['600068.XSHG', '601001.XSHG']
  189. ['600068.XSHG', '601666.XSHG']
  190. ['600089.XSHG', '600581.XSHG']
  191. ['600100.XSHG', '600117.XSHG']
  192. ['600117.XSHG', '600295.XSHG']
  193. ['600117.XSHG', '600339.XSHG']
  194. ['600117.XSHG', '601168.XSHG']
  195. ['600117.XSHG', '601233.XSHG']
  196. ['600126.XSHG', '600282.XSHG']
  197. ['600126.XSHG', '600327.XSHG']
  198. ['600126.XSHG', '600569.XSHG']
  199. ['600126.XSHG', '600581.XSHG']
  200. ['600126.XSHG', '600808.XSHG']
  201. ['600126.XSHG', '600963.XSHG']
  202. ['600160.XSHG', '600449.XSHG']
  203. ['600160.XSHG', '601216.XSHG']
  204. ['600160.XSHG', '601311.XSHG']
  205. ['600188.XSHG', '600295.XSHG']
  206. ['600188.XSHG', '601001.XSHG']
  207. ['600231.XSHG', '600282.XSHG']
  208. ['600269.XSHG', '601618.XSHG']
  209. ['600282.XSHG', '600307.XSHG']
  210. ['600282.XSHG', '600569.XSHG']
  211. ['600282.XSHG', '600808.XSHG']
  212. ['600282.XSHG', '600963.XSHG']
  213. ['600307.XSHG', '600581.XSHG']
  214. ['600307.XSHG', '600808.XSHG']
  215. ['600307.XSHG', '600963.XSHG']
  216. ['600320.XSHG', '600548.XSHG']
  217. ['600320.XSHG', '601600.XSHG']
  218. ['600330.XSHG', '600883.XSHG']
  219. ['600330.XSHG', '601268.XSHG']
  220. ['600331.XSHG', '600581.XSHG']
  221. ['600348.XSHG', '600508.XSHG']
  222. ['600348.XSHG', '600997.XSHG']
  223. ['600348.XSHG', '601001.XSHG']
  224. ['600368.XSHG', '600527.XSHG']
  225. ['600375.XSHG', '600581.XSHG']
  226. ['600391.XSHG', '601100.XSHG']
  227. ['600449.XSHG', '601311.XSHG']
  228. ['600449.XSHG', '601519.XSHG']
  229. ['600460.XSHG', '601908.XSHG']
  230. ['600477.XSHG', '600581.XSHG']
  231. ['600508.XSHG', '600546.XSHG']
  232. ['600508.XSHG', '600997.XSHG']
  233. ['600522.XSHG', '600973.XSHG']
  234. ['600550.XSHG', '600831.XSHG']
  235. ['600569.XSHG', '600808.XSHG']
  236. ['600569.XSHG', '600963.XSHG']
  237. ['600581.XSHG', '600963.XSHG']
  238. ['600581.XSHG', '601001.XSHG']
  239. ['600581.XSHG', '601168.XSHG']
  240. ['600581.XSHG', '601666.XSHG']
  241. ['600586.XSHG', '601268.XSHG']
  242. ['600595.XSHG', '601001.XSHG']
  243. ['600595.XSHG', '601168.XSHG']
  244. ['600595.XSHG', '601666.XSHG']
  245. ['600688.XSHG', '600871.XSHG']
  246. ['600785.XSHG', '600827.XSHG']
  247. ['600808.XSHG', '600963.XSHG']
  248. ['600827.XSHG', '601001.XSHG']
  249. ['600875.XSHG', '601001.XSHG']
  250. ['600883.XSHG', '601268.XSHG']
  251. ['601001.XSHG', '601101.XSHG']
  252. ['601001.XSHG', '601168.XSHG']
  253. ['601001.XSHG', '601666.XSHG']
  254. ['601101.XSHG', '601666.XSHG']
  255. ['601168.XSHG', '601666.XSHG']
stock1stock2annualized_returnsharpe
0000761.XSHE600548.XSHG0.4894732.411514
1000708.XSHE600327.XSHG0.4473372.021270
2600126.XSHG600327.XSHG0.4383801.946916
3000554.XSHE000707.XSHE0.4311231.331038
4000939.XSHE600819.XSHG0.4094711.919758
5600026.XSHG600963.XSHG0.4087911.681338
6600037.XSHG600327.XSHG0.3956241.691877
7600808.XSHG600963.XSHG0.3919881.724114
8000559.XSHE600126.XSHG0.3890431.413595
9000761.XSHE600320.XSHG0.3843251.807262
10600126.XSHG600963.XSHG0.3780641.662569
11600126.XSHG600808.XSHG0.3758251.513791
12000936.XSHE600477.XSHG0.3751351.707097
13000930.XSHE600026.XSHG0.3729241.524350
14600320.XSHG600548.XSHG0.3724992.083496
15000507.XSHE600391.XSHG0.3656371.813873
16000559.XSHE601666.XSHG0.3502350.925901
17600012.XSHG600428.XSHG0.3278341.722317
18000916.XSHE600033.XSHG0.3277951.406093
19600035.XSHG600126.XSHG0.3261671.442674
20600827.XSHG601001.XSHG0.3227050.957791
21000717.XSHE600808.XSHG0.3207371.293439
22000559.XSHE600477.XSHG0.3066701.218095
23000685.XSHE000988.XSHE0.3025931.692933
24000683.XSHE000936.XSHE0.3018041.550496
25000559.XSHE600026.XSHG0.2955101.279449
26600269.XSHG601618.XSHG0.2942151.486413
27600026.XSHG600126.XSHG0.2938841.441490
28600068.XSHG600126.XSHG0.2894571.261351
29000159.XSHE600595.XSHG0.2889820.946365
30600020.XSHG600033.XSHG0.2882431.489764
31600126.XSHG600569.XSHG0.2876071.371374
32000635.XSHE600819.XSHG0.2851351.364688
33600068.XSHG600320.XSHG0.2735131.262845
34600785.XSHG600827.XSHG0.2726580.842093
35000089.XSHE600595.XSHG0.2699031.256524
36000898.XSHE600808.XSHG0.2697171.074201
37000717.XSHE600282.XSHG0.2674781.270872
38600282.XSHG600808.XSHG0.2664021.181157
39000916.XSHE600035.XSHG0.2643251.079520
40000089.XSHE600037.XSHG0.2642011.467101
41600026.XSHG600068.XSHG0.2639591.107977
42600026.XSHG600331.XSHG0.2610250.977858
43600020.XSHG600035.XSHG0.2601761.119975
44600569.XSHG600963.XSHG0.2600061.154372
45600307.XSHG600963.XSHG0.2584881.322409
46000898.XSHE600022.XSHG0.2582461.100292
47600282.XSHG600963.XSHG0.2574961.175741
48600307.XSHG600808.XSHG0.2560711.062023
49600126.XSHG600282.XSHG0.2556571.318676
50600033.XSHG600035.XSHG0.2556341.055682
51000709.XSHE601618.XSHG0.2531291.062565
52600026.XSHG600307.XSHG0.2531190.985825
53600026.XSHG600375.XSHG0.2507931.063874
54000066.XSHE600126.XSHG0.2474931.469341
55000830.XSHE600320.XSHG0.2470011.370327
56600320.XSHG601600.XSHG0.2465340.966634
57000717.XSHE600307.XSHG0.2458051.202750
58000417.XSHE000685.XSHE0.2450311.189700
59600330.XSHG600883.XSHG0.2434371.086147
  1. 174 rows × 4 columns
  1. a = list(outfile[2])
  2. 'percentage of outperform HS300: %f' % (1.*len([x for x in a if x>0.117]) / len(a))
  3. 'percentage of outperform HS300: 0.741379'