# -*- coding: utf-8 -*- """ Created on Wed Feb 5 21:56:42 2020 @author: cpan """ """MC2-P1: Market simulator.""" import pandas as pd import numpy as np # import datetime as dt # import os from util import get_data, plot_data def normalize_data(df): return df/df.iloc[0,:] def fill_missing_values(df_data): '''First fill forward and then fill backward''' df_data.fillna(method="ffill", inplace=True) df_data.fillna(method="bfill", inplace=True) def get_orders(orders_file): if isinstance(orders_file, pd.DataFrame): # orders_df = orders_file.set_index('Date') orders_df = orders_file else: orders_df = pd.read_csv(orders_file, index_col = 'Date', parse_dates = True, na_values = ['nan']) orders_df = orders_df.dropna() orders_df = orders_df.sort_index() return orders_df def compute_daily_returns(df): daily_returns = df.copy() daily_returns[1:] = (df[1:] / df[:-1].values) - 1 daily_returns.iloc[0, :] = 0 return daily_returns def compute_portfolio_stats(price, allocs=[0.1,0.2,0,3,0.4], rfr=0.0, sf=252.0): norm_price = normalize_data(price) norm_positions_val = norm_price * allocs if len(norm_positions_val.columns) == 1: norm_portfolio_val = norm_positions_val else: norm_portfolio_val = norm_positions_val.sum(axis=1).to_frame('PORTFOLIO') cr = norm_portfolio_val.iloc[-1] / norm_portfolio_val.iloc[0] -1 daily_returns = compute_daily_returns(norm_portfolio_val) daily_returns = daily_returns[1:] # remove first row (all zeros) adr = daily_returns.mean() sddr = daily_returns.std() sr = np.sqrt(sf) * (adr - rfr)/sddr return cr, adr, sddr, sr def plot_against_SPY(df): df_temp = df.copy() if 'SPY' not in df_temp.columns: df_SPY = get_data(['SPY'], pd.to_datetime(df_temp.index.values)) df_temp['SPY'] = df_SPY.values else: df_SPY = df_temp['SPY'] df_temp = normalize_data(df_temp) plot_data(df_temp) return df_SPY def compute_portvals(orders_file = "./orders/orders.csv", start_val = 1000000, commission=9.95, impact=0.005): #read in order data orders_df = get_orders(orders_file) #scan symbols symbols = list(set(orders_df['Symbol'].values)) #get date range dates = pd.date_range(orders_df.index.values[0], orders_df.index.values[-1]) #read in prices prices = get_data(symbols, dates) # fill_missing_values(prices) # included in get_data() function prices = prices[symbols] #add an extra column 'Cash' and initialize it to all ones prices['Cash'] = np.ones(prices.shape[0]) #duplicate price df into a units df, intialize it to all zeros positions=prices*0.0 #initialize cash position with starting value positions.loc[positions.index.values[0],['Cash']]=start_val #adjust positions to show how stock units and cash are changing over time with orders for index, row in orders_df.iterrows(): stock_sym = row['Symbol'] order_price = prices.loc[index, stock_sym] order_shrs = row['Shares'] if row['Order'].upper() == 'BUY': if positions.loc[index, 'Cash'] < order_shrs*order_price +\ commission + order_shrs*order_price*impact: # print('Not enough cash to excute the order:\n', row) pass else: #update positions on transaction days positions.loc[index, stock_sym] += order_shrs positions.loc[index, "Cash"] -= order_shrs*order_price #deduct commission positions.loc[index,"Cash"] -= commission #impact = no. of orders in transaction * price of each share * impact. positions.loc[index,"Cash"] -= order_shrs*order_price*impact elif row['Order'].upper() == 'SELL': if positions.loc[index, stock_sym] < order_shrs: # print('Not enough shares to sell to fill the order:\n', row) pass else: positions.loc[index, stock_sym] -= order_shrs positions.loc[index, "Cash"] += order_shrs*order_price #deduct commission positions.loc[index,"Cash"] -= commission #impact = no. of orders in transaction * price of each share * impact. positions.loc[index,"Cash"] -= order_shrs*order_price*impact # propagate positions beyond transaction days start_row = positions.index.get_loc(index) + 1 positions.iloc[start_row:, :] = positions.iloc[start_row-1].values #calculate port_vals port_vals=prices*positions port_vals.insert(0, 'Portfolio', port_vals.sum(axis=1)) return port_vals def test_code(): of = "./orders/orders-05.csv" sv = 1000000 # Process orders portvals = compute_portvals(orders_file = of, start_val = sv) if isinstance(portvals, pd.DataFrame): portvals = portvals[portvals.columns[0]].to_frame() # just get the first column else: print("warning, code did not return a DataFrame") # Get portfolio stats start_date = pd.to_datetime(portvals.index.values[0]) end_date = pd.to_datetime(portvals.index.values[-1]) price_SPY = plot_against_SPY(portvals) #portfolio stats calculated similar to assess_portfolio rfr=0 sf=252 cr, adr, sddr, sr = compute_portfolio_stats(portvals, [1.0], rfr, sf) crSP,adrSP,sddrSP,srSP = compute_portfolio_stats(price_SPY, [1.0], rfr, sf) # Compare portfolio against $SPX print("\nDate Range: {} to {}".format(start_date.date(), end_date.date())) print() print("Sharpe Ratio: {}, {}".format(sr, srSP)) print() print("Cumulative Return: {}, {}".format(cr, crSP)) print() print("Standard Deviation: {}, {}".format(sddr, sddrSP)) print() print("Average Daily Return: {}, {}".format(adr, adrSP)) print() print("Final Portfolio Value: {:.2f}".format(portvals['Portfolio'].iloc[-1])) if __name__ == "__main__": test_code()