diff --git a/Cole Wins Above Replacement Portfolio.ipynb b/Cole Wins Above Replacement Portfolio.ipynb index f8fae6a..334a26c 100644 --- a/Cole Wins Above Replacement Portfolio.ipynb +++ b/Cole Wins Above Replacement Portfolio.ipynb @@ -460,7 +460,7 @@ "CWARP_W_String=str('CWARP_'+percentage_weight_asset+'_asset')\n", "\n", "risk_ret_df=pd.DataFrame(index=['Start_Date','End_Date','CWARP','+Sortino','+Ret_To_MaxDD','Sharpe','Sortino','Max_DD'],columns=ticker_list)\n", - "new_risk_ret_df=pd.DataFrame(index=['Return','Vol','Sharpe','Sortino','Max_DD','Ret_To_MaxDD','CWARP_25%_asset'],columns=ticker_list)\n", + "new_risk_ret_df=pd.DataFrame(index=['Return','Vol','Sharpe','Sortino','Max_DD','Ret_To_MaxDD',CWARP_W_String],columns=ticker_list)\n", "new_risk_ret_df=new_risk_ret_df.add_suffix('@'+percentage_weight_asset+' | '+replacement_port.name+'@'+percentage_weight_replace_port)\n", "new_risk_ret_df[replacement_port.name]=np.nan\n", "\n", @@ -468,12 +468,12 @@ "\n", "# evaluate metrics for all tickers in list above\n", "for i in range(0,len(ticker_list)):\n", - " temp_data=retrieve_yhoo_data(ticker_list[i])\n", + " temp_data=retrieve_yhoo_data(ticker_list[i], start_date, end_date)\n", " new_port = cwarp_new_port_data(new_asset=temp_data,replace_port=replacement_port,financing_rate=financing_rate,\n", " risk_free_rate=risk_free_rate,weight_asset=weight_asset,weight_replace_port=weight_replace_port,periodicity=periodicity)\n", " prices_df=pd.merge(prices_df,temp_data, left_index=True, right_index=True)\n", - " risk_ret_df.loc['Start_Date',ticker_list[i]]=min(temp_data.index)\n", - " risk_ret_df.loc['End_Date',ticker_list[i]]=max(temp_data.index)\n", + " risk_ret_df.loc['Start_Date',ticker_list[i]]=min(temp_data.index).date()\n", + " risk_ret_df.loc['End_Date',ticker_list[i]]=max(temp_data.index).date()\n", " \n", " risk_ret_df.loc['CWARP',ticker_list[i]]=cole_win_above_replace_port(new_asset=temp_data,replace_port=replacement_port,financing_rate=financing_rate,\n", " risk_free_rate=risk_free_rate,weight_asset=weight_asset,weight_replace_port=weight_replace_port,periodicity=periodicity)\n", @@ -532,231 +532,148 @@ { "data": { "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
tltiefgldlqdshyqqqxlkslvhygiyreemefaxlexlf
Start_Date2007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:002007-07-02 00:00:00
End_Date2020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:002020-12-30 00:00:00
CWARP35.07505921.35502819.8626810.3700564.9641823.0549180.176191-3.105972-5.412795-23.615039-24.563182-27.322877-33.078864-33.273558
+Sortino29.87491818.21635313.8739239.4404413.7346662.767763-0.26814-6.900377-3.632828-21.710574-24.392485-24.57957-34.119457-27.888017
+Ret_To_MaxDD40.48341224.57703626.16639211.3075676.2082723.3428760.6225010.84308-7.159884-25.473176-24.733494-29.9664-32.021834-38.256891
Sharpe0.5448660.7468210.4736680.6636131.0496950.7516630.6846620.3028020.4646210.285930.2393330.1922150.0910220.211968
Sortino0.7907511.1007680.6722880.9429791.6158621.0652660.9739250.4142360.6691270.4052080.3443970.2668240.1266710.306036
Max_DD0.2658540.1040140.455550.2176210.0223140.534040.5303580.7628020.3424650.7050020.6643430.6103730.7126280.822147
\n", - "
" + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
tlt ief gld lqd shy qqq xlk slv hyg iyr eem efa xle xlf
Start_Date2007-07-022007-07-022007-07-022007-07-022007-07-022007-07-022007-07-022007-07-022007-07-022007-07-022007-07-022007-07-022007-07-022007-07-02
End_Date2020-12-302020-12-302020-12-302020-12-302020-12-302020-12-302020-12-302020-12-302020-12-302020-12-302020-12-302020-12-302020-12-302020-12-30
CWARP35.07521.35519.86310.3704.9643.0550.176-3.106-5.413-23.615-24.563-27.323-33.079-33.274
+Sortino29.87518.21613.8749.4403.7352.768-0.268-6.900-3.633-21.711-24.392-24.580-34.119-27.888
+Ret_To_MaxDD40.48324.57726.16611.3086.2083.3430.6230.843-7.160-25.473-24.733-29.966-32.022-38.257
Sharpe0.5450.7470.4740.6641.0500.7520.6850.3030.4650.2860.2390.1920.0910.212
Sortino0.7911.1010.6720.9431.6161.0650.9740.4140.6690.4050.3440.2670.1270.306
Max_DD0.2660.1040.4560.2180.0220.5340.5300.7630.3420.7050.6640.6100.7130.822
" ], "text/plain": [ - " tlt ief gld \\\n", - "Start_Date 2007-07-02 00:00:00 2007-07-02 00:00:00 2007-07-02 00:00:00 \n", - "End_Date 2020-12-30 00:00:00 2020-12-30 00:00:00 2020-12-30 00:00:00 \n", - "CWARP 35.075059 21.355028 19.86268 \n", - "+Sortino 29.874918 18.216353 13.873923 \n", - "+Ret_To_MaxDD 40.483412 24.577036 26.166392 \n", - "Sharpe 0.544866 0.746821 0.473668 \n", - "Sortino 0.790751 1.100768 0.672288 \n", - "Max_DD 0.265854 0.104014 0.45555 \n", - "\n", - " lqd shy qqq \\\n", - "Start_Date 2007-07-02 00:00:00 2007-07-02 00:00:00 2007-07-02 00:00:00 \n", - "End_Date 2020-12-30 00:00:00 2020-12-30 00:00:00 2020-12-30 00:00:00 \n", - "CWARP 10.370056 4.964182 3.054918 \n", - "+Sortino 9.440441 3.734666 2.767763 \n", - "+Ret_To_MaxDD 11.307567 6.208272 3.342876 \n", - "Sharpe 0.663613 1.049695 0.751663 \n", - "Sortino 0.942979 1.615862 1.065266 \n", - "Max_DD 0.217621 0.022314 0.53404 \n", - "\n", - " xlk slv hyg \\\n", - "Start_Date 2007-07-02 00:00:00 2007-07-02 00:00:00 2007-07-02 00:00:00 \n", - "End_Date 2020-12-30 00:00:00 2020-12-30 00:00:00 2020-12-30 00:00:00 \n", - "CWARP 0.176191 -3.105972 -5.412795 \n", - "+Sortino -0.26814 -6.900377 -3.632828 \n", - "+Ret_To_MaxDD 0.622501 0.84308 -7.159884 \n", - "Sharpe 0.684662 0.302802 0.464621 \n", - "Sortino 0.973925 0.414236 0.669127 \n", - "Max_DD 0.530358 0.762802 0.342465 \n", - "\n", - " iyr eem efa \\\n", - "Start_Date 2007-07-02 00:00:00 2007-07-02 00:00:00 2007-07-02 00:00:00 \n", - "End_Date 2020-12-30 00:00:00 2020-12-30 00:00:00 2020-12-30 00:00:00 \n", - "CWARP -23.615039 -24.563182 -27.322877 \n", - "+Sortino -21.710574 -24.392485 -24.57957 \n", - "+Ret_To_MaxDD -25.473176 -24.733494 -29.9664 \n", - "Sharpe 0.28593 0.239333 0.192215 \n", - "Sortino 0.405208 0.344397 0.266824 \n", - "Max_DD 0.705002 0.664343 0.610373 \n", - "\n", - " xle xlf \n", - "Start_Date 2007-07-02 00:00:00 2007-07-02 00:00:00 \n", - "End_Date 2020-12-30 00:00:00 2020-12-30 00:00:00 \n", - "CWARP -33.078864 -33.273558 \n", - "+Sortino -34.119457 -27.888017 \n", - "+Ret_To_MaxDD -32.021834 -38.256891 \n", - "Sharpe 0.091022 0.211968 \n", - "Sortino 0.126671 0.306036 \n", - "Max_DD 0.712628 0.822147 " + "" ] }, "execution_count": 8, @@ -765,7 +682,7 @@ } ], "source": [ - "risk_ret_df.sort_values(by='CWARP', axis=1, ascending=False)" + "risk_ret_df.sort_values(by='CWARP', axis=1, ascending=False).style.set_precision(3)" ] }, { @@ -778,249 +695,145 @@ { "cell_type": "code", "execution_count": 9, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
tlt@25% | Classic 60/40@100%ief@25% | Classic 60/40@100%gld@25% | Classic 60/40@100%lqd@25% | Classic 60/40@100%shy@25% | Classic 60/40@100%qqq@25% | Classic 60/40@100%Classic 60/40xlk@25% | Classic 60/40@100%hyg@25% | Classic 60/40@100%slv@25% | Classic 60/40@100%iyr@25% | Classic 60/40@100%efa@25% | Classic 60/40@100%eem@25% | Classic 60/40@100%xlf@25% | Classic 60/40@100%xle@25% | Classic 60/40@100%
Return0.1006180.0918290.0996730.0930020.0816170.1157870.0840370.1123380.0899420.0980680.0912020.0815320.0864010.0844760.074215
Vol0.0770240.0780780.0887290.0864570.0803360.1179150.0810880.1186840.0972390.1112430.1308060.1207420.1297020.1354090.132069
Sharpe0.9098680.8314090.8060980.7775320.736720.7336420.7118290.7108590.6904710.6764610.5616020.5429090.5373790.5159020.477671
Sortino1.320491.2019531.1578021.1127251.0547121.0448811.0167401.0140140.9798030.9465810.7960.766830.7687320.7331910.669834
Max_DD0.2808710.2903710.3099440.3289260.3046650.4366610.3139350.4356580.3820380.381830.4822340.461580.4536580.5413740.435315
Ret_To_MaxDD0.3759680.3333990.3376520.2978860.2842390.2765710.2676240.269290.2484630.2698810.1994520.1874270.2014320.165240.181926
CWARP_25%_asset35.07505921.35502819.8626810.3700564.9641823.054918-1.4076470.176191-5.412795-3.105972-23.615039-27.322877-24.563182-33.273558-33.078864
\n", - "
" + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
tlt@25% | Classic 60/40@100% ief@25% | Classic 60/40@100% gld@25% | Classic 60/40@100% lqd@25% | Classic 60/40@100% shy@25% | Classic 60/40@100% qqq@25% | Classic 60/40@100% Classic 60/40 xlk@25% | Classic 60/40@100% hyg@25% | Classic 60/40@100% slv@25% | Classic 60/40@100% iyr@25% | Classic 60/40@100% efa@25% | Classic 60/40@100% eem@25% | Classic 60/40@100% xlf@25% | Classic 60/40@100% xle@25% | Classic 60/40@100%
Return0.1010.0920.1000.0930.0820.1160.0840.1120.0900.0980.0910.0820.0860.0840.074
Vol0.0770.0780.0890.0860.0800.1180.0810.1190.0970.1110.1310.1210.1300.1350.132
Sharpe0.9100.8310.8060.7780.7370.7340.7120.7110.6900.6760.5620.5430.5370.5160.478
Sortino1.3201.2021.1581.1131.0551.0451.0171.0140.9800.9470.7960.7670.7690.7330.670
Max_DD0.2810.2900.3100.3290.3050.4370.3140.4360.3820.3820.4820.4620.4540.5410.435
Ret_To_MaxDD0.3760.3330.3380.2980.2840.2770.2680.2690.2480.2700.1990.1870.2010.1650.182
CWARP_25%_asset35.07521.35519.86310.3704.9643.055-1.4080.176-5.413-3.106-23.615-27.323-24.563-33.274-33.079
" ], "text/plain": [ - " tlt@25% | Classic 60/40@100% ief@25% | Classic 60/40@100% \\\n", - "Return 0.100618 0.091829 \n", - "Vol 0.077024 0.078078 \n", - "Sharpe 0.909868 0.831409 \n", - "Sortino 1.32049 1.201953 \n", - "Max_DD 0.280871 0.290371 \n", - "Ret_To_MaxDD 0.375968 0.333399 \n", - "CWARP_25%_asset 35.075059 21.355028 \n", - "\n", - " gld@25% | Classic 60/40@100% lqd@25% | Classic 60/40@100% \\\n", - "Return 0.099673 0.093002 \n", - "Vol 0.088729 0.086457 \n", - "Sharpe 0.806098 0.777532 \n", - "Sortino 1.157802 1.112725 \n", - "Max_DD 0.309944 0.328926 \n", - "Ret_To_MaxDD 0.337652 0.297886 \n", - "CWARP_25%_asset 19.86268 10.370056 \n", - "\n", - " shy@25% | Classic 60/40@100% qqq@25% | Classic 60/40@100% \\\n", - "Return 0.081617 0.115787 \n", - "Vol 0.080336 0.117915 \n", - "Sharpe 0.73672 0.733642 \n", - "Sortino 1.054712 1.044881 \n", - "Max_DD 0.304665 0.436661 \n", - "Ret_To_MaxDD 0.284239 0.276571 \n", - "CWARP_25%_asset 4.964182 3.054918 \n", - "\n", - " Classic 60/40 xlk@25% | Classic 60/40@100% \\\n", - "Return 0.084037 0.112338 \n", - "Vol 0.081088 0.118684 \n", - "Sharpe 0.711829 0.710859 \n", - "Sortino 1.016740 1.014014 \n", - "Max_DD 0.313935 0.435658 \n", - "Ret_To_MaxDD 0.267624 0.26929 \n", - "CWARP_25%_asset -1.407647 0.176191 \n", - "\n", - " hyg@25% | Classic 60/40@100% slv@25% | Classic 60/40@100% \\\n", - "Return 0.089942 0.098068 \n", - "Vol 0.097239 0.111243 \n", - "Sharpe 0.690471 0.676461 \n", - "Sortino 0.979803 0.946581 \n", - "Max_DD 0.382038 0.38183 \n", - "Ret_To_MaxDD 0.248463 0.269881 \n", - "CWARP_25%_asset -5.412795 -3.105972 \n", - "\n", - " iyr@25% | Classic 60/40@100% efa@25% | Classic 60/40@100% \\\n", - "Return 0.091202 0.081532 \n", - "Vol 0.130806 0.120742 \n", - "Sharpe 0.561602 0.542909 \n", - "Sortino 0.796 0.76683 \n", - "Max_DD 0.482234 0.46158 \n", - "Ret_To_MaxDD 0.199452 0.187427 \n", - "CWARP_25%_asset -23.615039 -27.322877 \n", - "\n", - " eem@25% | Classic 60/40@100% xlf@25% | Classic 60/40@100% \\\n", - "Return 0.086401 0.084476 \n", - "Vol 0.129702 0.135409 \n", - "Sharpe 0.537379 0.515902 \n", - "Sortino 0.768732 0.733191 \n", - "Max_DD 0.453658 0.541374 \n", - "Ret_To_MaxDD 0.201432 0.16524 \n", - "CWARP_25%_asset -24.563182 -33.273558 \n", - "\n", - " xle@25% | Classic 60/40@100% \n", - "Return 0.074215 \n", - "Vol 0.132069 \n", - "Sharpe 0.477671 \n", - "Sortino 0.669834 \n", - "Max_DD 0.435315 \n", - "Ret_To_MaxDD 0.181926 \n", - "CWARP_25%_asset -33.078864 " + "" ] }, "execution_count": 9, @@ -1029,7 +842,7 @@ } ], "source": [ - "new_risk_ret_df.sort_values(by='Sharpe', axis=1, ascending=False)" + "new_risk_ret_df.sort_values(by='Sharpe', axis=1, ascending=False).style.set_precision(3)" ] }, { @@ -1042,7 +855,9 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "scrolled": false + }, "outputs": [ { "data": { diff --git a/cwarp_app.py b/cwarp_app.py index 11c9160..f3547aa 100644 --- a/cwarp_app.py +++ b/cwarp_app.py @@ -66,12 +66,13 @@ def main(): try: start_date = st.sidebar.date_input("Start Date", datetime.date(2007,7,1)).strftime("%Y-%m-%d") end_date = st.sidebar.date_input("End Date", datetime.date(2020,12,31)).strftime("%Y-%m-%d") - weight_asset = st.sidebar.slider('Diversifier Weight', min_value=0.0001, max_value=0.9999, value=.25) - risk_free_rate = st.sidebar.slider('Risk-Free Rate (annualized)', min_value=0.0, max_value=0.2, value=0.027) - financing_rate = st.sidebar.slider('Financing Rate (annualized)', min_value=0.0, max_value=0.2, value=0.0) + weight_asset = st.sidebar.slider('Diversifier Weight', min_value=0.0001, max_value=0.9999, value=.25, step=0.01) + weight_replace_port = st.sidebar.slider('Replacement Portfolio Weight', min_value=0.0001, max_value=1.0000, value=1.00, step=0.01) + risk_free_rate = st.sidebar.slider('Risk-Free Rate (annualized)', min_value=0.0, max_value=0.2, value=0.005, step=0.001, format='%.3f') + financing_rate = st.sidebar.slider('Financing Rate (annualized)', min_value=0.0, max_value=0.2, value=0.01) replacement_port_name = st.sidebar.text_input("Replacement Portfolio Name", "Plain 60/40") - ticker_string_ = "qqq, lqd, hyg, tlt, ief, shy, gld, efa, eem, iyr, xle, xlf" + ticker_string_ = "qqq, lqd, hyg, tlt, ief, shy, gld, slv, efa, eem, iyr, xle, xlk, xlf" ticker_string = st.text_input("Prospective Portfolio Diversifiers (comma separated)", ticker_string_) ticker_list=ticker_string.replace(' ','').split(',') @@ -88,14 +89,16 @@ def main(): D = retrieve_yhoo_data(replacement_port_tik[-1], start_date, end_date) replacement_port_list.append(D) - replacement_port = replacement_port_list[0] + replacement_port = replacement_port_list[0]*(replacement_port_w[0]/sum(replacement_port_w)) for k in range(1,len(replacement_port_list)): replacement_port += replacement_port_list[k]*(replacement_port_w[k]/sum(replacement_port_w)) + # with st.spinner("Pulling data..."): + # replacement_port=sum([retrieve_yhoo_data(sym, start_date, end_date)*replacement_port_w[i] for i, sym in enumerate(replacement_port_tik)]) replacement_port.name=replacement_port_name risk_ret_df=pd.DataFrame(index=['Start_Date','End_Date','CWARP','+Sortino','+Ret_To_MaxDD','Sharpe','Sortino','Max_DD'],columns=ticker_list) new_risk_ret_df=pd.DataFrame(index=['Return','Vol','Sharpe','Sortino','Max_DD','Ret_To_MaxDD',f'CWARP_{round(100*weight_asset)}%_asset'],columns=ticker_list) - new_risk_ret_df=new_risk_ret_df.add_suffix(f'@{round(100*weight_asset)}% | '+replacement_port.name+'@100%') + new_risk_ret_df=new_risk_ret_df.add_suffix(f'@{round(100*weight_asset)}% | '+replacement_port.name+f'{round(100*weight_replace_port)}%') new_risk_ret_df[replacement_port.name]=np.nan prices_df=pd.DataFrame(index=retrieve_yhoo_data(ticker_list[0], start_date, end_date).index) @@ -104,52 +107,66 @@ def main(): for i in range(0,len(ticker_list)): temp_data=retrieve_yhoo_data(ticker_list[i], start_date, end_date) prices_df=pd.merge(prices_df,temp_data, left_index=True, right_index=True) - risk_ret_df.loc['Start_Date',ticker_list[i]]=min(temp_data.index) - risk_ret_df.loc['End_Date',ticker_list[i]]=max(temp_data.index) + risk_ret_df.loc['Start_Date',ticker_list[i]]=min(temp_data.index).date() + risk_ret_df.loc['End_Date',ticker_list[i]]=max(temp_data.index).date() risk_ret_df.loc['CWARP',ticker_list[i]]=cole_win_above_replace_port(new_asset=temp_data, replace_port=replacement_port, risk_free_rate = risk_free_rate, financing_rate = financing_rate, - weight_asset = weight_asset - ) + weight_asset = weight_asset, + weight_replace_port = weight_replace_port, + periodicity=252) risk_ret_df.loc['+Sortino',ticker_list[i]]=cwarp_additive_sortino(new_asset=temp_data, replace_port=replacement_port, risk_free_rate = risk_free_rate, financing_rate = financing_rate, - weight_asset = weight_asset) + weight_asset = weight_asset, + weight_replace_port = weight_replace_port, + periodicity=252) risk_ret_df.loc['+Ret_To_MaxDD',ticker_list[i]]=cwarp_additive_ret_maxdd(new_asset=temp_data, replace_port=replacement_port, risk_free_rate = risk_free_rate, financing_rate = financing_rate, - weight_asset = weight_asset) - risk_ret_df.loc['Sharpe',ticker_list[i]]=sharpe_ratio(temp_data, risk_free_rate = risk_free_rate) - risk_ret_df.loc['Sortino',ticker_list[i]]=sortino_ratio(temp_data, risk_free_rate = risk_free_rate) + weight_asset = weight_asset, + weight_replace_port = weight_replace_port, + periodicity=252) + risk_ret_df.loc['Sharpe',ticker_list[i]]=sharpe_ratio(temp_data, risk_free = risk_free_rate, periodicity=252) + risk_ret_df.loc['Sortino',ticker_list[i]]=sortino_ratio(temp_data, risk_free = risk_free_rate, periodicity=252) risk_ret_df.loc['Max_DD',ticker_list[i]]=max_dd(temp_data) new_risk_ret_df.loc['Return',new_risk_ret_df.columns[i]]=cwarp_port_return(new_asset=temp_data,replace_port=replacement_port, risk_free_rate = risk_free_rate, financing_rate = financing_rate, - weight_asset = weight_asset) + weight_asset = weight_asset, + weight_replace_port = weight_replace_port, + periodicity = 252) new_risk_ret_df.loc['Vol',new_risk_ret_df.columns[i]]=cwarp_port_risk(new_asset=temp_data,replace_port=replacement_port, risk_free_rate = risk_free_rate, financing_rate = financing_rate, - weight_asset = weight_asset) + weight_asset = weight_asset, + weight_replace_port = weight_replace_port, + periodicity=252) cnpd = cwarp_new_port_data(new_asset=temp_data,replace_port=replacement_port, risk_free_rate = risk_free_rate, financing_rate = financing_rate, - weight_asset = weight_asset) + weight_asset = weight_asset, + weight_replace_port = weight_replace_port, + periodicity = 252) new_ports[ticker_list[i]] = cnpd - new_risk_ret_df.loc['Sharpe',new_risk_ret_df.columns[i]]=sharpe_ratio(cnpd.copy(), risk_free_rate = risk_free_rate) - new_risk_ret_df.loc['Sortino',new_risk_ret_df.columns[i]]=sortino_ratio(cnpd.copy(), risk_free_rate = risk_free_rate) + new_risk_ret_df.loc['Sharpe',new_risk_ret_df.columns[i]]=sharpe_ratio(cnpd.copy(), risk_free = risk_free_rate, periodicity=252) + new_risk_ret_df.loc['Sortino',new_risk_ret_df.columns[i]]=sortino_ratio(cnpd.copy(), risk_free = risk_free_rate, periodicity=252) new_risk_ret_df.loc['Max_DD',new_risk_ret_df.columns[i]]=max_dd(cnpd.copy()) - new_risk_ret_df.loc['Ret_To_MaxDD',new_risk_ret_df.columns[i]]=return_maxdd_ratio(cnpd.copy(), risk_free_rate = risk_free_rate) + new_risk_ret_df.loc['Ret_To_MaxDD',new_risk_ret_df.columns[i]]=return_maxdd_ratio(cnpd.copy(), risk_free = risk_free_rate, periodicity=252) new_risk_ret_df.loc[f'CWARP_{round(100*weight_asset)}%_asset',new_risk_ret_df.columns[i]]=risk_ret_df.loc['CWARP',ticker_list[i]] - new_risk_ret_df.loc['Return',[replacement_port_name]]=(replacement_port.mean()+1)**252-1 - new_risk_ret_df.loc['Vol',[replacement_port_name]]=replacement_port.std()*np.sqrt(252) - new_risk_ret_df.loc['Sharpe',[replacement_port_name]]=sharpe_ratio(replacement_port, risk_free_rate = risk_free_rate) - new_risk_ret_df.loc['Sortino',[replacement_port_name]]=sortino_ratio(replacement_port, risk_free_rate = risk_free_rate) + new_risk_ret_df.loc['Return',[replacement_port_name]]=annualized_return(replacement_port, periodicity=252) + new_risk_ret_df.loc['Vol',[replacement_port_name]]=target_downside_deviation(replacement_port, MAR=0)*np.sqrt(252) + new_risk_ret_df.loc['Sharpe',[replacement_port_name]]=sharpe_ratio(replacement_port, risk_free = risk_free_rate, periodicity=252) + new_risk_ret_df.loc['Sortino',[replacement_port_name]]=sortino_ratio(replacement_port, risk_free = risk_free_rate, periodicity=252) new_risk_ret_df.loc['Max_DD',[replacement_port_name]]=max_dd(replacement_port) - new_risk_ret_df.loc['Ret_To_MaxDD',[replacement_port_name]]=return_maxdd_ratio(replacement_port, risk_free_rate = risk_free_rate) + new_risk_ret_df.loc['Ret_To_MaxDD',[replacement_port_name]]=return_maxdd_ratio(replacement_port, risk_free = risk_free_rate, periodicity=252) + new_risk_ret_df.loc[f'CWARP_{round(100*weight_asset)}%_asset',[replacement_port_name]] = cole_win_above_replace_port(new_asset=replacement_port, replace_port=replacement_port, risk_free_rate=risk_free_rate, financing_rate=financing_rate, + weight_asset=weight_asset, weight_replace_port=weight_replace_port, periodicity=252) first_col = new_risk_ret_df.pop(replacement_port_name) new_risk_ret_df.insert(0, replacement_port_name, first_col) - st.write(risk_ret_df) - st.write(new_risk_ret_df) + # display dataframes + st.write(risk_ret_df.sort_values(by='CWARP', axis=1, ascending=False).style.set_precision(3)) + st.write(new_risk_ret_df.sort_values(by='Sharpe', axis=1, ascending=False).style.set_precision(3)) vol_arr=new_risk_ret_df.loc['Vol',new_risk_ret_df.columns[1:]] ret_arr=new_risk_ret_df.loc['Return',new_risk_ret_df.columns[1:]] sharpe_arr=new_risk_ret_df.loc['Sharpe',new_risk_ret_df.columns[1:]] diff --git a/cwarp_defs.py b/cwarp_defs.py index ca60d72..49bc14c 100644 --- a/cwarp_defs.py +++ b/cwarp_defs.py @@ -10,40 +10,115 @@ import pandas as pd #Risk and Reward Functions################################################################## -def sharpe_ratio(df,risk_free_rate=0,periodicity=252): - risk_free_rate=(1+risk_free_rate)**(1/periodicity)-1 - dfMean=np.mean(df)-risk_free_rate - dfSTD=np.std(df) +def sharpe_ratio(df,risk_free=0,periodicity=252): + """df - asset return series, e.g. daily returns based on daily close prices of asset + risk_free - annualized risk free rate (default is assumed to be 0) + periodicity - number of periods at desired frequency in one year + e.g. 252 business days in 1 year (default), + 12 months in 1 year, + 52 weeks in 1 year etc.""" + # convert return series to numpy array (in case Pandas series is provided) + df = np.asarray(df) + # convert annualized risk free rate into appropriate value for provided frequency of asset return series (df) + risk_free=(1+risk_free)**(1/periodicity)-1 + # calculate mean excess return based on return series provided + dfMean=np.nanmean(df)-risk_free + # calculate standard deviation of return series + dfSTD=np.nanstd(df) + # calculate Sharpe Ratio = Mean excess return / Std of returns * sqrt(periodicity) dfSharpe=dfMean/dfSTD*np.sqrt(periodicity) return dfSharpe -def sortino_ratio(df,risk_free_rate=0,periodicity=252): - neg_ndx = np.where(df<0)[0] - dfSTD= np.std(df[neg_ndx]) - risk_free_rate=(1+risk_free_rate)**(1/periodicity)-1 - dfMean=np.mean(df)-risk_free_rate - dfSortino=(dfMean/dfSTD)*np.sqrt(periodicity) +def target_downside_deviation(df, MAR=0, periodicity=252): + """df - asset return series, e.g. daily returns based on daily close prices of asset + minimum acceptable return (MAR) - value is subtracted from returns before root-mean-square calculation to obtain target downside deviation (TDD)""" + # convert return series to numpy array (in case Pandas series is provided) + df = np.asarray(df) + # TDD step 1: subtract mininum acceptable return (MAR) from period returns provided in df + df_ = df - MAR + # TDD step 2: zero out positive excess returns and calculate root-mean-square for resulting values + df2 = np.where(df_<0, df_, 0) + tdd = np.sqrt(np.nanmean(df2**2)) + return tdd + +def sortino_ratio(df,risk_free=0, periodicity=252, include_risk_free_in_vol=False): + """df - asset return series, e.g. daily returns based on daily close prices of asset + risk_free - annualized risk free rate (default is assumed to be 0). Note: risk free rate is assumed to be the target return/minimum acceptable return (MAR) + used in calculating both the mean excess return (numerator of Sortino ratio) and determining target downside deviation (TDD, the denominator of Sortino) + periodicity - number of periods at desired frequency in one year + e.g. 252 business days in 1 year (default), + 12 months in 1 year, + 52 weeks in 1 year etc.""" + # convert return series to numpy array (in case Pandas series is provided) + df = np.asarray(df) + # convert annualized risk free rate into appropriate value for provided frequency of asset return series (df) + risk_free=(1+risk_free)**(1/periodicity)-1 + # calculate mean excess return based on return series provided + dfMean=np.nanmean(df)-risk_free + # calculate target downside deviation (TDD) + # assume risk free rate is MAR + if include_risk_free_in_vol==True: MAR=risk_free + else: MAR=0 + tdd = target_downside_deviation(df, MAR=MAR) + # calculate Sortino Ratio = Mean excess return / TDD * sqrt(periodicity) + dfSortino=(dfMean/tdd)*np.sqrt(periodicity) return dfSortino -def return_maxdd_ratio(df,risk_free_rate=0,periodicity=252): - #drop zero means you will drop all zero values from the calculation - risk_free_rate=(1+risk_free_rate)**(1/periodicity)-1 - dfMean=(1+np.mean(df)-risk_free_rate)**(periodicity)-1 - maxDD=max_dd(df,use_window=False,window=252,return_data=False) - return dfMean/abs(maxDD) - -def annualized_return(returns,periodicity=252): - """Assumes returns is a pandas Series""" - #start_date=returns.index[0] - #end_date=returns.index[-1] - #difference_in_years = (end_date-start_date).days/days_in_year - difference_in_years = len(returns)/periodicity - r = np.cumprod(returns+1.) - start_NAV=1 +def annualized_return(df,periodicity=252): + """df - asset return series, e.g. returns based on daily close prices of asset + periodicity - number of periods at desired frequency in one year + e.g. 252 business days in 1 year (default), + 12 months in 1 year, + 52 weeks in 1 year etc.""" + # convert return series to numpy array (in case Pandas series is provided) + df = np.asarray(df) + # how many years of returns data is provided in df + difference_in_years = len(df)/periodicity + # starting net asset value / NAV (assumed to be 1) and cumulative returns (r) over time period provided in returns data + start_NAV=1.0 + r = np.nancumprod(df+start_NAV) + # end NAV based on final cumulative return end_NAV=r[-1] - AnnualReturn = (1 + (end_NAV - 1) / 1)** (1 / difference_in_years) - 1 + # determine annualized return + AnnualReturn = end_NAV**(1 / difference_in_years) - 1 return AnnualReturn +def max_dd(df, return_data=False): + """df - asset return series, e.g. returns based on daily close prices of asset + return_data - boolean value to determine if drawdown values over the return data time period should be return, instead of max DD""" + # convert return series to numpy array (in case Pandas series is provided) + df = np.asarray(df) + # calculate cumulative returns + start_NAV = 1 + r = np.nancumprod(df+start_NAV) + # calculate cumulative max returns (i.e. keep track of peak cumulative return up to that point in time, despite actual cumulative return at that point in time) + peak_r = np.maximum.accumulate(r) + # determine drawdowns relative to peak cumulative return achieved up to each point in time + dd = (r - peak_r) / peak_r + # return drawdown values over time period if return_data is set to True, otherwise return max drawdown which will be a positive number + if return_data==True: + out = dd + else: + out = np.abs(np.nanmin(dd)) + return out + +def return_maxdd_ratio(df,risk_free=0,periodicity=252): + """df - asset return series, e.g. returns based on daily close prices of asset + risk_free - annualized risk free rate (default is assumed to be 0) + periodicity - number of periods at desired frequency in one year + e.g. 252 business days in 1 year (default), + 12 months in 1 year, + 52 weeks in 1 year etc.""" + # convert return series to numpy array (in case Pandas series is provided) + df = np.asarray(df) + # convert annualized risk free rate into appropriate value for provided frequency of asset return series (df) + risk_free=(1+risk_free)**(1/periodicity)-1 + # determine annualized return to be used in numerator of return to max drawdown (RMDD) calculation + AnnualReturn = annualized_return(df, periodicity=periodicity) + # determine max drawdown to be used in the denominator of RMDD calculation + maxDD=max_dd(df,return_data=False) + return (AnnualReturn-risk_free)/abs(maxDD) + def avg_positive(ret,dropzero=1): if dropzero>0: positives = ret > 0 @@ -70,20 +145,6 @@ def win_pct(ret,dropzero=1): total=len(ret) return (win/total) -def max_dd(pct_df,use_window=False,window=252,return_data=False): - #calculates the maximum drawdown for the strategy cumulatively or over a rolling window period - #use_window initiates rolling period, otherwise is cumulative - #return data provides the raw datastream rather than the min - if use_window==False: - out=((((pct_df + 1).cumprod()-(pct_df + 1).cumprod().cummax())/(pct_df + 1).cumprod().cummax()).dropna()) - else: - out=(((pct_df + 1).cumprod()-(pct_df + 1).cumprod().rolling(window).max())/(pct_df + 1).cumprod().rolling(window).max()).dropna() - if return_data==True: - out = out - else: - out = min(out) - return out - def kelly(df,dropzero=0): if dropzero==1: df = df[(df!= 0)] avg_pos=df[df>=0].mean() @@ -133,84 +194,56 @@ def ReturnTable(daily_nav_df,freq='1M'): return_df -def cole_win_above_replace_port(new_asset, replace_port, - risk_free_rate=0, - financing_rate=0, - weight_asset=0.25, - weight_replace_port=1, - periodicity=252): - # Cole Win Above Replacement Portolio: Calculate additive return to unit of risk for a new asset on an existing portfolio - # new_asset = returns of the asset you are thinking of adding to your portfolio - # replace_port = returns of your pre-existing portfolio (e.g. S&P 500 Index, 60/40 Stock-Bond Portfolio) - # risk_free_rate = Tbill rate - # financing_rate = portfolio margin/borrowing cost to layer new asset on top of prevailing portfolio - # weight_asset = % weight you wish to overlay for the new asset on top of the previous portfolio, 25% overlay allocation is standard - # weight_replace_port = % weight of the replacement portfolio, 100% pre-existing portfolio value is standard - # periodicity = the frequency of the data you are sampling, typically 12 for monthly or 252 for trading day count - - risk_free_rate=(1+risk_free_rate)**(1/periodicity)-1 +def cole_win_above_replace_port(new_asset,replace_port,risk_free_rate=0,financing_rate=0,weight_asset=0.25,weight_replace_port=1,periodicity=252): + """Cole Win Above Replacement Portolio (CWARP): Total score to evaluate whether any new investment improves or hurts the return to risk of your total portfolio. + new_asset = returns of the asset you are thinking of adding to your portfolio + replace_port = returns of your pre-existing portfolio (e.g. S&P 500 Index, 60/40 Stock-Bond Portfolio) + risk_free_rate = Tbill rate (annualized) + financing_rate = portfolio margin/borrowing cost (annualized) to layer new asset on top of prevailing portfolio (e.g. LIBOR + 60bps). No financing rate is reasonable for derivate overlay products. + weight_asset = % weight you wish to overlay for the new asset on top of the previous portfolio, 25% overlay allocation is standard + weight_replace_port = % weight of the replacement portfolio, 100% pre-existing portfolio value is standard + periodicity = the frequency of the data you are sampling, typically 12 for monthly or 252 for trading day count""" + # convert annualized financing rate into appropriate value for provided periodicity + # risk_free_rate will be converted appropriately in respective Sortino and RMDD calcs financing_rate=(1+financing_rate)**(1/periodicity)-1 - replace_port_ex_rf=replace_port-risk_free_rate - new_port_returns_ex_rf=(new_asset-financing_rate)*weight_asset+(replace_port*weight_replace_port)-risk_free_rate - new_port=(new_asset-financing_rate)*weight_asset+(replace_port*weight_replace_port) - #Calculate Replacement Portfolio Sortino Ratio - neg_ndx = np.where(replace_port<0)[0] - replace_port_sortino=np.mean(replace_port_ex_rf)/(np.std(replace_port[neg_ndx]))*np.sqrt(periodicity) + replace_port_sortino = sortino_ratio(replace_port, risk_free=risk_free_rate, periodicity=periodicity) #Calculate Replacement Portfolio Return to Max Drawdown - maxdd_replace_port=min((((replace_port + 1).cumprod()-(replace_port + 1).cumprod().cummax())/(replace_port + 1).cumprod().cummax()).dropna()) - replace_port_return_maxdd= ((1+np.mean(replace_port_ex_rf))**(periodicity)-1)/abs(maxdd_replace_port) + replace_port_return_maxdd = return_maxdd_ratio(replace_port, risk_free=risk_free_rate, periodicity=periodicity) #Calculate New Portfolio Sortino Ratio - neg_ndx_new = np.where(new_port<0)[0] - new_port_sortino=np.mean(new_port_returns_ex_rf)/(np.std(new_port[neg_ndx_new]))*np.sqrt(periodicity) + new_port = (new_asset-financing_rate)*weight_asset+replace_port*weight_replace_port + new_port_sortino = sortino_ratio(new_port, risk_free=risk_free_rate, periodicity=periodicity) #Calculate New Portfolio Return to Max Drawdown - maxdd_new_port=min((((new_port + 1).cumprod()-(new_port + 1).cumprod().cummax())/(new_port + 1).cumprod().cummax()).dropna()) - new_port_return_maxdd= ((1+np.mean(new_port_returns_ex_rf))**(periodicity)-1)/abs(maxdd_new_port) + new_port_return_maxdd = return_maxdd_ratio(new_port, risk_free=risk_free_rate, periodicity=periodicity) #Final CWARP calculation - CWARP=(((new_port_return_maxdd/replace_port_return_maxdd)*(new_port_sortino/replace_port_sortino))**(1/2)-1)*100 + CWARP = ((new_port_return_maxdd/replace_port_return_maxdd*new_port_sortino/replace_port_sortino)**(1/2)-1)*100 + return CWARP -def cwarp_additive_sortino(new_asset,replace_port, - risk_free_rate=0, - financing_rate=0, - weight_asset=0.25, - weight_replace_port=1, - periodicity=252): - # new_asset = returns of the asset you are thinking of adding to your portfolio - # replace_port = returns of your pre-existing portfolio (e.g. S&P 500 Index, 60/40 Stock-Bond Portfolio) - # risk_free_rate = Tbill rate - # financing_rate = portfolio margin/borrowing cost to layer new asset on top of prevailing portfolio - # weight_asset = % weight you wish to overlay for the new asset on top of the previous portfolio, 25% overlay allocation is standard - # weight_replace_port = % weight of the replacement portfolio, 100% pre-existing portfolio value is standard - # periodicity = the frequency of the data you are sampling, typically 12 for monthly or 252 for trading day count - - risk_free_rate=(1+risk_free_rate)**(1/periodicity)-1 +def cwarp_additive_sortino(new_asset,replace_port,risk_free_rate=0,financing_rate=0,weight_asset=0.25,weight_replace_port=1,periodicity=252): + """Cole Win Above Replacement Portolio (CWARP) Sortino +: Isolates new investment effect on total portfolio Sortino Ratio, which is a portion of the holistic CWARP score. + new_asset = returns of the asset you are thinking of adding to your portfolio + replace_port = returns of your pre-existing portfolio (e.g. S&P 500 Index, 60/40 Stock-Bond Portfolio) + risk_free_rate = Tbill rate (annualized) + financing_rate = portfolio margin/borrowing cost (annualized) to layer new asset on top of prevailing portfolio (e.g. LIBOR + 60bps). No financing rate is reasonable for derivate overlay products. + weight_asset = % weight you wish to overlay for the new asset on top of the previous portfolio, 25% overlay allocation is standard + weight_replace_port = % weight of the replacement portfolio, 100% pre-existing portfolio value is standard + periodicity = the frequency of the data you are sampling, typically 12 for monthly or 252 for trading day count""" + # convert annualized financing rate into appropriate value for provided periodicity + # risk_free_rate will be converted appropriately in respective Sortino and RMDD calcs financing_rate=(1+financing_rate)**(1/periodicity)-1 - replace_port_ex_rf=replace_port-risk_free_rate - new_port_returns_ex_rf=(new_asset-financing_rate)*weight_asset+(replace_port*weight_replace_port)-risk_free_rate - new_port=(new_asset-financing_rate)*weight_asset+(replace_port*weight_replace_port) - #Calculate Replacement Portfolio Sortino Ratio - neg_ndx = np.where(replace_port<0)[0] - replace_port_sortino=np.mean(replace_port_ex_rf)/(np.std(replace_port[neg_ndx]))*np.sqrt(periodicity) - - #Calculate Replacement Portfolio Return to Max Drawdown - maxdd_replace_port=min((((replace_port + 1).cumprod()-(replace_port + 1).cumprod().cummax())/(replace_port + 1).cumprod().cummax()).dropna()) - replace_port_return_maxdd= ((1+np.mean(replace_port_ex_rf))**(periodicity)-1)/abs(maxdd_replace_port) + replace_port_sortino = sortino_ratio(replace_port, risk_free=risk_free_rate, periodicity=periodicity) #Calculate New Portfolio Sortino Ratio - neg_ndx_new = np.where(new_port<0)[0] - new_port_sortino=np.mean(new_port_returns_ex_rf)/(np.std(new_port[neg_ndx_new]))*np.sqrt(periodicity) - - #Calculate New Portfolio Return to Max Drawdown - maxdd_new_port=min((((new_port + 1).cumprod()-(new_port + 1).cumprod().cummax())/(new_port + 1).cumprod().cummax()).dropna()) - new_port_return_maxdd= ((1+np.mean(new_port_returns_ex_rf))**(periodicity)-1)/abs(maxdd_new_port) + new_port = (new_asset-financing_rate)*weight_asset+replace_port*weight_replace_port + new_port_sortino = sortino_ratio(new_port, risk_free=risk_free_rate, periodicity=periodicity) #Final calculation CWARP_add_sortino=((new_port_sortino/replace_port_sortino)-1)*100 @@ -218,58 +251,77 @@ def cwarp_additive_sortino(new_asset,replace_port, return CWARP_add_sortino def cwarp_additive_ret_maxdd(new_asset,replace_port,risk_free_rate=0,financing_rate=0,weight_asset=0.25,weight_replace_port=1,periodicity=252): - # new_asset = returns of the asset you are thinking of adding to your portfolio - # replace_port = returns of your pre-existing portfolio (e.g. S&P 500 Index, 60/40 Stock-Bond Portfolio) - # risk_free_rate = Tbill rate - # financing_rate = portfolio margin/borrowing cost to layer new asset on top of prevailing portfolio - # weight_asset = % weight you wish to overlay for the new asset on top of the previous portfolio, 25% overlay allocation is standard - # weight_replace_port = % weight of the replacement portfolio, 100% pre-existing portfolio value is standard - # periodicity = the frequency of the data you are sampling, typically 12 for monthly or 252 for trading day count - - risk_free_rate=(1+risk_free_rate)**(1/periodicity)-1 + """Cole Win Above Replacement Portolio (CWARP) Ret to Max DD +: Isolates new investment effect on total portfolio Return to MAXDD, which is a portion of the holistic CWARP score. + new_asset = returns of the asset you are thinking of adding to your portfolio + replace_port = returns of your pre-existing portfolio (e.g. S&P 500 Index, 60/40 Stock-Bond Portfolio) + risk_free_rate = Tbill rate (annualized) + financing_rate = portfolio margin/borrowing cost (annualized) to layer new asset on top of prevailing portfolio (e.g. LIBOR + 60bps). No financing rate is reasonable for derivate overlay products. + weight_asset = % weight you wish to overlay for the new asset on top of the previous portfolio, 25% overlay allocation is standard + weight_replace_port = % weight of the replacement portfolio, 100% pre-existing portfolio value is standard + periodicity = the frequency of the data you are sampling, typically 12 for monthly or 252 for trading day count""" + # convert annualized financing rate into appropriate value for provided periodicity + # risk_free_rate will be converted appropriately in respective Sortino and RMDD calcs financing_rate=(1+financing_rate)**(1/periodicity)-1 - replace_port_ex_rf=replace_port-risk_free_rate - new_port_returns_ex_rf=(new_asset-financing_rate)*weight_asset+(replace_port*weight_replace_port)-risk_free_rate - new_port=(new_asset-financing_rate)*weight_asset+(replace_port*weight_replace_port) - - #Calculate Replacement Portfolio Sortino Ratio - neg_ndx = np.where(replace_port<0)[0] - replace_port_sortino=np.mean(replace_port_ex_rf)/(np.std(replace_port[neg_ndx]))*np.sqrt(periodicity) - #Calculate Replacement Portfolio Return to Max Drawdown - maxdd_replace_port=min((((replace_port + 1).cumprod()-(replace_port + 1).cumprod().cummax())/(replace_port + 1).cumprod().cummax()).dropna()) - replace_port_return_maxdd= ((1+np.mean(replace_port_ex_rf))**(periodicity)-1)/abs(maxdd_replace_port) - - #Calculate New Portfolio Sortino Ratio - neg_ndx_new = np.where(new_port<0)[0] - new_port_sortino=np.mean(new_port_returns_ex_rf)/(np.std(new_port[neg_ndx_new]))*np.sqrt(periodicity) + replace_port_return_maxdd = return_maxdd_ratio(replace_port, risk_free=risk_free_rate, periodicity=periodicity) #Calculate New Portfolio Return to Max Drawdown - maxdd_new_port=min((((new_port + 1).cumprod()-(new_port + 1).cumprod().cummax())/(new_port + 1).cumprod().cummax()).dropna()) - new_port_return_maxdd= ((1+np.mean(new_port_returns_ex_rf))**(periodicity)-1)/abs(maxdd_new_port) + new_port = (new_asset-financing_rate)*weight_asset+replace_port*weight_replace_port + new_port_return_maxdd = return_maxdd_ratio(new_port, risk_free=risk_free_rate, periodicity=periodicity) #Final calculation CWARP_add_ret_maxdd=((new_port_return_maxdd/replace_port_return_maxdd)-1)*100 + return CWARP_add_ret_maxdd def cwarp_port_return(new_asset,replace_port,risk_free_rate=0,financing_rate=0,weight_asset=0.25,weight_replace_port=1,periodicity=252): + """Cole Win Above Replacement Portolio (CWARP) Portfolio Return: Returns of the aggregate portfolio after a new asset is financed and layered on top of the replacement portfolio. + new_asset = returns of the asset you are thinking of adding to your portfolio + replace_port = returns of your pre-existing portfolio (e.g. S&P 500 Index, 60/40 Stock-Bond Portfolio) + risk_free_rate = Tbill rate (annualized) + financing_rate = portfolio margin/borrowing cost (annualized) to layer new asset on top of prevailing portfolio (e.g. LIBOR + 60bps). No financing rate is reasonable for derivate overlay products. + weight_asset = % weight you wish to overlay for the new asset on top of the previous portfolio, 25% overlay allocation is standard + weight_replace_port = % weight of the replacement portfolio, 100% pre-existing portfolio value is standard + periodicity = the frequency of the data you are sampling, typically 12 for monthly or 252 for trading day count""" + # convert annual financing based on periodicity financing_rate=((financing_rate+1)**(1/periodicity)-1) - risk_free_rate=((risk_free_rate+1)**(1/periodicity)-1) - new_port=(new_asset-financing_rate)*weight_asset+replace_port*weight_replace_port-risk_free_rate - out=(np.mean(new_port)+1)**(periodicity)-1 + + # compose new portfolio + new_port=(new_asset-financing_rate)*weight_asset+replace_port*weight_replace_port + + # calculate annualized return of new portfolio and subtract risk-free rate + out = annualized_return(new_port, periodicity=periodicity) - risk_free_rate return out def cwarp_port_risk(new_asset,replace_port,risk_free_rate=0,financing_rate=0,weight_asset=0.25,weight_replace_port=1,periodicity=252): + """Cole Win Above Replacement Portolio (CWARP) Portfolio Risk: Volatility of the aggregate portfolio after a new asset is financed and layered on top of the replacement portfolio. + new_asset = returns of the asset you are thinking of adding to your portfolio + replace_port = returns of your pre-existing portfolio (e.g. S&P 500 Index, 60/40 Stock-Bond Portfolio) + risk_free_rate = Tbill rate (annualized) + financing_rate = portfolio margin/borrowing cost (annualized) to layer new asset on top of prevailing portfolio (e.g. LIBOR + 60bps). No financing rate is reasonable for derivate overlay products. + weight_asset = % weight you wish to overlay for the new asset on top of the previous portfolio, 25% overlay allocation is standard + weight_replace_port = % weight of the replacement portfolio, 100% pre-existing portfolio value is standard + periodicity = the frequency of the data you are sampling, typically 12 for monthly or 252 for trading day count""" + # convert annual financing and risk free rates based on periodicity financing_rate=((financing_rate+1)**(1/periodicity)-1) risk_free_rate=((risk_free_rate+1)**(1/periodicity)-1) + # compose new portfolio new_port=(new_asset-financing_rate)*weight_asset+replace_port*weight_replace_port - neg_ndx = np.where(new_port<0)[0] - out=np.std(new_port[neg_ndx])*np.sqrt(periodicity) - return out + # calculated target downside deviation (TDD) + tdd = target_downside_deviation(new_port, MAR=0)*np.sqrt(periodicity) + return tdd def cwarp_new_port_data(new_asset,replace_port,risk_free_rate=0,financing_rate=0,weight_asset=0.25,weight_replace_port=1,periodicity=252): + """Cole Win Above Replacement Portolio (CWARP) return stream: Return series after a new asset is financed and layered on top of the replacement portfolio. + new_asset = returns of the asset you are thinking of adding to your portfolio + replace_port = returns of your pre-existing portfolio (e.g. S&P 500 Index, 60/40 Stock-Bond Portfolio) + risk_free_rate = Tbill rate + financing_rate = portfolio margin/borrowing cost to layer new asset on top of prevailing portfolio (e.g. LIBOR + 60bps). No financing rate is reasonable for derivate overlay products. + weight_asset = % weight you wish to overlay for the new asset on top of the previous portfolio, 25% overlay allocation is standard + weight_replace_port = % weight of the replacement portfolio, 100% pre-existing portfolio value is standard + periodicity = the frequency of the data you are sampling, typically 12 for monthly or 252 for trading day count""" + # convert annual financing based on periodicity financing_rate=((financing_rate+1)**(1/periodicity)-1) - risk_free_rate=((risk_free_rate+1)**(1/periodicity)-1) new_port=(new_asset-financing_rate)*weight_asset+replace_port*weight_replace_port return new_port