180 lines
5.8 KiB
Python
180 lines
5.8 KiB
Python
"""MC2-P1: Market simulator.
|
|
|
|
Copyright 2018, Georgia Institute of Technology (Georgia Tech)
|
|
Atlanta, Georgia 30332
|
|
All Rights Reserved
|
|
|
|
Template code for CS 4646/7646
|
|
|
|
Georgia Tech asserts copyright ownership of this template and all derivative
|
|
works, including solutions to the projects assigned in this course. Students
|
|
and other users of this template code are advised not to share it with others
|
|
or to make it available on publicly viewable websites including repositories
|
|
such as github and gitlab. This copyright statement should not be removed
|
|
or edited.
|
|
|
|
We do grant permission to share solutions privately with non-students such
|
|
as potential employers. However, sharing with other current or future
|
|
students of CS 7646 is prohibited and subject to being investigated as a
|
|
GT honor code violation.
|
|
|
|
-----do not edit anything above this line---
|
|
|
|
Student Name: Tucker Balch (replace with your name)
|
|
GT User ID: felixm (replace with your User ID)
|
|
GT ID: 1337 (replace with your GT ID)
|
|
"""
|
|
|
|
import pandas as pd
|
|
from util import get_data, plot_data
|
|
from optimize_something.optimization import calculate_stats
|
|
|
|
|
|
def read_orders(orders_file):
|
|
"""
|
|
Parser orders into the form:
|
|
|
|
Date datetime64[ns]
|
|
Symbol object
|
|
Order object
|
|
Shares int32
|
|
|
|
This is how the order book looks like:
|
|
|
|
Date,Symbol,Order,Shares
|
|
2011-01-10,AAPL,BUY,1500
|
|
2011-01-10,AAPL,SELL,1500
|
|
"""
|
|
orders = pd.read_csv(orders_file,
|
|
index_col=['Date'],
|
|
dtype='|str, str, str, i4',
|
|
parse_dates=['Date'])
|
|
orders.sort_values(by="Date", inplace=True)
|
|
return orders
|
|
|
|
|
|
def get_order_book_info(orders):
|
|
"""Return start_date, end_date, and symbols (as a list)."""
|
|
start_date = orders.index[0]
|
|
end_date = orders.index[-1]
|
|
symbols = sorted(list((set(orders.Symbol.tolist()))))
|
|
return start_date, end_date, symbols
|
|
|
|
|
|
def get_portfolio_value(holding, prices):
|
|
"""Calculate the current portofolio value."""
|
|
value = 0
|
|
for ticker, shares in holding.items():
|
|
if ticker == 'cash':
|
|
value += shares
|
|
else:
|
|
value += shares * prices[ticker]
|
|
return value
|
|
|
|
|
|
def handle_order(date, order, holding, prices, commission, impact):
|
|
"""Process the order."""
|
|
symbol, order, shares = order
|
|
if shares == 0 and order == "":
|
|
return # empty order
|
|
if pd.isnull(shares):
|
|
return # shares is nan
|
|
|
|
# Allow indicating buying and selling via shares. If shares is positive we
|
|
# buy and if it is negative we sell.
|
|
if shares > 0 and order == "":
|
|
order = "BUY"
|
|
elif shares < 0 and order == "":
|
|
order = "SELL"
|
|
shares = abs(shares)
|
|
|
|
adj_closing_price = prices[symbol]
|
|
cost = shares * adj_closing_price
|
|
# Charge commission and deduct impact penalty
|
|
holding['cash'] -= (commission + impact * adj_closing_price * shares)
|
|
if order.upper() == "BUY":
|
|
# print(f"Buy {shares:6} of {symbol:4} on {date}")
|
|
holding['cash'] -= cost
|
|
holding[symbol] += shares
|
|
elif order.upper() == "SELL":
|
|
# print(f"Sell {shares:6} of {symbol:4} on {date}")
|
|
holding['cash'] += cost
|
|
holding[symbol] -= shares
|
|
else:
|
|
raise Exception("Unexpected order type.")
|
|
|
|
|
|
def compute_portvals(orders_file, start_val=1000000, commission=9.95, impact=0.005):
|
|
if isinstance(orders_file, pd.DataFrame):
|
|
orders = orders_file
|
|
else:
|
|
orders = read_orders(orders_file)
|
|
|
|
start_date, end_date, symbols = get_order_book_info(orders)
|
|
|
|
# Tickers in the orderbook over the date_range in the order book.
|
|
prices = get_data(symbols, pd.date_range(start_date, end_date))
|
|
prices['Portval'] = pd.Series(0.0, index=prices.index)
|
|
|
|
# A dictionary to keep track of the assets we are holding.
|
|
holding = {s: 0 for s in symbols}
|
|
holding['cash'] = start_val
|
|
|
|
# Iterate over all trading days that are in the (inclusive) range of the
|
|
# order book dates. This implicitly ignores orders placed on non-trading
|
|
# days.
|
|
for date, values in prices.iterrows():
|
|
# Process orders for that day.
|
|
for date, order in orders.loc[date:date].iterrows():
|
|
handle_order(date, order, holding, values, commission, impact)
|
|
# Compute portfolio value at the end of day.
|
|
values['Portval'] = get_portfolio_value(holding, values)
|
|
|
|
return prices[['Portval']]
|
|
|
|
|
|
def test_code():
|
|
of = "./orders/orders-02.csv"
|
|
sv = 1000000
|
|
|
|
portvals = compute_portvals(orders_file=of, start_val=sv)
|
|
|
|
if isinstance(portvals, pd.DataFrame):
|
|
portvals = portvals[portvals.columns[0]] # just get the first column
|
|
else:
|
|
raise Exception("warning, code did not return a DataFrame")
|
|
|
|
start_date = portvals.index[0]
|
|
end_date = portvals.index[-1]
|
|
cum_ret, avg_daily_ret, \
|
|
std_daily_ret, sharpe_ratio = calculate_stats(portvals.to_frame(), [1])
|
|
|
|
spy = get_data(['SPY'], pd.date_range(start_date, end_date))
|
|
cum_ret_SPY, avg_daily_ret_SPY, \
|
|
std_daily_ret_SPY, sharpe_ratio_SPY = calculate_stats(spy, [1])
|
|
|
|
# Compare portfolio against $SPY
|
|
print(f"Date Range: {start_date} to {end_date}")
|
|
print()
|
|
print(f"Sharpe Ratio of Fund: {sharpe_ratio}")
|
|
print(f"Sharpe Ratio of SPY : {sharpe_ratio_SPY}")
|
|
print()
|
|
print(f"Cumulative Return of Fund: {cum_ret}")
|
|
print(f"Cumulative Return of SPY : {cum_ret_SPY}")
|
|
print()
|
|
print(f"Standard Deviation of Fund: {std_daily_ret}")
|
|
print(f"Standard Deviation of SPY : {std_daily_ret_SPY}")
|
|
print()
|
|
print(f"Average Daily Return of Fund: {avg_daily_ret}")
|
|
print(f"Average Daily Return of SPY : {avg_daily_ret_SPY}")
|
|
print()
|
|
print(f"Final Portfolio Value: {portvals[-1]}")
|
|
|
|
|
|
def author():
|
|
return 'felixm'
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_code()
|