From f53f6a4d40fdd091b87a7925cbd27ac72e4035d4 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Mon, 12 Oct 2020 20:37:53 -0400 Subject: [PATCH] Implement theoretical optimal strategy and evaluate --- .../TheoreticallyOptimalStrategy.py | 63 ++++++++++++++++--- manual_strategy/indicators.py | 50 ++++++++++++++- 2 files changed, 104 insertions(+), 9 deletions(-) diff --git a/manual_strategy/TheoreticallyOptimalStrategy.py b/manual_strategy/TheoreticallyOptimalStrategy.py index fa55771..bebbdcf 100644 --- a/manual_strategy/TheoreticallyOptimalStrategy.py +++ b/manual_strategy/TheoreticallyOptimalStrategy.py @@ -1,17 +1,64 @@ import pandas as pd from util import get_data -from marketsim.marketsim import compute_portvals -from optimize_something.optimization import calculate_stats +from collections import namedtuple + +Position = namedtuple("Pos", ["cash", "shares", "transactions"]) def author(): return "felixm" -def testPolicy(symbol, sd, ed, sv): - print(f"{symbol=} {sd} - {ed} {sv=}") - # trade = date, shares (-2000, -1000, 0, 1000, 2000) - prices = get_data([symbol], pd.date_range(sd, ed)) - print(prices.index) - return +def new_positions(positions, price): + """Calculate all potential new positions and then keep the best three.""" + # Execute all possible transactions + new_positions = [] + for p in positions: + for t in [-2000, -1000, 0, 1000, 2000]: + ts = p.transactions + [t] + p_new = Position(p.cash - t * price, p.shares + t, ts) + new_positions.append(p_new) + # Keep the positions with the highest cash value for each amount of shares. + best = {} + for p in new_positions: + if p.shares not in [-1000, 0, 1000]: + pass + elif p.shares in best and p.cash > best[p.shares].cash: + best[p.shares] = p + elif not p.shares in best: + best[p.shares] = p + return list(best.values()) + + +def transactions_to_orders(transactions, prices, symbol): + order = pd.Series("", index=prices.index) + shares = pd.Series(0, index=prices.index) + + for i, t in enumerate(transactions): + if t > 0: + order.iloc[i] = "BUY" + shares.iloc[i] = t + if t < 0: + order.iloc[i] = "SELL" + shares.iloc[i] = -t + + prices["Symbol"] = pd.Series(symbol, index=prices.index) + prices["Order"] = order + prices["Shares"] = shares + prices.drop(columns=[symbol], inplace=True) + prices = prices[shares != 0] + return prices + + +def testPolicy(symbol, sd, ed, sv): + prices = get_data([symbol], pd.date_range(sd, ed)) + prices.drop(columns=["SPY"], inplace=True) + + positions = [Position(sv, 0, [])] + for date, price in prices.iterrows(): + positions = new_positions(positions, price[0]) + + price = prices.iloc[-1][symbol] + best_position = max(positions, key=lambda p: p.cash + p.shares * price) + return transactions_to_orders(best_position.transactions, prices, symbol) diff --git a/manual_strategy/indicators.py b/manual_strategy/indicators.py index b5fe395..1a5e249 100644 --- a/manual_strategy/indicators.py +++ b/manual_strategy/indicators.py @@ -1,5 +1,9 @@ +import pandas as pd import datetime as dt import TheoreticallyOptimalStrategy as tos +from util import plot_data, get_data +from marketsim.marketsim import compute_portvals +from optimize_something.optimization import calculate_stats def author(): @@ -7,10 +11,52 @@ def author(): def test_policy(): + symbol = "JPM" + start_value = 100000 sd = dt.datetime(2008, 1, 1) + # ed = dt.datetime(2008, 1, 30) ed = dt.datetime(2009, 12, 31) - tos.testPolicy(symbol="JPM", sd=sd, ed=ed, sv=100000) + orders = tos.testPolicy(symbol=symbol, sd=sd, ed=ed, sv=start_value) + portvals = compute_portvals(orders, start_value, 0, 0) + + start_date = portvals.index[0] + end_date = portvals.index[-1] + cum_ret, avg_daily_ret, \ + std_daily_ret, sharpe_ratio = calculate_stats(portvals, [1]) + + d = {"Symbol": [symbol, symbol], + "Order": ["BUY", "SELL"], + "Shares": [1000, 1000]} + orders = pd.DataFrame(data=d, index=[start_date, end_date]) + bench = compute_portvals(orders, start_value, 0, 0) + cum_ret_bench, avg_daily_ret_bench, \ + std_daily_ret_bench, sharpe_ratio_bench = calculate_stats(bench, [1]) + + # Compare portfolio against benchmark + print(f"Date Range: {start_date} to {end_date}") + print() + print(f"Sharpe Ratio of Optimal Strategy: {sharpe_ratio}") + print(f"Sharpe Ratio of bench: {sharpe_ratio_bench}") + print() + print(f"Cumulative Return of Optimal Strategy: {cum_ret}") + print(f"Cumulative Return of bench: {cum_ret_bench}") + print() + print(f"Standard Deviation of Optimal Strategy: {std_daily_ret}") + print(f"Standard Deviation of bench: {std_daily_ret_bench}") + print() + print(f"Average Daily Return of Optimal Strategy: {avg_daily_ret}") + print(f"Average Daily Return of bench: {avg_daily_ret_bench}") + print() + print(f"Final Portfolio Value Optimal: {portvals.iloc[-1][0]}") + print(f"Final Portfolio Value Bench: {bench.iloc[-1][0]}") + + portvals["Optimal"] = portvals["Portval"] + portvals["Bench"] = bench["Portval"] + portvals.drop(columns=["Portval"], inplace=True) + + #XXX: Save to file instead. + plot_data(portvals) def normalize(timeseries): return timeseries / timeseries.iloc[0] @@ -22,6 +68,8 @@ def bollinger_band(prices): def main(): test_policy() + + # plot_data(prices) # sd = dt.datetime(2008, 1, 1) # ed = dt.datetime(2009, 12, 31) # prices = get_data(['JPM'], pd.date_range(sd, ed))