Compare commits

...

44 Commits

Author SHA1 Message Date
e577ce34d2 Add support for trading view data 2021-06-20 15:04:30 -04:00
71f9b58803 Add util because I cannot remember why I would ignore it 2021-06-19 09:47:35 -04:00
0af5a9885d Add project 8 report to readme 2020-11-10 12:41:50 -05:00
063d9a75ae Finish project 8 and course! 2020-11-10 12:33:42 -05:00
6e1f70bcba Improve QLearner so that commission is considered 2020-11-10 11:01:21 -05:00
761a0366e4 Finish first version of Q trader 2020-11-09 15:07:52 -05:00
169dd8278d Implement binning and state calculation 2020-11-07 12:39:37 -05:00
889bcf68ca Finish experiment 1 and start with Q trader 2020-11-05 14:34:48 -05:00
5fbbc26929 Update StrategyLearner to pass tests 2020-11-04 17:32:02 -05:00
10d87aefd3 Add tree learners to strategy evaluation directory 2020-11-04 15:15:24 -05:00
05db89e8c2 Implement first version of strategy learner
This version does not pass the automatic test.
2020-11-04 15:14:27 -05:00
c40ffcf84b Show both MACD and indicator strat on figure
Prepare for strategy learner.
2020-11-04 09:23:42 -05:00
0519ae9336 Finish manual strategy for project 8
I struggled with the manual strategy, mostly because I tried to read
good triggers from the price action charts. Finally, I had the ingenious
(hmm) idea to scatter plot the 1, 3, and 5 day percentage returns over
different indicators. I can also use this information to train my Q
learner.
2020-11-03 19:05:43 -05:00
43e297c075 Change indicators to return their results and work on three indicator strat 2020-11-02 09:10:01 -05:00
4679910374 Implement first version of manual strategy 2020-10-27 19:57:46 -04:00
1798e9569e Make marketsim support buying and selling via sign of shares 2020-10-27 19:53:55 -04:00
85a9c4fcb3 Start working on strategy evaluation 2020-10-26 21:44:18 -04:00
d112dce5f5 Implement dyna-q to finish project 7 2020-10-19 08:56:24 -04:00
22022c3780 Update readme for project 7 2020-10-18 14:48:15 -04:00
d5aa22e9dd Implement Q learner 2020-10-18 14:44:32 -04:00
f5e91eba0a Add template for project 8 2020-10-15 16:46:50 -04:00
cefc6f7893 Add files for qlearning assignment 2020-10-15 16:44:21 -04:00
6a9e762012 Fix picture link in project 6 report 2020-10-15 13:11:40 -04:00
66ffe6d11b Add figures to report to finish project 6 2020-10-15 13:09:57 -04:00
9464943f75 Implment remaining indicators and add figures 2020-10-15 13:05:46 -04:00
5ac3d46c1f Implement SMA, price/SMA, and bollinger band 2020-10-13 20:46:56 -04:00
3e12fda047 Add figure 2020-10-13 19:50:43 -04:00
828160fc82 Add report links to main readme 2020-10-13 19:47:05 -04:00
7481b2d6cc Document optimal strategy for project 6 2020-10-13 19:40:43 -04:00
f53f6a4d40 Implement theoretical optimal strategy and evaluate 2020-10-12 20:37:53 -04:00
61bc03e230 Change marketsim to support orders dataframe as input 2020-10-12 20:07:05 -04:00
5a24622410 Start to implement testPolicy 2020-10-12 17:38:20 -04:00
d66b350390 Create template for project 6 2020-10-10 10:00:05 -04:00
cb9ae77ddc Finish project 5 marketsim successfully 2020-10-10 09:48:08 -04:00
cb72af1781 Implement marketsim without commission and impact 2020-10-09 10:39:17 -04:00
117d97b8e9 Parse orders and extract information (start/end date and symbols) 2020-10-08 21:28:12 -04:00
0429c497c7 Make zip naming consistent and start marketsim project 2020-10-08 20:24:49 -04:00
4e2a7a8e77 Add marketsim zip 2020-10-08 20:21:38 -04:00
d0c40f9af5 Finish project 4 2020-10-05 20:01:29 -04:00
381670705b Change best for LinReg to return optimal data 2020-10-05 17:27:09 -04:00
a662e302db Add my DT Learner to defeat_learners assignment 2020-10-05 12:49:58 -04:00
db537d7043 Start working on defeat learners assignment. 2020-09-27 16:22:16 -04:00
8ee47c9a1d Finish report for project 3. 2020-09-26 10:52:05 -04:00
3ef06ccc96 Create charts for project 3 report. 2020-09-26 10:29:53 -04:00
90 changed files with 5509 additions and 36 deletions

1
.gitignore vendored
View File

@@ -2,4 +2,3 @@ __pycache__
assess_learners/Data
data
grading
util.py

View File

@@ -34,7 +34,10 @@ unzip -n zips/*.zip -d ./
# Reports
- [Report 1](./martingale/martingale.md)
- [Report 2](./optimize_something/readme.md)
- [Report 3](#)
- [Report 2](./optimize_something/optimize_something.md)
- [Report 3](./assess_learners/assess_learners.md)
- No reports for projects 4 (defeat learners) and 5 (marketsim)
- [Report 6](./manual_strategy/manual_strategy.md)
- No report for project 7
- [Report 8](./strategy_evaluation/strategy_evaluation.md)

View File

@@ -0,0 +1,44 @@
# Report
## Experiment 1
Significant overfitting occurs for leaf sizes smaller than five. The chart shows
that the root-mean-square-error is significantly higher for the test data
(`rmse_out`) for leaf sizes smaller than five.
Between five and nine, the error for the test data is only slightly higher, so
there is small overfitting. Beyond that, the errors increase, and the error for
the test data is lower than for the training data. In other words, there is no
more overfitting for leaf sizes greater than nine.
![](figure_1.png)
## Experiment 2
For all bag sizes, the difference of the RMSE for the training data and the test
data is smaller than without bagging. The test data still has a lower RMSE up to
a leaf size of five. For greater leaf sizes, the RMSE for the test data is
smaller than for the training data for all bag sizes, so there is no
overfitting.
![](figure_2.png)
![](figure_3.png)
![](figure_4.png)
![](figure_5.png)
## Experiment 3
The Random Tree learner has a correlation of one for the training data. In other
words, it fits the training data perfectly. Consequently, the correlation for
the test data is worse than for the Decision Tree learner. The DT learner has a
higher correlation than the RT for all other leaf sizes, both for the training
and the test data.
![](figure_6.png)
![](figure_7.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -23,6 +23,8 @@ GT honor code violation.
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import math
import LinRegLearner as lrl
import DTLearner as dtl
@@ -30,8 +32,148 @@ import RTLearner as rtl
import BagLearner as bgl
import InsaneLearner as isl
import sys
from dataclasses import dataclass
if __name__=="__main__":
@dataclass
class EvaluationResult:
rmse_in: float
rmse_out: float
corr_in: float
corr_out: float
def test_learner(data, learner_class, **kwargs):
trainX, trainY, testX, testY = data
print("\n-----------")
print(f"name={learner_class.__name__} {kwargs=}")
learner = learner_class(**kwargs)
learner.addEvidence(trainX, trainY)
print(learner.author())
# evaluate in sample
predY = learner.query(trainX) # get the predictions
rmse = math.sqrt(((trainY - predY) ** 2).sum()/trainY.shape[0])
print()
print("In sample results")
print(f"RMSE: {rmse}")
c = np.corrcoef(predY, y=trainY)
print(f"corr: {c[0,1]}")
# evaluate out of sample
predY = learner.query(testX) # get the predictions
rmse = math.sqrt(((testY - predY) ** 2).sum()/testY.shape[0])
print()
print("Out of sample results")
print(f"RMSE: {rmse}")
c = np.corrcoef(predY, y=testY)
print(f"corr: {c[0,1]}")
print()
def test_learners(data):
test_learner(data, lrl.LinRegLearner)
# test_learner(data, dtl.DTLearner, leaf_size=1)
# test_learner(data, rtl.RTLearner, leaf_size=6)
test_learner(data, bgl.BagLearner, learner=dtl.DTLearner, bags=20, kwargs = {'leaf_size': 5})
# test_learner(data, isl.InsaneLearner)
def eval_learner(data, learner_class, **kwargs):
trainX, trainY, testX, testY = data
learner = learner_class(**kwargs)
learner.addEvidence(trainX, trainY)
# evaluate in sample
predY = learner.query(trainX) # get the predictions
rmse_in_sample = math.sqrt(((trainY - predY) ** 2).sum()/trainY.shape[0])
corr_in_sample = np.corrcoef(predY, y=trainY)[0,1]
# evaluate out of sample
predY = learner.query(testX) # get the predictions
rmse_out_sample = math.sqrt(((testY - predY) ** 2).sum()/testY.shape[0])
corr_out_sample = np.corrcoef(predY, y=testY)[0,1]
r = EvaluationResult(rmse_in_sample, rmse_out_sample,
corr_in_sample, corr_out_sample)
return r
def experiment_1(data):
"""
Does overfitting occur with respect to leaf_size? Use the dataset
Istanbul.csv with DTLearner. For which values of leaf_size does overfitting
occur? Use RMSE as your metric for assessing overfitting. Support your
assertion with graphs/charts. (Don't use bagging).
"""
results = [[i, r.rmse_in, r.rmse_out, r.corr_in, r.corr_out]
for i in range(1, 15)
if (r := eval_learner(data, dtl.DTLearner, leaf_size=i))]
cs = ["leaf_size", "rmse_in", "rmse_out", "corr_in", "corr_out"]
df = pd.DataFrame(results, columns=cs)
df.plot(title="DT Learner RMSE depending on leaf size for Istanbul dataset",
xlabel="leaf size", ylabel="RMSE",
x="leaf_size", y=["rmse_in", "rmse_out"], kind="line")
plt.savefig("figure_1.png")
def experiment_2(data):
"""
Can bagging reduce or eliminate overfitting with respect to leaf_size?
Again use the dataset Istanbul.csv with DTLearner. To investigate this
choose a fixed number of bags to use and vary leaf_size to evaluate.
Provide charts to validate your conclusions. Use RMSE as your metric.
"""
def run_learner(leaf_size, bag_size):
r = eval_learner(data, bgl.BagLearner, learner=dtl.DTLearner,
bags=bag_size,
kwargs={'leaf_size': leaf_size})
return [r.rmse_in, r.rmse_out]
for i, bag_size in enumerate([5, 10, 15, 20]):
results = [[leaf_size] + run_learner(leaf_size, bag_size=bag_size)
for leaf_size in range(1, 10)]
cs = ["leaf_size", "rmse_in", "rmse_out"]
df = pd.DataFrame(results, columns=cs)
df.plot(title=f"Bag of {bag_size} DT Learners RMSE over leaf size",
xlabel="leaf size", ylabel="RMSE",
x="leaf_size", y=["rmse_in", "rmse_out"], kind="line")
plt.savefig(f"figure_{i + 2}.png")
def experiment_3(data):
"""
Quantitatively compare "classic" decision trees (DTLearner) versus random
trees (RTLearner). In which ways is one method better than the other?
Provide at least two quantitative measures. Important, using two similar
measures that illustrate the same broader metric does not count as two.
(For example, do not use two measures for accuracy.) Note for this part of
the report you must conduct new experiments, don't use the results of the
experiments above for this(RMSE is not allowed as a new experiment).
"""
def run_learner(leaf_size):
r1 = eval_learner(data, dtl.DTLearner, leaf_size=leaf_size)
r2 = eval_learner (data, rtl.RTLearner, leaf_size=leaf_size)
return [r1.corr_in, r1.corr_out, r2.corr_in, r2.corr_out]
results = [[leaf_size] + run_learner(leaf_size)
for leaf_size in range(1, 10)]
cs = ["leaf_size", "DT_corr_in", "DT_corr_out",
"RT_corr_in", "RT_corr_out"]
df = pd.DataFrame(results, columns=cs)
df.plot(title=f"Correlations of DT and RT for training data",
xlabel="leaf size", ylabel="Correlation",
x="leaf_size", y=["DT_corr_in", "RT_corr_in"],
kind="line")
plt.savefig(f"figure_6.png")
df.plot(title=f"Correlations of DT and RT for test data",
xlabel="leaf size", ylabel="Correlation",
x="leaf_size", y=["DT_corr_out", "RT_corr_out"],
kind="line")
plt.savefig(f"figure_7.png")
def main():
if len(sys.argv) != 2:
print("Usage: python testlearner.py <filename>")
sys.exit(1)
@@ -48,38 +190,14 @@ if __name__=="__main__":
trainY = data[:train_rows,-1]
testX = data[train_rows:,0:-1]
testY = data[train_rows:,-1]
print(f"{testX.shape}")
print(f"{testY.shape}")
data = (trainX, trainY, testX, testY)
def test_learner(learner_class, **kwargs):
print("\n-----------")
print(f"name={learner_class.__name__} {kwargs=}")
learner = learner_class(**kwargs)
learner.addEvidence(trainX, trainY)
print(learner.author())
# test_learners(data)
experiment_1(data)
experiment_2(data)
experiment_3(data)
# evaluate in sample
predY = learner.query(trainX) # get the predictions
rmse = math.sqrt(((trainY - predY) ** 2).sum()/trainY.shape[0])
print()
print("In sample results")
print(f"RMSE: {rmse}")
c = np.corrcoef(predY, y=trainY)
print(f"corr: {c[0,1]}")
# evaluate out of sample
predY = learner.query(testX) # get the predictions
rmse = math.sqrt(((testY - predY) ** 2).sum()/testY.shape[0])
print()
print("Out of sample results")
print(f"RMSE: {rmse}")
c = np.corrcoef(predY, y=testY)
print(f"corr: {c[0,1]}")
print()
# test_learner(lrl.LinRegLearner)
test_learner(dtl.DTLearner, leaf_size=1)
# test_learner(rtl.RTLearner, leaf_size=6)
test_learner(bgl.BagLearner, learner=dtl.DTLearner, bags=20, kwargs = {'leaf_size': 5})
test_learner(isl.InsaneLearner)
if __name__=="__main__":
main()

View File

@@ -0,0 +1,77 @@
import numpy as np
class AbstractTreeLearner:
LEAF = -1
NA = -1
def author(self):
return 'felixm' # replace tb34 with your Georgia Tech username
def create_node(self, factor, split_value, left, right):
return np.array([(factor, split_value, left, right), ],
dtype='|i4, f4, i4, i4')
def query_point(self, point):
node_index = 0
while self.rel_tree[node_index][0] != self.LEAF:
node = self.rel_tree[node_index]
split_factor = node[0]
split_value = node[1]
if point[split_factor] <= split_value:
# Recurse into left sub-tree.
node_index += node[2]
else:
node_index += node[3]
v = self.rel_tree[node_index][1]
return v
def query(self, points):
"""
@summary: Estimate a set of test points given the model we built.
@param points: should be a numpy array with each row corresponding to a specific query.
@returns the estimated values according to the saved model.
"""
query_point = lambda p: self.query_point(p)
r = np.apply_along_axis(query_point, 1, points)
return r
def build_tree(self, xs, y):
"""
@summary: Build a decision tree from the training data.
@param dataX: X values of data to add
@param dataY: the Y training values
"""
assert(xs.shape[0] == y.shape[0])
assert(xs.shape[0] > 0) # If this is 0 something went wrong.
if xs.shape[0] <= self.leaf_size:
value = np.mean(y)
if value < -0.2:
value = -1
elif value > 0.2:
value = 1
else:
value = 0
return self.create_node(self.LEAF, value, self.NA, self.NA)
if np.all(y[0] == y):
return self.create_node(self.LEAF, y[0], self.NA, self.NA)
i, split_value = self.get_i_and_split_value(xs, y)
select_l = xs[:, i] <= split_value
select_r = xs[:, i] > split_value
lt = self.build_tree(xs[select_l], y[select_l])
rt = self.build_tree(xs[select_r], y[select_r])
root = self.create_node(i, split_value, 1, lt.shape[0] + 1)
root = np.concatenate([root, lt, rt])
return root
def addEvidence(self, data_x, data_y):
"""
@summary: Add training data to learner
@param dataX: X values of data to add
@param dataY: the Y training values
"""
self.rel_tree = self.build_tree(data_x, data_y)

View File

@@ -0,0 +1,36 @@
import pandas as pd
import util as ut
import datetime as dt
class BenchmarkStrategy:
def __init__(self, verbose=False, impact=0.0, commission=0.0, units=1000):
self.verbose = verbose
self.impact = impact
self.commission = commission
self.units = units
def addEvidence(self, symbol=0, sd=0, ed=0, sv=0):
"""Keep this so that API is valid."""
pass
def testPolicy(self, symbol="IBM",
sd=dt.datetime(2009, 1, 1),
ed=dt.datetime(2010, 1, 1),
sv=10000):
"""Benchmark is to buy 1000 shares and hold."""
dates = pd.date_range(sd, ed)
prices = ut.get_data([symbol], dates, addSPY=False,
colname='close', datecol='time')
orders = pd.DataFrame(index=prices.index)
orders["Symbol"] = symbol
orders["Order"] = ""
orders["Shares"] = 0
orders.iloc[0] = [symbol, "BUY", self.units]
orders.iloc[-1] = [symbol, "SELL", -self.units]
if self.verbose:
print(type(orders)) # it better be a DataFrame!
print(orders)
return orders

View File

@@ -0,0 +1,114 @@
import datetime as dt
import pandas as pd
import util
import indicators
class ManualStrategy:
def __init__(self, verbose=False, impact=0.0, commission=0.0):
self.verbose = verbose
self.impact = impact
self.commission = commission
# this method should create a QLearner, and train it for trading
def addEvidence(self, symbol="IBM",
sd=dt.datetime(2008, 1, 1),
ed=dt.datetime(2009, 1, 1),
sv=10000):
# add your code to do learning here
# example usage of the old backward compatible util function
syms = [symbol]
dates = pd.date_range(sd, ed)
prices_all = util.get_data(syms, dates) # automatically adds SPY
prices = prices_all[syms] # only portfolio symbols
# prices_SPY = prices_all['SPY'] # only SPY, for comparison later
if self.verbose:
print(prices)
# example use with new colname
# automatically adds SPY
volume_all = util.get_data(syms, dates, colname="Volume")
volume = volume_all[syms] # only portfolio symbols
# volume_SPY = volume_all['SPY'] # only SPY, for comparison later
if self.verbose:
print(volume)
def macd_strat(self, macd, orders):
"""Strategy based on MACD cross."""
def strat(ser):
m = macd.loc[ser.index]
prev_macd, prev_signal, _ = m.iloc[0]
cur_macd, cur_signal, _ = m.iloc[1]
shares = 0
if cur_macd < -1 and prev_macd < prev_signal \
and cur_macd > cur_signal:
if self.holding == 0:
shares = 1000
elif self.holding == -1000:
shares = 2000
elif cur_macd > 1 and prev_macd > prev_signal \
and cur_macd < cur_signal:
if self.holding == 0:
shares = -1000
elif self.holding == 1000:
shares = -2000
self.holding += shares
return shares
orders['Shares'] = orders['Shares'].rolling(2).apply(strat)
def three_indicator_strat(self, macd, rsi, price_sma, orders):
"""Strategy based on three indicators. Thresholds selected based on
scatter plots."""
def strat(row):
shares = 0
_, _, macd_diff = macd.loc[row.name]
cur_rsi = rsi.loc[row.name][0]
cur_price_sma = price_sma.loc[row.name][0]
if self.holding == -1000 and cur_price_sma < 0.9:
shares = 2000
elif self.holding == 0 and cur_price_sma < 0.9:
shares = 1000
elif self.holding == -1000 and cur_rsi > 80:
shares = 2000
elif self.holding == 0 and cur_rsi > 80:
shares = 1000
elif self.holding == -1000 and macd_diff < -0.5:
shares = 2000
elif self.holding == 0 and macd_diff < -0.5:
shares = 1000
elif self.holding == 1000 and cur_price_sma > 1.1:
shares = -2000
elif self.holding == 0 and cur_price_sma > 1.1:
shares = -1000
self.holding += shares
return shares
orders['Shares'] = orders.apply(strat, axis=1)
def testPolicy(self, symbol="IBM",
sd=dt.datetime(2009, 1, 1),
ed=dt.datetime(2010, 1, 1),
sv=10000, macd_strat=False):
self.holding = 0
df = util.get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
orders = pd.DataFrame(index=df.index)
orders["Symbol"] = symbol
orders["Order"] = ""
orders["Shares"] = 0
macd = indicators.macd(df, symbol)
rsi = indicators.rsi(df, symbol)
price_sma = indicators.price_sma(df, symbol, [8])
if macd_strat:
self.macd_strat(macd, orders)
else:
self.three_indicator_strat(macd, rsi, price_sma, orders)
return orders

170
crypto_eval/QLearner.py Normal file
View File

@@ -0,0 +1,170 @@
import datetime as dt
import pandas as pd
import util
import indicators
from qlearning_robot.QLearner import QLearner as Learner
from dataclasses import dataclass
@dataclass
class Holding:
cash: int
shares: int
equity: int
class QLearner(object):
def __init__(self, verbose=False, impact=0.0, units=1000, commission=0.0, testing=False, n_bins=5):
self.verbose = verbose
self.impact = impact
self.commission = commission
self.testing = testing # Decides which type of order df to return.
self.indicators = ['macd_diff', 'rsi', 'price_sma_8']
self.n_bins = n_bins
self.bins = {}
self.num_states = self.get_num_states()
self.num_actions = 3 # buy, sell, hold
self.learner = Learner(self.num_states, self.num_actions)
self.units = units
def row_to_state(self, holding, df_row):
"""Transforms a row into a state value."""
holding = (holding + self.units) // self.units
assert(holding in [0, 1, 2])
# For each indicator that goes into the state the interval becomes
# smaller based on how many bins the indicator has. The first
# 'indicator' is the information about how many shares we are currently
# holding. So for example, if I have 450 states then the intervall (aka
# remaining_states) is 150 because there are three values for holding:
# holding = 0 -> state = 0 * 150 = 0
# holding = 1 -> state = 1 * 150 = 150
# holding = 2 -> state = 2 * 150 = 300
remaining_states = self.num_states // 3
state = holding * remaining_states
for indicator in self.indicators:
value = df_row[indicator]
bin_n = self.indicator_value_to_bin(indicator, value)
remaining_states //= self.n_bins
state += bin_n * remaining_states
return state
def indicator_value_to_bin(self, indicator, value):
for i, upper_bound in enumerate(self.bins[indicator]):
if value < upper_bound:
return i
return i + 1
def add_indicators(self, df, symbol):
"""Add indicators for learning to DataFrame."""
for indicator in self.indicators:
if indicator == "macd_diff":
indicators.macd(df, symbol)
df.drop(columns=["macd", "macd_signal"], inplace=True)
elif indicator == "rsi":
indicators.rsi(df, symbol)
elif indicator.startswith("price_sma_"):
period = int(indicator.replace("price_sma_", ""))
indicators.price_sma(df, symbol, [period])
df.drop(columns=["SPY"], inplace=True)
df.dropna(inplace=True)
def bin_indicators(self, df):
"""Create bins for indicators."""
for indicator in self.indicators:
ser, bins = pd.qcut(df[indicator], self.n_bins, retbins=True)
self.bins[indicator] = bins[1:self.n_bins]
def get_num_states(self):
"""Return the total num of states."""
num_states = 3 # Three states holding (1000, 0, -1000)
for _ in self.indicators:
num_states *= self.n_bins
return num_states
def handle_order(self, action, holding, adj_closing_price):
shares = 0
if action == 0: # buy
if holding.shares == 0 or holding.shares == -self.units:
shares = self.units
elif action == 1: # sell
if holding.shares== 0 or holding.shares == self.units:
shares = -self.units
elif action == 2: # hold
shares = 0
cost = shares * adj_closing_price
if shares != 0:
# Charge commission and deduct impact penalty
holding.cash -= self.commission
holding.cash -= (self.impact * adj_closing_price * abs(shares))
holding.cash -= cost
holding.shares += shares
holding.equity = holding.cash + holding.shares * adj_closing_price
def get_reward(self, equity, new_equity):
if new_equity > equity:
return 1
return -1
def train(self, df, symbol, sv):
holding = Holding(sv, 0, sv)
row = df.iloc[0]
state = self.row_to_state(holding.shares, row)
action = self.learner.querysetstate(state)
adj_closing_price = row[symbol]
equity = holding.equity
self.handle_order(action, holding, adj_closing_price)
for index, row in df.iloc[1:].iterrows():
adj_closing_price = row[symbol]
new_equity = holding.cash + holding.shares * adj_closing_price
r = self.get_reward(equity, new_equity)
s_prime = self.row_to_state(holding.shares, row)
a = self.learner.query(s_prime, r)
equity = new_equity
self.handle_order(a, holding, adj_closing_price)
if self.verbose:
print(f"{holding=} {s_prime=} {r=} {a=}")
def addEvidence(self, symbol="IBM", sd=dt.datetime(2008, 1, 1), ed=dt.datetime(2009, 1, 1), sv=10000):
df = util.get_data([symbol], pd.date_range(sd, ed))
self.add_indicators(df, symbol)
self.bin_indicators(df)
for _ in range(15):
self.train(df, symbol, sv)
def testPolicy(self, symbol="IBM", sd=dt.datetime(2009, 1, 1), ed=dt.datetime(2010, 1, 1), sv=10000):
df = util.get_data([symbol], pd.date_range(sd, ed))
orders = pd.DataFrame(index=df.index)
orders["Symbol"] = symbol
orders["Order"] = ""
orders["Shares"] = 0
shares = orders["Shares"]
self.add_indicators(df, symbol)
holding = 0
for index, row in df.iterrows():
state = self.row_to_state(holding, row)
action = self.learner.querysetstate(state)
if action == 0: # buy
if holding == 0 or holding == -self.units:
holding += self.units
orders.loc[index, "Shares"] = self.units
elif action == 1: # sell
if holding == 0 or holding == self.units:
holding -= self.units
orders.loc[index, "Shares"] = -self.units
elif action == 2: # hold
pass
if self.testing:
return orders
else:
return orders[["Shares"]]

30
crypto_eval/RTLearner.py Normal file
View File

@@ -0,0 +1,30 @@
import numpy as np
from AbstractTreeLearner import AbstractTreeLearner
class RTLearner(AbstractTreeLearner):
def __init__(self, leaf_size = 1, verbose = False):
self.leaf_size = leaf_size
self.verbose = verbose
def get_i_and_split_value(self, xs, y):
"""
@summary: Pick a random i and split value.
Make sure that not all X are the same for i and also pick
different values to average the split_value from.
"""
i = np.random.randint(0, xs.shape[1])
while np.all(xs[0,i] == xs[:,i]):
i = np.random.randint(0, xs.shape[1])
# I don't know about the performance of this, but at least it
# terminates reliably. If the two elements are the same something is
# wrong.
a = np.array(list(set(xs[:, i])))
r1, r2 = np.random.choice(a, size = 2, replace = False)
assert(r1 != r2)
split_value = (r1 + r2) / 2.0
return i, split_value

View File

@@ -0,0 +1,106 @@
import datetime as dt
import pandas as pd
import util
import indicators
from RTLearner import RTLearner
class StrategyLearner(object):
def __init__(self, verbose=False, impact=0.0, commission=0.0, testing=False):
self.verbose = verbose
self.impact = impact
self.commission = commission
self.testing = testing
def _get_volume(self):
"""For reference."""
volume_all = ut.get_data(syms, dates, colname="Volume")
volume = volume_all[syms] # only portfolio symbols
# volume_SPY = volume_all['SPY'] # only SPY, for comparison later
if self.verbose:
print(volume)
def _add_indicators(self, df, symbol):
"""Add indicators for learning to DataFrame."""
df.drop(columns=["SPY"], inplace=True)
indicators.macd(df, symbol)
indicators.rsi(df, symbol)
indicators.price_sma(df, symbol, [8])
indicators.price_delta(df, symbol, 3)
df.dropna(inplace=True)
def addEvidence(self, symbol="IBM",
sd=dt.datetime(2008, 1, 1),
ed=dt.datetime(2009, 1, 1),
sv=10000):
self.y_threshold = 0.2
self.indicators = ['macd_diff', 'rsi', 'price_sma_8']
df = util.get_data([symbol], pd.date_range(sd, ed))
self._add_indicators(df, symbol)
def classify_y(row):
if row > self.y_threshold:
return 1
elif row < -self.y_threshold:
return -1
else:
pass
return 0
def set_y_threshold(pct):
if max(pct) < 0.2:
self.y_threshold = 0.02
self.learner = RTLearner(leaf_size = 5)
# self.learner = BagLearner(RTLearner, 3, {'leaf_size': 5})
data_x = df[self.indicators].to_numpy()
pct = df['pct_3']
# This is a hack to get a low enough buy/sell threshold for the
# cyclic the test 'ML4T-220' where the max pct_3 is 0.0268.
set_y_threshold(pct)
y = pct.apply(classify_y)
self.learner.addEvidence(data_x, y.to_numpy())
return y
def strat(self, data_y, orders):
self.holding = 0
def strat(row):
y = int(data_y.loc[row.name][0])
shares = 0
if self.holding == 0 and y == 1:
shares = 1000
elif self.holding == -1000 and y == 1:
shares = 2000
elif self.holding == 0 and y == -1:
shares = -1000
elif self.holding == 1000 and y == -1:
shares = -2000
self.holding += shares
return shares
orders["Shares"] = orders.apply(strat, axis=1)
def testPolicy(self, symbol="IBM",
sd=dt.datetime(2009, 1, 1),
ed=dt.datetime(2010, 1, 1),
sv=10000):
df = util.get_data([symbol], pd.date_range(sd, ed))
self._add_indicators(df, symbol)
data_x = df[self.indicators].to_numpy()
data_y = pd.DataFrame(index=df.index, data=self.learner.query(data_x))
orders = pd.DataFrame(index=df.index)
orders["Symbol"] = symbol
orders["Order"] = ""
orders["Shares"] = 0
self.strat(data_y, orders)
if self.testing:
return orders
else:
return orders[["Shares"]]

237
crypto_eval/experiment1.py Normal file
View File

@@ -0,0 +1,237 @@
import pandas as pd
import datetime as dt
import sys
import util
import indicators
import crypto_eval.marketsim as marketsim
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor
from BenchmarkStrategy import BenchmarkStrategy
from ManualStrategy import ManualStrategy
from StrategyLearner import StrategyLearner
from QLearner import QLearner
def plot_indicators(symbol, df):
fig, ax = plt.subplots(4, sharex=True)
price_sma = indicators.price_sma(df, symbol, [8])
bb = indicators.bollinger_band(df, symbol)
rsi = indicators.rsi(df, symbol)
macd = indicators.macd(df, symbol).copy()
df[[symbol]].plot(ax=ax[0])
bb.plot(ax=ax[0])
price_sma.plot(ax=ax[1])
macd.plot(ax=ax[2])
rsi.plot(ax=ax[3])
for a in ax.flat:
a.grid()
m = MultiCursor(fig.canvas, ax, color='r', lw=0.5)
plt.show()
sys.exit(0)
def visualize_correlations(symbol, df):
indicators.price_sma(df, symbol, [8, 21])
indicators.price_delta(df, symbol, 5)
indicators.price_delta(df, symbol, 3)
indicators.price_delta(df, symbol, 1)
indicators.macd(df, symbol)
indicators.rsi(df, symbol)
# df = df[df['rsi'] > 80]
fig, ax = plt.subplots(3, 2) # sharex=True)
df.plot.scatter(x="price_sma_8", y="pct_5", ax=ax[0, 0])
df.plot.scatter(x="price_sma_8", y="pct_3", ax=ax[1, 0])
df.plot.scatter(x="price_sma_8", y="pct_1", ax=ax[2, 0])
# df.plot.scatter(x="rsi", y="pct_5", ax=ax[0, 1])
# df.plot.scatter(x="rsi", y="pct_3", ax=ax[1, 1])
# df.plot.scatter(x="rsi", y="pct_1", ax=ax[2, 1])
df.plot.scatter(x="macd_diff", y="pct_5", ax=ax[0, 1])
df.plot.scatter(x="macd_diff", y="pct_3", ax=ax[1, 1])
df.plot.scatter(x="macd_diff", y="pct_1", ax=ax[2, 1])
for a in ax.flat:
a.grid()
plt.show()
sys.exit(0)
def compare_manual_strategies(symbol, sv, sd, ed):
df = util.get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
bs = BenchmarkStrategy()
orders = bs.testPolicy(symbol, sd, ed, sv)
df["Benchmark"] = marketsim.compute_portvals(orders, sv)
df["Orders Benchmark"] = orders["Shares"]
ms = ManualStrategy()
orders = ms.testPolicy(symbol, sd, ed, sv, macd_strat=True)
df["MACD Strat"] = marketsim.compute_portvals(orders, sv)
df["Orders MACD"] = orders["Shares"]
# df["Holding Manual"] = orders["Shares"].cumsum()
orders = ms.testPolicy(symbol, sd, ed, sv)
df["Three Strat"] = marketsim.compute_portvals(orders, sv)
df["Orders Three"] = orders["Shares"]
fig, ax = plt.subplots(3, sharex=True)
df[[symbol]].plot(ax=ax[0])
df[["Benchmark", "MACD Strat", "Three Strat"]].plot(ax=ax[1])
df[["Orders Benchmark", "Orders MACD", "Orders Three"]].plot(ax=ax[2])
for a in ax:
a.grid()
MultiCursor(fig.canvas, ax, color='r', lw=0.5)
# plt.show()
fig.set_size_inches(10, 8, forward=True)
plt.savefig('figure_1.png', dpi=fig.dpi)
def compare_all_strategies(symbol, sv, sd, ed):
df = util.get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
normalize = indicators.normalize
bs = BenchmarkStrategy()
orders = bs.testPolicy(symbol, sd, ed, sv)
df["Benchmark"] = normalize(marketsim.compute_portvals(orders, sv))
df["Orders Benchmark"] = orders["Shares"]
ms = ManualStrategy()
orders = ms.testPolicy(symbol, sd, ed, sv)
df["Manual"] = normalize(marketsim.compute_portvals(orders, sv))
df["Orders Manual"] = orders["Shares"]
sl = StrategyLearner(testing=True)
sl.addEvidence(symbol, sd, ed, sv)
orders = sl.testPolicy(symbol, sd, ed, sv)
df["Strategy"] = normalize(marketsim.compute_portvals(orders, sv))
df["Orders Strategy"] = orders["Shares"]
fig, ax = plt.subplots(3, sharex=True)
df[[symbol]].plot(ax=ax[0])
df[["Benchmark", "Manual", "Strategy"]].plot(ax=ax[1])
df[["Orders Benchmark", "Orders Manual", "Orders Strategy"]].plot(ax=ax[2])
for a in ax:
a.grid()
MultiCursor(fig.canvas, ax, color='r', lw=0.5)
# plt.show()
fig.set_size_inches(10, 8, forward=True)
plt.savefig('figure_2.png', dpi=fig.dpi)
def compare_number_trades():
symbol = "JPM"
sv = 10000
sd = dt.datetime(2008, 1, 1) # in-sample
ed = dt.datetime(2009, 12, 31) # in-sample
df = util.get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
print(f"| commission | n_orders |")
print(f"-------------------------")
for commission in [0, 9.95, 20, 50, 100]:
ql = QLearner(testing=True, commission=commission, impact=0.005)
ql.addEvidence(symbol, sd, ed, sv)
orders = ql.testPolicy(symbol, sd, ed, sv)
n_orders = orders[orders["Shares"] != 0].shape[0]
print(f"| {commission} | {n_orders} |")
def compare_q_learners():
symbol = "JPM"
sv = 10000
sd = dt.datetime(2008, 1, 1) # in-sample
ed = dt.datetime(2009, 12, 31) # in-sample
sd_out = dt.datetime(2010, 1, 1) # out-sample
ed_out = dt.datetime(2011, 12, 31) # out-sample
df = util.get_data([symbol], pd.date_range(sd_out, ed_out))
df.drop(columns=["SPY"], inplace=True)
bs = BenchmarkStrategy()
orders = bs.testPolicy(symbol, sd_out, ed_out, sv)
df["Benchmark"] = indicators.normalize(marketsim.compute_portvals(orders, sv))
df["Orders Benchmark"] = orders["Shares"]
ql = QLearner(testing=True, verbose=False)
ql.addEvidence(symbol, sd, ed, sv)
orders = ql.testPolicy(symbol, sd_out, ed_out, sv)
df["QL 5"] = indicators.normalize(marketsim.compute_portvals(orders, sv))
df["Orders QL 5"] = orders["Shares"]
ql = QLearner(testing=True, verbose=False, n_bins=4)
ql.addEvidence(symbol, sd, ed, sv)
orders = ql.testPolicy(symbol, sd_out, ed_out, sv)
df["QL 4"] = indicators.normalize(marketsim.compute_portvals(orders, sv))
df["Orders QL 4"] = orders["Shares"]
fig, ax = plt.subplots(3, sharex=True)
df[[symbol]].plot(ax=ax[0])
df[["Benchmark", "QL 5", "QL 4"]].plot(ax=ax[1])
df[["Orders Benchmark", "Orders QL 5", "Orders QL 4"]].plot(ax=ax[2])
for a in ax:
a.grid()
m = MultiCursor(fig.canvas, ax, color='r', lw=0.5)
fig.set_size_inches(10, 8, forward=True)
plt.savefig('figure_4.png', dpi=fig.dpi)
sys.exit(0)
def experiment1(create_report=False):
symbol = "COINBASE_BTCUSD_1D"
sv = 10000
sd = dt.datetime(2020, 1, 1) # in-sample
ed = dt.datetime(2020, 12, 31) # in-sample
sd_out = dt.datetime(2020, 1, 1) # out-sample
ed_out = dt.datetime(2020, 12, 31) # out-sample
df = util.get_data([symbol], pd.date_range(sd_out, ed_out), addSPY=True)
# if create_report:
# compare_manual_strategies(symbol, sv, sd, ed)
# compare_all_strategies(symbol, sv, sd, ed)
# sys.exit(0)
# visualize_correlations(symbol, df)
# plot_indicators(symbol, df)
# compare_number_trades(symbol, sv, sd, ed)
# compare_q_learners()
# return
bs = BenchmarkStrategy(units=1)
orders = bs.testPolicy(symbol, sd_out, ed_out, sv)
pvs = marketsim.compute_portvals(orders, start_val=sv)
df["Benchmark"] = indicators.normalize(pvs)
df["Orders Benchmark"] = orders["Shares"]
ql = QLearner(testing=True, verbose=False, units=1)
ql.addEvidence(symbol, sd, ed, sv)
orders = ql.testPolicy(symbol, sd_out, ed_out, sv)
df["QL"] = indicators.normalize(marketsim.compute_portvals(orders, sv))
df["Orders QL"] = orders["Shares"]
fig, ax = plt.subplots(3, sharex=True)
df[[symbol]].plot(ax=ax[0])
df[["Benchmark", "QL"]].plot(ax=ax[1])
df[["Orders Benchmark", "Orders QL"]].plot(ax=ax[2])
for a in ax:
a.grid()
m = MultiCursor(fig.canvas, ax, color='r', lw=0.5)
plt.show()
# fig.set_size_inches(10, 8, forward=True)
# plt.savefig('figure_4.png', dpi=fig.dpi)
if __name__ == "__main__":
experiment1()

View File

@@ -0,0 +1,8 @@
import experiment1
def experiment2():
experiment1.compare_number_trades()
if __name__ == "__main__":
experiment2()

View File

@@ -0,0 +1,339 @@
"""MC3-P3: Strategy Learner - grading script.
Usage:
- Switch to a student feedback directory first (will write "points.txt" and "comments.txt" in pwd).
- Run this script with both ml4t/ and student solution in PYTHONPATH, e.g.:
PYTHONPATH=ml4t:MC1-P2/jdoe7 python ml4t/mc2_p1_grading/grade_marketsim.py
Copyright 2017, Georgia Tech Research Corporation
Atlanta, Georgia 30332-0415
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: tb34 (replace with your User ID)
GT ID: 900897987 (replace with your GT ID)
"""
import pytest
from grading.grading import grader, GradeResult, run_with_timeout, IncorrectOutput
import os
import sys
import traceback as tb
import datetime as dt
import numpy as np
import pandas as pd
from collections import namedtuple
import time
import util
import random
# Test cases
StrategyTestCase = namedtuple('Strategy', ['description','insample_args','outsample_args','benchmark_type','benchmark','impact','train_time','test_time','max_time','seed'])
strategy_test_cases = [
StrategyTestCase(
description="ML4T-220",
insample_args=dict(symbol="ML4T-220",sd=dt.datetime(2008,1,1),ed=dt.datetime(2009,12,31),sv=100000),
outsample_args=dict(symbol="ML4T-220",sd=dt.datetime(2010,1,1),ed=dt.datetime(2011,12,31),sv=100000),
benchmark_type='clean',
benchmark=1.0, #benchmark updated Apr 24 2017
impact=0.0,
train_time=25,
test_time=5,
max_time=60,
seed=1481090000
),
StrategyTestCase(
description="AAPL",
insample_args=dict(symbol="AAPL",sd=dt.datetime(2008,1,1),ed=dt.datetime(2009,12,31),sv=100000),
outsample_args=dict(symbol="AAPL",sd=dt.datetime(2010,1,1),ed=dt.datetime(2011,12,31),sv=100000),
benchmark_type='stock',
benchmark=0.1581999999999999, #benchmark computed Nov 22 2017
impact=0.0,
train_time=25,
test_time=5,
max_time=60,
seed=1481090000
),
StrategyTestCase(
description="SINE_FAST_NOISE",
insample_args=dict(symbol="SINE_FAST_NOISE",sd=dt.datetime(2008,1,1),ed=dt.datetime(2009,12,31),sv=100000),
outsample_args=dict(symbol="SINE_FAST_NOISE",sd=dt.datetime(2010,1,1),ed=dt.datetime(2011,12,31),sv=100000),
benchmark_type='noisy',
benchmark=2.0, #benchmark updated Apr 24 2017
impact=0.0,
train_time=25,
test_time=5,
max_time=60,
seed=1481090000
),
StrategyTestCase(
description="UNH - In sample",
insample_args=dict(symbol="UNH",sd=dt.datetime(2008,1,1),ed=dt.datetime(2009,12,31),sv=100000),
outsample_args=dict(symbol="UNH",sd=dt.datetime(2010,1,1),ed=dt.datetime(2011,12,31),sv=100000),
benchmark_type='stock',
benchmark= -0.25239999999999996, #benchmark computed Nov 22 2017
impact=0.0,
train_time=25,
test_time=5,
max_time=60,
seed=1481090000
),
]
max_points = 60.0
html_pre_block = True # surround comments with HTML <pre> tag (for T-Square comments field)
MAX_HOLDINGS = 1000
# Test functon(s)
@pytest.mark.parametrize("description, insample_args, outsample_args, benchmark_type, benchmark, impact, train_time, test_time, max_time, seed", strategy_test_cases)
def test_strategy(description, insample_args, outsample_args, benchmark_type, benchmark, impact, train_time, test_time, max_time, seed, grader):
"""Test StrategyLearner.
Requires test description, insample args (dict), outsample args (dict), benchmark_type (str), benchmark (float)
max time (seconds), points for this test case (int), random seed (long), and a grader fixture.
"""
points_earned = 0.0 # initialize points for this test case
try:
incorrect = True
if not 'StrategyLearner' in globals():
import importlib
m = importlib.import_module('StrategyLearner')
globals()['StrategyLearner'] = m
outsample_cr_to_beat = None
if benchmark_type == 'clean':
outsample_cr_to_beat = benchmark
def timeoutwrapper_strategylearner():
#Set fixed seed for repetability
np.random.seed(seed)
random.seed(seed)
learner = StrategyLearner.StrategyLearner(verbose=False,impact=impact)
tmp = time.time()
learner.addEvidence(**insample_args)
train_t = time.time()-tmp
tmp = time.time()
insample_trades_1 = learner.testPolicy(**insample_args)
test_t = time.time()-tmp
insample_trades_2 = learner.testPolicy(**insample_args)
tmp = time.time()
outsample_trades = learner.testPolicy(**outsample_args)
out_test_t = time.time()-tmp
return insample_trades_1, insample_trades_2, outsample_trades, train_t, test_t, out_test_t
msgs = []
in_trades_1, in_trades_2, out_trades, train_t, test_t, out_test_t = run_with_timeout(timeoutwrapper_strategylearner,max_time,(),{})
incorrect = False
if len(in_trades_1.shape)!=2 or in_trades_1.shape[1]!=1:
incorrect=True
msgs.append(" First insample trades DF has invalid shape: {}".format(in_trades_1.shape))
elif len(in_trades_2.shape)!=2 or in_trades_2.shape[1]!=1:
incorrect=True
msgs.append(" Second insample trades DF has invalid shape: {}".format(in_trades_2.shape))
elif len(out_trades.shape)!=2 or out_trades.shape[1]!=1:
incorrect=True
msgs.append(" Out-of-sample trades DF has invalid shape: {}".format(out_trades.shape))
else:
tmp_csum=0.0
for date,trade in in_trades_1.iterrows():
tmp_csum+= trade.iloc[0]
if (trade.iloc[0]!=0) and\
(trade.abs().iloc[0]!=MAX_HOLDINGS) and\
(trade.abs().iloc[0]!=2*MAX_HOLDINGS):
incorrect=True
msgs.append(" illegal trade in first insample DF. abs(trade) not one of ({},{},{}).\n Date {}, Trade {}".format(0,MAX_HOLDINGS,2*MAX_HOLDINGS,date,trade))
break
elif abs(tmp_csum)>MAX_HOLDINGS:
incorrect=True
msgs.append(" holdings more than {} long or short in first insample DF. Date {}, Trade {}".format(MAX_HOLDINGS,date,trade))
break
tmp_csum=0.0
for date,trade in in_trades_2.iterrows():
tmp_csum+= trade.iloc[0]
if (trade.iloc[0]!=0) and\
(trade.abs().iloc[0]!=MAX_HOLDINGS) and\
(trade.abs().iloc[0]!=2*MAX_HOLDINGS):
incorrect=True
msgs.append(" illegal trade in second insample DF. abs(trade) not one of ({},{},{}).\n Date {}, Trade {}".format(0,MAX_HOLDINGS,2*MAX_HOLDINGS,date,trade))
break
elif abs(tmp_csum)>MAX_HOLDINGS:
incorrect=True
msgs.append(" holdings more than {} long or short in second insample DF. Date {}, Trade {}".format(MAX_HOLDINGS,date,trade))
break
tmp_csum=0.0
for date,trade in out_trades.iterrows():
tmp_csum+= trade.iloc[0]
if (trade.iloc[0]!=0) and\
(trade.abs().iloc[0]!=MAX_HOLDINGS) and\
(trade.abs().iloc[0]!=2*MAX_HOLDINGS):
incorrect=True
msgs.append(" illegal trade in out-of-sample DF. abs(trade) not one of ({},{},{}).\n Date {}, Trade {}".format(0,MAX_HOLDINGS,2*MAX_HOLDINGS,date,trade))
break
elif abs(tmp_csum)>MAX_HOLDINGS:
incorrect=True
msgs.append(" holdings more than {} long or short in out-of-sample DF. Date {}, Trade {}".format(MAX_HOLDINGS,date,trade))
break
# if (((in_trades_1.abs()!=0) & (in_trades_1.abs()!=MAX_HOLDINGS) & (in_trades_1.abs()!=2*MAX_HOLDINGS)).any().any() or\
# ((in_trades_2.abs()!=0) & (in_trades_2.abs()!=MAX_HOLDINGS) & (in_trades_2.abs()!=2*MAX_HOLDINGS)).any().any() or\
# ((out_trades.abs()!=0) & (out_trades.abs()!=MAX_HOLDINGS) & (out_trades.abs()!=2*MAX_HOLDINGS)).any().any()):
# incorrect = True
# msgs.append(" illegal trade. abs(trades) not one of ({},{},{})".format(0,MAX_HOLDINGS,2*MAX_HOLDINGS))
# if ((in_trades_1.cumsum().abs()>MAX_HOLDINGS).any()[0]) or ((in_trades_2.cumsum().abs()>MAX_HOLDINGS).any()[0]) or ((out_trades.cumsum().abs()>MAX_HOLDINGS).any()[0]):
# incorrect = True
# msgs.append(" holdings more than {} long or short".format(MAX_HOLDINGS))
if not(incorrect):
if train_t>train_time:
incorrect=True
msgs.append(" addEvidence() took {} seconds, max allowed {}".format(train_t,train_time))
else:
points_earned += 1.0
if test_t > test_time:
incorrect = True
msgs.append(" testPolicy() took {} seconds, max allowed {}".format(test_t,test_time))
else:
points_earned += 2.0
if not((in_trades_1 == in_trades_2).all()[0]):
incorrect = True
mismatches = in_trades_1.join(in_trades_2,how='outer',lsuffix='1',rsuffix='2')
mismatches = mismatches[mismatches.iloc[:,0]!=mismatches.iloc[:,1]]
msgs.append(" consecutive calls to testPolicy() with same input did not produce same output:")
msgs.append(" Mismatched trades:\n {}".format(mismatches))
else:
points_earned += 2.0
student_insample_cr = evalPolicy2(insample_args['symbol'],in_trades_1,insample_args['sv'],insample_args['sd'],insample_args['ed'],market_impact=impact,commission_cost=0.0)
student_outsample_cr = evalPolicy2(outsample_args['symbol'],out_trades, outsample_args['sv'],outsample_args['sd'],outsample_args['ed'],market_impact=impact,commission_cost=0.0)
if student_insample_cr <= benchmark:
incorrect = True
msgs.append(" in-sample return ({}) did not beat benchmark ({})".format(student_insample_cr,benchmark))
else:
points_earned += 5.0
if outsample_cr_to_beat is None:
if out_test_t > test_time:
incorrect = True
msgs.append(" out-sample took {} seconds, max of {}".format(out_test_t,test_time))
else:
points_earned += 5.0
else:
if student_outsample_cr < outsample_cr_to_beat:
incorrect = True
msgs.append(" out-sample return ({}) did not beat benchmark ({})".format(student_outsample_cr,outsample_cr_to_beat))
else:
points_earned += 5.0
if incorrect:
inputs_str = " insample_args: {}\n" \
" outsample_args: {}\n" \
" benchmark_type: {}\n" \
" benchmark: {}\n" \
" train_time: {}\n" \
" test_time: {}\n" \
" max_time: {}\n" \
" seed: {}\n".format(insample_args, outsample_args, benchmark_type, benchmark, train_time, test_time, max_time,seed)
raise IncorrectOutput("Test failed on one or more output criteria.\n Inputs:\n{}\n Failures:\n{}".format(inputs_str, "\n".join(msgs)))
except Exception as e:
# Test result: failed
msg = "Test case description: {}\n".format(description)
# Generate a filtered stacktrace, only showing erroneous lines in student file(s)
tb_list = tb.extract_tb(sys.exc_info()[2])
for i in range(len(tb_list)):
row = tb_list[i]
tb_list[i] = (os.path.basename(row[0]), row[1], row[2], row[3]) # show only filename instead of long absolute path
# tb_list = [row for row in tb_list if row[0] in ['QLearner.py','StrategyLearner.py']]
if tb_list:
msg += "Traceback:\n"
msg += ''.join(tb.format_list(tb_list)) # contains newlines
elif 'grading_traceback' in dir(e):
msg += "Traceback:\n"
msg += ''.join(tb.format_list(e.grading_traceback))
msg += "{}: {}".format(e.__class__.__name__, str(e))
# Report failure result to grader, with stacktrace
grader.add_result(GradeResult(outcome='failed', points=points_earned, msg=msg))
raise
else:
# Test result: passed (no exceptions)
grader.add_result(GradeResult(outcome='passed', points=points_earned, msg=None))
def compute_benchmark(sd,ed,sv,symbol,market_impact,commission_cost,max_holdings):
date_idx = util.get_data([symbol,],pd.date_range(sd,ed)).index
orders = pd.DataFrame(index=date_idx)
orders['orders'] = 0; orders['orders'][0] = max_holdings; orders['orders'][-1] = -max_holdings
return evalPolicy2(symbol,orders,sv,sd,ed,market_impact,commission_cost)
def evalPolicy(student_trades,sym_prices,startval):
ending_cash = startval - student_trades.mul(sym_prices,axis=0).sum()
ending_stocks = student_trades.sum()*sym_prices.iloc[-1]
return float((ending_cash+ending_stocks)/startval)-1.0
def evalPolicy2(symbol, student_trades, startval, sd, ed, market_impact,commission_cost):
orders_df = pd.DataFrame(columns=['Shares','Order','Symbol'])
for row_idx in student_trades.index:
nshares = student_trades.loc[row_idx][0]
if nshares == 0:
continue
order = 'sell' if nshares < 0 else 'buy'
new_row = pd.DataFrame([[abs(nshares),order,symbol],],columns=['Shares','Order','Symbol'],index=[row_idx,])
orders_df = orders_df.append(new_row)
portvals = compute_portvals(orders_df, sd, ed, startval,market_impact,commission_cost)
return float(portvals[-1]/portvals[0])-1
def compute_portvals(orders_df, start_date, end_date, startval, market_impact=0.0, commission_cost=0.0):
"""Simulate the market for the given date range and orders file."""
symbols = []
orders = []
orders_df = orders_df.sort_index()
for date, order in orders_df.iterrows():
shares = order['Shares']
action = order['Order']
symbol = order['Symbol']
if action.lower() == 'sell':
shares *= -1
order = (date, symbol, shares)
orders.append(order)
symbols.append(symbol)
symbols = list(set(symbols))
dates = pd.date_range(start_date, end_date)
prices_all = util.get_data(symbols, dates)
prices = prices_all[symbols]
prices = prices.fillna(method='ffill').fillna(method='bfill')
prices['_CASH'] = 1.0
trades = pd.DataFrame(index=prices.index, columns=symbols)
trades = trades.fillna(0)
cash = pd.Series(index=prices.index)
cash = cash.fillna(0)
cash.iloc[0] = startval
for date, symbol, shares in orders:
price = prices[symbol][date]
val = shares * price
# transaction cost model
val += commission_cost + (pd.np.abs(shares)*price*market_impact)
positions = prices.loc[date] * trades.sum()
totalcash = cash.sum()
if (date < prices.index.min()) or (date > prices.index.max()):
continue
trades[symbol][date] += shares
cash[date] -= val
trades['_CASH'] = cash
holdings = trades.cumsum()
df_portvals = (prices * holdings).sum(axis=1)
return df_portvals
if __name__ == "__main__":
pytest.main(["-s", __file__])

140
crypto_eval/indicators.py Normal file
View File

@@ -0,0 +1,140 @@
import pandas as pd
import datetime as dt
import matplotlib.pyplot as plt
from util import get_data
def author():
return "felixm"
def normalize(timeseries):
return timeseries / timeseries.iloc[0]
def bollinger_band(df, symbol, period=20, m=2):
boll_sma = df[symbol].rolling(period).mean()
std = df[symbol].rolling(period).std()
boll_up = boll_sma + m * std
boll_lo = boll_sma - m * std
key_sma, key_up, key_lo = "boll_sma", "boll_up", "boll_lo"
df[key_sma] = boll_sma
df[key_up] = boll_up
df[key_lo] = boll_lo
return df[[key_sma, key_up, key_lo]]
def sma(df, symbol, period):
"""Adds SMA for one or multiple periods to df and returns SMAs"""
if type(period) is int:
period = [period]
keys = []
for p in period:
key = f"sma_{p}"
df[key] = df[symbol].rolling(p).mean()
keys.append(key)
return df[keys]
def ema(df, symbol, period):
"""Adds EMA for one or multiple periods to df and returns EMAs"""
if type(period) is int:
period = [period]
keys = []
for p in period:
key = f"ema_{p}"
df[key] = df[symbol].ewm(span=p).mean()
keys.append(key)
return df[keys]
def price_sma(df, symbol, period):
"""Calculates SMA and adds new column price divided by SMA to the df."""
if type(period) is int:
period = [period]
keys = []
for p in period:
key = f"price_sma_{p}"
sma = df[symbol].rolling(p).mean()
df[key] = df[symbol] / sma
keys.append(key)
return df[keys]
def rsi(df, symbol, period=14):
"""Calculates relative strength index over given period."""
def rsi(x):
pct = x.pct_change()
avg_gain = pct[pct > 0].mean()
avg_loss = pct[pct <= 0].abs().mean()
rsi = 100 - (100 /
(1 + ((avg_gain / period) /
(avg_loss / period))))
return rsi
key = "rsi"
# Add one to get 'period' price changes (first change is nan).
period += 1
df[key] = df[symbol].rolling(period).apply(rsi)
return df[[key]]
def macd(df, symbol):
macd = df[symbol].ewm(span=12).mean() - df[symbol].ewm(span=26).mean()
k1 = "macd"
k2 = "macd_signal"
k3 = "macd_diff"
df[k1] = macd
df[k2] = macd.rolling(9).mean()
df[k3] = df[k1] - df[k2]
return df[[k1, k2, k3]]
def price_delta(df, symbol, period=1):
"""Calculate percentage change for period."""
k = f"pct_{period}"
df[k] = df[symbol].pct_change(periods=period)
df[k] = df[k].shift(-period)
return df[k]
def test_indicators():
symbol = "JPM"
sd = dt.datetime(2008, 1, 1)
ed = dt.datetime(2009, 12, 31)
df = get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
df_orig = df.copy()
# df = normalize(df)
sma(df, symbol, 21)
ema(df, symbol, 21)
df.plot(title="21 SMA and EMA")
plt.savefig('figure_1.png')
df = df_orig.copy()
sma(df, symbol, 8)
price_sma(df, symbol, 8)
df.plot(title="SMA and price / SMA", subplots=True)
plt.savefig('figure_2.png')
df = df_orig.copy()
bollinger_band(df, symbol)
df.plot(title="Bollinger Band")
plt.savefig('figure_3.png')
df = df_orig.copy()
rsi(df, symbol)
fig, axes = plt.subplots(nrows=2, sharex=True)
df[symbol].plot(ax=axes[0], title="JPM price action")
df["JPM-rsi(14)"].plot(ax=axes[1], title="RSI")
plt.savefig('figure_4.png')
df = df_orig.copy()
macd(df, symbol)
fig, axes = plt.subplots(nrows=2, sharex=True)
df[symbol].plot(ax=axes[0], title="JPM price action")
df[["JPM-macd", "JPM-macd-signal"]].plot(ax=axes[1])
plt.savefig('figure_5.png')

179
crypto_eval/marketsim.py Normal file
View File

@@ -0,0 +1,179 @@
"""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()

View File

@@ -0,0 +1,8 @@
from experiment1 import experiment1
from experiment2 import experiment2
if __name__ == "__main__":
experiment1(create_report=True)
experiment2()

View File

@@ -0,0 +1,108 @@
import numpy as np
class DTLearner:
LEAF = -1
NA = -1
def __init__(self, leaf_size=1, verbose=False):
self.leaf_size = leaf_size
self.verbose = verbose
def author(self):
return 'felixm' # replace tb34 with your Georgia Tech username
def create_node(self, factor, split_value, left, right):
return np.array([(factor, split_value, left, right), ],
dtype='|i4, f4, i4, i4')
def query_point(self, point):
node_index = 0
while self.rel_tree[node_index][0] != self.LEAF:
node = self.rel_tree[node_index]
split_factor = node[0]
split_value = node[1]
if point[split_factor] <= split_value:
# Recurse into left sub-tree.
node_index += node[2]
else:
node_index += node[3]
v = self.rel_tree[node_index][1]
return v
def query(self, points):
"""
@summary: Estimate a set of test points given the model we built.
@param points: should be a numpy array with each row corresponding to a specific query.
@returns the estimated values according to the saved model.
"""
def query_point(p): return self.query_point(p)
r = np.apply_along_axis(query_point, 1, points)
return r
def build_tree(self, xs, y):
"""
@summary: Build a decision tree from the training data.
@param dataX: X values of data to add
@param dataY: the Y training values
"""
assert(xs.shape[0] == y.shape[0])
assert(xs.shape[0] > 0) # If this is 0 something went wrong.
if xs.shape[0] <= self.leaf_size:
value = np.mean(y)
return self.create_node(self.LEAF, value, self.NA, self.NA)
if np.all(y[0] == y):
return self.create_node(self.LEAF, y[0], self.NA, self.NA)
i, split_value = self.get_i_and_split_value(xs, y)
select_l = xs[:, i] <= split_value
select_r = xs[:, i] > split_value
lt = self.build_tree(xs[select_l], y[select_l])
rt = self.build_tree(xs[select_r], y[select_r])
root = self.create_node(i, split_value, 1, lt.shape[0] + 1)
root = np.concatenate([root, lt, rt])
return root
def addEvidence(self, data_x, data_y):
"""
@summary: Add training data to learner
@param dataX: X values of data to add
@param dataY: the Y training values
"""
self.rel_tree = self.build_tree(data_x, data_y)
def get_correlations(self, xs, y):
""" Return a list of sorted 2-tuples where the first element
is the correlation and the second element is the index. Sorted by
highest correlation first. """
# a = np.argmax([abs(np.corrcoef(xs[:,i], y)[0, 1])
# for i in range(xs.shape[1])])
correlations = []
for i in range(xs.shape[1]):
c = abs(np.corrcoef(xs[:, i], y=y)[0, 1])
correlations.append((c, i))
correlations.sort(reverse=True)
return correlations
def get_i_and_split_value(self, xs, y):
# If all elements are true we would get one sub-tree with zero
# elements, but we need at least one element in both trees. We avoid
# zero-trees in two steps. First we take the average between the median
# value and a smaller value an use that as the new split value. If that
# doesn't work (when all values are the same) we choose the X with the
# next smaller correlation. We assert that not all values are
# smaller/equal to the split value at the end.
for _, i in self.get_correlations(xs, y):
split_value = np.median(xs[:, i])
select = xs[:, i] <= split_value
if select.all():
for value in xs[:, i]:
if value < split_value:
split_value = (value + split_value) / 2.0
select = xs[:, i] <= split_value
if not select.all():
break
assert(not select.all())
return i, split_value

View File

@@ -0,0 +1,57 @@
"""
A simple wrapper for linear regression. (c) 2015 Tucker Balch
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---
"""
import numpy as np
class LinRegLearner(object):
def __init__(self, verbose = False):
pass # move along, these aren't the drones you're looking for
def author(self):
return 'tb34' # replace tb34 with your Georgia Tech username
def addEvidence(self,dataX,dataY):
"""
@summary: Add training data to learner
@param dataX: X values of data to add
@param dataY: the Y training values
"""
# slap on 1s column so linear regression finds a constant term
newdataX = np.ones([dataX.shape[0],dataX.shape[1]+1])
newdataX[:,0:dataX.shape[1]]=dataX
# build and save the model
self.model_coefs, residuals, rank, s = np.linalg.lstsq(newdataX, dataY, rcond=None)
def query(self,points):
"""
@summary: Estimate a set of test points given the model we built.
@param points: should be a numpy array with each row corresponding to a specific query.
@returns the estimated values according to the saved model.
"""
return (self.model_coefs[:-1] * points).sum(axis = 1) + self.model_coefs[-1]
if __name__=="__main__":
print("the secret clue is 'zzyzx'")

View File

@@ -0,0 +1,68 @@
"""
template for generating data to fool learners (c) 2016 Tucker Balch
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: tb34 (replace with your User ID)
GT ID: 900897987 (replace with your GT ID)
"""
import numpy as np
import pandas as pd
import math
def best4LinReg(seed=1489683273):
"""
This function should return a dataset (X and Y) that will work better for
linear regression than decision trees.
We make Y a simple linear combination of X. That will give the Linear
Regression algorithm a very easy time (no RMSE at all) and beat the DT
easily.
"""
np.random.seed(seed)
X = np.random.random(size=(100, 2)) * 200 - 100
Y = X[:, 0] * -2 + X[:, 1] * 3
return X, Y
def best4DT(seed=1489683273):
"""
This function should return a dataset that will work better for decision
trees than linear regression.
Decision trees are better for categorizing discrete data. So if we set the
output values to integers that should help. Additionally, the smaller the
dataset the harder for the LR to create a nice curve.
"""
np.random.seed(seed)
X = np.random.random(size=(10, 10)) * 200 - 100
Y = np.asarray([i for i in range(0, 10)])
return X, Y
def author():
return 'felixm' # Change this to your user ID
if __name__ == "__main__":
print("they call me Tim.")

View File

@@ -0,0 +1,230 @@
"""MC3-H1: Best4{LR,DT} - grading script.
Usage:
- Switch to a student feedback directory first (will write "points.txt" and "comments.txt" in pwd).
- Run this script with both ml4t/ and student solution in PYTHONPATH, e.g.:
PYTHONPATH=ml4t:MC3-P1/jdoe7 python ml4t/mc3_p1_grading/grade_learners.py
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---
"""
import pytest
from grading.grading import grader, GradeResult, time_limit, run_with_timeout, IncorrectOutput
# These two lines will be commented out in the final grading script.
from LinRegLearner import LinRegLearner
from DTLearner import DTLearner
import os
import sys
import traceback as tb
import numpy as np
import pandas as pd
from collections import namedtuple
import math
import time
import functools
seconds_per_test_case = 5
max_points = 100.0
html_pre_block = True # surround comments with HTML <pre> tag (for T-Square comments field)
# Test cases
Best4TestCase = namedtuple('Best4TestCase', ['description', 'group','max_tests','needed_wins','row_limits','col_limits','seed'])
best4_test_cases = [
Best4TestCase(
description="Test Case 1: Best4LinReg",
group="best4lr",
max_tests=15,
needed_wins=10,
row_limits=(10,1000),
col_limits=(2,10),
seed=1489683274
),
Best4TestCase(
description="Test Case 2: Best4DT",
group="best4dt",
max_tests=15,
needed_wins=10,
row_limits=(10,1000),
col_limits=(2,10),
seed=1489683274
),
Best4TestCase(
description='Test for author() method',
group='author',
max_tests=None,
needed_wins=None,
row_limits=None,
col_limits=None,
seed=None,
),
]
# Test functon(s)
@pytest.mark.parametrize("description,group,max_tests,needed_wins,row_limits,col_limits,seed", best4_test_cases)
def test_learners(description, group, max_tests, needed_wins, row_limits, col_limits, seed, grader):
"""Test data generation methods beat given learner.
Requires test description, test case group, and a grader fixture.
"""
points_earned = 0.0 # initialize points for this test case
incorrect = True
msgs = []
try:
dataX, dataY = None,None
same_dataX, same_dataY = None,None
diff_dataX, diff_dataY = None,None
betterLearner, worseLearner = None, None
if group=='author':
try:
from gen_data import author
auth_string = run_with_timeout(author,seconds_per_test_case,(),{})
if auth_string == 'tb34':
incorrect = True
msgs.append(" Incorrect author name (tb34)")
points_earned = -10
elif auth_string == '':
incorrect = True
msgs.append(" Empty author name")
points_earned = -10
else:
incorrect = False
except Exception as e:
incorrect = True
msgs.append(" Exception occured when calling author() method: {}".format(e))
points_earned = -10
else:
if group=="best4dt":
from gen_data import best4DT
dataX, dataY = run_with_timeout(best4DT,seconds_per_test_case,(),{'seed':seed})
same_dataX,same_dataY = run_with_timeout(best4DT,seconds_per_test_case,(),{'seed':seed})
diff_dataX,diff_dataY = run_with_timeout(best4DT,seconds_per_test_case,(),{'seed':seed+1})
betterLearner = DTLearner
worseLearner = LinRegLearner
elif group=='best4lr':
from gen_data import best4LinReg
dataX, dataY = run_with_timeout(best4LinReg,seconds_per_test_case,(),{'seed':seed})
same_dataX, same_dataY = run_with_timeout(best4LinReg,seconds_per_test_case,(),{'seed':seed})
diff_dataX, diff_dataY = run_with_timeout(best4LinReg,seconds_per_test_case,(),{'seed':seed+1})
betterLearner = LinRegLearner
worseLearner = DTLearner
num_samples = dataX.shape[0]
cutoff = int(num_samples*0.6)
worse_better_err = []
for run in range(max_tests):
permutation = np.random.permutation(num_samples)
train_X,train_Y = dataX[permutation[:cutoff]], dataY[permutation[:cutoff]]
test_X,test_Y = dataX[permutation[cutoff:]], dataY[permutation[cutoff:]]
better = betterLearner()
worse = worseLearner()
better.addEvidence(train_X,train_Y)
worse.addEvidence(train_X,train_Y)
better_pred = better.query(test_X)
worse_pred = worse.query(test_X)
better_err = np.linalg.norm(test_Y-better_pred)
worse_err = np.linalg.norm(test_Y-worse_pred)
worse_better_err.append( (worse_err,better_err) )
worse_better_err.sort(key=functools.cmp_to_key(lambda a,b: int((b[0]-b[1])-(a[0]-a[1]))))
better_wins_count = 0
for worse_err,better_err in worse_better_err:
if better_err < 0.9*worse_err:
better_wins_count = better_wins_count+1
points_earned += 5.0
if better_wins_count >= needed_wins:
break
incorrect = False
if (dataX.shape[0] < row_limits[0]) or (dataX.shape[0]>row_limits[1]):
incorrect = True
msgs.append(" Invalid number of rows. Should be between {}, found {}".format(row_limits,dataX.shape[0]))
points_earned = max(0,points_earned-20)
if (dataX.shape[1] < col_limits[0]) or (dataX.shape[1]>col_limits[1]):
incorrect = True
msgs.append(" Invalid number of columns. Should be between {}, found {}".format(col_limits,dataX.shape[1]))
points_earned = max(0,points_earned-20)
if better_wins_count < needed_wins:
incorrect = True
msgs.append(" Better learner did not exceed worse learner. Expected {}, found {}".format(needed_wins,better_wins_count))
if not(np.array_equal(same_dataY,dataY)) or not(np.array_equal(same_dataX,dataX)):
incorrect = True
msgs.append(" Did not produce the same data with the same seed.\n"+\
" First dataX:\n{}\n".format(dataX)+\
" Second dataX:\n{}\n".format(same_dataX)+\
" First dataY:\n{}\n".format(dataY)+\
" Second dataY:\n{}\n".format(same_dataY))
points_earned = max(0,points_earned-20)
if np.array_equal(diff_dataY,dataY) and np.array_equal(diff_dataX,dataX):
incorrect = True
msgs.append(" Did not produce different data with different seeds.\n"+\
" First dataX:\n{}\n".format(dataX)+\
" Second dataX:\n{}\n".format(diff_dataX)+\
" First dataY:\n{}\n".format(dataY)+\
" Second dataY:\n{}\n".format(diff_dataY))
points_earned = max(0,points_earned-20)
if incorrect:
if group=='author':
raise IncorrectOutput("Test failed on one or more criteria.\n {}".format('\n'.join(msgs)))
else:
inputs_str = " Residuals: {}".format(worse_better_err)
raise IncorrectOutput("Test failed on one or more output criteria.\n Inputs:\n{}\n Failures:\n{}".format(inputs_str, "\n".join(msgs)))
else:
if group != 'author':
avg_ratio = 0.0
worse_better_err.sort(key=functools.cmp_to_key(lambda a,b: int(np.sign((b[0]-b[1])-(a[0]-a[1])))))
for we,be in worse_better_err[:10]:
avg_ratio += (float(we) - float(be))
avg_ratio = avg_ratio/10.0
if group=="best4dt":
grader.add_performance(np.array([avg_ratio,0]))
else:
grader.add_performance(np.array([0,avg_ratio]))
except Exception as e:
# Test result: failed
msg = "Description: {} (group: {})\n".format(description, group)
# Generate a filtered stacktrace, only showing erroneous lines in student file(s)
tb_list = tb.extract_tb(sys.exc_info()[2])
for i in range(len(tb_list)):
row = tb_list[i]
tb_list[i] = (os.path.basename(row[0]), row[1], row[2], row[3]) # show only filename instead of long absolute path
tb_list = [row for row in tb_list if (row[0] == 'gen_data.py')]
if tb_list:
msg += "Traceback:\n"
msg += ''.join(tb.format_list(tb_list)) # contains newlines
elif 'grading_traceback' in dir(e):
msg += "Traceback:\n"
msg += ''.join(tb.format_list(e.grading_traceback))
msg += "{}: {}".format(e.__class__.__name__, str(e))
# Report failure result to grader, with stacktrace
grader.add_result(GradeResult(outcome='failed', points=points_earned, msg=msg))
raise
else:
# Test result: passed (no exceptions)
grader.add_result(GradeResult(outcome='passed', points=points_earned, msg=None))
if __name__ == "__main__":
pytest.main(["-s", __file__])

View File

@@ -0,0 +1,104 @@
"""
Test best4 data generator. (c) 2016 Tucker Balch
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---
"""
import numpy as np
import math
import LinRegLearner as lrl
import DTLearner as dt
from gen_data import best4LinReg, best4DT
# compare two learners' rmse out of sample
def compare_os_rmse(learner1, learner2, X, Y):
# compute how much of the data is training and testing
train_rows = int(math.floor(0.6 * X.shape[0]))
test_rows = X.shape[0] - train_rows
# separate out training and testing data
train = np.random.choice(X.shape[0], size=train_rows, replace=False)
test = np.setdiff1d(np.array(range(X.shape[0])), train)
trainX = X[train, :]
trainY = Y[train]
testX = X[test, :]
testY = Y[test]
# train the learners
learner1.addEvidence(trainX, trainY) # train it
learner2.addEvidence(trainX, trainY) # train it
# evaluate learner1 out of sample
predY = learner1.query(testX) # get the predictions
rmse1 = math.sqrt(((testY - predY) ** 2).sum()/testY.shape[0])
# evaluate learner2 out of sample
predY = learner2.query(testX) # get the predictions
rmse2 = math.sqrt(((testY - predY) ** 2).sum()/testY.shape[0])
return rmse1, rmse2
def test_code():
# create two learners and get data
lrlearner = lrl.LinRegLearner(verbose=False)
dtlearner = dt.DTLearner(verbose=False, leaf_size=1)
X, Y = best4LinReg()
# compare the two learners
rmseLR, rmseDT = compare_os_rmse(lrlearner, dtlearner, X, Y)
# share results
print()
print("best4LinReg() results")
print(f"RMSE LR : {rmseLR}")
print(f"RMSE DT : {rmseDT}")
if rmseLR < 0.9 * rmseDT:
print("LR < 0.9 DT: pass")
else:
print("LR >= 0.9 DT: fail")
print
# get data that is best for a random tree
lrlearner = lrl.LinRegLearner(verbose=False)
dtlearner = dt.DTLearner(verbose=False, leaf_size=1)
X, Y = best4DT()
# compare the two learners
rmseLR, rmseDT = compare_os_rmse(lrlearner, dtlearner, X, Y)
# share results
print()
print("best4RT() results")
print(f"RMSE LR : {rmseLR}")
print(f"RMSE DT : {rmseDT}")
if rmseDT < 0.9 * rmseLR:
print("DT < 0.9 LR: pass")
else:
print("DT >= 0.9 LR: fail")
print
if __name__ == "__main__":
test_code()

View File

@@ -0,0 +1,64 @@
import pandas as pd
from util import get_data
from collections import namedtuple
Position = namedtuple("Pos", ["cash", "shares", "transactions"])
def author():
return "felixm"
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,168 @@
import pandas as pd
import datetime as dt
import matplotlib.pyplot as plt
import TheoreticallyOptimalStrategy as tos
from util import get_data
from marketsim.marketsim import compute_portvals
from optimize_something.optimization import calculate_stats
def author():
return "felixm"
def test_optimal_strategy():
symbol = "JPM"
start_value = 100000
sd = dt.datetime(2008, 1, 1)
ed = dt.datetime(2009, 12, 31)
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)
portvals.plot(title="Optimal strategy versus 1000 shares of JPM")
plt.savefig('figure_6.png')
def normalize(timeseries):
return timeseries / timeseries.iloc[0]
def bollinger_band(df, symbol, period=20, m=2):
boll_sma = df[symbol].rolling(period).mean()
std = df[symbol].rolling(period).std()
boll_up = boll_sma + m * std
boll_lo = boll_sma - m * std
df[f"{symbol}-Boll({period})-sma"] = boll_sma
df[f"{symbol}-Boll({period})-up"] = boll_up
df[f"{symbol}-Boll({period})-lo"] = boll_lo
def sma(df, symbol, period):
"""Adds a new column to the dataframe SMA(period)"""
df[f"{symbol}-sma({period})"] = df[symbol].rolling(period).mean()
def ema(df, symbol, period):
"""Adds a new column to the dataframe EMA(period)"""
df[f"{symbol}-ema({period})"] = df[symbol].ewm(span=period).mean()
def price_sma(df, symbol, period):
"""Calculates SMA and adds new column price divided by SMA to the df."""
sma = df[symbol].rolling(period).mean()
df[f"{symbol}-price/sma({period})"] = df[symbol] / sma
def rsi(df, symbol, period=14):
"""Calculates relative strength index over given period."""
def rsi(x):
pct = x.pct_change()
avg_gain = pct[pct > 0].mean()
avg_loss = pct[pct <= 0].abs().mean()
rsi = 100 - (100 /
(1 + ((avg_gain / period) /
(avg_loss / period))))
return rsi
key = f"{symbol}-rsi({period})"
# Add one to get 'period' price changes (first change is nan).
period += 1
df[key] = df[symbol].rolling(period).apply(rsi)
def macd(df, symbol):
macd = df[symbol].ewm(span=12).mean() - df[symbol].ewm(span=26).mean()
df[f"{symbol}-macd"] = macd
df[f"{symbol}-macd-signal"] = macd.rolling(9).mean()
def price_delta(df, symbol, period=1):
"""Calculate delta between previous day and today."""
df[f"{symbol}-diff({period})"] = df[symbol].diff(periods=period)
def test_indicators():
symbol = "JPM"
sd = dt.datetime(2008, 1, 1)
ed = dt.datetime(2009, 12, 31)
df = get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
df_orig = df.copy()
# df = normalize(df)
sma(df, symbol, 21)
ema(df, symbol, 21)
df.plot(title="21 SMA and EMA")
plt.savefig('figure_1.png')
df = df_orig.copy()
sma(df, symbol, 8)
price_sma(df, symbol, 8)
df.plot(title="SMA and price / SMA", subplots=True)
plt.savefig('figure_2.png')
df = df_orig.copy()
bollinger_band(df, symbol)
df.plot(title="Bollinger Band")
plt.savefig('figure_3.png')
df = df_orig.copy()
rsi(df, symbol)
fig, axes = plt.subplots(nrows=2, sharex=True)
df[symbol].plot(ax=axes[0], title="JPM price action")
df["JPM-rsi(14)"].plot(ax=axes[1], title="RSI")
plt.savefig('figure_4.png')
df = df_orig.copy()
macd(df, symbol)
fig, axes = plt.subplots(nrows=2, sharex=True)
df[symbol].plot(ax=axes[0], title="JPM price action")
df[["JPM-macd", "JPM-macd-signal"]].plot(ax=axes[1])
plt.savefig('figure_5.png')
def main():
test_optimal_strategy()
test_indicators()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,72 @@
# Indicators
## SMA and EMA
![SMA and EMA](figure_1.png)
## SMA/Price
![SMA/Price](figure_2.png)
## Bollinger Band
![Bollinger Band](figure_3.png)
## RSI
![RSI](figure_4.png)
## MACD
![MACD](figure_5.png)
# Optimal Strategy
The optimal strategy works by applying every possible buy/sell action to the
current positions. A position is cash value, the current amount of shares, and
previous transactions.
The algorithm then starts with a single initial position with the initial cash
amount, no shares, and no transactions. The algorithm first executes all
possible trades (sell 2000, sell 1000, do nothing, buy 1000, buy 2000). Next, it
filters the invalid holdings, i.e., the other ones where shares are not equal to
-1000, 0, or 1000. Finally, the algorithm keeps the highest cash value for each
amount of shares.
That is the critical insight that makes the algorithm work. Since the same
amount of shares has the same future potential for creating cash, the only
differentiating factor is the current cash value. Accordingly, for each day, the
algorithm ends up with three new positions. The algorithm could drop the
position with zero shares because buying and selling 2000 shares leverages
future price movements optimally.
After each trading day, the positions have the same value. I did not expect
that, but it makes sense because the algorithm only keeps the best position from
the previous day. In other words, we can simplify the algorithm by selecting the
best position and then compute the two different positions. Since we already
established that holding zero shares does not make sense, the only two
possibilities are buying or selling 2000 depending on the current amount of
shares.
![Optimal strategy versus holding 1000 shares](figure_6.png)
```
Date Range: 2008-01-02 00:00:00 to 2009-12-31 00:00:00
Sharpe Ratio of Optimal Strategy: 13.322769848217233
Sharpe Ratio of bench: 0.1569184064240322
Cumulative Return of Optimal Strategy: 5.7861
Cumulative Return of bench: 0.012299999999999978
Standard Deviation of Optimal Strategy: 0.004547823197907996
Standard Deviation of bench: 0.01700436627121376
Average Daily Return of Optimal Strategy: 0.0038167861508578214
Average Daily Return of bench: 0.00016808697819094233
Final Portfolio Value Optimal: 678610.0
Final Portfolio Value Bench: 101230.0
```

View File

@@ -0,0 +1,3 @@
def author():
return "felixm"

View File

@@ -0,0 +1,384 @@
"""MC2-P1: Market simulator - grading script.
Usage:
- Switch to a student feedback directory first (will write "points.txt" and "comments.txt" in pwd).
- Run this script with both ml4t/ and student solution in PYTHONPATH, e.g.:
PYTHONPATH=ml4t:MC1-P2/jdoe7 python ml4t/mc2_p1_grading/grade_marketsim.py
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---
"""
import pytest
from grading.grading import grader, GradeResult, time_limit, run_with_timeout, IncorrectOutput
import os
import sys
import traceback as tb
import numpy as np
import pandas as pd
from collections import namedtuple
from util import get_data
from util import get_orders_data_file
# Student code
main_code = "marketsim" # module name to import
# Test cases
MarketsimTestCase = namedtuple('MarketsimTestCase', ['description', 'group', 'inputs', 'outputs'])
marketsim_test_cases = [
MarketsimTestCase(
description="Orders 1",
group='basic',
inputs=dict(
orders_file='orders-01.csv',
start_val=1000000,
commission=0.,
impact=0.
),
outputs=dict(
num_days = 245 ,
last_day_portval = 1115569.2,
sharpe_ratio = 0.612340613407 ,
avg_daily_ret = 0.00055037432146
)
),
MarketsimTestCase(
description="Orders 2",
group='basic',
inputs=dict(
orders_file='orders-02.csv',
start_val=1000000,
commission=0.,
impact=0.
),
outputs=dict(
num_days = 245 ,
last_day_portval = 1095003.35,
sharpe_ratio = 1.01613520942,
avg_daily_ret = 0.000390534819609
)
),
MarketsimTestCase(
description="Orders 3",
group='basic',
inputs=dict(
orders_file='orders-03.csv',
start_val=1000000,
commission=0.,
impact=0.
),
outputs=dict(
num_days = 240,
last_day_portval = 857616.0,
sharpe_ratio = -0.759896272199,
avg_daily_ret = -0.000571326189931
)
),
MarketsimTestCase(
description="Orders 4",
group='basic',
inputs=dict(
orders_file='orders-04.csv',
start_val=1000000,
commission=0.,
impact=0.
),
outputs=dict(
num_days = 233,
last_day_portval = 923545.4,
sharpe_ratio = -0.266030146916,
avg_daily_ret = -0.000240200768212
)
),
MarketsimTestCase(
description="Orders 5",
group='basic',
inputs=dict(
orders_file='orders-05.csv',
start_val=1000000,
commission=0.,
impact=0.
),
outputs=dict(
num_days = 296,
last_day_portval = 1415563.0,
sharpe_ratio = 2.19591520826,
avg_daily_ret = 0.00121733290744
)
),
MarketsimTestCase(
description="Orders 6",
group='basic',
inputs=dict(
orders_file='orders-06.csv',
start_val=1000000,
commission=0.,
impact=0.
),
outputs=dict(
num_days = 210,
last_day_portval = 894604.3,
sharpe_ratio = -1.23463930987,
avg_daily_ret = -0.000511281541086
)
),
MarketsimTestCase(
description="Orders 7",
group='basic',
inputs=dict(
orders_file='orders-07.csv',
start_val=1000000,
commission=0.,
impact=0.
),
outputs=dict(
num_days = 237,
last_day_portval = 1106563.3,
sharpe_ratio = 2.10356512897,
avg_daily_ret = 0.0004345040621
)
),
MarketsimTestCase(
description="Orders 8",
group='basic',
inputs=dict(
orders_file='orders-08.csv',
start_val=1000000,
commission=0.,
impact=0.
),
outputs=dict(
num_days = 229,
last_day_portval = 1074884.1,
sharpe_ratio = 0.941858298061,
avg_daily_ret = 0.000332404156893
)
),
MarketsimTestCase(
description="Orders 9",
group='basic',
inputs=dict(
orders_file='orders-09.csv',
start_val=1000000,
commission=0.,
impact=0.
),
outputs=dict(
num_days = 37,
last_day_portval = 1067710.0,
sharpe_ratio = 2.90848480553,
avg_daily_ret = 0.00187252252117
)
),
#########################
# Commission and impact #
#########################
MarketsimTestCase(
description="Orders 11, commission",
group='commission',
inputs=dict(
orders_file='orders-11.csv',
start_val=1000000,
commission=9.95,
impact=0.
),
outputs=dict(
num_days = 37,
last_day_portval = 1045700.45,
sharpe_ratio = 1.80327835983,
avg_daily_ret = 0.00130745837411
)
),
MarketsimTestCase(
description="Orders 12, impact",
group='impact',
inputs=dict(
orders_file='orders-12.csv',
start_val=1000000,
commission=0.,
impact=0.005
),
outputs=dict(
num_days = 240,
last_day_portval = 1705686.6665,
sharpe_ratio = 1.26427802107,
avg_daily_ret = 0.00290246139389
)
),
MarketsimTestCase(
description="Orders 10, impact and commission",
group='both',
inputs=dict(
orders_file='orders-10.csv',
start_val=1000000,
commission=9.95,
impact=0.005
),
outputs=dict(
num_days = 141,
last_day_portval = 1026658.3265,
sharpe_ratio = 0.627643575702,
avg_daily_ret = 0.000222013722594
)
),
MarketsimTestCase(
description="author() test",
group='author',
inputs=None,
outputs=None
),
#######################
# Withheld test cases #
#######################
]
seconds_per_test_case = 10 # execution time limit
# Grading parameters (picked up by module-level grading fixtures)
max_points = 100.0 # 9.5 * 10 + 2.5 * 2 + 1 secret point
html_pre_block = True # surround comments with HTML <pre> tag (for T-Square comments field)
# Test functon(s)
@pytest.mark.parametrize("description,group,inputs,outputs", marketsim_test_cases)
def test_marketsim(description, group, inputs, outputs, grader):
"""Test compute_portvals() returns correct daily portfolio values.
Requires test description, test case group, inputs, expected outputs, and a grader fixture.
"""
points_earned = 0.0 # initialize points for this test case
try:
# Try to import student code (only once)
if not main_code in globals():
import importlib
# * Import module
mod = importlib.import_module(main_code)
globals()[main_code] = mod
# * Import methods to test
for m in ['compute_portvals']:
globals()[m] = getattr(mod, m)
incorrect = False
msgs = []
if group == 'author':
try:
auth_string = run_with_timeout(marketsim.author,seconds_per_test_case,(),{})
if auth_string == 'tb34':
incorrect = True
msgs.append(" Incorrect author name (tb34)")
points_earned = -10
elif auth_string == '':
incorrect = True
msgs.append(" Empty author name")
points_earned = -10
except Exception as e:
incorrect = True
msgs.append(" Exception occured when calling author() method: {}".format(e))
points_earned = -10
else:
# Unpack test case
orders_file = inputs['orders_file']
start_val = inputs['start_val']
impct = inputs['impact']
commish = inputs['commission']
portvals = None
fullpath_orders_file = get_orders_data_file(orders_file)
portvals = run_with_timeout(compute_portvals,seconds_per_test_case,(),{'orders_file':fullpath_orders_file,'start_val':start_val,'commission':commish,'impact':impct})
# * Check return type is correct, coax into Series
assert (type(portvals) == pd.Series) or (type(portvals) == pd.DataFrame and len(portvals.columns) == 1), "You must return a Series or single-column DataFrame!"
if type(portvals) == pd.DataFrame:
portvals = portvals[portvals.columns[0]] # convert single-column DataFrame to Series
if portvals.isnull().values.any():
incorrect=True
msgs.append("Portfolio values cannot be NaNs!")
else:
if group == 'basic':
if len(portvals) != outputs['num_days']:
incorrect=True
msgs.append(" Incorrect number of days: {}, expected {}".format(len(portvals), outputs['num_days']))
else:
points_earned += 2.0
if abs(portvals[-1]-outputs['last_day_portval']) > (0.001*outputs['last_day_portval']):
incorrect=True
msgs.append(" Incorrect final value: {}, expected {}".format(portvals[-1],outputs['last_day_portval']))
else:
points_earned += 5.0
adr,sr = get_stats(portvals)
if abs(sr-outputs['sharpe_ratio']) > abs(0.001*outputs['sharpe_ratio']):
incorrect=True
msgs.append(" Incorrect sharpe ratio: {}, expected {}".format(sr,outputs['sharpe_ratio']))
else:
points_earned += 1.0
if abs(adr-outputs['avg_daily_ret']) > abs(0.001*outputs['avg_daily_ret']):
incorrect=True
msgs.append(" Incorrect avg daily return: {}, expected {}".format(adr,outputs['avg_daily_ret']))
else:
points_earned += 1.0
elif group=='commission' or group=='impact' or group=='both':
if abs(portvals[-1]-outputs['last_day_portval']) > 0.001:#(0.001*outputs['last_day_portval']):
incorrect = True
msgs.append(" Incorrect final value: {}, expected {}".format(portvals[-1],outputs['last_day_portval']))
else:
points_earned += 2.0
if incorrect:
# inputs_str = " orders_file: {}\n" \
# " start_val: {}\n".format(orders_file, start_val)
raise IncorrectOutput("Test failed on one or more output criteria.\n Inputs:\n{}\n Failures:\n{}".format(inputs, "\n".join(msgs)))
except Exception as e:
# Test result: failed
msg = "Test case description: {}\n".format(description)
# Generate a filtered stacktrace, only showing erroneous lines in student file(s)
tb_list = tb.extract_tb(sys.exc_info()[2])
if 'grading_traceback' in dir(e):
tb_list = e.grading_traceback
for i in range(len(tb_list)):
row = tb_list[i]
tb_list[i] = (os.path.basename(row[0]), row[1], row[2], row[3]) # show only filename instead of long absolute path
tb_list = [row for row in tb_list if row[0] == 'marketsim.py']
if tb_list:
msg += "Traceback:\n"
msg += ''.join(tb.format_list(tb_list)) # contains newlines
msg += "{}: {}".format(e.__class__.__name__, str(e))
# Report failure result to grader, with stacktrace
grader.add_result(GradeResult(outcome='failed', points=max(points_earned,0), msg=msg))
raise
else:
# Test result: passed (no exceptions)
grader.add_result(GradeResult(outcome='passed', points=points_earned, msg=None))
def get_stats(port_val):
daily_rets = (port_val / port_val.shift(1)) - 1
daily_rets = daily_rets[1:]
avg_daily_ret = daily_rets.mean()
std_daily_ret = daily_rets.std()
sharpe_ratio = np.sqrt(252) * daily_rets.mean() / std_daily_ret
return avg_daily_ret, sharpe_ratio
if __name__ == "__main__":
pytest.main(["-s", __file__])

179
marketsim/marketsim.py Normal file
View File

@@ -0,0 +1,179 @@
"""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()

View File

@@ -0,0 +1,18 @@
Date,Symbol,Order,Shares
2011-01-10,AAPL,BUY,1500
2011-01-13,AAPL,SELL,1500
2011-01-13,IBM,BUY,4000
2011-01-26,GOOG,BUY,1000
2011-02-02,XOM,SELL,4000
2011-02-10,XOM,BUY,4000
2011-03-03,GOOG,SELL,1000
2011-03-03,GOOG,SELL,2200
2011-06-03,IBM,SELL,3300
2011-05-03,IBM,BUY,1500
2011-06-10,AAPL,BUY,1200
2011-08-01,GOOG,BUY,55
2011-08-01,GOOG,SELL,55
2011-12-20,AAPL,SELL,1200
2011-12-21,AAPL,BUY,20
2011-12-27,GOOG,BUY,2200
2011-12-28,IBM,SELL,2200
1 Date Symbol Order Shares
2 2011-01-10 AAPL BUY 1500
3 2011-01-13 AAPL SELL 1500
4 2011-01-13 IBM BUY 4000
5 2011-01-26 GOOG BUY 1000
6 2011-02-02 XOM SELL 4000
7 2011-02-10 XOM BUY 4000
8 2011-03-03 GOOG SELL 1000
9 2011-03-03 GOOG SELL 2200
10 2011-06-03 IBM SELL 3300
11 2011-05-03 IBM BUY 1500
12 2011-06-10 AAPL BUY 1200
13 2011-08-01 GOOG BUY 55
14 2011-08-01 GOOG SELL 55
15 2011-12-20 AAPL SELL 1200
16 2011-12-21 AAPL BUY 20
17 2011-12-27 GOOG BUY 2200
18 2011-12-28 IBM SELL 2200

View File

@@ -0,0 +1,18 @@
Date,Symbol,Order,Shares
2011-01-10,AAPL,BUY,1500
2011-01-10,AAPL,SELL,1500
2011-01-13,AAPL,SELL,1500
2011-01-13,IBM,BUY,4000
2011-01-26,GOOG,BUY,1000
2011-02-02,XOM,SELL,4000
2011-02-10,XOM,BUY,4000
2011-03-03,GOOG,SELL,1000
2011-06-03,IBM,SELL,3300
2011-05-03,IBM,BUY,1500
2011-06-10,AAPL,BUY,1200
2011-08-01,GOOG,BUY,55
2011-08-01,GOOG,SELL,55
2011-08-01,GOOG,BUY,55
2011-12-20,AAPL,SELL,1200
2011-12-21,AAPL,BUY,20
2011-12-28,IBM,SELL,2200
1 Date Symbol Order Shares
2 2011-01-10 AAPL BUY 1500
3 2011-01-10 AAPL SELL 1500
4 2011-01-13 AAPL SELL 1500
5 2011-01-13 IBM BUY 4000
6 2011-01-26 GOOG BUY 1000
7 2011-02-02 XOM SELL 4000
8 2011-02-10 XOM BUY 4000
9 2011-03-03 GOOG SELL 1000
10 2011-06-03 IBM SELL 3300
11 2011-05-03 IBM BUY 1500
12 2011-06-10 AAPL BUY 1200
13 2011-08-01 GOOG BUY 55
14 2011-08-01 GOOG SELL 55
15 2011-08-01 GOOG BUY 55
16 2011-12-20 AAPL SELL 1200
17 2011-12-21 AAPL BUY 20
18 2011-12-28 IBM SELL 2200

View File

@@ -0,0 +1,14 @@
Date,Symbol,Order,Shares
2011-01-10,AAPL,BUY,1500
2011-01-13,AAPL,BUY,1500
2011-01-26,GOOG,BUY,1000
2011-02-02,XOM,SELL,4000
2011-02-10,XOM,BUY,4000
2011-03-03,GOOG,SELL,1000
2011-03-03,IBM,SELL,1200
2011-06-03,IBM,SELL,3300
2011-05-03,IBM,BUY,1500
2011-06-10,AAPL,SELL,3000
2011-08-01,GOOG,BUY,55
2011-08-01,GOOG,BUY,55
2011-12-20,GOOG,SELL,110
1 Date Symbol Order Shares
2 2011-01-10 AAPL BUY 1500
3 2011-01-13 AAPL BUY 1500
4 2011-01-26 GOOG BUY 1000
5 2011-02-02 XOM SELL 4000
6 2011-02-10 XOM BUY 4000
7 2011-03-03 GOOG SELL 1000
8 2011-03-03 IBM SELL 1200
9 2011-06-03 IBM SELL 3300
10 2011-05-03 IBM BUY 1500
11 2011-06-10 AAPL SELL 3000
12 2011-08-01 GOOG BUY 55
13 2011-08-01 GOOG BUY 55
14 2011-12-20 GOOG SELL 110

View File

@@ -0,0 +1,15 @@
Date,Symbol,Order,Shares
2008-01-14,GOOG,SELL,1000
2008-01-17,GOOG,BUY,1000
2008-03-19,AAPL,SELL,3000
2008-03-31,IBM,SELL,2000
2008-05-02,BAC,BUY,5000
2008-05-09,BAC,SELL,5000
2008-06-02,IBM,BUY,2000
2008-06-02,AAPL,BUY,3100
2008-06-02,AAPL,BUY,4300
2008-07-23,AAPL,SELL,3500
2008-07-10,GOOG,SELL,1000
2008-08-07,IBM,SELL,55
2008-08-11,IBM,BUY,55
2008-12-12,GOOG,BUY,1000
1 Date Symbol Order Shares
2 2008-01-14 GOOG SELL 1000
3 2008-01-17 GOOG BUY 1000
4 2008-03-19 AAPL SELL 3000
5 2008-03-31 IBM SELL 2000
6 2008-05-02 BAC BUY 5000
7 2008-05-09 BAC SELL 5000
8 2008-06-02 IBM BUY 2000
9 2008-06-02 AAPL BUY 3100
10 2008-06-02 AAPL BUY 4300
11 2008-07-23 AAPL SELL 3500
12 2008-07-10 GOOG SELL 1000
13 2008-08-07 IBM SELL 55
14 2008-08-11 IBM BUY 55
15 2008-12-12 GOOG BUY 1000

View File

@@ -0,0 +1,15 @@
Date,Symbol,Order,Shares
2009-05-04,GLD,BUY,3000
2009-06-08,AAPL,BUY,1500
2009-07-07,IBM,BUY,4000
2009-07-07,GOOG,BUY,1000
2009-10-19,IBM,SELL,4000
2010-01-13,XOM,BUY,3000
2010-02-26,GOOG,SELL,1000
2010-03-09,XOM,SELL,900
2010-04-20,GLD,SELL,3000
2010-05-03,BAC,BUY,1500
2010-05-27,BAC,SELL,1500
2010-06-29,XOM,SELL,2100
2010-07-02,AAPL,SELL,300
2010-07-06,AAPL,SELL,1200
1 Date Symbol Order Shares
2 2009-05-04 GLD BUY 3000
3 2009-06-08 AAPL BUY 1500
4 2009-07-07 IBM BUY 4000
5 2009-07-07 GOOG BUY 1000
6 2009-10-19 IBM SELL 4000
7 2010-01-13 XOM BUY 3000
8 2010-02-26 GOOG SELL 1000
9 2010-03-09 XOM SELL 900
10 2010-04-20 GLD SELL 3000
11 2010-05-03 BAC BUY 1500
12 2010-05-27 BAC SELL 1500
13 2010-06-29 XOM SELL 2100
14 2010-07-02 AAPL SELL 300
15 2010-07-06 AAPL SELL 1200

View File

@@ -0,0 +1,15 @@
Date,Symbol,Order,Shares
2007-01-10,AAPL,BUY,2500
2007-01-17,AAPL,SELL,1500
2007-01-19,IBM,BUY,400
2007-01-26,GOOG,BUY,1000
2007-02-02,XOM,SELL,4000
2007-02-16,XOM,BUY,700
2007-03-05,GOOG,SELL,550
2007-03-05,IBM,SELL,2200
2007-05-14,XOM,SELL,880
2007-05-15,XOM,BUY,550
2007-09-13,GOOG,SELL,1000
2007-10-10,XOM,BUY,100
2007-10-12,XOM,SELL,3000
2007-11-07,XOM,BUY,400
1 Date Symbol Order Shares
2 2007-01-10 AAPL BUY 2500
3 2007-01-17 AAPL SELL 1500
4 2007-01-19 IBM BUY 400
5 2007-01-26 GOOG BUY 1000
6 2007-02-02 XOM SELL 4000
7 2007-02-16 XOM BUY 700
8 2007-03-05 GOOG SELL 550
9 2007-03-05 IBM SELL 2200
10 2007-05-14 XOM SELL 880
11 2007-05-15 XOM BUY 550
12 2007-09-13 GOOG SELL 1000
13 2007-10-10 XOM BUY 100
14 2007-10-12 XOM SELL 3000
15 2007-11-07 XOM BUY 400

View File

@@ -0,0 +1,15 @@
Date,Symbol,Order,Shares
2009-01-14,AAPL,BUY,150
2009-01-21,AAPL,SELL,150
2009-01-21,IBM,BUY,400
2009-01-30,GOOG,BUY,100
2009-02-02,XOM,SELL,400
2009-02-10,XOM,BUY,400
2009-03-03,GOOG,SELL,100
2009-03-03,IBM,SELL,220
2009-06-03,IBM,SELL,330
2009-05-08,IBM,BUY,1500
2009-06-10,AAPL,BUY,1200
2009-08-03,GOOG,BUY,550
2009-08-03,GOOG,SELL,550
2009-12-21,AAPL,SELL,1200
1 Date Symbol Order Shares
2 2009-01-14 AAPL BUY 150
3 2009-01-21 AAPL SELL 150
4 2009-01-21 IBM BUY 400
5 2009-01-30 GOOG BUY 100
6 2009-02-02 XOM SELL 400
7 2009-02-10 XOM BUY 400
8 2009-03-03 GOOG SELL 100
9 2009-03-03 IBM SELL 220
10 2009-06-03 IBM SELL 330
11 2009-05-08 IBM BUY 1500
12 2009-06-10 AAPL BUY 1200
13 2009-08-03 GOOG BUY 550
14 2009-08-03 GOOG SELL 550
15 2009-12-21 AAPL SELL 1200

View File

@@ -0,0 +1,15 @@
Date,Symbol,Order,Shares
2010-01-12,AAPL,BUY,150
2010-01-21,IBM,BUY,400
2010-01-21,GOOG,BUY,100
2010-01-27,AAPL,SELL,150
2010-02-05,XOM,SELL,400
2010-02-11,GOOG,SELL,100
2010-03-10,IBM,SELL,220
2010-04-15,XOM,BUY,400
2010-05-20,GOOG,BUY,550
2010-06-03,IBM,SELL,330
2010-06-24,AAPL,BUY,1200
2010-08-18,IBM,BUY,1500
2010-09-15,GOOG,SELL,550
2010-12-07,AAPL,SELL,1200
1 Date Symbol Order Shares
2 2010-01-12 AAPL BUY 150
3 2010-01-21 IBM BUY 400
4 2010-01-21 GOOG BUY 100
5 2010-01-27 AAPL SELL 150
6 2010-02-05 XOM SELL 400
7 2010-02-11 GOOG SELL 100
8 2010-03-10 IBM SELL 220
9 2010-04-15 XOM BUY 400
10 2010-05-20 GOOG BUY 550
11 2010-06-03 IBM SELL 330
12 2010-06-24 AAPL BUY 1200
13 2010-08-18 IBM BUY 1500
14 2010-09-15 GOOG SELL 550
15 2010-12-07 AAPL SELL 1200

View File

@@ -0,0 +1,10 @@
Date,Symbol,Order,Shares
2011-01-10,AAPL,BUY,2500
2011-01-10,AAPL,BUY,2500
2011-01-13,AAPL,SELL,5000
2011-01-13,IBM,BUY,4000
2011-01-26,GOOG,BUY,1000
2011-02-02,XOM,SELL,4000
2011-02-10,XOM,BUY,4000
2011-03-03,GOOG,SELL,1000
2011-03-03,IBM,SELL,4000
1 Date Symbol Order Shares
2 2011-01-10 AAPL BUY 2500
3 2011-01-10 AAPL BUY 2500
4 2011-01-13 AAPL SELL 5000
5 2011-01-13 IBM BUY 4000
6 2011-01-26 GOOG BUY 1000
7 2011-02-02 XOM SELL 4000
8 2011-02-10 XOM BUY 4000
9 2011-03-03 GOOG SELL 1000
10 2011-03-03 IBM SELL 4000

View File

@@ -0,0 +1,13 @@
Date,Symbol,Order,Shares
2011-01-10,AAPL,BUY,1500
2011-01-13,AAPL,SELL,1500
2011-01-13,IBM,BUY,4000
2011-01-26,GOOG,BUY,1000
2011-02-02,XOM,SELL,4000
2011-02-10,XOM,BUY,4000
2011-03-03,GOOG,SELL,1000
2011-03-03,IBM,SELL,2200
2011-06-03,IBM,SELL,3300
2011-05-03,IBM,BUY,1500
2011-08-01,GOOG,BUY,55
2011-08-01,GOOG,SELL,55
1 Date Symbol Order Shares
2 2011-01-10 AAPL BUY 1500
3 2011-01-13 AAPL SELL 1500
4 2011-01-13 IBM BUY 4000
5 2011-01-26 GOOG BUY 1000
6 2011-02-02 XOM SELL 4000
7 2011-02-10 XOM BUY 4000
8 2011-03-03 GOOG SELL 1000
9 2011-03-03 IBM SELL 2200
10 2011-06-03 IBM SELL 3300
11 2011-05-03 IBM BUY 1500
12 2011-08-01 GOOG BUY 55
13 2011-08-01 GOOG SELL 55

View File

@@ -0,0 +1,10 @@
Date,Symbol,Order,Shares
2011-01-10,AAPL,BUY,2500
2011-01-10,AAPL,BUY,2500
2011-01-13,AAPL,SELL,5000
2011-01-13,IBM,BUY,4000
2011-01-26,GOOG,BUY,1000
2011-02-02,XOM,SELL,12000
2011-02-10,XOM,BUY,4000
2011-03-03,GOOG,SELL,1000
2011-03-03,IBM,SELL,4000
1 Date Symbol Order Shares
2 2011-01-10 AAPL BUY 2500
3 2011-01-10 AAPL BUY 2500
4 2011-01-13 AAPL SELL 5000
5 2011-01-13 IBM BUY 4000
6 2011-01-26 GOOG BUY 1000
7 2011-02-02 XOM SELL 12000
8 2011-02-10 XOM BUY 4000
9 2011-03-03 GOOG SELL 1000
10 2011-03-03 IBM SELL 4000

View File

@@ -0,0 +1,15 @@
Date,Symbol,Order,Shares
2011-01-10,AAPL,BUY,1500
2011-01-13,AAPL,SELL,1500
2011-01-13,IBM,BUY,4000
2011-01-26,GOOG,BUY,1000
2011-02-02,XOM,SELL,4000
2011-02-10,XOM,BUY,4000
2011-03-03,GOOG,SELL,1000
2011-03-03,IBM,SELL,2200
2011-06-03,IBM,SELL,3300
2011-05-03,IBM,BUY,1500
2011-06-10,AAPL,BUY,10000
2011-08-01,GOOG,BUY,55
2011-08-01,GOOG,SELL,55
2011-12-20,AAPL,SELL,1200
1 Date Symbol Order Shares
2 2011-01-10 AAPL BUY 1500
3 2011-01-13 AAPL SELL 1500
4 2011-01-13 IBM BUY 4000
5 2011-01-26 GOOG BUY 1000
6 2011-02-02 XOM SELL 4000
7 2011-02-10 XOM BUY 4000
8 2011-03-03 GOOG SELL 1000
9 2011-03-03 IBM SELL 2200
10 2011-06-03 IBM SELL 3300
11 2011-05-03 IBM BUY 1500
12 2011-06-10 AAPL BUY 10000
13 2011-08-01 GOOG BUY 55
14 2011-08-01 GOOG SELL 55
15 2011-12-20 AAPL SELL 1200

View File

@@ -0,0 +1,3 @@
Date,Symbol,Order,Shares
2011-01-05,AAPL,BUY,1500
2011-01-20,AAPL,SELL,1500
1 Date Symbol Order Shares
2 2011-01-05 AAPL BUY 1500
3 2011-01-20 AAPL SELL 1500

138
qlearning_robot/QLearner.py Normal file
View File

@@ -0,0 +1,138 @@
"""
Template for implementing QLearner (c) 2015 Tucker Balch
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---
"""
import random
import numpy as np
class QLearner(object):
def __init__(self,
num_states=100,
num_actions=4,
alpha=0.2,
gamma=0.9,
rar=0.5,
radr=0.99,
dyna=0,
verbose=False):
self.verbose = verbose
self.num_actions = num_actions
self.num_states = num_states
self.s = 0
self.a = 0
self.alpha = alpha
self.gamma = gamma
self.rar = rar
self.radr = radr
self.dyna = dyna
if self.dyna > 0:
self.model = {}
self.state_action_list = []
# self.q = np.random.random((num_states, num_actions))
self.q = np.zeros((num_states, num_actions))
def _get_a(self, s):
"""Get best action for state. Considers rar."""
if random.random() < self.rar:
a = random.randint(0, self.num_actions - 1)
else:
a = np.argmax(self.q[s])
return a
def _update_q(self, s, a, r, s_prime):
"""Updates the Q table."""
q_old = self.q[s][a]
alpha = self.alpha
# estimate optimal future value
a_max = np.argmax(self.q[s_prime])
q_future = self.q[s_prime][a_max]
# calculate new value and update table
q_new = (1 - alpha) * q_old + alpha * (r + self.gamma * q_future)
self.q[s][a] = q_new
if self.verbose:
print(f"{q_old=} {q_future=} {q_new=}")
def querysetstate(self, s):
"""
@summary: Update the state without updating the Q-table
@param s: The new state
@returns: The selected action
"""
a = self._get_a(s)
if self.verbose:
print(f"s = {s}, a = {a}")
self.s = s
self.a = a
return self.a
def query(self, s_prime, r):
"""
@summary: Update the Q table and return an action
@param s_prime: The new state
@param r: The reward
@returns: The selected action
"""
self._update_q(self.s, self.a, r, s_prime)
a = self._get_a(s_prime)
# Update random action rate
self.rar = self.rar * self.radr
if self.dyna > 0:
self._update_model(self.s, self.a, r, s_prime)
self._dyna_q()
self.a = a
self.s = s_prime
return self.a
def _update_model(self, s, a, r, s_prime):
state_action = (s, a)
if not state_action in self.model:
self.model[state_action] = (r, s_prime)
self.state_action_list.append(state_action)
def _dyna_q(self):
for _ in range(self.dyna):
s, a = random.choice(self.state_action_list)
r, s_prime = self.model[(s, a)]
self._update_q(s, a, r, s_prime)
def author(self):
return 'felixm'
if __name__ == "__main__":
q = QLearner(verbose=True, dyna=2)
q.querysetstate(2)
q.query(15, 1.00)
q.querysetstate(15)
q.query(17, 0.10)

View File

@@ -0,0 +1,387 @@
"""MC3-P2: Q-learning & Dyna - grading script.
Usage:
- Switch to a student feedback directory first (will write "points.txt" and "comments.txt" in pwd).
- Run this script with both ml4t/ and student solution in PYTHONPATH, e.g.:
PYTHONPATH=ml4t:MC1-P2/jdoe7 python ml4t/mc2_p1_grading/grade_marketsim.py
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: tb34 (replace with your User ID)
GT ID: 900897987 (replace with your GT ID)
"""
import pytest
from grading.grading import grader, GradeResult, run_with_timeout, IncorrectOutput
import os
import sys
import traceback as tb
import datetime as dt
import random
import numpy as np
import pandas as pd
from collections import namedtuple
import util
# Student modules to import
main_code = "QLearner" # module name to import
robot_qlearning_testing_seed=1490652871
QLearningTestCase = namedtuple('QLearning', ['description', 'group','world_file','best_reward','median_reward','max_time','points'])
qlearning_test_cases = [
QLearningTestCase(
description="World 1",
group='nodyna',
world_file='world01.csv',
best_reward=-17,
median_reward=-29.5,
max_time=2,
points=9.5
),
QLearningTestCase(
description="World 2",
group='nodyna',
world_file='world02.csv',
best_reward=-14,
median_reward=-19,
max_time=2,
points=9.5
),
QLearningTestCase(
description="World 4",
group='nodyna',
world_file='world04.csv',
best_reward=-24,
median_reward=-33,
max_time=2,
points=9.5
),
QLearningTestCase(
description="World 6",
group='nodyna',
world_file='world06.csv',
best_reward=-16,
median_reward=-23.5,
max_time=2,
points=9.5
),
QLearningTestCase(
description="World 7",
group='nodyna',
world_file='world07.csv',
best_reward=-14,
median_reward=-26,
max_time=2,
points=9.5
),
QLearningTestCase(
description="World 8",
group='nodyna',
world_file='world08.csv',
best_reward=-14,
median_reward=-19,
max_time=2,
points=9.5
),
QLearningTestCase(
description="World 9",
group='nodyna',
world_file='world09.csv',
best_reward=-15,
median_reward=-20,
max_time=2,
points=9.5
),
QLearningTestCase(
description="World 10",
group='nodyna',
world_file='world10.csv',
best_reward=-28,
median_reward=-42,
max_time=2,
points=9.5
),
# Dyna test cases
QLearningTestCase(
description="World 1, dyna=200",
group='dyna',
world_file='world01.csv',
best_reward=-12,
median_reward=-29.5,
max_time=10,
points=2.5
),
QLearningTestCase(
description="World 2, dyna=200",
group='dyna',
world_file='world02.csv',
best_reward=-14,
median_reward=-19,
max_time=10,
points=2.5
),
QLearningTestCase(
description="Author check",
group='author',
world_file='world01.csv',
best_reward=0,
median_reward=0,
max_time=10,
points=0
),
]
max_points = 100.0
html_pre_block = True # surround comments with HTML <pre> tag (for T-Square comments field)
# Test functon(s)
@pytest.mark.parametrize("description,group,world_file,best_reward,median_reward,max_time,points", qlearning_test_cases)
def test_qlearning(description, group, world_file, best_reward, median_reward, max_time, points, grader):
points_earned = 0.0 # initialize points for this test case
try:
incorrect = True
if not 'QLearner' in globals():
import importlib
m = importlib.import_module('QLearner')
globals()['QLearner'] = m
# Unpack test case
world = np.array([list(map(float,s.strip().split(','))) for s in util.get_robot_world_file(world_file).readlines()])
student_reward = None
student_author = None
msgs = []
if group=='nodyna':
def timeoutwrapper_nodyna():
# Note: the following will NOT be commented durring final grading
# random.seed(robot_qlearning_testing_seed)
# np.random.seed(robot_qlearning_testing_seed)
learner = QLearner.QLearner(num_states=100,\
num_actions = 4, \
alpha = 0.2, \
gamma = 0.9, \
rar = 0.98, \
radr = 0.999, \
dyna = 0, \
verbose=False)
return qltest(worldmap=world,iterations=500,max_steps=10000,learner=learner,verbose=False)
student_reward = run_with_timeout(timeoutwrapper_nodyna,max_time,(),{})
incorrect = False
if student_reward < 1.5*median_reward:
incorrect = True
msgs.append(" Reward too low, expected %s, found %s"%(median_reward,student_reward))
elif group=='dyna':
def timeoutwrapper_dyna():
# Note: the following will NOT be commented durring final grading
# random.seed(robot_qlearning_testing_seed)
# np.random.seed(robot_qlearning_testing_seed)
learner = QLearner.QLearner(num_states=100,\
num_actions = 4, \
alpha = 0.2, \
gamma = 0.9, \
rar = 0.5, \
radr = 0.99, \
dyna = 200, \
verbose=False)
return qltest(worldmap=world,iterations=50,max_steps=10000,learner=learner,verbose=False)
student_reward = run_with_timeout(timeoutwrapper_dyna,max_time,(),{})
incorrect = False
if student_reward < 1.5*median_reward:
incorrect = True
msgs.append(" Reward too low, expected %s, found %s"%(median_reward,student_reward))
elif group=='author':
points_earned = -20
def timeoutwrapper_author():
# Note: the following will NOT be commented durring final grading
# random.seed(robot_qlearning_testing_seed)
# np.random.seed(robot_qlearning_testing_seed)
learner = QLearner.QLearner(num_states=100,\
num_actions = 4, \
alpha = 0.2, \
gamma = 0.9, \
rar = 0.98, \
radr = 0.999, \
dyna = 0, \
verbose=False)
return learner.author()
student_author = run_with_timeout(timeoutwrapper_author,max_time,(),{})
student_reward = best_reward+1
incorrect = False
if (student_author is None) or (student_author=='tb34'):
incorrect = True
msgs.append(" author() method not implemented correctly. Found {}".format(student_author))
else:
points_earned = points
if (not incorrect):
points_earned += points
if incorrect:
inputs_str = " group: {}\n" \
" world_file: {}\n"\
" median_reward: {}\n".format(group, world_file, median_reward)
raise IncorrectOutput("Test failed on one or more output criteria.\n Inputs:\n{}\n Failures:\n{}".format(inputs_str, "\n".join(msgs)))
except Exception as e:
# Test result: failed
msg = "Test case description: {}\n".format(description)
# Generate a filtered stacktrace, only showing erroneous lines in student file(s)
tb_list = tb.extract_tb(sys.exc_info()[2])
for i in range(len(tb_list)):
row = tb_list[i]
tb_list[i] = (os.path.basename(row[0]), row[1], row[2], row[3]) # show only filename instead of long absolute path
tb_list = [row for row in tb_list if row[0] in ['QLearner.py','StrategyLearner.py']]
if tb_list:
msg += "Traceback:\n"
msg += ''.join(tb.format_list(tb_list)) # contains newlines
elif 'grading_traceback' in dir(e):
msg += "Traceback:\n"
msg += ''.join(tb.format_list(e.grading_traceback))
msg += "{}: {}".format(e.__class__.__name__, str(e))
# Report failure result to grader, with stacktrace
grader.add_result(GradeResult(outcome='failed', points=points_earned, msg=msg))
raise
else:
# Test result: passed (no exceptions)
grader.add_result(GradeResult(outcome='passed', points=points_earned, msg=None))
def getrobotpos(data):
R = -999
C = -999
for row in range(0, data.shape[0]):
for col in range(0, data.shape[1]):
if data[row,col] == 2:
C = col
R = row
if (R+C)<0:
print("warning: start location not defined")
return R, C
# find where the goal is in the map
def getgoalpos(data):
R = -999
C = -999
for row in range(0, data.shape[0]):
for col in range(0, data.shape[1]):
if data[row,col] == 3:
C = col
R = row
if (R+C)<0:
print("warning: goal location not defined")
return (R, C)
# move the robot and report reward
def movebot(data,oldpos,a):
testr, testc = oldpos
randomrate = 0.20 # how often do we move randomly
quicksandreward = -100 # penalty for stepping on quicksand
# decide if we're going to ignore the action and
# choose a random one instead
if random.uniform(0.0, 1.0) <= randomrate: # going rogue
a = random.randint(0,3) # choose the random direction
# update the test location
if a == 0: #north
testr = testr - 1
elif a == 1: #east
testc = testc + 1
elif a == 2: #south
testr = testr + 1
elif a == 3: #west
testc = testc - 1
reward = -1 # default reward is negative one
# see if it is legal. if not, revert
if testr < 0: # off the map
testr, testc = oldpos
elif testr >= data.shape[0]: # off the map
testr, testc = oldpos
elif testc < 0: # off the map
testr, testc = oldpos
elif testc >= data.shape[1]: # off the map
testr, testc = oldpos
elif data[testr, testc] == 1: # it is an obstacle
testr, testc = oldpos
elif data[testr, testc] == 5: # it is quicksand
reward = quicksandreward
data[testr, testc] = 6 # mark the event
elif data[testr, testc] == 6: # it is still quicksand
reward = quicksandreward
data[testr, testc] = 6 # mark the event
elif data[testr, testc] == 3: # it is the goal
reward = 1 # for reaching the goal
return (testr, testc), reward #return the new, legal location
# convert the location to a single integer
def discretize(pos):
return pos[0]*10 + pos[1]
def qltest(worldmap, iterations, max_steps, learner, verbose):
# each iteration involves one trip to the goal
startpos = getrobotpos(worldmap) #find where the robot starts
goalpos = getgoalpos(worldmap) #find where the goal is
# max_reward = -float('inf')
all_rewards = list()
for iteration in range(1,iterations+1):
total_reward = 0
data = worldmap.copy()
robopos = startpos
state = discretize(robopos) #convert the location to a state
action = learner.querysetstate(state) #set the state and get first action
count = 0
while (robopos != goalpos) & (count<max_steps):
#move to new location according to action and then get a new action
newpos, stepreward = movebot(data,robopos,action)
if newpos == goalpos:
r = 1 # reward for reaching the goal
else:
r = stepreward # negative reward for not being at the goal
state = discretize(newpos)
action = learner.query(state,r)
if data[robopos] != 6:
data[robopos] = 4 # mark where we've been for map printing
if data[newpos] != 6:
data[newpos] = 2 # move to new location
robopos = newpos # update the location
#if verbose: time.sleep(1)
total_reward += stepreward
count = count + 1
if verbose and (count == max_steps):
print("timeout")
if verbose: printmap(data)
if verbose: print(f"{iteration} {total_reward}")
# if max_reward < total_reward:
# max_reward = total_reward
all_rewards.append(total_reward)
# return max_reward
return np.median(all_rewards)
if __name__ == "__main__":
pytest.main(["-s", __file__])

View File

@@ -0,0 +1,223 @@
"""
Test a Q Learner in a navigation problem. (c) 2015 Tucker Balch
2016-10-20 Added "quicksand" and uncertain actions.
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: tb34 (replace with your User ID)
GT ID: 900897987 (replace with your GT ID)
"""
import numpy as np
import random as rand
import time
import math
import QLearner as ql
# print out the map
def printmap(data):
print("--------------------")
for row in range(0, data.shape[0]):
for col in range(0, data.shape[1]):
if data[row,col] == 0: # Empty space
print(" ", end=' ')
if data[row,col] == 1: # Obstacle
print("O", end=' ')
if data[row,col] == 2: # El roboto
print("*", end=' ')
if data[row,col] == 3: # Goal
print("X", end=' ')
if data[row,col] == 4: # Trail
print(".", end=' ')
if data[row,col] == 5: # Quick sand
print("~", end=' ')
if data[row,col] == 6: # Stepped in quicksand
print("@", end=' ')
print()
print("--------------------")
# find where the robot is in the map
def getrobotpos(data):
R = -999
C = -999
for row in range(0, data.shape[0]):
for col in range(0, data.shape[1]):
if data[row,col] == 2:
C = col
R = row
if (R+C)<0:
print("warning: start location not defined")
return R, C
# find where the goal is in the map
def getgoalpos(data):
R = -999
C = -999
for row in range(0, data.shape[0]):
for col in range(0, data.shape[1]):
if data[row,col] == 3:
C = col
R = row
if (R+C)<0:
print("warning: goal location not defined")
return (R, C)
# move the robot and report reward
def movebot(data,oldpos,a):
testr, testc = oldpos
randomrate = 0.20 # how often do we move randomly
quicksandreward = -100 # penalty for stepping on quicksand
# decide if we're going to ignore the action and
# choose a random one instead
if rand.uniform(0.0, 1.0) <= randomrate: # going rogue
a = rand.randint(0,3) # choose the random direction
# update the test location
if a == 0: #north
testr = testr - 1
elif a == 1: #east
testc = testc + 1
elif a == 2: #south
testr = testr + 1
elif a == 3: #west
testc = testc - 1
reward = -1 # default reward is negative one
# see if it is legal. if not, revert
if testr < 0: # off the map
testr, testc = oldpos
elif testr >= data.shape[0]: # off the map
testr, testc = oldpos
elif testc < 0: # off the map
testr, testc = oldpos
elif testc >= data.shape[1]: # off the map
testr, testc = oldpos
elif data[testr, testc] == 1: # it is an obstacle
testr, testc = oldpos
elif data[testr, testc] == 5: # it is quicksand
reward = quicksandreward
data[testr, testc] = 6 # mark the event
elif data[testr, testc] == 6: # it is still quicksand
reward = quicksandreward
data[testr, testc] = 6 # mark the event
elif data[testr, testc] == 3: # it is the goal
reward = 1 # for reaching the goal
return (testr, testc), reward #return the new, legal location
# convert the location to a single integer
def discretize(pos):
return pos[0]*10 + pos[1]
def test(map, epochs, learner, verbose):
# each epoch involves one trip to the goal
startpos = getrobotpos(map) #find where the robot starts
goalpos = getgoalpos(map) #find where the goal is
scores = np.zeros((epochs,1))
for epoch in range(1,epochs+1):
total_reward = 0
data = map.copy()
robopos = startpos
state = discretize(robopos) #convert the location to a state
action = learner.querysetstate(state) #set the state and get first action
count = 0
while (robopos != goalpos) & (count<10000):
#move to new location according to action and then get a new action
newpos, stepreward = movebot(data,robopos,action)
if newpos == goalpos:
r = 1 # reward for reaching the goal
else:
r = stepreward # negative reward for not being at the goal
state = discretize(newpos)
action = learner.query(state,r)
if data[robopos] != 6:
data[robopos] = 4 # mark where we've been for map printing
if data[newpos] != 6:
data[newpos] = 2 # move to new location
robopos = newpos # update the location
#if verbose: time.sleep(1)
total_reward += stepreward
count = count + 1
if count == 100000:
print("timeout")
if verbose: printmap(data)
if verbose: print(f"{epoch}, {total_reward}")
scores[epoch-1,0] = total_reward
return np.median(scores)
# run the code to test a learner
def test_code():
verbose = False # print lots of debug stuff if True
# read in the map
filename = 'testworlds/world01.csv'
inf = open(filename)
data = np.array([list(map(float,s.strip().split(','))) for s in inf.readlines()])
originalmap = data.copy() #make a copy so we can revert to the original map later
if verbose: printmap(data)
rand.seed(5)
######## run non-dyna test ########
learner = ql.QLearner(num_states=100,\
num_actions = 4, \
alpha = 0.2, \
gamma = 0.9, \
rar = 0.98, \
radr = 0.999, \
dyna = 0, \
verbose=False) #initialize the learner
epochs = 500
total_reward = test(data, epochs, learner, verbose)
print(f"{epochs}, median total_reward {total_reward}")
print()
non_dyna_score = total_reward
######## run dyna test ########
learner = ql.QLearner(num_states=100,\
num_actions = 4, \
alpha = 0.2, \
gamma = 0.9, \
rar = 0.5, \
radr = 0.99, \
dyna = 200, \
verbose=False) #initialize the learner
epochs = 50
data = originalmap.copy()
total_reward = test(data, epochs, learner, verbose)
print(f"{epochs}, median total_reward {total_reward}")
dyna_score = total_reward
print()
print()
print(f"results for {filename}")
print(f"non_dyna_score: {non_dyna_score}")
print(f"dyna_score : {dyna_score}")
if __name__=="__main__":
test_code()

View File

@@ -0,0 +1,10 @@
3,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0
0,0,1,1,1,1,1,0,0,0
0,5,1,0,0,0,1,0,0,0
0,5,1,0,0,0,1,0,0,0
0,0,1,0,0,0,1,0,0,0
0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0
0,0,0,0,2,0,0,0,0,0
1 3 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0 0
4 0 0 1 1 1 1 1 0 0 0
5 0 5 1 0 0 0 1 0 0 0
6 0 5 1 0 0 0 1 0 0 0
7 0 0 1 0 0 0 1 0 0 0
8 0 0 0 0 0 0 0 0 0 0
9 0 0 0 0 0 0 0 0 0 0
10 0 0 0 0 2 0 0 0 0 0

View File

@@ -0,0 +1,10 @@
0,1,0,1,0,0,0,0,0,0
0,1,0,1,0,0,0,0,0,0
0,1,0,0,0,0,0,0,0,0
0,1,0,1,1,1,1,1,1,1
2,1,0,1,0,0,0,0,0,0
0,1,0,1,0,0,1,0,0,3
0,0,0,1,0,0,1,0,0,0
0,1,0,0,0,0,1,1,1,1
0,1,0,1,0,0,0,0,0,0
0,0,0,1,0,0,0,0,0,0
1 0 1 0 1 0 0 0 0 0 0
2 0 1 0 1 0 0 0 0 0 0
3 0 1 0 0 0 0 0 0 0 0
4 0 1 0 1 1 1 1 1 1 1
5 2 1 0 1 0 0 0 0 0 0
6 0 1 0 1 0 0 1 0 0 3
7 0 0 0 1 0 0 1 0 0 0
8 0 1 0 0 0 0 1 1 1 1
9 0 1 0 1 0 0 0 0 0 0
10 0 0 0 1 0 0 0 0 0 0

View File

@@ -0,0 +1,10 @@
0,0,0,1,0,0,0,1,0,3
0,1,0,1,0,1,0,1,0,0
0,1,0,1,0,1,0,1,0,1
0,1,0,1,0,1,0,1,0,0
0,1,0,1,0,1,0,1,1,0
0,1,0,1,0,1,0,1,0,0
0,1,0,1,0,1,0,1,0,1
0,1,0,1,0,1,0,1,0,0
0,1,0,1,0,1,0,1,1,0
2,1,0,0,0,1,0,0,0,0
1 0 0 0 1 0 0 0 1 0 3
2 0 1 0 1 0 1 0 1 0 0
3 0 1 0 1 0 1 0 1 0 1
4 0 1 0 1 0 1 0 1 0 0
5 0 1 0 1 0 1 0 1 1 0
6 0 1 0 1 0 1 0 1 0 0
7 0 1 0 1 0 1 0 1 0 1
8 0 1 0 1 0 1 0 1 0 0
9 0 1 0 1 0 1 0 1 1 0
10 2 1 0 0 0 1 0 0 0 0

View File

@@ -0,0 +1,10 @@
0,0,0,0,0,1,0,1,0,3
0,0,0,0,0,1,0,1,0,0
0,0,0,1,0,1,0,1,0,1
0,0,0,1,0,1,0,1,0,0
0,0,0,1,0,0,0,1,1,0
2,0,0,1,1,1,0,1,0,0
0,0,0,1,0,1,0,0,0,1
0,0,5,0,0,1,0,1,0,0
0,0,1,1,1,1,0,1,1,0
0,0,0,0,0,1,0,0,0,0
1 0 0 0 0 0 1 0 1 0 3
2 0 0 0 0 0 1 0 1 0 0
3 0 0 0 1 0 1 0 1 0 1
4 0 0 0 1 0 1 0 1 0 0
5 0 0 0 1 0 0 0 1 1 0
6 2 0 0 1 1 1 0 1 0 0
7 0 0 0 1 0 1 0 0 0 1
8 0 0 5 0 0 1 0 1 0 0
9 0 0 1 1 1 1 0 1 1 0
10 0 0 0 0 0 1 0 0 0 0

View File

@@ -0,0 +1,10 @@
0,1,0,0,0,1,0,1,0,3
1,0,0,0,0,0,1,0,0,0
0,0,1,1,0,1,0,1,0,1
1,1,0,0,0,0,1,0,0,0
0,0,0,0,0,0,0,0,1,0
0,1,0,1,1,0,0,1,0,0
1,0,0,0,0,1,0,0,0,1
0,0,0,0,0,0,0,1,0,0
1,0,1,0,0,1,0,0,1,0
2,0,0,0,0,0,1,0,0,0
1 0 1 0 0 0 1 0 1 0 3
2 1 0 0 0 0 0 1 0 0 0
3 0 0 1 1 0 1 0 1 0 1
4 1 1 0 0 0 0 1 0 0 0
5 0 0 0 0 0 0 0 0 1 0
6 0 1 0 1 1 0 0 1 0 0
7 1 0 0 0 0 1 0 0 0 1
8 0 0 0 0 0 0 0 1 0 0
9 1 0 1 0 0 1 0 0 1 0
10 2 0 0 0 0 0 1 0 0 0

View File

@@ -0,0 +1,10 @@
0,1,0,0,0,1,0,1,0,2
1,0,0,0,0,0,1,0,0,0
0,0,1,1,0,1,0,1,0,1
1,1,0,0,0,0,1,0,0,0
0,0,0,0,0,0,0,0,1,0
0,1,0,1,1,0,0,1,0,0
1,0,0,0,0,1,0,0,0,1
0,0,0,0,0,0,0,1,0,0
1,0,1,0,0,1,0,0,1,0
3,0,0,0,0,0,1,0,0,0
1 0 1 0 0 0 1 0 1 0 2
2 1 0 0 0 0 0 1 0 0 0
3 0 0 1 1 0 1 0 1 0 1
4 1 1 0 0 0 0 1 0 0 0
5 0 0 0 0 0 0 0 0 1 0
6 0 1 0 1 1 0 0 1 0 0
7 1 0 0 0 0 1 0 0 0 1
8 0 0 0 0 0 0 0 1 0 0
9 1 0 1 0 0 1 0 0 1 0
10 3 0 0 0 0 0 1 0 0 0

View File

@@ -0,0 +1,10 @@
0,0,0,0,2,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0
0,0,1,1,1,1,1,0,0,0
0,0,1,0,3,0,1,0,0,0
0,0,1,0,0,0,1,0,0,0
0,0,1,0,0,0,1,0,0,0
0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0
1 0 0 0 0 2 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0 0
4 0 0 1 1 1 1 1 0 0 0
5 0 0 1 0 3 0 1 0 0 0
6 0 0 1 0 0 0 1 0 0 0
7 0 0 1 0 0 0 1 0 0 0
8 0 0 0 0 0 0 0 0 0 0
9 0 0 0 0 0 0 0 0 0 0
10 0 0 0 0 0 0 0 0 0 0

View File

@@ -0,0 +1,10 @@
0,1,0,1,0,0,0,0,0,0
0,1,0,1,0,0,0,0,0,0
0,1,0,0,0,0,0,0,0,0
0,1,0,1,1,1,1,1,1,1
3,1,0,1,0,0,0,0,0,0
0,1,0,1,0,0,1,0,0,2
0,0,0,1,0,0,1,0,0,0
0,1,0,0,0,0,1,1,1,1
0,1,0,1,0,0,0,0,0,0
0,0,0,1,0,0,0,0,0,0
1 0 1 0 1 0 0 0 0 0 0
2 0 1 0 1 0 0 0 0 0 0
3 0 1 0 0 0 0 0 0 0 0
4 0 1 0 1 1 1 1 1 1 1
5 3 1 0 1 0 0 0 0 0 0
6 0 1 0 1 0 0 1 0 0 2
7 0 0 0 1 0 0 1 0 0 0
8 0 1 0 0 0 0 1 1 1 1
9 0 1 0 1 0 0 0 0 0 0
10 0 0 0 1 0 0 0 0 0 0

View File

@@ -0,0 +1,10 @@
0,0,0,0,0,2,0,0,0,0
0,0,0,1,0,0,1,0,0,0
0,1,0,1,0,0,1,0,1,0
0,1,0,1,1,1,1,0,1,0
0,1,0,0,1,0,0,0,1,0
0,1,1,1,1,1,1,1,1,0
0,0,0,0,1,0,0,0,0,0
0,0,0,0,1,0,0,1,0,0
0,0,0,0,1,0,0,1,0,0
0,0,0,0,1,3,0,1,0,0
1 0 0 0 0 0 2 0 0 0 0
2 0 0 0 1 0 0 1 0 0 0
3 0 1 0 1 0 0 1 0 1 0
4 0 1 0 1 1 1 1 0 1 0
5 0 1 0 0 1 0 0 0 1 0
6 0 1 1 1 1 1 1 1 1 0
7 0 0 0 0 1 0 0 0 0 0
8 0 0 0 0 1 0 0 1 0 0
9 0 0 0 0 1 0 0 1 0 0
10 0 0 0 0 1 3 0 1 0 0

View File

@@ -0,0 +1,10 @@
0,0,0,0,0,0,0,0,0,0
0,0,0,1,0,0,1,0,0,0
0,1,0,1,0,0,1,0,1,0
0,1,0,1,1,1,1,0,1,0
0,1,0,0,1,0,0,0,1,0
0,1,1,1,1,0,1,1,1,0
0,0,0,0,1,0,0,0,0,0
0,0,0,0,1,0,0,1,0,0
0,0,0,0,1,0,0,1,0,0
0,0,0,2,1,3,0,1,0,0
1 0 0 0 0 0 0 0 0 0 0
2 0 0 0 1 0 0 1 0 0 0
3 0 1 0 1 0 0 1 0 1 0
4 0 1 0 1 1 1 1 0 1 0
5 0 1 0 0 1 0 0 0 1 0
6 0 1 1 1 1 0 1 1 1 0
7 0 0 0 0 1 0 0 0 0 0
8 0 0 0 0 1 0 0 1 0 0
9 0 0 0 0 1 0 0 1 0 0
10 0 0 0 2 1 3 0 1 0 0

View File

@@ -0,0 +1,77 @@
import numpy as np
class AbstractTreeLearner:
LEAF = -1
NA = -1
def author(self):
return 'felixm' # replace tb34 with your Georgia Tech username
def create_node(self, factor, split_value, left, right):
return np.array([(factor, split_value, left, right), ],
dtype='|i4, f4, i4, i4')
def query_point(self, point):
node_index = 0
while self.rel_tree[node_index][0] != self.LEAF:
node = self.rel_tree[node_index]
split_factor = node[0]
split_value = node[1]
if point[split_factor] <= split_value:
# Recurse into left sub-tree.
node_index += node[2]
else:
node_index += node[3]
v = self.rel_tree[node_index][1]
return v
def query(self, points):
"""
@summary: Estimate a set of test points given the model we built.
@param points: should be a numpy array with each row corresponding to a specific query.
@returns the estimated values according to the saved model.
"""
query_point = lambda p: self.query_point(p)
r = np.apply_along_axis(query_point, 1, points)
return r
def build_tree(self, xs, y):
"""
@summary: Build a decision tree from the training data.
@param dataX: X values of data to add
@param dataY: the Y training values
"""
assert(xs.shape[0] == y.shape[0])
assert(xs.shape[0] > 0) # If this is 0 something went wrong.
if xs.shape[0] <= self.leaf_size:
value = np.mean(y)
if value < -0.2:
value = -1
elif value > 0.2:
value = 1
else:
value = 0
return self.create_node(self.LEAF, value, self.NA, self.NA)
if np.all(y[0] == y):
return self.create_node(self.LEAF, y[0], self.NA, self.NA)
i, split_value = self.get_i_and_split_value(xs, y)
select_l = xs[:, i] <= split_value
select_r = xs[:, i] > split_value
lt = self.build_tree(xs[select_l], y[select_l])
rt = self.build_tree(xs[select_r], y[select_r])
root = self.create_node(i, split_value, 1, lt.shape[0] + 1)
root = np.concatenate([root, lt, rt])
return root
def addEvidence(self, data_x, data_y):
"""
@summary: Add training data to learner
@param dataX: X values of data to add
@param dataY: the Y training values
"""
self.rel_tree = self.build_tree(data_x, data_y)

View File

@@ -0,0 +1,34 @@
import pandas as pd
import util as ut
import datetime as dt
class BenchmarkStrategy:
def __init__(self, verbose=False, impact=0.0, commission=0.0):
self.verbose = verbose
self.impact = impact
self.commission = commission
def addEvidence(self, symbol=0, sd=0, ed=0, sv=0):
"""Keep this so that API is valid."""
pass
def testPolicy(self, symbol="IBM",
sd=dt.datetime(2009, 1, 1),
ed=dt.datetime(2010, 1, 1),
sv=10000):
"""Benchmark is to buy 1000 shares and hold."""
dates = pd.date_range(sd, ed)
prices = ut.get_data([symbol], dates) # automatically adds SPY
orders = pd.DataFrame(index=prices.index)
orders["Symbol"] = symbol
orders["Order"] = ""
orders["Shares"] = 0
orders.iloc[0] = [symbol, "BUY", 1000]
orders.iloc[-1] = [symbol, "SELL", -1000]
if self.verbose:
print(type(orders)) # it better be a DataFrame!
print(orders)
return orders

View File

@@ -0,0 +1,114 @@
import datetime as dt
import pandas as pd
import util
import indicators
class ManualStrategy:
def __init__(self, verbose=False, impact=0.0, commission=0.0):
self.verbose = verbose
self.impact = impact
self.commission = commission
# this method should create a QLearner, and train it for trading
def addEvidence(self, symbol="IBM",
sd=dt.datetime(2008, 1, 1),
ed=dt.datetime(2009, 1, 1),
sv=10000):
# add your code to do learning here
# example usage of the old backward compatible util function
syms = [symbol]
dates = pd.date_range(sd, ed)
prices_all = util.get_data(syms, dates) # automatically adds SPY
prices = prices_all[syms] # only portfolio symbols
# prices_SPY = prices_all['SPY'] # only SPY, for comparison later
if self.verbose:
print(prices)
# example use with new colname
# automatically adds SPY
volume_all = util.get_data(syms, dates, colname="Volume")
volume = volume_all[syms] # only portfolio symbols
# volume_SPY = volume_all['SPY'] # only SPY, for comparison later
if self.verbose:
print(volume)
def macd_strat(self, macd, orders):
"""Strategy based on MACD cross."""
def strat(ser):
m = macd.loc[ser.index]
prev_macd, prev_signal, _ = m.iloc[0]
cur_macd, cur_signal, _ = m.iloc[1]
shares = 0
if cur_macd < -1 and prev_macd < prev_signal \
and cur_macd > cur_signal:
if self.holding == 0:
shares = 1000
elif self.holding == -1000:
shares = 2000
elif cur_macd > 1 and prev_macd > prev_signal \
and cur_macd < cur_signal:
if self.holding == 0:
shares = -1000
elif self.holding == 1000:
shares = -2000
self.holding += shares
return shares
orders['Shares'] = orders['Shares'].rolling(2).apply(strat)
def three_indicator_strat(self, macd, rsi, price_sma, orders):
"""Strategy based on three indicators. Thresholds selected based on
scatter plots."""
def strat(row):
shares = 0
_, _, macd_diff = macd.loc[row.name]
cur_rsi = rsi.loc[row.name][0]
cur_price_sma = price_sma.loc[row.name][0]
if self.holding == -1000 and cur_price_sma < 0.9:
shares = 2000
elif self.holding == 0 and cur_price_sma < 0.9:
shares = 1000
elif self.holding == -1000 and cur_rsi > 80:
shares = 2000
elif self.holding == 0 and cur_rsi > 80:
shares = 1000
elif self.holding == -1000 and macd_diff < -0.5:
shares = 2000
elif self.holding == 0 and macd_diff < -0.5:
shares = 1000
elif self.holding == 1000 and cur_price_sma > 1.1:
shares = -2000
elif self.holding == 0 and cur_price_sma > 1.1:
shares = -1000
self.holding += shares
return shares
orders['Shares'] = orders.apply(strat, axis=1)
def testPolicy(self, symbol="IBM",
sd=dt.datetime(2009, 1, 1),
ed=dt.datetime(2010, 1, 1),
sv=10000, macd_strat=False):
self.holding = 0
df = util.get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
orders = pd.DataFrame(index=df.index)
orders["Symbol"] = symbol
orders["Order"] = ""
orders["Shares"] = 0
macd = indicators.macd(df, symbol)
rsi = indicators.rsi(df, symbol)
price_sma = indicators.price_sma(df, symbol, [8])
if macd_strat:
self.macd_strat(macd, orders)
else:
self.three_indicator_strat(macd, rsi, price_sma, orders)
return orders

View File

@@ -0,0 +1,169 @@
import datetime as dt
import pandas as pd
import util
import indicators
from qlearning_robot.QLearner import QLearner as Learner
from dataclasses import dataclass
@dataclass
class Holding:
cash: int
shares: int
equity: int
class QLearner(object):
def __init__(self, verbose=False, impact=0.0, commission=0.0, testing=False, n_bins=5):
self.verbose = verbose
self.impact = impact
self.commission = commission
self.testing = testing # Decides which type of order df to return.
self.indicators = ['macd_diff', 'rsi', 'price_sma_8']
self.n_bins = n_bins
self.bins = {}
self.num_states = self.get_num_states()
self.num_actions = 3 # buy, sell, hold
self.learner = Learner(self.num_states, self.num_actions)
def row_to_state(self, holding, df_row):
"""Transforms a row into a state value."""
holding = (holding + 1000) // 1000
assert(holding in [0, 1, 2])
# For each indicator that goes into the state the interval becomes
# smaller based on how many bins the indicator has. The first
# 'indicator' is the information about how many shares we are currently
# holding. So for example, if I have 450 states then the intervall (aka
# remaining_states) is 150 because there are three values for holding:
# holding = 0 -> state = 0 * 150 = 0
# holding = 1 -> state = 1 * 150 = 150
# holding = 2 -> state = 2 * 150 = 300
remaining_states = self.num_states // 3
state = holding * remaining_states
for indicator in self.indicators:
value = df_row[indicator]
bin_n = self.indicator_value_to_bin(indicator, value)
remaining_states //= self.n_bins
state += bin_n * remaining_states
return state
def indicator_value_to_bin(self, indicator, value):
for i, upper_bound in enumerate(self.bins[indicator]):
if value < upper_bound:
return i
return i + 1
def add_indicators(self, df, symbol):
"""Add indicators for learning to DataFrame."""
for indicator in self.indicators:
if indicator == "macd_diff":
indicators.macd(df, symbol)
df.drop(columns=["macd", "macd_signal"], inplace=True)
elif indicator == "rsi":
indicators.rsi(df, symbol)
elif indicator.startswith("price_sma_"):
period = int(indicator.replace("price_sma_", ""))
indicators.price_sma(df, symbol, [period])
df.drop(columns=["SPY"], inplace=True)
df.dropna(inplace=True)
def bin_indicators(self, df):
"""Create bins for indicators."""
for indicator in self.indicators:
ser, bins = pd.qcut(df[indicator], self.n_bins, retbins=True)
self.bins[indicator] = bins[1:self.n_bins]
def get_num_states(self):
"""Return the total num of states."""
num_states = 3 # Three states holding (1000, 0, -1000)
for _ in self.indicators:
num_states *= self.n_bins
return num_states
def handle_order(self, action, holding, adj_closing_price):
shares = 0
if action == 0: # buy
if holding.shares == 0 or holding.shares == -1000:
shares = 1000
elif action == 1: # sell
if holding.shares== 0 or holding.shares == 1000:
shares = -1000
elif action == 2: # hold
shares = 0
cost = shares * adj_closing_price
if shares != 0:
# Charge commission and deduct impact penalty
holding.cash -= self.commission
holding.cash -= (self.impact * adj_closing_price * abs(shares))
holding.cash -= cost
holding.shares += shares
holding.equity = holding.cash + holding.shares * adj_closing_price
def get_reward(self, equity, new_equity):
if new_equity > equity:
return 1
return -1
def train(self, df, symbol, sv):
holding = Holding(sv, 0, sv)
row = df.iloc[0]
state = self.row_to_state(holding.shares, row)
action = self.learner.querysetstate(state)
adj_closing_price = row[symbol]
equity = holding.equity
self.handle_order(action, holding, adj_closing_price)
for index, row in df.iloc[1:].iterrows():
adj_closing_price = row[symbol]
new_equity = holding.cash + holding.shares * adj_closing_price
r = self.get_reward(equity, new_equity)
s_prime = self.row_to_state(holding.shares, row)
a = self.learner.query(s_prime, r)
equity = new_equity
self.handle_order(a, holding, adj_closing_price)
if self.verbose:
print(f"{holding=} {s_prime=} {r=} {a=}")
def addEvidence(self, symbol="IBM", sd=dt.datetime(2008, 1, 1), ed=dt.datetime(2009, 1, 1), sv=10000):
df = util.get_data([symbol], pd.date_range(sd, ed))
self.add_indicators(df, symbol)
self.bin_indicators(df)
for _ in range(15):
self.train(df, symbol, sv)
def testPolicy(self, symbol="IBM", sd=dt.datetime(2009, 1, 1), ed=dt.datetime(2010, 1, 1), sv=10000):
df = util.get_data([symbol], pd.date_range(sd, ed))
orders = pd.DataFrame(index=df.index)
orders["Symbol"] = symbol
orders["Order"] = ""
orders["Shares"] = 0
shares = orders["Shares"]
self.add_indicators(df, symbol)
holding = 0
for index, row in df.iterrows():
state = self.row_to_state(holding, row)
action = self.learner.querysetstate(state)
if action == 0: # buy
if holding == 0 or holding == -1000:
holding += 1000
orders.loc[index, "Shares"] = 1000
elif action == 1: # sell
if holding == 0 or holding == 1000:
holding -= 1000
orders.loc[index, "Shares"] = -1000
elif action == 2: # hold
pass
if self.testing:
return orders
else:
return orders[["Shares"]]

View File

@@ -0,0 +1,30 @@
import numpy as np
from AbstractTreeLearner import AbstractTreeLearner
class RTLearner(AbstractTreeLearner):
def __init__(self, leaf_size = 1, verbose = False):
self.leaf_size = leaf_size
self.verbose = verbose
def get_i_and_split_value(self, xs, y):
"""
@summary: Pick a random i and split value.
Make sure that not all X are the same for i and also pick
different values to average the split_value from.
"""
i = np.random.randint(0, xs.shape[1])
while np.all(xs[0,i] == xs[:,i]):
i = np.random.randint(0, xs.shape[1])
# I don't know about the performance of this, but at least it
# terminates reliably. If the two elements are the same something is
# wrong.
a = np.array(list(set(xs[:, i])))
r1, r2 = np.random.choice(a, size = 2, replace = False)
assert(r1 != r2)
split_value = (r1 + r2) / 2.0
return i, split_value

View File

@@ -0,0 +1,106 @@
import datetime as dt
import pandas as pd
import util
import indicators
from RTLearner import RTLearner
class StrategyLearner(object):
def __init__(self, verbose=False, impact=0.0, commission=0.0, testing=False):
self.verbose = verbose
self.impact = impact
self.commission = commission
self.testing = testing
def _get_volume(self):
"""For reference."""
volume_all = ut.get_data(syms, dates, colname="Volume")
volume = volume_all[syms] # only portfolio symbols
# volume_SPY = volume_all['SPY'] # only SPY, for comparison later
if self.verbose:
print(volume)
def _add_indicators(self, df, symbol):
"""Add indicators for learning to DataFrame."""
df.drop(columns=["SPY"], inplace=True)
indicators.macd(df, symbol)
indicators.rsi(df, symbol)
indicators.price_sma(df, symbol, [8])
indicators.price_delta(df, symbol, 3)
df.dropna(inplace=True)
def addEvidence(self, symbol="IBM",
sd=dt.datetime(2008, 1, 1),
ed=dt.datetime(2009, 1, 1),
sv=10000):
self.y_threshold = 0.2
self.indicators = ['macd_diff', 'rsi', 'price_sma_8']
df = util.get_data([symbol], pd.date_range(sd, ed))
self._add_indicators(df, symbol)
def classify_y(row):
if row > self.y_threshold:
return 1
elif row < -self.y_threshold:
return -1
else:
pass
return 0
def set_y_threshold(pct):
if max(pct) < 0.2:
self.y_threshold = 0.02
self.learner = RTLearner(leaf_size = 5)
# self.learner = BagLearner(RTLearner, 3, {'leaf_size': 5})
data_x = df[self.indicators].to_numpy()
pct = df['pct_3']
# This is a hack to get a low enough buy/sell threshold for the
# cyclic the test 'ML4T-220' where the max pct_3 is 0.0268.
set_y_threshold(pct)
y = pct.apply(classify_y)
self.learner.addEvidence(data_x, y.to_numpy())
return y
def strat(self, data_y, orders):
self.holding = 0
def strat(row):
y = int(data_y.loc[row.name][0])
shares = 0
if self.holding == 0 and y == 1:
shares = 1000
elif self.holding == -1000 and y == 1:
shares = 2000
elif self.holding == 0 and y == -1:
shares = -1000
elif self.holding == 1000 and y == -1:
shares = -2000
self.holding += shares
return shares
orders["Shares"] = orders.apply(strat, axis=1)
def testPolicy(self, symbol="IBM",
sd=dt.datetime(2009, 1, 1),
ed=dt.datetime(2010, 1, 1),
sv=10000):
df = util.get_data([symbol], pd.date_range(sd, ed))
self._add_indicators(df, symbol)
data_x = df[self.indicators].to_numpy()
data_y = pd.DataFrame(index=df.index, data=self.learner.query(data_x))
orders = pd.DataFrame(index=df.index)
orders["Symbol"] = symbol
orders["Order"] = ""
orders["Shares"] = 0
self.strat(data_y, orders)
if self.testing:
return orders
else:
return orders[["Shares"]]

View File

@@ -0,0 +1,236 @@
import pandas as pd
import datetime as dt
import sys
import util
import indicators
import marketsim.marketsim as marketsim
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor
from BenchmarkStrategy import BenchmarkStrategy
from ManualStrategy import ManualStrategy
from StrategyLearner import StrategyLearner
from QLearner import QLearner
def plot_indicators(symbol, df):
fig, ax = plt.subplots(4, sharex=True)
price_sma = indicators.price_sma(df, symbol, [8])
bb = indicators.bollinger_band(df, symbol)
rsi = indicators.rsi(df, symbol)
macd = indicators.macd(df, symbol).copy()
df[[symbol]].plot(ax=ax[0])
bb.plot(ax=ax[0])
price_sma.plot(ax=ax[1])
macd.plot(ax=ax[2])
rsi.plot(ax=ax[3])
for a in ax.flat:
a.grid()
m = MultiCursor(fig.canvas, ax, color='r', lw=0.5)
plt.show()
sys.exit(0)
def visualize_correlations(symbol, df):
indicators.price_sma(df, symbol, [8, 21])
indicators.price_delta(df, symbol, 5)
indicators.price_delta(df, symbol, 3)
indicators.price_delta(df, symbol, 1)
indicators.macd(df, symbol)
indicators.rsi(df, symbol)
# df = df[df['rsi'] > 80]
fig, ax = plt.subplots(3, 2) # sharex=True)
df.plot.scatter(x="price_sma_8", y="pct_5", ax=ax[0, 0])
df.plot.scatter(x="price_sma_8", y="pct_3", ax=ax[1, 0])
df.plot.scatter(x="price_sma_8", y="pct_1", ax=ax[2, 0])
# df.plot.scatter(x="rsi", y="pct_5", ax=ax[0, 1])
# df.plot.scatter(x="rsi", y="pct_3", ax=ax[1, 1])
# df.plot.scatter(x="rsi", y="pct_1", ax=ax[2, 1])
df.plot.scatter(x="macd_diff", y="pct_5", ax=ax[0, 1])
df.plot.scatter(x="macd_diff", y="pct_3", ax=ax[1, 1])
df.plot.scatter(x="macd_diff", y="pct_1", ax=ax[2, 1])
for a in ax.flat:
a.grid()
plt.show()
sys.exit(0)
def compare_manual_strategies(symbol, sv, sd, ed):
df = util.get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
bs = BenchmarkStrategy()
orders = bs.testPolicy(symbol, sd, ed, sv)
df["Benchmark"] = marketsim.compute_portvals(orders, sv)
df["Orders Benchmark"] = orders["Shares"]
ms = ManualStrategy()
orders = ms.testPolicy(symbol, sd, ed, sv, macd_strat=True)
df["MACD Strat"] = marketsim.compute_portvals(orders, sv)
df["Orders MACD"] = orders["Shares"]
# df["Holding Manual"] = orders["Shares"].cumsum()
orders = ms.testPolicy(symbol, sd, ed, sv)
df["Three Strat"] = marketsim.compute_portvals(orders, sv)
df["Orders Three"] = orders["Shares"]
fig, ax = plt.subplots(3, sharex=True)
df[[symbol]].plot(ax=ax[0])
df[["Benchmark", "MACD Strat", "Three Strat"]].plot(ax=ax[1])
df[["Orders Benchmark", "Orders MACD", "Orders Three"]].plot(ax=ax[2])
for a in ax:
a.grid()
MultiCursor(fig.canvas, ax, color='r', lw=0.5)
# plt.show()
fig.set_size_inches(10, 8, forward=True)
plt.savefig('figure_1.png', dpi=fig.dpi)
def compare_all_strategies(symbol, sv, sd, ed):
df = util.get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
normalize = indicators.normalize
bs = BenchmarkStrategy()
orders = bs.testPolicy(symbol, sd, ed, sv)
df["Benchmark"] = normalize(marketsim.compute_portvals(orders, sv))
df["Orders Benchmark"] = orders["Shares"]
ms = ManualStrategy()
orders = ms.testPolicy(symbol, sd, ed, sv)
df["Manual"] = normalize(marketsim.compute_portvals(orders, sv))
df["Orders Manual"] = orders["Shares"]
sl = StrategyLearner(testing=True)
sl.addEvidence(symbol, sd, ed, sv)
orders = sl.testPolicy(symbol, sd, ed, sv)
df["Strategy"] = normalize(marketsim.compute_portvals(orders, sv))
df["Orders Strategy"] = orders["Shares"]
fig, ax = plt.subplots(3, sharex=True)
df[[symbol]].plot(ax=ax[0])
df[["Benchmark", "Manual", "Strategy"]].plot(ax=ax[1])
df[["Orders Benchmark", "Orders Manual", "Orders Strategy"]].plot(ax=ax[2])
for a in ax:
a.grid()
MultiCursor(fig.canvas, ax, color='r', lw=0.5)
# plt.show()
fig.set_size_inches(10, 8, forward=True)
plt.savefig('figure_2.png', dpi=fig.dpi)
def compare_number_trades():
symbol = "JPM"
sv = 10000
sd = dt.datetime(2008, 1, 1) # in-sample
ed = dt.datetime(2009, 12, 31) # in-sample
df = util.get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
print(f"| commission | n_orders |")
print(f"-------------------------")
for commission in [0, 9.95, 20, 50, 100]:
ql = QLearner(testing=True, commission=commission, impact=0.005)
ql.addEvidence(symbol, sd, ed, sv)
orders = ql.testPolicy(symbol, sd, ed, sv)
n_orders = orders[orders["Shares"] != 0].shape[0]
print(f"| {commission} | {n_orders} |")
def compare_q_learners():
symbol = "JPM"
sv = 10000
sd = dt.datetime(2008, 1, 1) # in-sample
ed = dt.datetime(2009, 12, 31) # in-sample
sd_out = dt.datetime(2010, 1, 1) # out-sample
ed_out = dt.datetime(2011, 12, 31) # out-sample
df = util.get_data([symbol], pd.date_range(sd_out, ed_out))
df.drop(columns=["SPY"], inplace=True)
bs = BenchmarkStrategy()
orders = bs.testPolicy(symbol, sd_out, ed_out, sv)
df["Benchmark"] = indicators.normalize(marketsim.compute_portvals(orders, sv))
df["Orders Benchmark"] = orders["Shares"]
ql = QLearner(testing=True, verbose=False)
ql.addEvidence(symbol, sd, ed, sv)
orders = ql.testPolicy(symbol, sd_out, ed_out, sv)
df["QL 5"] = indicators.normalize(marketsim.compute_portvals(orders, sv))
df["Orders QL 5"] = orders["Shares"]
ql = QLearner(testing=True, verbose=False, n_bins=4)
ql.addEvidence(symbol, sd, ed, sv)
orders = ql.testPolicy(symbol, sd_out, ed_out, sv)
df["QL 4"] = indicators.normalize(marketsim.compute_portvals(orders, sv))
df["Orders QL 4"] = orders["Shares"]
fig, ax = plt.subplots(3, sharex=True)
df[[symbol]].plot(ax=ax[0])
df[["Benchmark", "QL 5", "QL 4"]].plot(ax=ax[1])
df[["Orders Benchmark", "Orders QL 5", "Orders QL 4"]].plot(ax=ax[2])
for a in ax:
a.grid()
m = MultiCursor(fig.canvas, ax, color='r', lw=0.5)
fig.set_size_inches(10, 8, forward=True)
plt.savefig('figure_4.png', dpi=fig.dpi)
sys.exit(0)
def experiment1(create_report=False):
symbol = "JPM"
sv = 10000
sd = dt.datetime(2008, 1, 1) # in-sample
ed = dt.datetime(2009, 12, 31) # in-sample
sd_out = dt.datetime(2010, 1, 1) # out-sample
ed_out = dt.datetime(2011, 12, 31) # out-sample
df = util.get_data([symbol], pd.date_range(sd_out, ed_out))
df.drop(columns=["SPY"], inplace=True)
if create_report:
compare_manual_strategies(symbol, sv, sd, ed)
compare_all_strategies(symbol, sv, sd, ed)
sys.exit(0)
# visualize_correlations(symbol, df)
# plot_indicators(symbol, df)
# compare_number_trades(symbol, sv, sd, ed)
# compare_q_learners()
bs = BenchmarkStrategy()
orders = bs.testPolicy(symbol, sd_out, ed_out, sv)
df["Benchmark"] = indicators.normalize(marketsim.compute_portvals(orders, sv))
df["Orders Benchmark"] = orders["Shares"]
ql = QLearner(testing=True, verbose=False)
ql.addEvidence(symbol, sd, ed, sv)
orders = ql.testPolicy(symbol, sd_out, ed_out, sv)
df["QL"] = indicators.normalize(marketsim.compute_portvals(orders, sv))
df["Orders QL"] = orders["Shares"]
fig, ax = plt.subplots(3, sharex=True)
df[[symbol]].plot(ax=ax[0])
df[["Benchmark", "QL"]].plot(ax=ax[1])
df[["Orders Benchmark", "Orders QL"]].plot(ax=ax[2])
for a in ax:
a.grid()
m = MultiCursor(fig.canvas, ax, color='r', lw=0.5)
plt.show()
# fig.set_size_inches(10, 8, forward=True)
# plt.savefig('figure_4.png', dpi=fig.dpi)
if __name__ == "__main__":
experiment1()

View File

@@ -0,0 +1,8 @@
import experiment1
def experiment2():
experiment1.compare_number_trades()
if __name__ == "__main__":
experiment2()

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -0,0 +1,339 @@
"""MC3-P3: Strategy Learner - grading script.
Usage:
- Switch to a student feedback directory first (will write "points.txt" and "comments.txt" in pwd).
- Run this script with both ml4t/ and student solution in PYTHONPATH, e.g.:
PYTHONPATH=ml4t:MC1-P2/jdoe7 python ml4t/mc2_p1_grading/grade_marketsim.py
Copyright 2017, Georgia Tech Research Corporation
Atlanta, Georgia 30332-0415
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: tb34 (replace with your User ID)
GT ID: 900897987 (replace with your GT ID)
"""
import pytest
from grading.grading import grader, GradeResult, run_with_timeout, IncorrectOutput
import os
import sys
import traceback as tb
import datetime as dt
import numpy as np
import pandas as pd
from collections import namedtuple
import time
import util
import random
# Test cases
StrategyTestCase = namedtuple('Strategy', ['description','insample_args','outsample_args','benchmark_type','benchmark','impact','train_time','test_time','max_time','seed'])
strategy_test_cases = [
StrategyTestCase(
description="ML4T-220",
insample_args=dict(symbol="ML4T-220",sd=dt.datetime(2008,1,1),ed=dt.datetime(2009,12,31),sv=100000),
outsample_args=dict(symbol="ML4T-220",sd=dt.datetime(2010,1,1),ed=dt.datetime(2011,12,31),sv=100000),
benchmark_type='clean',
benchmark=1.0, #benchmark updated Apr 24 2017
impact=0.0,
train_time=25,
test_time=5,
max_time=60,
seed=1481090000
),
StrategyTestCase(
description="AAPL",
insample_args=dict(symbol="AAPL",sd=dt.datetime(2008,1,1),ed=dt.datetime(2009,12,31),sv=100000),
outsample_args=dict(symbol="AAPL",sd=dt.datetime(2010,1,1),ed=dt.datetime(2011,12,31),sv=100000),
benchmark_type='stock',
benchmark=0.1581999999999999, #benchmark computed Nov 22 2017
impact=0.0,
train_time=25,
test_time=5,
max_time=60,
seed=1481090000
),
StrategyTestCase(
description="SINE_FAST_NOISE",
insample_args=dict(symbol="SINE_FAST_NOISE",sd=dt.datetime(2008,1,1),ed=dt.datetime(2009,12,31),sv=100000),
outsample_args=dict(symbol="SINE_FAST_NOISE",sd=dt.datetime(2010,1,1),ed=dt.datetime(2011,12,31),sv=100000),
benchmark_type='noisy',
benchmark=2.0, #benchmark updated Apr 24 2017
impact=0.0,
train_time=25,
test_time=5,
max_time=60,
seed=1481090000
),
StrategyTestCase(
description="UNH - In sample",
insample_args=dict(symbol="UNH",sd=dt.datetime(2008,1,1),ed=dt.datetime(2009,12,31),sv=100000),
outsample_args=dict(symbol="UNH",sd=dt.datetime(2010,1,1),ed=dt.datetime(2011,12,31),sv=100000),
benchmark_type='stock',
benchmark= -0.25239999999999996, #benchmark computed Nov 22 2017
impact=0.0,
train_time=25,
test_time=5,
max_time=60,
seed=1481090000
),
]
max_points = 60.0
html_pre_block = True # surround comments with HTML <pre> tag (for T-Square comments field)
MAX_HOLDINGS = 1000
# Test functon(s)
@pytest.mark.parametrize("description, insample_args, outsample_args, benchmark_type, benchmark, impact, train_time, test_time, max_time, seed", strategy_test_cases)
def test_strategy(description, insample_args, outsample_args, benchmark_type, benchmark, impact, train_time, test_time, max_time, seed, grader):
"""Test StrategyLearner.
Requires test description, insample args (dict), outsample args (dict), benchmark_type (str), benchmark (float)
max time (seconds), points for this test case (int), random seed (long), and a grader fixture.
"""
points_earned = 0.0 # initialize points for this test case
try:
incorrect = True
if not 'StrategyLearner' in globals():
import importlib
m = importlib.import_module('StrategyLearner')
globals()['StrategyLearner'] = m
outsample_cr_to_beat = None
if benchmark_type == 'clean':
outsample_cr_to_beat = benchmark
def timeoutwrapper_strategylearner():
#Set fixed seed for repetability
np.random.seed(seed)
random.seed(seed)
learner = StrategyLearner.StrategyLearner(verbose=False,impact=impact)
tmp = time.time()
learner.addEvidence(**insample_args)
train_t = time.time()-tmp
tmp = time.time()
insample_trades_1 = learner.testPolicy(**insample_args)
test_t = time.time()-tmp
insample_trades_2 = learner.testPolicy(**insample_args)
tmp = time.time()
outsample_trades = learner.testPolicy(**outsample_args)
out_test_t = time.time()-tmp
return insample_trades_1, insample_trades_2, outsample_trades, train_t, test_t, out_test_t
msgs = []
in_trades_1, in_trades_2, out_trades, train_t, test_t, out_test_t = run_with_timeout(timeoutwrapper_strategylearner,max_time,(),{})
incorrect = False
if len(in_trades_1.shape)!=2 or in_trades_1.shape[1]!=1:
incorrect=True
msgs.append(" First insample trades DF has invalid shape: {}".format(in_trades_1.shape))
elif len(in_trades_2.shape)!=2 or in_trades_2.shape[1]!=1:
incorrect=True
msgs.append(" Second insample trades DF has invalid shape: {}".format(in_trades_2.shape))
elif len(out_trades.shape)!=2 or out_trades.shape[1]!=1:
incorrect=True
msgs.append(" Out-of-sample trades DF has invalid shape: {}".format(out_trades.shape))
else:
tmp_csum=0.0
for date,trade in in_trades_1.iterrows():
tmp_csum+= trade.iloc[0]
if (trade.iloc[0]!=0) and\
(trade.abs().iloc[0]!=MAX_HOLDINGS) and\
(trade.abs().iloc[0]!=2*MAX_HOLDINGS):
incorrect=True
msgs.append(" illegal trade in first insample DF. abs(trade) not one of ({},{},{}).\n Date {}, Trade {}".format(0,MAX_HOLDINGS,2*MAX_HOLDINGS,date,trade))
break
elif abs(tmp_csum)>MAX_HOLDINGS:
incorrect=True
msgs.append(" holdings more than {} long or short in first insample DF. Date {}, Trade {}".format(MAX_HOLDINGS,date,trade))
break
tmp_csum=0.0
for date,trade in in_trades_2.iterrows():
tmp_csum+= trade.iloc[0]
if (trade.iloc[0]!=0) and\
(trade.abs().iloc[0]!=MAX_HOLDINGS) and\
(trade.abs().iloc[0]!=2*MAX_HOLDINGS):
incorrect=True
msgs.append(" illegal trade in second insample DF. abs(trade) not one of ({},{},{}).\n Date {}, Trade {}".format(0,MAX_HOLDINGS,2*MAX_HOLDINGS,date,trade))
break
elif abs(tmp_csum)>MAX_HOLDINGS:
incorrect=True
msgs.append(" holdings more than {} long or short in second insample DF. Date {}, Trade {}".format(MAX_HOLDINGS,date,trade))
break
tmp_csum=0.0
for date,trade in out_trades.iterrows():
tmp_csum+= trade.iloc[0]
if (trade.iloc[0]!=0) and\
(trade.abs().iloc[0]!=MAX_HOLDINGS) and\
(trade.abs().iloc[0]!=2*MAX_HOLDINGS):
incorrect=True
msgs.append(" illegal trade in out-of-sample DF. abs(trade) not one of ({},{},{}).\n Date {}, Trade {}".format(0,MAX_HOLDINGS,2*MAX_HOLDINGS,date,trade))
break
elif abs(tmp_csum)>MAX_HOLDINGS:
incorrect=True
msgs.append(" holdings more than {} long or short in out-of-sample DF. Date {}, Trade {}".format(MAX_HOLDINGS,date,trade))
break
# if (((in_trades_1.abs()!=0) & (in_trades_1.abs()!=MAX_HOLDINGS) & (in_trades_1.abs()!=2*MAX_HOLDINGS)).any().any() or\
# ((in_trades_2.abs()!=0) & (in_trades_2.abs()!=MAX_HOLDINGS) & (in_trades_2.abs()!=2*MAX_HOLDINGS)).any().any() or\
# ((out_trades.abs()!=0) & (out_trades.abs()!=MAX_HOLDINGS) & (out_trades.abs()!=2*MAX_HOLDINGS)).any().any()):
# incorrect = True
# msgs.append(" illegal trade. abs(trades) not one of ({},{},{})".format(0,MAX_HOLDINGS,2*MAX_HOLDINGS))
# if ((in_trades_1.cumsum().abs()>MAX_HOLDINGS).any()[0]) or ((in_trades_2.cumsum().abs()>MAX_HOLDINGS).any()[0]) or ((out_trades.cumsum().abs()>MAX_HOLDINGS).any()[0]):
# incorrect = True
# msgs.append(" holdings more than {} long or short".format(MAX_HOLDINGS))
if not(incorrect):
if train_t>train_time:
incorrect=True
msgs.append(" addEvidence() took {} seconds, max allowed {}".format(train_t,train_time))
else:
points_earned += 1.0
if test_t > test_time:
incorrect = True
msgs.append(" testPolicy() took {} seconds, max allowed {}".format(test_t,test_time))
else:
points_earned += 2.0
if not((in_trades_1 == in_trades_2).all()[0]):
incorrect = True
mismatches = in_trades_1.join(in_trades_2,how='outer',lsuffix='1',rsuffix='2')
mismatches = mismatches[mismatches.iloc[:,0]!=mismatches.iloc[:,1]]
msgs.append(" consecutive calls to testPolicy() with same input did not produce same output:")
msgs.append(" Mismatched trades:\n {}".format(mismatches))
else:
points_earned += 2.0
student_insample_cr = evalPolicy2(insample_args['symbol'],in_trades_1,insample_args['sv'],insample_args['sd'],insample_args['ed'],market_impact=impact,commission_cost=0.0)
student_outsample_cr = evalPolicy2(outsample_args['symbol'],out_trades, outsample_args['sv'],outsample_args['sd'],outsample_args['ed'],market_impact=impact,commission_cost=0.0)
if student_insample_cr <= benchmark:
incorrect = True
msgs.append(" in-sample return ({}) did not beat benchmark ({})".format(student_insample_cr,benchmark))
else:
points_earned += 5.0
if outsample_cr_to_beat is None:
if out_test_t > test_time:
incorrect = True
msgs.append(" out-sample took {} seconds, max of {}".format(out_test_t,test_time))
else:
points_earned += 5.0
else:
if student_outsample_cr < outsample_cr_to_beat:
incorrect = True
msgs.append(" out-sample return ({}) did not beat benchmark ({})".format(student_outsample_cr,outsample_cr_to_beat))
else:
points_earned += 5.0
if incorrect:
inputs_str = " insample_args: {}\n" \
" outsample_args: {}\n" \
" benchmark_type: {}\n" \
" benchmark: {}\n" \
" train_time: {}\n" \
" test_time: {}\n" \
" max_time: {}\n" \
" seed: {}\n".format(insample_args, outsample_args, benchmark_type, benchmark, train_time, test_time, max_time,seed)
raise IncorrectOutput("Test failed on one or more output criteria.\n Inputs:\n{}\n Failures:\n{}".format(inputs_str, "\n".join(msgs)))
except Exception as e:
# Test result: failed
msg = "Test case description: {}\n".format(description)
# Generate a filtered stacktrace, only showing erroneous lines in student file(s)
tb_list = tb.extract_tb(sys.exc_info()[2])
for i in range(len(tb_list)):
row = tb_list[i]
tb_list[i] = (os.path.basename(row[0]), row[1], row[2], row[3]) # show only filename instead of long absolute path
# tb_list = [row for row in tb_list if row[0] in ['QLearner.py','StrategyLearner.py']]
if tb_list:
msg += "Traceback:\n"
msg += ''.join(tb.format_list(tb_list)) # contains newlines
elif 'grading_traceback' in dir(e):
msg += "Traceback:\n"
msg += ''.join(tb.format_list(e.grading_traceback))
msg += "{}: {}".format(e.__class__.__name__, str(e))
# Report failure result to grader, with stacktrace
grader.add_result(GradeResult(outcome='failed', points=points_earned, msg=msg))
raise
else:
# Test result: passed (no exceptions)
grader.add_result(GradeResult(outcome='passed', points=points_earned, msg=None))
def compute_benchmark(sd,ed,sv,symbol,market_impact,commission_cost,max_holdings):
date_idx = util.get_data([symbol,],pd.date_range(sd,ed)).index
orders = pd.DataFrame(index=date_idx)
orders['orders'] = 0; orders['orders'][0] = max_holdings; orders['orders'][-1] = -max_holdings
return evalPolicy2(symbol,orders,sv,sd,ed,market_impact,commission_cost)
def evalPolicy(student_trades,sym_prices,startval):
ending_cash = startval - student_trades.mul(sym_prices,axis=0).sum()
ending_stocks = student_trades.sum()*sym_prices.iloc[-1]
return float((ending_cash+ending_stocks)/startval)-1.0
def evalPolicy2(symbol, student_trades, startval, sd, ed, market_impact,commission_cost):
orders_df = pd.DataFrame(columns=['Shares','Order','Symbol'])
for row_idx in student_trades.index:
nshares = student_trades.loc[row_idx][0]
if nshares == 0:
continue
order = 'sell' if nshares < 0 else 'buy'
new_row = pd.DataFrame([[abs(nshares),order,symbol],],columns=['Shares','Order','Symbol'],index=[row_idx,])
orders_df = orders_df.append(new_row)
portvals = compute_portvals(orders_df, sd, ed, startval,market_impact,commission_cost)
return float(portvals[-1]/portvals[0])-1
def compute_portvals(orders_df, start_date, end_date, startval, market_impact=0.0, commission_cost=0.0):
"""Simulate the market for the given date range and orders file."""
symbols = []
orders = []
orders_df = orders_df.sort_index()
for date, order in orders_df.iterrows():
shares = order['Shares']
action = order['Order']
symbol = order['Symbol']
if action.lower() == 'sell':
shares *= -1
order = (date, symbol, shares)
orders.append(order)
symbols.append(symbol)
symbols = list(set(symbols))
dates = pd.date_range(start_date, end_date)
prices_all = util.get_data(symbols, dates)
prices = prices_all[symbols]
prices = prices.fillna(method='ffill').fillna(method='bfill')
prices['_CASH'] = 1.0
trades = pd.DataFrame(index=prices.index, columns=symbols)
trades = trades.fillna(0)
cash = pd.Series(index=prices.index)
cash = cash.fillna(0)
cash.iloc[0] = startval
for date, symbol, shares in orders:
price = prices[symbol][date]
val = shares * price
# transaction cost model
val += commission_cost + (pd.np.abs(shares)*price*market_impact)
positions = prices.loc[date] * trades.sum()
totalcash = cash.sum()
if (date < prices.index.min()) or (date > prices.index.max()):
continue
trades[symbol][date] += shares
cash[date] -= val
trades['_CASH'] = cash
holdings = trades.cumsum()
df_portvals = (prices * holdings).sum(axis=1)
return df_portvals
if __name__ == "__main__":
pytest.main(["-s", __file__])

View File

@@ -0,0 +1,140 @@
import pandas as pd
import datetime as dt
import matplotlib.pyplot as plt
from util import get_data
def author():
return "felixm"
def normalize(timeseries):
return timeseries / timeseries.iloc[0]
def bollinger_band(df, symbol, period=20, m=2):
boll_sma = df[symbol].rolling(period).mean()
std = df[symbol].rolling(period).std()
boll_up = boll_sma + m * std
boll_lo = boll_sma - m * std
key_sma, key_up, key_lo = "boll_sma", "boll_up", "boll_lo"
df[key_sma] = boll_sma
df[key_up] = boll_up
df[key_lo] = boll_lo
return df[[key_sma, key_up, key_lo]]
def sma(df, symbol, period):
"""Adds SMA for one or multiple periods to df and returns SMAs"""
if type(period) is int:
period = [period]
keys = []
for p in period:
key = f"sma_{p}"
df[key] = df[symbol].rolling(p).mean()
keys.append(key)
return df[keys]
def ema(df, symbol, period):
"""Adds EMA for one or multiple periods to df and returns EMAs"""
if type(period) is int:
period = [period]
keys = []
for p in period:
key = f"ema_{p}"
df[key] = df[symbol].ewm(span=p).mean()
keys.append(key)
return df[keys]
def price_sma(df, symbol, period):
"""Calculates SMA and adds new column price divided by SMA to the df."""
if type(period) is int:
period = [period]
keys = []
for p in period:
key = f"price_sma_{p}"
sma = df[symbol].rolling(p).mean()
df[key] = df[symbol] / sma
keys.append(key)
return df[keys]
def rsi(df, symbol, period=14):
"""Calculates relative strength index over given period."""
def rsi(x):
pct = x.pct_change()
avg_gain = pct[pct > 0].mean()
avg_loss = pct[pct <= 0].abs().mean()
rsi = 100 - (100 /
(1 + ((avg_gain / period) /
(avg_loss / period))))
return rsi
key = "rsi"
# Add one to get 'period' price changes (first change is nan).
period += 1
df[key] = df[symbol].rolling(period).apply(rsi)
return df[[key]]
def macd(df, symbol):
macd = df[symbol].ewm(span=12).mean() - df[symbol].ewm(span=26).mean()
k1 = "macd"
k2 = "macd_signal"
k3 = "macd_diff"
df[k1] = macd
df[k2] = macd.rolling(9).mean()
df[k3] = df[k1] - df[k2]
return df[[k1, k2, k3]]
def price_delta(df, symbol, period=1):
"""Calculate percentage change for period."""
k = f"pct_{period}"
df[k] = df[symbol].pct_change(periods=period)
df[k] = df[k].shift(-period)
return df[k]
def test_indicators():
symbol = "JPM"
sd = dt.datetime(2008, 1, 1)
ed = dt.datetime(2009, 12, 31)
df = get_data([symbol], pd.date_range(sd, ed))
df.drop(columns=["SPY"], inplace=True)
df_orig = df.copy()
# df = normalize(df)
sma(df, symbol, 21)
ema(df, symbol, 21)
df.plot(title="21 SMA and EMA")
plt.savefig('figure_1.png')
df = df_orig.copy()
sma(df, symbol, 8)
price_sma(df, symbol, 8)
df.plot(title="SMA and price / SMA", subplots=True)
plt.savefig('figure_2.png')
df = df_orig.copy()
bollinger_band(df, symbol)
df.plot(title="Bollinger Band")
plt.savefig('figure_3.png')
df = df_orig.copy()
rsi(df, symbol)
fig, axes = plt.subplots(nrows=2, sharex=True)
df[symbol].plot(ax=axes[0], title="JPM price action")
df["JPM-rsi(14)"].plot(ax=axes[1], title="RSI")
plt.savefig('figure_4.png')
df = df_orig.copy()
macd(df, symbol)
fig, axes = plt.subplots(nrows=2, sharex=True)
df[symbol].plot(ax=axes[0], title="JPM price action")
df[["JPM-macd", "JPM-macd-signal"]].plot(ax=axes[1])
plt.savefig('figure_5.png')

View File

@@ -0,0 +1,75 @@
This document is the final report for the machine learning for trading
course. I have implemented two manual strategies, a random tree
learner-based strategy and one based on Q-learning.
# Experiment 1
I have implemented two manual strategies. The first strategy buys on a
bullish MACD cross with a MACD smaller than zero and sells on a bearish
MACD cross with a MACD greater than one.
The second strategy uses MACD diff (the difference between the MACD and
the MACD signal), RSI, and price SMA with a period of eight. I have
plotted the metrics over their one, three, and five days return to find
reasonable thresholds for the strategy.
![Scatter plot to find reasonable thresholds.](figure_3.png)
Based on the scatter plots, I have created a list of buy and sell
signals. Each signal uses the current number of shares owned and one of
the three indicators. The following figure shows the result for both
manual strategies compared to the benchmark. Both approaches do well in
the in-sample period but worse afterward, which I expected because I
cherry-picked the thresholds based on the in-sample period's scatter
plots.
![First strategy based on MACD. Better than just holding.](figure_1.png)
Next, I have implemented a random tree-based strategy learner. The
learner uses a leaf size of five and no bagging. A smaller leaf size
would result in overfitting to the in-sample data. But as the following
screenshot shows, five works well, and the RT learner does well for the
out of sample data.
![Manual strategy compared to RT learner.](figure_2.png)
I have also implemented a strategy learner based on Q-learning. The
Q-learner uses fifteen training runs on the in-sample data. It mostly
does well for the out of sample data, but it looks like the RT-based
strategy learner is better.
I am using a bin-size of five for the three indicators mentioned before.
That results in 375 (3x5x5x5) states with only about 500 in-sample data
points. Probably the Q-learner is overfitting to the in-sample data.
Indeed, with bin sizes of four, the Q learner performs better for the
out-of-sample data.
![Strategy learner based on Q-Learning with using four and five bins
for discretization out of sample.](figure_4.png)
# Experiment 2
Experiment 2 aims to show that the strategy learner trades differently
when there is a commission, and the impact is not zero. The RT-based
trader does not consider the commission value, but the Q-learning based
trader does.
However, it seems like a commission smaller than $10 does not affect
the number of trades significantly. Only when the commission is around
$50 or with a slippage of 1% we see considerably fewer transactions.
| commission | n_orders |
|------------|----------|
| 9.95 | 79 |
| 20 | 83 |
| 50 | 63 |
| 100 | 37 |
# Closing Remarks
Machine Learning for Trading is a great course. It gives an excellent
introduction to finance, trading, and machine learning without getting lost in
technical or mathematical details. I have enjoyed building decision tree
learners and a Q learner from first principles. At the same time, the course
accurately teaches powerful libraries such as NumPy and Pandas.

View File

@@ -0,0 +1,8 @@
from experiment1 import experiment1
from experiment2 import experiment2
if __name__ == "__main__":
experiment1(create_report=True)
experiment2()

68
util.py Normal file
View File

@@ -0,0 +1,68 @@
"""MLT: Utility code.
Copyright 2017, Georgia Tech Research Corporation
Atlanta, Georgia 30332-0415
All Rights Reserved
"""
import os
import pandas as pd
def symbol_to_path(symbol, base_dir=None):
"""Return CSV file path given ticker symbol."""
if base_dir is None:
base_dir = os.environ.get("MARKET_DATA_DIR", '../data/')
return os.path.join(base_dir, "{}.csv".format(str(symbol)))
def get_data(symbols, dates, addSPY=True, colname='Adj Close', datecol='Date'):
"""Read stock data (adjusted close) for given symbols from CSV files."""
df = pd.DataFrame(index=dates)
if addSPY and 'SPY' not in symbols: # add SPY for reference, if absent
# handles the case where symbols is np array of 'object'
symbols = ['SPY'] + list(symbols)
for symbol in symbols:
if 'BTC' in symbol or 'ETH' in symbol:
colname = 'close'
datecol = 'time'
elif symbol == 'SPY':
colname = 'close'
datecol = 'time'
else:
colname = 'Adj Close'
datecol = 'Date'
df_temp = pd.read_csv(symbol_to_path(symbol),
index_col=datecol,
parse_dates=True, usecols=[datecol, colname],
na_values=['nan'])
df_temp = df_temp.rename(columns={colname: symbol})
if datecol == 'time':
df_temp['date'] = pd.to_datetime(df_temp.index, unit='s')
df_temp['date'] = pd.DatetimeIndex(df_temp['date']).normalize()
df_temp.set_index('date', drop=True, inplace=True)
df = df.join(df_temp)
if symbol == 'SPY': # drop dates SPY did not trade
pass
# df = df.dropna(subset=["SPY"])
return df
def plot_data(df, title="Stock prices", xlabel="Date", ylabel="Price"):
import matplotlib.pyplot as plt
"""Plot stock prices with a custom title and meaningful axis labels."""
ax = df.plot(title=title, fontsize=12)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
plt.show()
def get_orders_data_file(basefilename):
return open(os.path.join(os.environ.get("ORDERS_DATA_DIR",'orders/'),basefilename))
def get_learner_data_file(basefilename):
return open(os.path.join(os.environ.get("LEARNER_DATA_DIR",'Data/'),basefilename),'r')
def get_robot_world_file(basefilename):
return open(os.path.join(os.environ.get("ROBOT_WORLDS_DIR",'testworlds/'),basefilename))

Binary file not shown.

BIN
zips/20Spring_marketsim.zip Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.