def value_at_risk(self): # 半方差公式 只算下降的 def cal_half_dev(returns): # 均值 mu = returns.mean() tmp = returns["returnss" < mu] half_deviation = (sum((mu - tmp)**2) / len(returns))**0.5 return half_deviation close = pd.DataFrame()["close"] simple_return = ffn.to_returns(close) # 半方差的大小 res = cal_half_dev(simple_return) print(res) # 历史模拟 simple_return.quantile(0.05) # 风险值最差0.05的位置 norm.ppf(0.05, simple_return.mean(), simple_return.std()) # 最差0.05的均值期望 worst_return = simple_return[ "returnss" < simple_return.quantile(0.05)].mean() # 最大回撤 展开 price = (1 + simple_return).cumprod() # 返回的依然是数组 simple_return.cummax() - price # 最大回撤 ffn.calc_max_drawdown(price) ffn.calc_max_drawdown((1 + simple_return).cumprod())
def sma_base(cls, name=None, ts_code=None, count=365, fast_ma=8, slow_ma=60): """ """ df = cls.get_hist(name, ts_code, count) symbol = 'net' data = df data['fast_ma'] = data[symbol].rolling(fast_ma).mean() data['slow_ma'] = data[symbol].rolling(slow_ma).mean() data.dropna(inplace=True) data['position'] = np.where(data['fast_ma'] > data['slow_ma'], 1, 0) data['position'] = data['position'].shift(1) # 因为当天的收益是拿不到的, data.plot(secondary_y='position', figsize=(14, 6)) plt.show() data['returns'] = np.log(data[symbol] / data[symbol].shift(1)) data['strat'] = data['position'] * data['returns'] data.dropna(inplace=True) data['hold_earnings'] = data[['returns']].cumsum().apply(np.exp) data['strat_earnings'] = data[['strat']].cumsum().apply(np.exp) ax = data[['hold_earnings', 'strat_earnings']].plot(figsize=(14, 6)) data['position'].plot(ax=ax, secondary_y='position', style='--', figsize=(14, 6)) plt.show() print(np.exp(data[['returns', 'strat']].sum())) print(data[['returns','strat']].std()) # 计算最大回撤 hold_max_drawdown = ffn.calc_max_drawdown(data.hold_earnings) strat_max_drawdown = ffn.calc_max_drawdown(data.strat_earnings) print(f'hold_max_drawdown: {hold_max_drawdown}') print(f'strat_max_drawdown: {strat_max_drawdown}')
def maxR2MDD(weights, Returns): weights = np.array(weights) RET = np.sum(Returns * weights, axis=1) value = (RET + 1).cumprod() MDD = ffn.calc_max_drawdown(value) R2MDDadj = (np.average(RET) * 252) / (MDD) return -R2MDDadj
def equity_curve_stats(equity_curves): stats = {} end_equity = [eq['EQ'].iloc[-1] for eq in equity_curves] stats['equity_max'] = max(end_equity) stats['equity_min'] = min(end_equity) stats['dd_max_avg_pct'] = 0 stats['dd_max_pct'] = 0 stats['perf_avg_pct'] = 0 stats['risk_ruin'] = 0 performances = [] max_drawdowns = [] for eq in equity_curves: eq_curve = eq['EQ'] if eq_curve.iloc[-1] == 0: stats['risk_ruin'] += 1 perf = ((eq_curve.iloc[-1] - eq_curve.iloc[0]) / eq_curve.iloc[0]) * 100 stats['perf_avg_pct'] += perf performances.append(perf) max_dd = -ffn.calc_max_drawdown(eq_curve) * 100 stats['dd_max_pct'] = max(max_dd, stats['dd_max_pct']) stats['dd_max_avg_pct'] += max_dd max_drawdowns.append(max_dd) stats['dd_max_avg_pct'] /= len(equity_curves) stats['perf_avg_pct'] /= len(equity_curves) stats['risk_ruin'] = stats['risk_ruin'] * 100 / len(equity_curves) stats['perf_median_pct'] = statistics.median(performances) stats['dd_max_median_pct'] = statistics.median(max_drawdowns) return stats
def performance(x): winpct = len(x[x > 0]) / len(x[x != 0]) annRet = (1 + x).cumprod()[-1]**(245 / len(x)) - 1 sharpe = ffn.calc_risk_return_ratio(x) maxDD = ffn.calc_max_drawdown((1 + x).cumprod()) perfo=pd.Series([winpct,annRet,sharpe,maxDD],index=['win rate', 'annualized return',\ 'sharpe ratio','maximum drawdown']) return (perfo)
def __call__(self, oriinfiles, commands, outhead): # 1. 只有两个文件 strategy_names = [ command[list(command.keys())[0]]["strategy_name"] for command in commands ] outjson = {"filename": []} outjson.update({i1 + "年化": [] for i1 in strategy_names}) outjson.update({i1 + "累计": [] for i1 in strategy_names}) outjson.update({i1 + "回撤": [] for i1 in strategy_names}) outjson.update({i1 + "夏普": [] for i1 in strategy_names}) for infile in oriinfiles: pdobj = pd.read_csv(infile, header=0, index_col="date", encoding="utf8") for command in commands: tkey = list(command.keys())[0] tval = list(command.values())[0] strategy_head = command[tkey]["strategy_name"] pdobj = self.funcmap[tkey](pdobj, tval, strategy_head) pdobj = self.get_rewards(pdobj, strategy_head) (filepath, tfilename) = os.path.split(infile) outjson["filename"].append(tfilename) fname = os.path.join(filepath, "{}{}".format(outhead, tfilename)) pdobj.to_csv(fname, index=True, header=True, encoding="utf-8") pdlenth = len(pdobj) wealth_cols = [ i2 for i2 in pdobj.columns if re.search("_wealth$", i2) ] for id2, colname in enumerate(wealth_cols): annRet = np.power(pdobj[colname][-1], 252 / pdlenth) sharpe = ffn.calc_risk_return_ratio(pdobj[colname]) outjson[strategy_names[id2] + "年化"].append(annRet) outjson[strategy_names[id2] + "累计"].append(pdobj[colname][-1]) outjson[strategy_names[id2] + "夏普"].append(sharpe) outjson[strategy_names[id2] + "回撤"].append( ffn.calc_max_drawdown(pdobj[colname])) pdobjout = pd.DataFrame(outjson) pdobjout.set_index("filename", inplace=True) (filepath, _) = os.path.split(oriinfiles[0]) fname = os.path.join(filepath, "{}回测统计.csv".format(outhead)) pdobjout.to_csv(fname, index=True, header=True, encoding="utf-8") return None
from scipy.stats import norm SAPower = pd.read_csv('SAPower.csv', index_col='Date') SAPower.index = pd.to_datetime(SAPower.index) DalianRP = pd.read_csv('DalianRP.csv', index_col='Date') DalianRP.index = pd.to_datetime(DalianRP.index) returnS = ffn.to_returns(SAPower.Close).dropna() returnD = ffn.to_returns(DalianRP.Close).dropna() print(returnS.std()) print(returnD.std()) def cal_half_dev(returns): mu = returns.mean() temp = returns[returns < mu] half_deviation = (sum((mu - temp)**2) / len(returns))**0.5 return half_deviation print(cal_half_dev(returnS)) print(cal_half_dev(returnD)) print(returnS.quantile(0.05)) print(returnD.quantile(0.05)) print(norm.ppf(0.05, returnS.mean(), returnS.std())) print(norm.ppf(0.05, returnD.mean(), returnD.std())) print(returnS[returnS <= returnS.quantile(0.05)].mean()) print(returnD[returnD <= returnD.quantile(0.05)].mean()) print(ffn.calc_max_drawdown((1 + returnS).cumprod())) print(ffn.calc_max_drawdown((1 + returnD).cumprod()))
def MaxDrawdown2(return_list): value = (1 + return_list).cumprod() MDD = ffn.calc_max_drawdown(value) return -MDD
def app(): selection = ["Stock Market Analysis", "Portfolio Assessment"] # Selections choice = st.sidebar.selectbox("Dashboard Selection", selection) if choice == 'Stock Market Analysis': # Building both our selectbox and URL from the JSE's website st.title('Jamaica Stock Exchange Quantitative DashBoard 📈') st.write( '**Contact the author: Samuel Lawrence - ** http://www.samuel-lawrence.co.uk' ) st.write( "More About the Jamaica Stock Exchange: https://www.jamstockex.com/ " ) Index = 'https://www.jamstockex.com/market-data/download-data/price-history/' df_index = pd.read_html(Index) df_index = df_index[0] Stock_Select = df_index['Instrument'] # Creating Select Box Stock_Selection = st.multiselect('Select multiple stocks', Stock_Select) try: # Date selection for Analysis date = st.date_input( "Analysis Dates", (datetime.date(2019, 1, 3), datetime.date(2020, 1, 10))) # We need these datse to pass into our URl since_date = str(date[0]) until_date = str(date[1]) # We need dates in datetime format for our calculations # until_date_calender = date[1] # since_date_calender = date[0] except (IndexError, NameError, UnboundLocalError): pass for x in range( 0, 10 ): # Multiple attempts because the JSE website gives us HTTP errors randomly try: try: # Extracting Data from the JSE based on stock chosen JSE_URL = 'https://www.jamstockex.com/market-data/download-data/price-history/{}/' + since_date + '/' + until_date stock_dfs = [] for stock in Stock_Selection: dfs = pd.read_html(JSE_URL.format(stock)) if not dfs: st.write(f'No tables found for {stock}') continue stock_dfs.append(dfs[0]) full_df = pd.concat(stock_dfs) display_columns = [ "Date", "Instrument", # "Volume (non block) ($)", "Close Price ($)" ] # Creating Dataframe for stocks sub_df = full_df[display_columns] sub_pivot = sub_df.pivot(index='Date', columns='Instrument') # st.write(sub_pivot) data = pd.DataFrame(sub_pivot.to_records( )) # Turning our pivot table back into a dataframe # Cleaning Data for readability data.columns = [ hdr.replace("('Close Price ($)',", "").replace(")", "").replace("'", "").replace("'", "").replace( " ", "") \ for hdr in data.columns] # Turning columns back into ticker names # Readable format for FFN data['Date'] = pd.to_datetime(data.Date, infer_datetime_format=True) data = data.set_index('Date') # Prepping for analysis GS = ffn.GroupStats(data) GS.set_riskfree_rate(.03) perf = data.calc_stats() returns = data.to_log_returns().dropna() st.subheader(" Prices History: ") st.write(data.tail()) st.subheader("Graph of stocks: ") st.line_chart(data) # Breaking up calculations for easier analysis Analyzer_choice = st.selectbox("Analysis Type", [ "Ratios", "Returns", "Look Back Returns", "Portfolio Weights", "Change Analysis", "Machine Learning - Clustering", "Correlation", "Stocks Summary" ]) # Analysis Choices start here if Analyzer_choice == "Correlation": st.subheader("Correlation of returns:") st.write( "**More about this analysis:** https://en.wikipedia.org/wiki/Heat_map " ) st.pyplot(ffn.plot_corr_heatmap(returns)) if Analyzer_choice == "Stocks Summary": st.subheader('Stocks Summary:') General_stats = perf.stats st.write(General_stats) if Analyzer_choice == "Ratios": st.subheader('Calmar Ratio') st.write( "**More about this ratio:** https://www.investopedia.com/terms/c/calmarratio.asp" ) st.write(ffn.calc_calmar_ratio(data)) st.subheader("Risk / Return Ratio ") st.write(ffn.calc_risk_return_ratio(data)) st.write( "**More about this ratio:** https://www.investopedia.com/terms/r/riskrewardratio.asp" "#:~:text=The%20risk%2Freward%20ratio%20marks," "undertake%20to%20earn%20these%20returns.") st.subheader("Sortino ratio") # Experimental # Number of periods # num_years = (until_date_calender.year - since_date_calender.year) st.write( ffn.calc_sortino_ratio(returns, rf=0.0, annualize=True)) st.write( "**More about this ratio:** https://www.investopedia.com/terms/s/sortinoratio.asp " ) st.subheader("Sharpe Ratio") # Experimental st.write(ffn.calc_sharpe(data, rf=0.0, annualize=True)) st.write( "**More about this ratio:** https://www.investopedia.com/terms/s/sharperatio.asp " ) st.subheader('Max Drawdown') st.write( "**More about this ratio:** https://www.investopedia.com/terms/m/maximum-drawdown-mdd" ".asp#:~:text=A%20maximum%20drawdown%20(MDD)%20is," "over%20a%20specified%20time%20period. ") st.write(ffn.calc_max_drawdown(data)) if Analyzer_choice == "Portfolio Weights": st.subheader("ERC Risk Parity Portfolio Weights") st.write( ffn.calc_erc_weights( returns=returns).as_format('.2%')) st.write( '**About this ratio:** A calculation of the equal risk contribution / risk parity ' 'weights given the portfolio returns.') st.subheader("Inverse Volatility Weights") st.write( ffn.calc_inv_vol_weights(returns).as_format('.2%')) st.write( "**About this ratio:** A calculation of weights proportional to the inverse volatility of " "each stock ") st.subheader("Mean Variance Weights") st.write( returns.calc_mean_var_weights().as_format('.2%')) st.write( "**About this ratio:** optimal portolio based on classic Markowitz Mean/Variance " "Optimisation methods") st.write( "**More information at:** https://www.investopedia.com/terms/m/meanvariance-analysis" ".asp") if Analyzer_choice == "Returns": st.subheader('Total returns over the period') st.write(ffn.calc_total_return(data).as_format('.2%')) st.subheader('CAGR - compound annual growth rate.') st.write( "**More about this ratio:** https://www.investopedia.com/terms/c/cagr.asp" ) st.write(ffn.calc_cagr(data).as_format('.2%')) st.subheader("Distribution of returns") st.pyplot(ax=returns.hist(figsize=(20, 10), bins=30), clear_figure=True) if Analyzer_choice == "Look Back Returns": st.subheader('Look Back Returns over the period:') st.write(GS.display_lookback_returns()) if Analyzer_choice == "Machine Learning - Clustering": ## st.subheader("Threshold Clustering Algorithm (FTCA)") st.write( "Grouping Stocks based on similar characteristics") thresh = returns.calc_ftca(threshold=0.1) thresh_df = pd.DataFrame.from_dict(thresh, orient='index') st.write(thresh_df) st.write( "**More info about this :** http://cssanalytics.wordpress.com/2013/11/26/fast-threshold" "-clustering-algorithm-ftca/") # if Analyzer_choice == "Change Analysis": # Counting the amout of changes in dataframe # def Change(Data): # if x > -0.5 and x <= 0.5: # return 'Slight or No change' # elif x > 0.5 and x <= 1: # return 'Slight Positive' # elif x > -1 and x <= -0.5: # return 'Slight Negative' # elif x > 1 and x <= 3: # return 'Positive' # elif x > -3 and x <= -1: # return 'Negative' # elif x > 3 and x <= 7: # return 'Among top gainers' # elif x > -7 and x <= -3: # return 'Among top losers' # elif x > 7: # return 'Bull run' # elif x <= -7: # return # # test = data # for stock in test.columns: # # test[stock] = test[stock].apply(Change) # # for Categorical_Values in test.columns: # cat_num = test[Categorical_Values].value_counts().plot(kind='pie', figsize=(10, 5)) # st.write(cat_num) #st.write(test) # if Analyzer_choice == "Volume Analysis": # st.subheader("Coming Soon") # https://www.investopedia.com/terms/v/volume-analysis.asp#:~:text=What%20is%20Volume%20Analysis, # in%20a%20given%20time%20period.&text=By%20analyzing%20trends%20in%20volume, # changes%20in%20a%20security's%20price. except HTTPError: sleep(1) else: break # st.error('Try again later') except (ValueError, UnboundLocalError): pass st.write( "Github repo: https://github.com/SamuelLawrence876/JSE-Quant-Webapp") if choice == 'Portfolio Assessment': st.title('Under Construction!')
def _run_single(self, start_date: date, end_date: date) -> Simulator.Result: class AccountRow(NamedTuple): dt: date value: float isins: List[str] names: List[Optional[str]] account_list = [ AccountRow(dt=start_date, value=100, isins=[], names=[]) ] # jump start to first data point if not available dt = max(start_date, self._prices_df.first_valid_index() + self._buy_sell_gap) while dt < end_date: trunc_date = dt - self._buy_sell_gap prediction = self.predict(trunc_date) max_funds = prediction.funds max_isins = [fund.isin for fund in max_funds] curr_hold_interval = self._hold_interval # curr_hold_interval = calc_hold_interval(self._prices_df, dt, max_isins, self._hold_interval) if len(max_funds): next_dt = (dt + curr_hold_interval).date() max_names = [f.name for f in max_funds] next_return = calc_returns(self._prices_df[max_isins], next_dt, curr_hold_interval, self._fees_df).mean() account_list.append( AccountRow(dt=next_dt, value=account_list[-1].value * (1 + next_return), isins=max_isins, names=max_names)) dt = (next_dt + self._buy_sell_gap).date() else: next_dt = (dt + BDAY).date() # carry forward account_list.append( AccountRow(dt=next_dt, value=account_list[-1].value * (1 - DAILY_PLATFORM_FEES), isins=[], names=[])) dt = next_dt account_list[-1] = account_list[-1]._replace( dt=min(account_list[-1].dt, end_date)) account = pd.DataFrame.from_records( [row._asdict() for row in account_list], index="dt") account.index.name = None total_returns = (account.iloc[-1, :].loc["value"] - account.iloc[0, :].loc["value"]) \ / account.iloc[0, :].loc["value"] annual_returns = (1 + total_returns)**(365.25 / (end_date - start_date).days) - 1 print(account.to_string()) return Simulator.Result( account=account, returns=total_returns, annual_returns=annual_returns, max_drawdown=calc_max_drawdown(account["value"]), sharpe_ratio=calc_sharpe_ratio(account["value"]), start_date=start_date, end_date=end_date)
cal_half_dev(returnD) #历史模拟法 returnS.quantile(0.05) returnD.quantile(0.05) #协方差矩阵法 from scipy.stats import norm norm.ppf(0.05, returnS.mean(), returnS.std()) norm.ppf(0.05, returnD.mean(), returnD.std()) returnS[returnS <= returnS.quantile(0.05)].mean() returnD[returnD <= returnD.quantile(0.05)].mean() import datetime r = pd.Series([0, 0.1, -0.1, -0.01, 0.01, 0.02], index=[datetime.date(2015, 7, x) for x in range(3, 9)]) r value = (1 + r).cumprod() value D = value.cummax() - value D d = D / (D + value) d MDD = D.max() MDD mdd = d.max() mdd ffn.calc_max_drawdown(value) ffn.calc_max_drawdown((1 + returnS).cumprod()) ffn.calc_max_drawdown((1 + returnD).cumprod())
cal_half_dev(retTSMC) cal_half_dev(retFoxConn) retTSMC.quantile(0.05) retFoxConn.quantile(0.05) from scipy.stats import norm norm.ppf(0.05, retTSMC.mean(), retTSMC.std()) norm.ppf(0.05, retFoxConn.mean(), retFoxConn.std()) retTSMC[retTSMC <= retTSMC.quantile(0.05)].mean() retFoxConn[retFoxConn <= retFoxConn.quantile(0.05)].mean() import datetime r = pd.Series([0, 0.1, -0.1, -0.01, 0.01, 0.02], index=[datetime.date(2015, 7, x) for x in range(3, 9)]) r value = (1 + r).cumprod() value D = value.cummax() - value D d = D / (D + value) d MDD = D.max() MDD mdd = d.max() mdd ffn.calc_max_drawdown(value) ffn.calc_max_drawdown((1 + retTSMC).cumprod()) ffn.calc_max_drawdown((1 + retFoxConn).cumprod())
# 用风险价值Value at Rist, VaR度量风险 # 历史模拟,下跌超过5%的概率 dailyreturn_xianbank_without_nan.quantile(0.05) dailyreturn_xibuzhengquan_without_nan.quantile(0.05) # 协方差矩阵法,下跌超过5%的概率 from scipy.stats import norm norm.ppf(0.05, dailyreturn_xianbank_without_nan.mean(), dailyreturn_xianbank_without_nan.std()) norm.ppf(0.05, dailyreturn_xibuzhengquan_without_nan.mean(), dailyreturn_xibuzhengquan_without_nan.std()) # 期望亏空,超过VaR水平的损失的期望值,也就是最坏的α%损失的平均值,越小越好 dailyreturn_xianbank_without_nan[ dailyreturn_xianbank_without_nan <= dailyreturn_xianbank_without_nan.quantile(0.05)].mean() dailyreturn_xibuzhengquan_without_nan[ dailyreturn_xibuzhengquan_without_nan <= dailyreturn_xibuzhengquan_without_nan.quantile(0.05)].mean() # 最大回撤:一段周期内从最高点下跌到最低点的最大值,越小越好 # 例:最高收益80%,最低收益50%,最大回撤30% value = (1 + dailyreturn_xianbank_without_nan).cumprod() D = value.cummax() - value # 回撤 d = D / (D + value) # 回撤率 MDD = D.max() # 最大回撤 mdd = d.max() # 最大回撤率 # 利用ffn模块计算最大回撤率 # (1 + return_daxiangsu).cumprod()为收益率序列 ffn.calc_max_drawdown((1 + dailyreturn_xianbank_without_nan).cumprod())
def minMDD(weights, Returns): weights = np.array(weights) RET = np.sum(Returns * weights, axis=1) value = (RET + 1).cumprod() MDD = ffn.calc_max_drawdown(value) return (-MDD)
print(cal_half_dev(returnS)) print(cal_half_dev(returnD)) #计算VaR #历史模拟法 returnS.quantile(0.05) returnD.quantile(0.05) #协方差矩阵法 from scipy.stats import norm norm.ppf(0.05, returnS.mean(), returnS.std()) norm.ppf(0.05, returnD.mean(), returnD.std()) #计算最大回测 ffn.calc_max_drawdown((1 + returnS).cumprod()) ffn.calc_max_drawdown((1 + returnD).cumprod()) print("600343 股票最大回撤是: %.4f" % ffn.calc_max_drawdown((1 + returnS).cumprod())) #print ("600343 股票最大回撤是: %s" % format(ffn.calc_max_drawdown((1+returnS).cumprod()),'.4%')) #第二种方法 import pandas as pd import numpy as np data = pd.read_csv('SAPower.csv', index_col='Date') #计算日收益率(G3-G2)/G2 data['return'] = (data['Close'].shift(-1) - data['Close']) / data['Close'] #data['Close'].plot() #计算累积收益率cumret=(1+return).cumsum data['cumret'] = np.cumprod(1 + data['return']) #fig = plt.figure()
import datetime import pandas as pd import ffn r = pd.Series([0, 0.1, -0.1, -0.01, 0.01, 0.02], index=[datetime.date(2015, 7, x) for x in range(3, 9)]) print(r) value = (1 + r).cumprod() print(value) D = value.cummax() - value print(D) d = D / (D + value) print(d) MDD = D.max() print(MDD) mdd = d.max() print(mdd) print(ffn.calc_max_drawdown(value))
# -*- coding: utf-8 -*- """ Created on Tue Sep 25 10:55:37 2018 @author: DELL """ import pandas as pd import os import ffn price = [ 1.0, 1.01, 1.05, 1.1, 1.11, 1.07, 1.03, 1.03, 1.01, 1.02, 1.04, 1.05, 1.07, 1.06, 1.05, 1.06, 1.07, 1.09, 1.12, 1.18, 1.15, 1.15, 1.18, 1.16, 1.19, 1.17, 1.17, 1.18, 1.19, 1.23 ] data = pd.DataFrame(price) returnS = ffn.to_returns(data).dropna() max_dropdown = ffn.calc_max_drawdown((1 + returnS).cumprod()) print("最大回撤是: %.4f" % max_dropdown)