Python Finance Fundamentals

Shape Ratio

[http://www.investopedia.com/terms/s/sharperatio.asp]

  • What is a porfolio? Analytically and Mathematically speaking?

A portfolio is just a set of allocations in variety of securities.

For example:

 - 20% in APPL (Apple)
 - 30% in FB (Facebook)
 - 50% in GOOG (Google)

These percentages should add up to 100% (or if defined as weights whey should ad up to 1).

Key statistics for portfolio

  • Daily Returns - The percent returned from 1 day to the next for a stock.
  • Cumulative Return - The amount returned after an entire time period.
  • Avg. Daily Return - Mean of Daily Returns
  • Std. Daily Return - Std. Dev of Daily Returns

The Sharpe Ratio is a measure for calculating risk-adjusted return, and this ratio has become the industry standard for such calculations.


Sharpe ratio = (Mean portfolio return − Risk-free rate)/Standard deviation of portfolio return

The original Sharpe Ratio

Annualized Sharpe Ratio = K-value * SR

K-values for various sampling rates:

  • Daily = sqrt(252) since, there are 252 business days in year
  • Weekly = sqrt(52) since, there are 52 weeks in a year
  • Monthly = sqrt(12) since, there are 12 months in a year

Since I'm based in the USA, I will use a very low risk-free rate (the rate you would get if you just put your money in a bank, its currently very low in the USA, let's just say its ~0% return). If you are in a different country with higher rates for your trading currency, you can use this trick to convert a yearly rate with a daily rate:

daily_rate = ((1.0 + yearly_rate)**(1/252))-1

Other values people use are things like the 3-month treasury bill or LIBOR.

Portfolio Allocation


In [23]:
import pandas as pd
import quandl

In [24]:
aapl = pd.read_csv('AAPL_CLOSE', index_col='Date', parse_dates=True)
cisco = pd.read_csv('CISCO_CLOSE', index_col='Date', parse_dates=True)
ibm = pd.read_csv('IBM_CLOSE', index_col='Date', parse_dates=True)
amzn = pd.read_csv('AMZN_CLOSE', index_col='Date', parse_dates=True)

In [25]:
aapl.head()


Out[25]:
Adj. Close
Date
2012-01-03 53.063218
2012-01-04 53.348386
2012-01-05 53.940658
2012-01-06 54.504543
2012-01-09 54.418089

In [26]:
aapl.index


Out[26]:
DatetimeIndex(['2012-01-03', '2012-01-04', '2012-01-05', '2012-01-06',
               '2012-01-09', '2012-01-10', '2012-01-11', '2012-01-12',
               '2012-01-13', '2012-01-17',
               ...
               '2016-12-16', '2016-12-19', '2016-12-20', '2016-12-21',
               '2016-12-22', '2016-12-23', '2016-12-27', '2016-12-28',
               '2016-12-29', '2016-12-30'],
              dtype='datetime64[ns]', name='Date', length=1258, freq=None)

In [27]:
cisco.head()


Out[27]:
Adj. Close
Date
2012-01-03 15.752778
2012-01-04 16.057180
2012-01-05 15.997991
2012-01-06 15.938801
2012-01-09 16.040268

In [28]:
ibm.head()


Out[28]:
Adj. Close
Date
2012-01-03 160.830881
2012-01-04 160.174781
2012-01-05 159.415086
2012-01-06 157.584912
2012-01-09 156.764786

In [29]:
amzn.head()


Out[29]:
Adj. Close
Date
2012-01-03 179.03
2012-01-04 177.51
2012-01-05 177.61
2012-01-06 182.61
2012-01-09 178.56

In [30]:
for stock_df in (aapl, cisco, ibm, amzn):
    stock_df['returns'] = stock_df['Adj. Close'].pct_change()
    stock_df['cumulative return'] = (1 + stock_df['returns']).cumprod()

In [31]:
aapl.head()


Out[31]:
Adj. Close returns cumulative return
Date
2012-01-03 53.063218 NaN NaN
2012-01-04 53.348386 0.005374 1.005374
2012-01-05 53.940658 0.011102 1.016536
2012-01-06 54.504543 0.010454 1.027162
2012-01-09 54.418089 -0.001586 1.025533

In [32]:
# Another method for calculating cumulative (directly from Adj. Close)
for stock_df in (aapl, cisco, ibm, amzn):
    stock_df['Normed Return'] = stock_df['Adj. Close'] / stock_df.iloc[0]['Adj. Close']

In [33]:
aapl.head()


Out[33]:
Adj. Close returns cumulative return Normed Return
Date
2012-01-03 53.063218 NaN NaN 1.000000
2012-01-04 53.348386 0.005374 1.005374 1.005374
2012-01-05 53.940658 0.011102 1.016536 1.016536
2012-01-06 54.504543 0.010454 1.027162 1.027162
2012-01-09 54.418089 -0.001586 1.025533 1.025533

Let's allocate

  • 30% in apple
  • 20% in cisco
  • 40% in amazon
  • 10% in ibm

In [34]:
list(zip((aapl, cisco, ibm, amzn), [.3,.2,.4,.1]))


Out[34]:
[(            Adj. Close   returns  cumulative return  Normed Return
  Date                                                              
  2012-01-03   53.063218       NaN                NaN       1.000000
  2012-01-04   53.348386  0.005374           1.005374       1.005374
  2012-01-05   53.940658  0.011102           1.016536       1.016536
  2012-01-06   54.504543  0.010454           1.027162       1.027162
  2012-01-09   54.418089 -0.001586           1.025533       1.025533
  2012-01-10   54.612933  0.003580           1.029205       1.029205
  2012-01-11   54.523898 -0.001630           1.027527       1.027527
  2012-01-12   54.374217 -0.002745           1.024706       1.024706
  2012-01-13   54.170341 -0.003749           1.020864       1.020864
  2012-01-17   54.801324  0.011648           1.032755       1.032755
  2012-01-18   55.370370  0.010384           1.043479       1.043479
  2012-01-19   55.194882 -0.003169           1.040172       1.040172
  2012-01-20   54.233569 -0.017417           1.022056       1.022056
  2012-01-23   55.150881  0.016914           1.039343       1.039343
  2012-01-24   54.247763 -0.016375           1.022323       1.022323
  2012-01-25   57.634941  0.062439           1.086156       1.086156
  2012-01-26   57.372999 -0.004545           1.081220       1.081220
  2012-01-27   57.714943  0.005960           1.087664       1.087664
  2012-01-30   58.454316  0.012811           1.101598       1.101598
  2012-01-31   58.902069  0.007660           1.110036       1.110036
  2012-02-01   58.864648 -0.000635           1.109331       1.109331
  2012-02-02   58.726580 -0.002346           1.106729       1.106729
  2012-02-03   59.314982  0.010019           1.117817       1.117817
  2012-02-06   59.868544  0.009333           1.128249       1.128249
  2012-02-07   60.495655  0.010475           1.140068       1.140068
  2012-02-08   61.508583  0.016744           1.159157       1.159157
  2012-02-09   63.636377  0.034593           1.199256       1.199256
  2012-02-10   63.668635  0.000507           1.199864       1.199864
  2012-02-13   64.853180  0.018605           1.222187       1.222187
  2012-02-14   65.738363  0.013649           1.238869       1.238869
  ...                ...       ...                ...            ...
  2016-11-17  109.032563 -0.000364           2.054767       2.054767
  2016-11-18  109.141645  0.001000           2.056823       2.056823
  2016-11-21  110.797711  0.015174           2.088032       2.088032
  2016-11-22  110.867126  0.000627           2.089340       2.089340
  2016-11-23  110.301883 -0.005098           2.078688       2.078688
  2016-11-25  110.857210  0.005035           2.089154       2.089154
  2016-11-28  110.639046 -0.001968           2.085042       2.085042
  2016-11-29  110.529963 -0.000986           2.082986       2.082986
  2016-11-30  109.597807 -0.008434           2.065420       2.065420
  2016-12-01  108.576401 -0.009320           2.046171       2.046171
  2016-12-02  108.982980  0.003745           2.053833       2.053833
  2016-12-05  108.199572 -0.007188           2.039069       2.039069
  2016-12-06  109.032563  0.007699           2.054767       2.054767
  2016-12-07  110.103551  0.009823           2.074951       2.074951
  2016-12-08  111.184456  0.009817           2.095321       2.095321
  2016-12-09  112.999187  0.016322           2.129520       2.129520
  2016-12-12  112.354610 -0.005704           2.117373       2.117373
  2016-12-13  114.228840  0.016681           2.152693       2.152693
  2016-12-14  114.228840  0.000000           2.152693       2.152693
  2016-12-15  114.853583  0.005469           2.164467       2.164467
  2016-12-16  115.002331  0.001295           2.167270       2.167270
  2016-12-19  115.666741  0.005777           2.179791       2.179791
  2016-12-20  115.974154  0.002658           2.185585       2.185585
  2016-12-21  116.083236  0.000941           2.187640       2.187640
  2016-12-22  115.319661 -0.006578           2.173250       2.173250
  2016-12-23  115.547742  0.001978           2.177549       2.177549
  2016-12-27  116.281568  0.006351           2.191378       2.191378
  2016-12-28  115.785740 -0.004264           2.182034       2.182034
  2016-12-29  115.755990 -0.000257           2.181473       2.181473
  2016-12-30  114.853583 -0.007796           2.164467       2.164467
  
  [1258 rows x 4 columns], 0.3),
 (            Adj. Close   returns  cumulative return  Normed Return
  Date                                                              
  2012-01-03   15.752778       NaN                NaN       1.000000
  2012-01-04   16.057180  0.019324           1.019324       1.019324
  2012-01-05   15.997991 -0.003686           1.015566       1.015566
  2012-01-06   15.938801 -0.003700           1.011809       1.011809
  2012-01-09   16.040268  0.006366           1.018250       1.018250
  2012-01-10   15.921890 -0.007380           1.010735       1.010735
  2012-01-11   16.124824  0.012746           1.023618       1.023618
  2012-01-12   16.192469  0.004195           1.027912       1.027912
  2012-01-13   16.116369 -0.004700           1.023081       1.023081
  2012-01-17   16.323531  0.012854           1.036232       1.036232
  2012-01-18   16.522238  0.012173           1.048846       1.048846
  2012-01-19   16.733627  0.012794           1.062265       1.062265
  2012-01-20   16.843550  0.006569           1.069243       1.069243
  2012-01-23   16.767450 -0.004518           1.064412       1.064412
  2012-01-24   16.754766 -0.000756           1.063607       1.063607
  2012-01-25   16.767450  0.000757           1.064412       1.064412
  2012-01-26   16.767450  0.000000           1.064412       1.064412
  2012-01-27   16.539149 -0.013616           1.049919       1.049919
  2012-01-30   16.539149  0.000000           1.049919       1.049919
  2012-01-31   16.611021  0.004346           1.054482       1.054482
  2012-02-01   16.742083  0.007890           1.062802       1.062802
  2012-02-02   16.742083  0.000000           1.062802       1.062802
  2012-02-03   16.987295  0.014646           1.078368       1.078368
  2012-02-06   17.071851  0.004978           1.083736       1.083736
  2012-02-07   17.080307  0.000495           1.084273       1.084273
  2012-02-08   17.274786  0.011386           1.096618       1.096618
  2012-02-09   16.911195 -0.021047           1.073537       1.073537
  2012-02-10   16.822411 -0.005250           1.067901       1.067901
  2012-02-13   16.936562  0.006786           1.075148       1.075148
  2012-02-14   16.970384  0.001997           1.077295       1.077295
  ...                ...       ...                ...            ...
  2016-11-17   29.263492 -0.048147           1.857672       1.857672
  2016-11-18   29.390089  0.004326           1.865708       1.865708
  2016-11-21   29.263492 -0.004307           1.857672       1.857672
  2016-11-22   29.107679 -0.005324           1.847781       1.847781
  2016-11-23   28.932390 -0.006022           1.836653       1.836653
  2016-11-25   29.302445  0.012790           1.860145       1.860145
  2016-11-28   29.136894 -0.005650           1.849635       1.849635
  2016-11-29   29.049250 -0.003008           1.844072       1.844072
  2016-11-30   29.039511 -0.000335           1.843453       1.843453
  2016-12-01   28.679196 -0.012408           1.820580       1.820580
  2016-12-02   28.484430 -0.006791           1.808216       1.808216
  2016-12-05   28.757102  0.009573           1.825526       1.825526
  2016-12-06   28.562336 -0.006773           1.813162       1.813162
  2016-12-07   29.166109  0.021139           1.851490       1.851490
  2016-12-08   29.166109  0.000000           1.851490       1.851490
  2016-12-09   29.273230  0.003673           1.858290       1.858290
  2016-12-12   29.380351  0.003659           1.865090       1.865090
  2016-12-13   29.789358  0.013921           1.891054       1.891054
  2016-12-14   29.662760 -0.004250           1.883018       1.883018
  2016-12-15   29.828311  0.005581           1.893527       1.893527
  2016-12-16   29.789358 -0.001306           1.891054       1.891054
  2016-12-19   29.945170  0.005230           1.900945       1.900945
  2016-12-20   29.760143 -0.006179           1.889200       1.889200
  2016-12-21   29.623807 -0.004581           1.880545       1.880545
  2016-12-22   29.662760  0.001315           1.883018       1.883018
  2016-12-23   29.730928  0.002298           1.887345       1.887345
  2016-12-27   29.877002  0.004913           1.896618       1.896618
  2016-12-28   29.623807 -0.008475           1.880545       1.880545
  2016-12-29   29.662760  0.001315           1.883018       1.883018
  2016-12-30   29.429042 -0.007879           1.868181       1.868181
  
  [1258 rows x 4 columns], 0.2),
 (            Adj. Close   returns  cumulative return  Normed Return
  Date                                                              
  2012-01-03  160.830881       NaN                NaN       1.000000
  2012-01-04  160.174781 -0.004079           0.995921       0.995921
  2012-01-05  159.415086 -0.004743           0.991197       0.991197
  2012-01-06  157.584912 -0.011481           0.979817       0.979817
  2012-01-09  156.764786 -0.005204           0.974718       0.974718
  2012-01-10  156.523065 -0.001542           0.973215       0.973215
  2012-01-11  157.394988  0.005571           0.978637       0.978637
  2012-01-12  155.866965 -0.009708           0.969136       0.969136
  2012-01-13  154.666992 -0.007699           0.961675       0.961675
  2012-01-17  155.392156  0.004689           0.966184       0.966184
  2012-01-18  156.315876  0.005944           0.971927       0.971927
  2012-01-19  155.841066 -0.003037           0.968975       0.968975
  2012-01-20  162.747384  0.044316           1.011916       1.011916
  2012-01-23  164.007787  0.007745           1.019753       1.019753
  2012-01-24  165.691203  0.010264           1.030220       1.030220
  2012-01-25  165.518545 -0.001042           1.029147       1.029147
  2012-01-26  164.871077 -0.003912           1.025121       1.025121
  2012-01-27  164.422167 -0.002723           1.022330       1.022330
  2012-01-30  166.183278  0.010711           1.033280       1.033280
  2012-01-31  166.269607  0.000519           1.033816       1.033816
  2012-02-01  166.286872  0.000104           1.033924       1.033924
  2012-02-02  165.345887 -0.005659           1.028073       1.028073
  2012-02-03  167.167428  0.011017           1.039399       1.039399
  2012-02-06  166.459530 -0.004235           1.034997       1.034997
  2012-02-07  166.917074  0.002749           1.037842       1.037842
  2012-02-08  167.219225  0.001810           1.039721       1.039721
  2012-02-09  167.375222  0.000933           1.040691       1.040691
  2012-02-10  166.759903 -0.003676           1.036865       1.036865
  2012-02-13  166.933232  0.001039           1.037943       1.037943
  2012-02-14  166.586574 -0.002077           1.035787       1.035787
  ...                ...       ...                ...            ...
  2016-11-17  157.001177  0.003202           0.976188       0.976188
  2016-11-18  157.580843  0.003692           0.979792       0.979792
  2016-11-21  159.919159  0.014839           0.994331       0.994331
  2016-11-22  159.820910 -0.000614           0.993720       0.993720
  2016-11-23  159.142995 -0.004242           0.989505       0.989505
  2016-11-25  160.282678  0.007161           0.996591       0.996591
  2016-11-28  161.638508  0.008459           1.005022       1.005022
  2016-11-29  160.665848 -0.006018           0.998974       0.998974
  2016-11-30  159.378792 -0.008011           0.990971       0.990971
  2016-12-01  157.020827 -0.014795           0.976310       0.976310
  2016-12-02  157.217324  0.001251           0.977532       0.977532
  2016-12-05  157.040476 -0.001125           0.976432       0.976432
  2016-12-06  157.541544  0.003191           0.979548       0.979548
  2016-12-07  161.903779  0.027689           1.006671       1.006671
  2016-12-08  162.463796  0.003459           1.010153       1.010153
  2016-12-09  163.603479  0.007015           1.017239       1.017239
  2016-12-12  162.601344 -0.006125           1.011008       1.011008
  2016-12-13  165.342479  0.016858           1.028052       1.028052
  2016-12-14  165.558625  0.001307           1.029396       1.029396
  2016-12-15  165.077208 -0.002908           1.026402       1.026402
  2016-12-16  163.809801 -0.007678           1.018522       1.018522
  2016-12-19  163.760677 -0.000300           1.018217       1.018217
  2016-12-20  164.664564  0.005520           1.023837       1.023837
  2016-12-21  164.399293 -0.001611           1.022187       1.022187
  2016-12-22  164.134021 -0.001614           1.020538       1.020538
  2016-12-23  163.790152 -0.002095           1.018400       1.018400
  2016-12-27  164.212620  0.002579           1.021027       1.021027
  2016-12-28  163.279259 -0.005684           1.015223       1.015223
  2016-12-29  163.682078  0.002467           1.017728       1.017728
  2016-12-30  163.082762 -0.003661           1.014002       1.014002
  
  [1258 rows x 4 columns], 0.4),
 (            Adj. Close   returns  cumulative return  Normed Return
  Date                                                              
  2012-01-03     179.030       NaN                NaN       1.000000
  2012-01-04     177.510 -0.008490           0.991510       0.991510
  2012-01-05     177.610  0.000563           0.992068       0.992068
  2012-01-06     182.610  0.028152           1.019997       1.019997
  2012-01-09     178.560 -0.022178           0.997375       0.997375
  2012-01-10     179.340  0.004368           1.001732       1.001732
  2012-01-11     178.900 -0.002453           0.999274       0.999274
  2012-01-12     175.930 -0.016601           0.982684       0.982684
  2012-01-13     178.420  0.014153           0.996593       0.996593
  2012-01-17     181.660  0.018159           1.014690       1.014690
  2012-01-18     189.440  0.042827           1.058147       1.058147
  2012-01-19     194.450  0.026446           1.086131       1.086131
  2012-01-20     190.930 -0.018102           1.066469       1.066469
  2012-01-23     186.090 -0.025350           1.039435       1.039435
  2012-01-24     187.000  0.004890           1.044518       1.044518
  2012-01-25     187.800  0.004278           1.048986       1.048986
  2012-01-26     193.316  0.029372           1.079797       1.079797
  2012-01-27     195.370  0.010625           1.091270       1.091270
  2012-01-30     192.150 -0.016482           1.073284       1.073284
  2012-01-31     194.440  0.011918           1.086075       1.086075
  2012-02-01     179.460 -0.077042           1.002402       1.002402
  2012-02-02     181.720  0.012593           1.015025       1.015025
  2012-02-03     187.680  0.032798           1.048316       1.048316
  2012-02-06     183.140 -0.024190           1.022957       1.022957
  2012-02-07     184.190  0.005733           1.028822       1.028822
  2012-02-08     185.480  0.007004           1.036027       1.036027
  2012-02-09     184.980 -0.002696           1.033235       1.033235
  2012-02-10     185.540  0.003027           1.036363       1.036363
  2012-02-13     191.590  0.032608           1.070156       1.070156
  2012-02-14     191.300 -0.001514           1.068536       1.068536
  ...                ...       ...                ...            ...
  2016-11-17     756.400  0.013275           4.224990       4.224990
  2016-11-18     760.160  0.004971           4.245992       4.245992
  2016-11-21     780.000  0.026100           4.356812       4.356812
  2016-11-22     785.330  0.006833           4.386583       4.386583
  2016-11-23     780.120 -0.006634           4.357482       4.357482
  2016-11-25     780.370  0.000320           4.358878       4.358878
  2016-11-28     766.770 -0.017428           4.282913       4.282913
  2016-11-29     762.520 -0.005543           4.259174       4.259174
  2016-11-30     750.570 -0.015672           4.192426       4.192426
  2016-12-01     743.650 -0.009220           4.153773       4.153773
  2016-12-02     740.340 -0.004451           4.135285       4.135285
  2016-12-05     759.360  0.025691           4.241524       4.241524
  2016-12-06     764.720  0.007059           4.271463       4.271463
  2016-12-07     770.420  0.007454           4.303301       4.303301
  2016-12-08     767.330 -0.004011           4.286041       4.286041
  2016-12-09     768.660  0.001733           4.293470       4.293470
  2016-12-12     760.120 -0.011110           4.245769       4.245769
  2016-12-13     774.340  0.018708           4.325197       4.325197
  2016-12-14     768.820 -0.007129           4.294364       4.294364
  2016-12-15     761.000 -0.010171           4.250684       4.250684
  2016-12-16     757.770 -0.004244           4.232643       4.232643
  2016-12-19     766.000  0.010861           4.278613       4.278613
  2016-12-20     771.220  0.006815           4.307770       4.307770
  2016-12-21     770.600 -0.000804           4.304307       4.304307
  2016-12-22     766.340 -0.005528           4.280512       4.280512
  2016-12-23     760.590 -0.007503           4.248394       4.248394
  2016-12-27     771.400  0.014213           4.308775       4.308775
  2016-12-28     772.130  0.000946           4.312853       4.312853
  2016-12-29     765.150 -0.009040           4.273865       4.273865
  2016-12-30     749.870 -0.019970           4.188516       4.188516
  
  [1258 rows x 4 columns], 0.1)]

In [35]:
for stock_df, allo in zip((aapl, cisco, ibm, amzn), [.3,.2,.4,.1]):
    stock_df['Allocation'] = stock_df['Normed Return'] * allo

In [36]:
aapl.head()


Out[36]:
Adj. Close returns cumulative return Normed Return Allocation
Date
2012-01-03 53.063218 NaN NaN 1.000000 0.300000
2012-01-04 53.348386 0.005374 1.005374 1.005374 0.301612
2012-01-05 53.940658 0.011102 1.016536 1.016536 0.304961
2012-01-06 54.504543 0.010454 1.027162 1.027162 0.308149
2012-01-09 54.418089 -0.001586 1.025533 1.025533 0.307660

In [37]:
for stock_df in (aapl, cisco, ibm, amzn):
    stock_df['Position Values'] = stock_df['Allocation'] * 1000000

In [38]:
aapl.drop(['returns', 'cumulative return'], inplace=True, axis=1)
aapl.head()


Out[38]:
Adj. Close Normed Return Allocation Position Values
Date
2012-01-03 53.063218 1.000000 0.300000 300000.000000
2012-01-04 53.348386 1.005374 0.301612 301612.236461
2012-01-05 53.940658 1.016536 0.304961 304960.727573
2012-01-06 54.504543 1.027162 0.308149 308148.724558
2012-01-09 54.418089 1.025533 0.307660 307659.946988

In [39]:
# Creating larger porfolio dataframe
all_pos_vals = [aapl['Position Values'], cisco['Position Values'], ibm['Position Values'], amzn['Position Values']]
portfolio_val = pd.concat(all_pos_vals, axis=1)
portfolio_val.columns = ['AAPL Pos', 'CISCO Pos', 'IBM Pos', 'AMZN Pos']
portfolio_val.head()


Out[39]:
AAPL Pos CISCO Pos IBM Pos AMZN Pos
Date
2012-01-03 300000.000000 200000.000000 400000.000000 100000.000000
2012-01-04 301612.236461 203864.734300 398368.223296 99150.980283
2012-01-05 304960.727573 203113.258186 396478.797638 99206.836843
2012-01-06 308148.724558 202361.782072 391926.999463 101999.664861
2012-01-09 307659.946988 203650.026838 389887.278583 99737.474166

In [40]:
# Summing along the row
portfolio_val['Total Pos'] = portfolio_val.sum(axis=1)
portfolio_val.head()


Out[40]:
AAPL Pos CISCO Pos IBM Pos AMZN Pos Total Pos
Date
2012-01-03 300000.000000 200000.000000 400000.000000 100000.000000 1.000000e+06
2012-01-04 301612.236461 203864.734300 398368.223296 99150.980283 1.002996e+06
2012-01-05 304960.727573 203113.258186 396478.797638 99206.836843 1.003760e+06
2012-01-06 308148.724558 202361.782072 391926.999463 101999.664861 1.004437e+06
2012-01-09 307659.946988 203650.026838 389887.278583 99737.474166 1.000935e+06

In [41]:
import matplotlib.pyplot as plt
%matplotlib inline

In [42]:
portfolio_val['Total Pos'].plot(figsize=(10,8))
plt.title('Total Portfolio Value');



In [43]:
portfolio_val.drop('Total Pos', axis=1).plot(figsize=(10,8));


Portfolio Allocation continued... 1


In [44]:
portfolio_val.head()


Out[44]:
AAPL Pos CISCO Pos IBM Pos AMZN Pos Total Pos
Date
2012-01-03 300000.000000 200000.000000 400000.000000 100000.000000 1.000000e+06
2012-01-04 301612.236461 203864.734300 398368.223296 99150.980283 1.002996e+06
2012-01-05 304960.727573 203113.258186 396478.797638 99206.836843 1.003760e+06
2012-01-06 308148.724558 202361.782072 391926.999463 101999.664861 1.004437e+06
2012-01-09 307659.946988 203650.026838 389887.278583 99737.474166 1.000935e+06

In [45]:
portfolio_val['Daily Returns'] = portfolio_val['Total Pos'].pct_change()
portfolio_val.head()


Out[45]:
AAPL Pos CISCO Pos IBM Pos AMZN Pos Total Pos Daily Returns
Date
2012-01-03 300000.000000 200000.000000 400000.000000 100000.000000 1.000000e+06 NaN
2012-01-04 301612.236461 203864.734300 398368.223296 99150.980283 1.002996e+06 0.002996
2012-01-05 304960.727573 203113.258186 396478.797638 99206.836843 1.003760e+06 0.000761
2012-01-06 308148.724558 202361.782072 391926.999463 101999.664861 1.004437e+06 0.000675
2012-01-09 307659.946988 203650.026838 389887.278583 99737.474166 1.000935e+06 -0.003487

Average Daily Return


In [46]:
portfolio_val['Daily Returns'].mean()


Out[46]:
0.00054423307162152791

Standard Deviation


In [47]:
portfolio_val['Daily Returns'].std()


Out[47]:
0.010568287769162552

Plotting Daily Returns


In [48]:
portfolio_val['Daily Returns'].plot(kind='hist', bins=100, figsize=(8,5));



In [49]:
portfolio_val['Daily Returns'].plot(kind='kde', figsize=(8,5));


Overall cumulative return


In [50]:
overall_cum_ret = 100 * (portfolio_val['Total Pos'][-1] / portfolio_val['Total Pos'][0] - 1)
overall_cum_ret


Out[50]:
84.742851816654593

In [51]:
portfolio_val['Total Pos'][-1]


Out[51]:
1847428.518166546

Thus we have gained over 84.74% on our initial portfolio investment till date which amounts to 1847428.51 USD


In [52]:
# Calculating sharpe ration assuming that risk_free return is zero.
sharpe_ratio = portfolio_val['Daily Returns'].mean() / portfolio_val['Daily Returns'].std()
sharpe_ratio


Out[52]:
0.051496806626477189

Therefore, our sharpe ratio is 0.054196806626477189


In [53]:
# Calculating annualized sharpe ratio
annual_sharpe_ratio = (252**0.5) * sharpe_ratio
annual_sharpe_ratio


Out[53]:
0.81748646188585039

Therefore, our annualized sharpe ratio is 0.81748646188585039

Sharpe ratio acceptable measures:

  • ratio >=1 and < 2 - Good
  • ratio >=2 and < 3 - Very Good
  • ratio >=3 and higher - Excellent

The basic purpose of sharpe ratio is to allow investors to analyze how much greater a return here he or she is obtaining in relation to the level of additional risk taken to generate that return.

Portfolio Optimization

“Modern Portfolio Theory (MPT), a hypothesis put forth by Harry Markowitz in his paper “Portfolio Selection,” (published in 1952 by the Journal of Finance) is an investment theory based on the idea that risk-averse investors can construct portfolios to optimize or maximize expected return based on a given level of market risk, emphasizing that risk is an inherent part of higher reward. It is one of the most important and influential economic theories dealing with finance and investment.

Goal: Optimize the portfolio holding to obtain the best Sharpe ratio.

  • Below are some techniques
    • Monto Carlo Simulation: Guessing and checking a bunch of random allocations and see which one has the best Sharpe ratio.
    • Optimization algorithms: Using the method called as minimization, we attempt to minimize the negative Sharpe ratio.

Marcos Portfolio Optimization

Bullet (Efficient frontier)

[http://www.bfjlaward.com/pdf/26063/59-69_Lopez%20Color_JPM_0708.pdf]



Portfolio Optimization continued... 1


In [54]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

In [55]:
aapl = pd.read_csv('AAPL_CLOSE', index_col='Date', parse_dates=True)
cisco = pd.read_csv('CISCO_CLOSE', index_col='Date', parse_dates=True)
ibm = pd.read_csv('IBM_CLOSE', index_col='Date', parse_dates=True)
amzn = pd.read_csv('AMZN_CLOSE', index_col='Date', parse_dates=True)

In [56]:
aapl.head()


Out[56]:
Adj. Close
Date
2012-01-03 53.063218
2012-01-04 53.348386
2012-01-05 53.940658
2012-01-06 54.504543
2012-01-09 54.418089

In [57]:
stocks = pd.concat([aapl, cisco, ibm, amzn], axis=1)
stocks.columns = ['aapl', 'cisco', 'ibm', 'amzn']
stocks.head()


Out[57]:
aapl cisco ibm amzn
Date
2012-01-03 53.063218 15.752778 160.830881 179.03
2012-01-04 53.348386 16.057180 160.174781 177.51
2012-01-05 53.940658 15.997991 159.415086 177.61
2012-01-06 54.504543 15.938801 157.584912 182.61
2012-01-09 54.418089 16.040268 156.764786 178.56

In [58]:
stocks.pct_change().mean()


Out[58]:
aapl     0.000750
cisco    0.000599
ibm      0.000081
amzn     0.001328
dtype: float64

In [59]:
# Pearson correlation coefficient
# [https://statistics.laerd.com/statistical-guides/pearson-correlation-coefficient-statistical-guide.php]
stocks.pct_change().corr()


Out[59]:
aapl cisco ibm amzn
aapl 1.000000 0.301990 0.297498 0.235487
cisco 0.301990 1.000000 0.424672 0.284470
ibm 0.297498 0.424672 1.000000 0.258492
amzn 0.235487 0.284470 0.258492 1.000000

Log Returns vs Arithmetic Returns

We will now switch over to using log returns instead of arithmetic returns, for many of our use cases they are almost the same,but most technical analyses require detrending/normalizing the time series and using log returns is a nice way to do that. Log returns are convenient to work with in many of the algorithms we will encounter.

For a full analysis of why we use log returns, check this great article.


In [60]:
# Arithmetic daily returns
stocks.pct_change().head()


Out[60]:
aapl cisco ibm amzn
Date
2012-01-03 NaN NaN NaN NaN
2012-01-04 0.005374 0.019324 -0.004079 -0.008490
2012-01-05 0.011102 -0.003686 -0.004743 0.000563
2012-01-06 0.010454 -0.003700 -0.011481 0.028152
2012-01-09 -0.001586 0.006366 -0.005204 -0.022178

In [61]:
# Logarithmic daily returns
log_ret = np.log(stocks/stocks.shift(1))
log_ret.head()


Out[61]:
aapl cisco ibm amzn
Date
2012-01-03 NaN NaN NaN NaN
2012-01-04 0.005360 0.019139 -0.004088 -0.008526
2012-01-05 0.011041 -0.003693 -0.004754 0.000563
2012-01-06 0.010400 -0.003707 -0.011547 0.027763
2012-01-09 -0.001587 0.006346 -0.005218 -0.022428

In [62]:
log_ret.hist(bins=100, figsize=(12,8))
plt.tight_layout()



In [63]:
# Mean of logarithmic returns
log_ret.mean()


Out[63]:
aapl     0.000614
cisco    0.000497
ibm      0.000011
amzn     0.001139
dtype: float64

In [64]:
# Covariance of logarithmic returns
log_ret.cov()


Out[64]:
aapl cisco ibm amzn
aapl 0.000271 0.000071 0.000057 0.000075
cisco 0.000071 0.000204 0.000072 0.000079
ibm 0.000057 0.000072 0.000140 0.000059
amzn 0.000075 0.000079 0.000059 0.000375

===========================================================


In [65]:
# resetting the random seed
np.random.seed(101)

# know the stocks
print(stocks.columns)

# create random allocation weights
weights = np.random.random(4)
print('Random weights: {}'.format(weights))

# rebalance the weights to add up to 1.0
weights = weights/np.sum(weights)
print('Rebalanced weights: {}'.format(weights))

# expected return
exp_ret = np.sum((log_ret.mean() * weights) * 252)
print('Expected return: {}'.format(exp_ret))

# expected volatility
exp_vol = np.sqrt(np.dot(weights.T, np.dot(log_ret.cov() * 252, weights)))
print('Expected volatility: {}'.format(exp_vol))

# Sharpe Ratio
SR = exp_ret/exp_vol
print('Sharpe Ratio: {}'.format(SR))


Index(['aapl', 'cisco', 'ibm', 'amzn'], dtype='object')
Random weights: [ 0.51639863  0.57066759  0.02847423  0.17152166]
Rebalanced weights: [ 0.40122278  0.44338777  0.02212343  0.13326603]
Expected return: 0.15599272049632004
Expected volatility: 0.18502649565909488
Sharpe Ratio: 0.8430831483926032

Portfolio Optimization continued... 2

Monte Carlo Simulation Method


In [66]:
# resetting the random seed
np.random.seed(101)

num_portfolios = 25000
all_weights = np.zeros((num_portfolios, len(stocks.columns)))
exp_ret_arr = np.zeros(num_portfolios)
exp_vol_arr = np.zeros(num_portfolios)
sharpe_arr = np.zeros(num_portfolios)

for i in range(num_portfolios):
    weights = np.random.random(4)
    weights = weights/np.sum(weights)
    
    all_weights[i,:] = weights
    
    exp_ret_arr[i] = np.sum((log_ret.mean() * weights) * 252)
    exp_vol_arr[i] = np.sqrt(np.dot(weights.T, np.dot(log_ret.cov() * 252, weights)))
    sharpe_arr[i] = exp_ret_arr[i]/exp_vol_arr[i]

In [67]:
# maximum sharpe ratio obtained
sharpe_arr.max()


Out[67]:
1.0303260551271067

In [68]:
# index of maximum sharpe ratio
sharpe_arr.argmax()


Out[68]:
1420

In [69]:
# allocation weights at the maximum sharpe ratio
all_weights[1420, :]


Out[69]:
array([ 0.26188068,  0.20759516,  0.00110226,  0.5294219 ])

In [70]:
plt.figure(figsize=(12,8))
plt.scatter(exp_vol_arr, exp_ret_arr, c=sharpe_arr, cmap='plasma')
plt.colorbar(label='Sharpe ratio')
plt.xlabel('Volatility')
plt.ylabel('Return');

max_sr_ret = exp_ret_arr[1420]
max_sr_vol = exp_vol_arr[1420]
plt.scatter(max_sr_vol, max_sr_ret, c='red', s=50, edgecolors='black');


Additional homework


In [71]:
stocks.head()


Out[71]:
aapl cisco ibm amzn
Date
2012-01-03 53.063218 15.752778 160.830881 179.03
2012-01-04 53.348386 16.057180 160.174781 177.51
2012-01-05 53.940658 15.997991 159.415086 177.61
2012-01-06 54.504543 15.938801 157.584912 182.61
2012-01-09 54.418089 16.040268 156.764786 178.56

In [72]:
all_positions_returns = ((1 + stocks.pct_change()).cumprod() * all_weights[1420, :] * 1000000).dropna().sum(axis=1)
all_positions_returns[-1]


Out[72]:
3173267.1639771275

In [73]:
100 * (all_positions_returns[-1] / all_positions_returns[0] - 1)


Out[73]:
217.03520685521443

Plotting the monte carlo best porfolio (26,21,0,53) vs default (30,20,10,40) (aapl, cisco, ibm, amzn)


In [74]:
all_positions_returns.plot(figsize=(10,8));



In [75]:
portfolio_val['Total Pos'].plot(figsize=(10,8))
plt.title('Total Portfolio Value');



In [76]:
((1 + stocks.pct_change()).cumprod() * all_weights[1420, :] * 1000000).plot(figsize=(10,8));



In [77]:
portfolio_val.drop(['Total Pos', 'Daily Returns'], axis=1).plot(figsize=(10,8));


Portfolio Optimization continued... 3


In [78]:
def get_ret_vol_sr(weights):
    weights = np.array(weights)
    ret = np.sum(log_ret.mean() * weights) * 252
    vol = np.sqrt(np.dot(weights.T, np.dot(log_ret.cov() * 252, weights)))
    sr = ret/vol
    return np.array([ret, vol, sr])

In [79]:
from scipy.optimize import minimize

In [80]:
help(minimize)


Help on function minimize in module scipy.optimize._minimize:

minimize(fun, x0, args=(), method=None, jac=None, hess=None, hessp=None, bounds=None, constraints=(), tol=None, callback=None, options=None)
    Minimization of scalar function of one or more variables.
    
    In general, the optimization problems are of the form::
    
        minimize f(x) subject to
    
        g_i(x) >= 0,  i = 1,...,m
        h_j(x)  = 0,  j = 1,...,p
    
    where x is a vector of one or more variables.
    ``g_i(x)`` are the inequality constraints.
    ``h_j(x)`` are the equality constrains.
    
    Optionally, the lower and upper bounds for each element in x can also be
    specified using the `bounds` argument.
    
    Parameters
    ----------
    fun : callable
        Objective function.
    x0 : ndarray
        Initial guess.
    args : tuple, optional
        Extra arguments passed to the objective function and its
        derivatives (Jacobian, Hessian).
    method : str or callable, optional
        Type of solver.  Should be one of
    
            - 'Nelder-Mead' :ref:`(see here) <optimize.minimize-neldermead>`
            - 'Powell'      :ref:`(see here) <optimize.minimize-powell>`
            - 'CG'          :ref:`(see here) <optimize.minimize-cg>`
            - 'BFGS'        :ref:`(see here) <optimize.minimize-bfgs>`
            - 'Newton-CG'   :ref:`(see here) <optimize.minimize-newtoncg>`
            - 'L-BFGS-B'    :ref:`(see here) <optimize.minimize-lbfgsb>`
            - 'TNC'         :ref:`(see here) <optimize.minimize-tnc>`
            - 'COBYLA'      :ref:`(see here) <optimize.minimize-cobyla>`
            - 'SLSQP'       :ref:`(see here) <optimize.minimize-slsqp>`
            - 'dogleg'      :ref:`(see here) <optimize.minimize-dogleg>`
            - 'trust-ncg'   :ref:`(see here) <optimize.minimize-trustncg>`
            - custom - a callable object (added in version 0.14.0),
              see below for description.
    
        If not given, chosen to be one of ``BFGS``, ``L-BFGS-B``, ``SLSQP``,
        depending if the problem has constraints or bounds.
    jac : bool or callable, optional
        Jacobian (gradient) of objective function. Only for CG, BFGS,
        Newton-CG, L-BFGS-B, TNC, SLSQP, dogleg, trust-ncg.
        If `jac` is a Boolean and is True, `fun` is assumed to return the
        gradient along with the objective function. If False, the
        gradient will be estimated numerically.
        `jac` can also be a callable returning the gradient of the
        objective. In this case, it must accept the same arguments as `fun`.
    hess, hessp : callable, optional
        Hessian (matrix of second-order derivatives) of objective function or
        Hessian of objective function times an arbitrary vector p.  Only for
        Newton-CG, dogleg, trust-ncg.
        Only one of `hessp` or `hess` needs to be given.  If `hess` is
        provided, then `hessp` will be ignored.  If neither `hess` nor
        `hessp` is provided, then the Hessian product will be approximated
        using finite differences on `jac`. `hessp` must compute the Hessian
        times an arbitrary vector.
    bounds : sequence, optional
        Bounds for variables (only for L-BFGS-B, TNC and SLSQP).
        ``(min, max)`` pairs for each element in ``x``, defining
        the bounds on that parameter. Use None for one of ``min`` or
        ``max`` when there is no bound in that direction.
    constraints : dict or sequence of dict, optional
        Constraints definition (only for COBYLA and SLSQP).
        Each constraint is defined in a dictionary with fields:
    
            type : str
                Constraint type: 'eq' for equality, 'ineq' for inequality.
            fun : callable
                The function defining the constraint.
            jac : callable, optional
                The Jacobian of `fun` (only for SLSQP).
            args : sequence, optional
                Extra arguments to be passed to the function and Jacobian.
    
        Equality constraint means that the constraint function result is to
        be zero whereas inequality means that it is to be non-negative.
        Note that COBYLA only supports inequality constraints.
    tol : float, optional
        Tolerance for termination. For detailed control, use solver-specific
        options.
    options : dict, optional
        A dictionary of solver options. All methods accept the following
        generic options:
    
            maxiter : int
                Maximum number of iterations to perform.
            disp : bool
                Set to True to print convergence messages.
    
        For method-specific options, see :func:`show_options()`.
    callback : callable, optional
        Called after each iteration, as ``callback(xk)``, where ``xk`` is the
        current parameter vector.
    
    Returns
    -------
    res : OptimizeResult
        The optimization result represented as a ``OptimizeResult`` object.
        Important attributes are: ``x`` the solution array, ``success`` a
        Boolean flag indicating if the optimizer exited successfully and
        ``message`` which describes the cause of the termination. See
        `OptimizeResult` for a description of other attributes.
    
    
    See also
    --------
    minimize_scalar : Interface to minimization algorithms for scalar
        univariate functions
    show_options : Additional options accepted by the solvers
    
    Notes
    -----
    This section describes the available solvers that can be selected by the
    'method' parameter. The default method is *BFGS*.
    
    **Unconstrained minimization**
    
    Method :ref:`Nelder-Mead <optimize.minimize-neldermead>` uses the
    Simplex algorithm [1]_, [2]_. This algorithm is robust in many
    applications. However, if numerical computation of derivative can be
    trusted, other algorithms using the first and/or second derivatives
    information might be preferred for their better performance in
    general.
    
    Method :ref:`Powell <optimize.minimize-powell>` is a modification
    of Powell's method [3]_, [4]_ which is a conjugate direction
    method. It performs sequential one-dimensional minimizations along
    each vector of the directions set (`direc` field in `options` and
    `info`), which is updated at each iteration of the main
    minimization loop. The function need not be differentiable, and no
    derivatives are taken.
    
    Method :ref:`CG <optimize.minimize-cg>` uses a nonlinear conjugate
    gradient algorithm by Polak and Ribiere, a variant of the
    Fletcher-Reeves method described in [5]_ pp.  120-122. Only the
    first derivatives are used.
    
    Method :ref:`BFGS <optimize.minimize-bfgs>` uses the quasi-Newton
    method of Broyden, Fletcher, Goldfarb, and Shanno (BFGS) [5]_
    pp. 136. It uses the first derivatives only. BFGS has proven good
    performance even for non-smooth optimizations. This method also
    returns an approximation of the Hessian inverse, stored as
    `hess_inv` in the OptimizeResult object.
    
    Method :ref:`Newton-CG <optimize.minimize-newtoncg>` uses a
    Newton-CG algorithm [5]_ pp. 168 (also known as the truncated
    Newton method). It uses a CG method to the compute the search
    direction. See also *TNC* method for a box-constrained
    minimization with a similar algorithm.
    
    Method :ref:`dogleg <optimize.minimize-dogleg>` uses the dog-leg
    trust-region algorithm [5]_ for unconstrained minimization. This
    algorithm requires the gradient and Hessian; furthermore the
    Hessian is required to be positive definite.
    
    Method :ref:`trust-ncg <optimize.minimize-trustncg>` uses the
    Newton conjugate gradient trust-region algorithm [5]_ for
    unconstrained minimization. This algorithm requires the gradient
    and either the Hessian or a function that computes the product of
    the Hessian with a given vector.
    
    **Constrained minimization**
    
    Method :ref:`L-BFGS-B <optimize.minimize-lbfgsb>` uses the L-BFGS-B
    algorithm [6]_, [7]_ for bound constrained minimization.
    
    Method :ref:`TNC <optimize.minimize-tnc>` uses a truncated Newton
    algorithm [5]_, [8]_ to minimize a function with variables subject
    to bounds. This algorithm uses gradient information; it is also
    called Newton Conjugate-Gradient. It differs from the *Newton-CG*
    method described above as it wraps a C implementation and allows
    each variable to be given upper and lower bounds.
    
    Method :ref:`COBYLA <optimize.minimize-cobyla>` uses the
    Constrained Optimization BY Linear Approximation (COBYLA) method
    [9]_, [10]_, [11]_. The algorithm is based on linear
    approximations to the objective function and each constraint. The
    method wraps a FORTRAN implementation of the algorithm. The
    constraints functions 'fun' may return either a single number
    or an array or list of numbers.
    
    Method :ref:`SLSQP <optimize.minimize-slsqp>` uses Sequential
    Least SQuares Programming to minimize a function of several
    variables with any combination of bounds, equality and inequality
    constraints. The method wraps the SLSQP Optimization subroutine
    originally implemented by Dieter Kraft [12]_. Note that the
    wrapper handles infinite values in bounds by converting them into
    large floating values.
    
    **Custom minimizers**
    
    It may be useful to pass a custom minimization method, for example
    when using a frontend to this method such as `scipy.optimize.basinhopping`
    or a different library.  You can simply pass a callable as the ``method``
    parameter.
    
    The callable is called as ``method(fun, x0, args, **kwargs, **options)``
    where ``kwargs`` corresponds to any other parameters passed to `minimize`
    (such as `callback`, `hess`, etc.), except the `options` dict, which has
    its contents also passed as `method` parameters pair by pair.  Also, if
    `jac` has been passed as a bool type, `jac` and `fun` are mangled so that
    `fun` returns just the function values and `jac` is converted to a function
    returning the Jacobian.  The method shall return an ``OptimizeResult``
    object.
    
    The provided `method` callable must be able to accept (and possibly ignore)
    arbitrary parameters; the set of parameters accepted by `minimize` may
    expand in future versions and then these parameters will be passed to
    the method.  You can find an example in the scipy.optimize tutorial.
    
    .. versionadded:: 0.11.0
    
    References
    ----------
    .. [1] Nelder, J A, and R Mead. 1965. A Simplex Method for Function
        Minimization. The Computer Journal 7: 308-13.
    .. [2] Wright M H. 1996. Direct search methods: Once scorned, now
        respectable, in Numerical Analysis 1995: Proceedings of the 1995
        Dundee Biennial Conference in Numerical Analysis (Eds. D F
        Griffiths and G A Watson). Addison Wesley Longman, Harlow, UK.
        191-208.
    .. [3] Powell, M J D. 1964. An efficient method for finding the minimum of
       a function of several variables without calculating derivatives. The
       Computer Journal 7: 155-162.
    .. [4] Press W, S A Teukolsky, W T Vetterling and B P Flannery.
       Numerical Recipes (any edition), Cambridge University Press.
    .. [5] Nocedal, J, and S J Wright. 2006. Numerical Optimization.
       Springer New York.
    .. [6] Byrd, R H and P Lu and J. Nocedal. 1995. A Limited Memory
       Algorithm for Bound Constrained Optimization. SIAM Journal on
       Scientific and Statistical Computing 16 (5): 1190-1208.
    .. [7] Zhu, C and R H Byrd and J Nocedal. 1997. L-BFGS-B: Algorithm
       778: L-BFGS-B, FORTRAN routines for large scale bound constrained
       optimization. ACM Transactions on Mathematical Software 23 (4):
       550-560.
    .. [8] Nash, S G. Newton-Type Minimization Via the Lanczos Method.
       1984. SIAM Journal of Numerical Analysis 21: 770-778.
    .. [9] Powell, M J D. A direct search optimization method that models
       the objective and constraint functions by linear interpolation.
       1994. Advances in Optimization and Numerical Analysis, eds. S. Gomez
       and J-P Hennart, Kluwer Academic (Dordrecht), 51-67.
    .. [10] Powell M J D. Direct search algorithms for optimization
       calculations. 1998. Acta Numerica 7: 287-336.
    .. [11] Powell M J D. A view of algorithms for optimization without
       derivatives. 2007.Cambridge University Technical Report DAMTP
       2007/NA03
    .. [12] Kraft, D. A software package for sequential quadratic
       programming. 1988. Tech. Rep. DFVLR-FB 88-28, DLR German Aerospace
       Center -- Institute for Flight Mechanics, Koln, Germany.
    
    Examples
    --------
    Let us consider the problem of minimizing the Rosenbrock function. This
    function (and its respective derivatives) is implemented in `rosen`
    (resp. `rosen_der`, `rosen_hess`) in the `scipy.optimize`.
    
    >>> from scipy.optimize import minimize, rosen, rosen_der
    
    A simple application of the *Nelder-Mead* method is:
    
    >>> x0 = [1.3, 0.7, 0.8, 1.9, 1.2]
    >>> res = minimize(rosen, x0, method='Nelder-Mead', tol=1e-6)
    >>> res.x
    array([ 1.,  1.,  1.,  1.,  1.])
    
    Now using the *BFGS* algorithm, using the first derivative and a few
    options:
    
    >>> res = minimize(rosen, x0, method='BFGS', jac=rosen_der,
    ...                options={'gtol': 1e-6, 'disp': True})
    Optimization terminated successfully.
             Current function value: 0.000000
             Iterations: 26
             Function evaluations: 31
             Gradient evaluations: 31
    >>> res.x
    array([ 1.,  1.,  1.,  1.,  1.])
    >>> print(res.message)
    Optimization terminated successfully.
    >>> res.hess_inv
    array([[ 0.00749589,  0.01255155,  0.02396251,  0.04750988,  0.09495377],  # may vary
           [ 0.01255155,  0.02510441,  0.04794055,  0.09502834,  0.18996269],
           [ 0.02396251,  0.04794055,  0.09631614,  0.19092151,  0.38165151],
           [ 0.04750988,  0.09502834,  0.19092151,  0.38341252,  0.7664427 ],
           [ 0.09495377,  0.18996269,  0.38165151,  0.7664427,   1.53713523]])
    
    
    Next, consider a minimization problem with several constraints (namely
    Example 16.4 from [5]_). The objective function is:
    
    >>> fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2
    
    There are three constraints defined as:
    
    >>> cons = ({'type': 'ineq', 'fun': lambda x:  x[0] - 2 * x[1] + 2},
    ...         {'type': 'ineq', 'fun': lambda x: -x[0] - 2 * x[1] + 6},
    ...         {'type': 'ineq', 'fun': lambda x: -x[0] + 2 * x[1] + 2})
    
    And variables must be positive, hence the following bounds:
    
    >>> bnds = ((0, None), (0, None))
    
    The optimization problem is solved using the SLSQP method as:
    
    >>> res = minimize(fun, (2, 0), method='SLSQP', bounds=bnds,
    ...                constraints=cons)
    
    It should converge to the theoretical solution (1.4 ,1.7).


In [81]:
# Helper functions
def neg_sharpe(weights):
    return get_ret_vol_sr(weights)[2] * -1.0

In [82]:
def check_sum(weights):
    # return 0 if the sum of the weights is 1
    return np.sum(weights) - 1.0

In [83]:
constraints = ({'type' : 'eq', 'fun' : check_sum})

# The (min, max) value bound of each portfolio allocation, essentially
# a number between 0 and 1
bounds = ((0,1),(0,1),(0,1),(0,1))

# initial allocation guess
init_guess = [0.25,0.25,0.25,0.25]

In [92]:
opt_results = minimize(fun=neg_sharpe, x0=init_guess, method='SLSQP', bounds=bounds, constraints=constraints)
opt_results


Out[92]:
     fun: -1.0307168703347112
     jac: array([  5.64157963e-05,   4.18424606e-05,   3.39921728e-01,
        -4.45097685e-05])
 message: 'Optimization terminated successfully.'
    nfev: 42
     nit: 7
    njev: 7
  status: 0
 success: True
       x: array([ 0.26628977,  0.20418983,  0.        ,  0.5295204 ])

In [85]:
# best allocation portfolio
opt_results.x


Out[85]:
array([ 0.26628977,  0.20418983,  0.        ,  0.5295204 ])

In [87]:
# returns array([returns, volatility, Sharpe ratio])
get_ret_vol_sr(opt_results.x)


Out[87]:
array([ 0.21885915,  0.21233683,  1.03071687])

Plotting the frontier


In [93]:
# Finding the frontier between the y value (expected log returns) 0 to 0.3
frontier_y = np.linspace(0, 0.3, 100)

def minimize_vol(weights):
    return get_ret_vol_sr(weights)[1]    # returns volatility

In [96]:
frontier_volatility = []

for possible_return in frontier_y:
    constraints = ({'type':'eq', 'fun':check_sum},
                  {'type':'eq', 'fun': lambda weights: get_ret_vol_sr(weights)[0] - possible_return})
    result = minimize(fun=minimize_vol,x0=init_guess,method='SLSQP', constraints=constraints)
    frontier_volatility.append(result['fun'])

In [98]:
plt.figure(figsize=(12,8))
plt.scatter(exp_vol_arr, exp_ret_arr, c=sharpe_arr, cmap='plasma')
plt.colorbar(label='Sharpe ratio')
plt.xlabel('Volatility')
plt.ylabel('Return');

plt.plot(frontier_volatility, frontier_y, 'g--', linewidth=3);



Key Financial Topics

  • Type of Funds
  • Order Books
  • Latency Arbitrage (HFT)
  • Short Selling

Types of Funds

Three major types:

1. ETF - Exchange Traded Funds
2. Mutual Funds
3. Hedge Funds

ETFs are exchange traded funds that are constituted of a basket of funds, bonds, commodities, securities etc. Their holding are completely public and transparent and individuals can buy trade the marketable security.

Typically people investing in ETFs are more intrested in a diversified portfolio and want to keep their investment in an ETF for a longer period of time.

One of the most common ETFs is the Spider (SPY) which tracks the S&P500

A mutual fund is an investment vechicle made up of a pool of funds collected from many investors for the purpose of investing in securities such as stocks, bonds, money market instruments and similar assets.

Mutual funds are operated by money managers, whoc invest the fund's capital and attempt to produce captial gains and income for the fund's investors.

A mutual fund's portfolio is structured and maintained to match the investment objectives stated in its prospectus.

Mutual Funds disclose their holdings typically once a quarter, although this can vary by fund.

Hedge funds are alternative investments using pooled funds that employ numerous different strategies to earn active return, or alpha, for their investors.

Hedge funds may be aggressively managed or make use of deritives and leverage in both domestic and international markets with the goal of generating high returns (either in an absolute sense or over a specified market benchmark).

Essentially the goal of hedge funds is to beat the market, or beat the benchmarks.

It is important to note that hedge funds are generally only accessible to accredited investors as they require less SEC regulations than other funds.

One aspect that has set the hedge fund industry apart is the fact that hedge funds face less regulation than mutual funds and other investment vehicles.

Fees associated with each?

  • ETF Funds
    • Expense ratio - 0.01% - 1% (percentage of AUM (assets under management))
  • Mutual Funds
    • Expense ratio - 0.5% - 3%
  • Hedge Funds
    • 2% of fund. 20% of profits (2 and 20 rule)

Liquidity associated with each?

  • ETF Funds
    • Buy/Sell just like a stock.
  • Mutual Funds
    • Buy/Sell at end of day through broker.
  • Hedge Funds
    • Depends on agreement

Making an order includes the following:

  • Buy or sell
  • Symbol
  • Number of shares
  • LIMIT or MARKET
  • Price (only needed for a LIMIT order)

Example orders:

- BUY, AAPL, 200, MARKET
- SELL, TSLA, 400, MARKET
- BUY, AMD, 2000, LIMIT, 13.95
- SELL, NVDA, 150, LIMIT, 160.99
  • More information on the videos

Short Selling

[http://www.investopedia.com/terms/s/shortselling.asp]

  • More information on the videos

CAPM - Capital Asset Pricing Model

[http://www.investopedia.com/terms/c/capm.asp]

Portfolio Returns:

$r_p(t) = \sum\limits_{i}^{n}w_i r_i(t)$

Market Weights:

$ w_i = \frac{MarketCap_i}{\sum_{j}^{n}{MarketCap_j}} $

CAPM of a portfolio

$ r_p(t) = \beta_pr_m(t) + \sum\limits_{i}^{n}w_i \alpha_i(t)$


In [101]:
from scipy import stats

In [103]:
help(stats.linregress)


Help on function linregress in module scipy.stats._stats_mstats_common:

linregress(x, y=None)
    Calculate a linear least-squares regression for two sets of measurements.
    
    Parameters
    ----------
    x, y : array_like
        Two sets of measurements.  Both arrays should have the same length.
        If only x is given (and y=None), then it must be a two-dimensional
        array where one dimension has length 2.  The two sets of measurements
        are then found by splitting the array along the length-2 dimension.
    
    Returns
    -------
    slope : float
        slope of the regression line
    intercept : float
        intercept of the regression line
    rvalue : float
        correlation coefficient
    pvalue : float
        two-sided p-value for a hypothesis test whose null hypothesis is
        that the slope is zero.
    stderr : float
        Standard error of the estimated gradient.
    
    See also
    --------
    :func:`scipy.optimize.curve_fit` : Use non-linear
     least squares to fit a function to data.
    :func:`scipy.optimize.leastsq` : Minimize the sum of
     squares of a set of equations.
    
    Examples
    --------
    >>> import matplotlib.pyplot as plt
    >>> from scipy import stats
    >>> np.random.seed(12345678)
    >>> x = np.random.random(10)
    >>> y = np.random.random(10)
    >>> slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
    
    To get coefficient of determination (r_squared)
    
    >>> print("r-squared:", r_value**2)
    ('r-squared:', 0.080402268539028335)
    
    Plot the data along with the fitted line
    
    >>> plt.plot(x, y, 'o', label='original data')
    >>> plt.plot(x, intercept + slope*x, 'r', label='fitted line')
    >>> plt.legend()
    >>> plt.show()


In [114]:
import pandas as pd
import pandas_datareader as web
import datetime

In [115]:
start = datetime.datetime(2010, 1, 4)
end = datetime.datetime(2017, 7, 25)
spy_etf = web.DataReader('SPY', 'google', start=start, end= end)

In [117]:
spy_etf.info()


<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 251 entries, 2016-10-10 to 2017-10-06
Data columns (total 5 columns):
Open      250 non-null float64
High      250 non-null float64
Low       250 non-null float64
Close     251 non-null float64
Volume    251 non-null int64
dtypes: float64(4), int64(1)
memory usage: 11.8 KB

In [119]:
spy_etf.tail()


Out[119]:
Open High Low Close Volume
Date
2017-10-02 251.49 252.32 251.29 252.32 59022985
2017-10-03 252.46 252.89 252.23 252.86 66810169
2017-10-04 252.69 253.44 252.56 253.16 55953619
2017-10-05 253.54 254.68 253.20 254.66 63522757
2017-10-06 254.15 254.70 253.85 254.37 80645998

In [126]:
start = pd.to_datetime('2010-01-04')
end = pd.to_datetime('2017-07-18')

In [127]:
aapl = web.DataReader('AAPL','google',start,end)
aapl.head()


Out[127]:
Open High Low Close Volume
Date
2016-10-10 115.02 116.75 114.72 116.05 36235956
2016-10-11 117.70 118.69 116.20 116.30 64041043
2016-10-12 117.35 117.98 116.75 117.34 37586787
2016-10-13 116.79 117.44 115.72 116.98 35192406
2016-10-14 117.88 118.17 117.13 117.63 35652191

In [128]:
import matplotlib.pyplot as plt
%matplotlib inline

In [130]:
aapl['Close'].plot(label='AAPL', figsize=(10,8))
spy_etf['Close'].plot(label='SPY Index')
plt.legend();



In [131]:
aapl['Cumulative'] = aapl['Close']/aapl['Close'].iloc[0]
spy_etf['Cumulative'] = spy_etf['Close']/spy_etf['Close'].iloc[0]

In [135]:
aapl.head()


Out[135]:
Open High Low Close Volume Cumulative
Date
2016-10-10 115.02 116.75 114.72 116.05 36235956 1.000000
2016-10-11 117.70 118.69 116.20 116.30 64041043 1.002154
2016-10-12 117.35 117.98 116.75 117.34 37586787 1.011116
2016-10-13 116.79 117.44 115.72 116.98 35192406 1.008014
2016-10-14 117.88 118.17 117.13 117.63 35652191 1.013615

In [134]:
aapl['Cumulative'].plot(label='AAPL', figsize=(10,8))
spy_etf['Cumulative'].plot(label='SPY')
plt.legend();



In [136]:
aapl['Daily Return'] = aapl['Close'].pct_change()
spy_etf['Daily Return'] = spy_etf['Close'].pct_change()

In [138]:
plt.scatter(aapl['Daily Return'], spy_etf['Daily Return'], alpha=0.5);



In [140]:
beta,alpha,r_value,p_value,std_err = stats.linregress(aapl['Daily Return'].iloc[1:],spy_etf['Daily Return'].iloc[1:])
beta


Out[140]:
0.21566065399157372

In [141]:
alpha


Out[141]:
0.00039854839953421559

In [142]:
r_value


Out[142]:
0.49087186338994626

Conclusion: Since, r_value aapl and spy_etf are not a good fit.

Conclusion: Use the Adjusted Close instead of Close, as Adj. Close does take into account stock splits and dividends.

  • EMH is an investment theory that states it is impossible to 'beat the market' because stock market efficiency causes existing share prices to always incorporate and reflect all relevant information.

  • According to the EMH, stocks always trade at their fair value on stock exchanges, making it impossible for investors to either purchase undervalued stocks or sell stocks for inflated prices.

  • As such, it should be impossible to outperform the overall market through expert stock selection or market timing, and the only way an investor can possibly obtain higher returns is by purchasing riskier investments.

  • So is the EMH true?
  • The success of different strategies and hedge funds implies the strongest interpretation of EMH is probably not true.

  • There are also events that have shown the market to be overvalued at certain points in history (financial crises, dot com bubble, etc)

  • While some aspects of EMH certainly are true, especially as information is more widely acceptable, we will continue on with the assumption that the market is not 100% efficient!

In [ ]: