StockChartingDash/marketsim.py
2024-06-19 10:33:24 -07:00

163 lines
6.0 KiB
Python

# -*- 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()