In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from pandas import DataFrame, Series

In [2]:
import pandas.io.data as web
names = ['AAPL', 'GOOG', 'MSFT', 'DELL', 'GS', 'MS', 'BAC', 'C']
def get_px(stock, start, end):
    return web.get_data_yahoo(stock, start, end)['Adj Close']


d:\Anaconda2\lib\site-packages\pandas\io\data.py:35: FutureWarning: 
The pandas.io.data module is moved to a separate package (pandas-datareader) and will be removed from pandas in a future version.
After installing the pandas-datareader package (https://github.com/pydata/pandas-datareader), you can change the import ``from pandas.io import data, wb`` to ``from pandas_datareader import data, wb``.
  FutureWarning)

In [3]:
px = DataFrame({n: get_px(n, '1/1/2009', '6/1/2012') for n in names})

In [4]:
px


Out[4]:
AAPL BAC C DELL GOOG GS MS MSFT
Date
2009-01-02 11.933430 13.672093 70.593968 10.39902 160.499779 78.706728 15.456186 16.716321
2009-01-05 12.437067 13.338162 70.000743 10.26359 163.861421 80.539223 16.029312 16.872549
2009-01-06 12.231930 13.624389 73.757840 10.68922 166.863420 80.475721 17.812368 17.069888
2009-01-07 11.967619 13.080558 70.692838 10.78596 160.844427 76.656504 16.465979 16.042077
2009-01-08 12.189851 12.918363 70.791707 10.90204 162.432840 77.482039 17.120978 16.543649
2009-01-09 11.911075 12.393614 66.737993 10.75694 157.377899 76.130340 17.339312 16.050300
2009-01-12 11.658599 10.905236 55.367817 10.30228 156.189080 70.460480 17.093688 16.009186
2009-01-13 11.533676 10.161046 58.333950 10.40869 157.003261 70.687274 17.157368 16.296974
2009-01-14 11.220711 9.731706 44.788608 9.97338 150.334921 68.664272 15.638131 15.696732
2009-01-15 10.964291 7.938019 37.867631 10.19587 149.345914 66.976921 14.792089 15.820069
2009-01-16 10.826218 6.850358 34.604885 9.93469 149.685575 66.269324 14.182575 16.206526
2009-01-20 10.283132 4.865853 27.683908 9.52840 141.234014 53.704913 11.917366 15.195160
2009-01-21 10.891967 6.373313 36.285695 9.86697 151.388865 63.411713 15.337923 15.935184
2009-01-22 11.619150 5.447847 30.748913 9.65416 153.097158 64.436822 15.992923 14.068680
2009-01-23 11.619150 5.953514 34.308273 9.80893 162.188089 67.956675 17.020908 14.142682
2009-01-26 11.787468 5.724533 32.924076 9.97338 161.773501 67.312573 17.357507 14.496248
2009-01-27 11.930800 6.201578 35.099241 9.74122 165.574702 70.995718 18.003410 14.520916
2009-01-28 12.387098 7.050717 41.624732 10.52477 174.161139 79.559470 21.213034 14.833372
2009-01-29 12.229300 6.468723 38.651539 9.62514 171.488809 75.041729 19.718898 14.463359
2009-01-30 11.851901 6.277905 35.182811 9.18983 169.096191 73.236448 18.658246 14.060457
2009-02-02 12.033368 5.724533 36.173876 9.00603 170.115172 75.812829 19.183960 14.660698
2009-02-03 12.226670 5.056671 34.290851 9.45101 170.055239 75.123372 19.313084 15.211605
2009-02-04 12.301624 4.484217 34.588173 9.47036 171.328962 79.804412 20.299951 15.318497
2009-02-05 12.684283 4.617790 34.984597 9.12212 176.683613 84.231436 21.388273 15.655620
2009-02-06 13.112966 5.848565 38.750644 9.15114 185.454867 87.606137 21.093135 16.165414
2009-02-09 13.479845 6.573672 39.147071 9.32526 189.196135 88.803612 21.775641 15.984520
2009-02-10 12.864435 5.304734 33.200681 8.86093 179.076231 82.008853 19.174739 15.458279
2009-02-11 12.731622 5.791320 36.570304 8.72550 178.841472 85.918787 21.130026 15.795401
2009-02-12 13.053792 5.600502 35.777449 8.87060 181.343964 86.708034 21.203811 15.836515
2009-02-13 13.039327 5.314275 34.588173 8.82224 178.661643 87.497274 21.148473 15.696732
... ... ... ... ... ... ... ... ...
2012-04-23 75.177318 7.913163 32.991162 15.57434 298.502008 105.129232 16.064004 28.512527
2012-04-24 73.675617 7.942184 33.159837 15.65173 300.335186 107.349412 16.461347 28.334991
2012-04-25 80.213692 7.990554 33.417815 15.88390 304.555978 107.227116 16.215373 28.583544
2012-04-26 79.911244 8.000228 33.616259 15.99030 307.428105 107.772748 16.329231 28.503652
2012-04-27 79.293207 7.980879 33.239216 15.90324 307.183354 107.631641 16.082539 28.388252
2012-04-30 76.792114 7.845446 32.782798 15.83553 302.123410 108.327797 16.395650 28.423760
2012-05-01 76.548846 8.038923 33.338436 15.86455 301.913607 108.497133 16.594902 28.414881
2012-05-02 77.055108 7.893815 32.445444 15.64689 303.327209 107.029552 16.082539 28.228468
2012-05-03 76.508079 7.739035 32.237015 15.70010 305.205309 104.922265 15.399386 28.192961
2012-05-04 74.329161 7.487516 31.363600 15.29381 298.187334 102.532750 15.181157 27.500564
2012-05-07 74.885397 7.700340 31.433076 15.30348 303.472045 103.520544 15.323480 27.207627
2012-05-08 74.714449 7.535885 31.085694 15.19708 306.089446 102.890241 15.029346 27.074474
2012-05-09 74.845950 7.477842 30.222203 15.15838 304.271262 100.933472 14.697258 27.305273
2012-05-10 75.022151 7.448821 30.420706 15.04230 306.524016 100.020939 14.801629 27.287519
2012-05-11 74.521148 7.303714 29.130432 14.91654 302.313200 96.079179 14.184893 27.660348
2012-05-14 73.404729 7.110238 27.929483 14.93589 301.698816 93.858999 13.568159 27.234258
2012-05-15 72.740665 7.061869 27.582103 14.83916 305.250263 93.953080 13.416348 26.993008
2012-05-16 71.808347 6.878067 26.718611 14.52477 314.151389 92.382016 12.847054 26.716019
2012-05-17 69.709642 6.752308 26.212426 14.45222 311.214326 91.328377 12.771148 26.555187
2012-05-18 69.743829 6.791003 25.815418 14.25875 299.900615 89.832574 12.666778 26.153107
2012-05-21 73.807111 6.607201 26.053623 14.48124 306.748784 90.792147 12.514966 26.581993
2012-05-22 73.240361 6.752308 26.718611 14.58765 300.100412 91.751713 12.628825 26.590928
2012-05-23 75.027411 6.936110 26.946889 12.08221 304.426106 92.231499 12.685754 26.010146
2012-05-24 74.338367 6.907088 26.460555 12.04351 301.528978 91.121410 12.628825 25.974404
2012-05-25 73.939925 6.916762 26.271976 12.05319 295.470050 90.970886 12.571896 25.965469
2012-05-28 NaN NaN NaN 12.05319 NaN NaN NaN NaN
2012-05-29 75.252272 7.197302 26.817863 12.24666 296.873645 92.502198 12.941937 26.412225
2012-05-30 76.159610 6.974505 25.805493 12.14992 293.821674 89.430135 12.420084 26.215653
2012-05-31 75.970256 7.119807 26.311678 11.92743 290.140354 90.460454 12.676266 26.081627
2012-06-01 73.768977 6.800143 25.200056 11.67592 285.205295 87.567990 12.078508 25.420428

868 rows × 8 columns


In [5]:
px = px.asfreq('B').fillna(method='pad')

In [6]:
rets = px.pct_change()

In [7]:
((1 + rets).cumprod() - 1).plot()


Out[7]:
<matplotlib.axes._subplots.AxesSubplot at 0x9867e80>

In [78]:
def calc_mom(price, lookback, lag):
    mom_ret = price.shift(lag).pct_change(lookback)
    ranks = mom_ret.rank(axis=1, ascending=False)
    #ranks = ranks.fillna(method='bfill')
    demeaned = ranks.sub(ranks.mean(axis=1), axis='index')

    return demeaned.divide(demeaned.std(axis=1), axis='index')

In [79]:
compound = lambda x: (1 + x).prod() - 1
daily_sr = lambda x: x.mean() / x.std()

def strat_sr(prices, lb, hold):
    # 计算投资组合权重
    freq = '%dB' % hold
    port = calc_mom(prices, lb, lag=1)
    daily_rets = prices.pct_change()
    
    # 计算投资组合收益
    port = port.shift(1).resample(freq, how='first')
    returns = daily_rets.resample(freq, how=compound)
    port_rets = (port * returns).sum(axis=1)

    return daily_sr(port_rets) * np.sqrt(252 / hold)
    #return port_rets

In [80]:
strat_sr(px, 70, 30)


d:\Anaconda2\lib\site-packages\ipykernel\__main__.py:11: FutureWarning: how in .resample() is deprecated
the new syntax is .resample(...).first()
d:\Anaconda2\lib\site-packages\ipykernel\__main__.py:12: FutureWarning: how in .resample() is deprecated
the new syntax is .resample(...)..apply(<func>)
Out[80]:
0.30444751284157867

In [83]:
from collections import defaultdict

lookbacks = range(20, 90, 5)
holdings = range(20, 90, 5)
dd = defaultdict(dict)
for lb in lookbacks:
    for hold in holdings:
        dd[lb][hold] = strat_sr(px, lb, hold)
ddf = DataFrame(dd)
ddf.index.name = 'Holding Period'
ddf.columns.name = 'Lookback Period'


d:\Anaconda2\lib\site-packages\ipykernel\__main__.py:11: FutureWarning: how in .resample() is deprecated
the new syntax is .resample(...).first()
d:\Anaconda2\lib\site-packages\ipykernel\__main__.py:12: FutureWarning: how in .resample() is deprecated
the new syntax is .resample(...)..apply(<func>)

In [84]:
def heatmap(df, cmap=plt.cm.gray_r):
    fig = plt.figure()
    ax = fig.add_subplot(111)
    axim = ax.imshow(df.values, cmap=cmap, interpolation='nearest')
    ax.set_xlabel(df.columns.name)
    ax.set_xticks(np.arange(len(df.columns)))
    ax.set_xticklabels(list(df.columns))
    ax.set_ylabel(df.index.name)
    ax.set_yticks(np.arange(len(df.index)))
    ax.set_yticklabels(list(df.index))
    plt.colorbar(axim)

In [85]:
heatmap(ddf)


期货合约转仓


In [86]:
# 标准普尔500指数的近似价格
px = web.get_data_yahoo('SPY')['Adj Close'] * 10
px


Out[86]:
Date
2010-01-04     992.92299
2010-01-05     995.55135
2010-01-06     996.25228
2010-01-07    1000.45775
2010-01-08    1003.78704
2010-01-11    1005.18888
2010-01-12     995.81425
2010-01-13    1004.22513
2010-01-14    1006.94113
2010-01-15     995.63899
2010-01-19    1008.08008
2010-01-20     997.82932
2010-01-21     978.64196
2010-01-22     956.82624
2010-01-25     961.73257
2010-01-26     957.70236
2010-01-27     962.25829
2010-01-28     951.21898
2010-01-29     940.88059
2010-02-01     955.51202
2010-02-02     967.07699
2010-02-03     962.25829
2010-02-04     932.55735
2010-02-05     934.48485
2010-02-08     927.73858
2010-02-09     939.39118
2010-02-10     937.55131
2010-02-11     947.36397
2010-02-12     946.57548
2010-02-16     961.46974
                 ...    
2016-06-13    2073.71580
2016-06-14    2069.63697
2016-06-15    2066.75204
2016-06-16    2072.91992
2016-06-17    2065.20004
2016-06-20    2078.50006
2016-06-21    2084.40002
2016-06-22    2081.00006
2016-06-23    2108.09998
2016-06-24    2032.40005
2016-06-27    1996.00006
2016-06-28    2031.99997
2016-06-29    2066.60004
2016-06-30    2094.79996
2016-07-01    2099.19998
2016-07-05    2084.10004
2016-07-06    2096.60004
2016-07-07    2095.29999
2016-07-08    2126.49994
2016-07-11    2133.99994
2016-07-12    2149.49997
2016-07-13    2149.19998
2016-07-14    2161.19995
2016-07-15    2158.30002
2016-07-18    2164.10004
2016-07-19    2161.90002
2016-07-20    2170.89996
2016-07-21    2162.70004
2016-07-22    2172.40005
2016-07-25    2166.49994
Name: Adj Close, dtype: float64

In [87]:
len(px)


Out[87]:
1651

In [88]:
from datetime import datetime
expiry = {'ESU2': datetime(2012, 9, 21),
         'ESZ2': datetime(2012, 12, 21)}
expiry = Series(expiry).order()


d:\Anaconda2\lib\site-packages\ipykernel\__main__.py:4: FutureWarning: order is deprecated, use sort_values(...)

In [89]:
expiry


Out[89]:
ESU2   2012-09-21
ESZ2   2012-12-21
dtype: datetime64[ns]

In [90]:
np.random.seed(12347)
N = 200
walk = (np.random.randint(0, 200, size=N) - 100) * 0.25
perturb = (np.random.randint(0, 20, size=N) - 10) * 0.25
walk = walk.cumsum()

rng = pd.date_range(px.index[0], periods=len(px) + N, freq='B')
near = np.concatenate([px.values, px.values[-1] + walk])
far = np.concatenate([px.values, px.values[-1] + walk + perturb])
prices = DataFrame({'ESU2': near, 'ESZ2': far}, index=rng)
prices.tail()


Out[90]:
ESU2 ESZ2
2017-01-31 2195.74994 2197.49994
2017-02-01 2181.99994 2184.24994
2017-02-02 2189.99994 2191.74994
2017-02-03 2206.49994 2205.74994
2017-02-06 2186.49994 2184.24994

In [91]:
def get_roll_weights(start, expiry, items, roll_periods=5):
    # start: 用于计算加权矩阵的第一天
    # expiry: 由”合约代码 -> 到期日期“组成的序列
    # items: 一组合约名称
    
    dates = pd.date_range(start, expiry[-1], freq='B')
    weights = DataFrame(np.zeros((len(dates), len(items))),
                       index=dates, columns=items)
    
    prev_date = weights.index[0]
    for i, (item, ex_date) in enumerate(expiry.iteritems()):
        if i < len(expiry) - 1:
            weights.ix[prev_date:ex_date - pd.offsets.BDay(), item] = 1
            roll_rng = pd.date_range(end=ex_date - pd.offsets.BDay(),
                                    periods=roll_periods + 1, freq='B')
            
            decay_weights = np.linspace(0, 1, roll_periods + 1)
            weights.ix[roll_rng, item] = 1 - decay_weights
            weights.ix[roll_rng, expiry.index[i + 1]] = decay_weights
        else:
            weights.ix[prev_date:, item] = 1
        prev_date = ex_date
    return weights

In [92]:
weights = get_roll_weights('6/1/2012', expiry, prices.columns)

In [93]:
weights.ix['2012-09-12':'2012-09-21']


Out[93]:
ESU2 ESZ2
2012-09-12 1.0 0.0
2012-09-13 1.0 0.0
2012-09-14 0.8 0.2
2012-09-17 0.6 0.4
2012-09-18 0.4 0.6
2012-09-19 0.2 0.8
2012-09-20 0.0 1.0
2012-09-21 0.0 1.0

In [96]:
rolled_returns = (prices['2012-06-01':'2012-12-21'].pct_change() * weights).sum(1)
rolled_returns


Out[96]:
2012-06-01    0.000000
2012-06-04    0.006593
2012-06-05   -0.004512
2012-06-06   -0.009504
2012-06-07   -0.001255
2012-06-08   -0.008720
2012-06-11    0.000149
2012-06-12   -0.004845
2012-06-13    0.016778
2012-06-14   -0.002357
2012-06-15    0.006867
2012-06-18    0.007407
2012-06-19    0.002621
2012-06-20   -0.009148
2012-06-21   -0.010112
2012-06-22   -0.008587
2012-06-25    0.000224
2012-06-26    0.016497
2012-06-27    0.018433
2012-06-28    0.000000
2012-06-29   -0.006994
2012-07-02   -0.000871
2012-07-03   -0.006905
2012-07-04    0.019833
2012-07-05    0.001937
2012-07-06    0.005014
2012-07-09    0.001212
2012-07-10    0.000854
2012-07-11    0.001636
2012-07-12   -0.000497
                ...   
2012-11-12    0.011129
2012-11-13   -0.007429
2012-11-14    0.005752
2012-11-15   -0.009077
2012-11-16   -0.003081
2012-11-19   -0.004215
2012-11-20   -0.001340
2012-11-21   -0.010808
2012-11-22    0.016996
2012-11-23    0.025630
2012-11-26   -0.002259
2012-11-27    0.004392
2012-11-28   -0.002733
2012-11-29   -0.002877
2012-11-30    0.002542
2012-12-03    0.007950
2012-12-04   -0.000068
2012-12-05   -0.000680
2012-12-06    0.000680
2012-12-07   -0.000136
2012-12-10    0.006460
2012-12-11    0.002230
2012-12-12    0.005393
2012-12-13    0.001609
2012-12-14    0.000268
2012-12-17    0.005622
2012-12-18   -0.001198
2012-12-19    0.003931
2012-12-20   -0.003916
2012-12-21   -0.002466
Freq: B, dtype: float64

In [98]:
aapl = web.get_data_yahoo('AAPL', '2000-01-01', '2012-07-27')['Adj Close']
msft = web.get_data_yahoo('MSFT', '2000-01-01', '2012-07-27')['Adj Close']

aapl_rets = aapl.pct_change()
msft_rets = msft.pct_change()

In [99]:
aapl_rets.rolling(250).corr(msft_rets).plot()


Out[99]:
<matplotlib.axes._subplots.AxesSubplot at 0xa17bda0>

In [100]:
model = pd.ols(y=aapl_rets, x={'MSFT': msft_rets}, window=250)
model.beta


d:\Anaconda2\lib\site-packages\ipykernel\__main__.py:1: FutureWarning: The pandas.stats.ols module is deprecated and will be removed in a future version. We refer to external packages like statsmodels, see some examples here: http://statsmodels.sourceforge.net/stable/regression.html
  if __name__ == '__main__':
Out[100]:
MSFT intercept
Date
2000-12-28 0.429021 -0.002113
2000-12-29 0.421102 -0.001796
2001-01-02 0.420595 -0.001839
2001-01-03 0.433292 -0.001289
2001-01-04 0.432772 -0.001307
2001-01-05 0.431028 -0.001414
2001-01-08 0.428222 -0.001209
2001-01-09 0.426630 -0.000978
2001-01-10 0.416962 -0.001586
2001-01-11 0.423251 -0.001376
2001-01-12 0.423460 -0.001603
2001-01-16 0.435644 -0.001784
2001-01-17 0.436671 -0.002144
2001-01-18 0.450526 -0.001717
2001-01-19 0.448452 -0.001586
2001-01-22 0.446050 -0.001813
2001-01-23 0.447527 -0.001551
2001-01-24 0.445640 -0.001627
2001-01-25 0.445692 -0.001410
2001-01-26 0.442301 -0.001642
2001-01-29 0.453904 -0.000981
2001-01-30 0.453097 -0.000919
2001-01-31 0.448897 -0.001013
2001-02-01 0.443702 -0.001283
2001-02-02 0.444125 -0.001556
2001-02-05 0.442945 -0.001649
2001-02-06 0.445408 -0.001495
2001-02-07 0.442026 -0.001628
2001-02-08 0.437471 -0.001500
2001-02-09 0.445874 -0.001979
... ... ...
2012-06-15 0.593205 0.001955
2012-06-18 0.586628 0.001959
2012-06-19 0.578683 0.001934
2012-06-20 0.578544 0.001796
2012-06-21 0.576834 0.001830
2012-06-22 0.578831 0.001831
2012-06-25 0.583620 0.001829
2012-06-26 0.583268 0.001824
2012-06-27 0.584524 0.001844
2012-06-28 0.585499 0.001737
2012-06-29 0.589611 0.001714
2012-07-02 0.589536 0.001775
2012-07-03 0.589101 0.001784
2012-07-05 0.588199 0.001845
2012-07-06 0.585708 0.001897
2012-07-09 0.583953 0.001961
2012-07-10 0.584527 0.001904
2012-07-11 0.584493 0.001902
2012-07-12 0.581595 0.001868
2012-07-13 0.581908 0.001733
2012-07-16 0.590144 0.001788
2012-07-17 0.599806 0.001617
2012-07-18 0.591985 0.001553
2012-07-19 0.591652 0.001565
2012-07-20 0.593072 0.001521
2012-07-23 0.585991 0.001553
2012-07-24 0.580706 0.001591
2012-07-25 0.590413 0.001481
2012-07-26 0.589297 0.001440
2012-07-27 0.592174 0.001386

2913 rows × 2 columns


In [101]:
model.beta['MSFT'].plot()


Out[101]:
<matplotlib.axes._subplots.AxesSubplot at 0xcd84518>

In [ ]: