-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathPyAlgosim.py
155 lines (119 loc) · 6.2 KB
/
PyAlgosim.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# This module provides an interface to run simulations. It has many methods that take in the algorithm function, and run it against the specified dates, parameters, etc.
import sqlite3
import os
import copy
import json
import PyBank
# helper functions
def normalize_date(datetime_date):
"""
Returns an integer, of the form YYYY/MM/DD, which is useful when determining the records to pull from the database.
"""
year = datetime_date.year
month = datetime_date.month
day = datetime_date.day
daystring = "0" + str(day) if len(str(day)) == 1 else str(day)
monthstring = "0" + str(month) if len(str(month)) == 1 else str(month)
yearstring = str(year)
return int(yearstring + monthstring + daystring)
class PyAlgosim:
def __init__(self, account, db_path="./utils/stocks.db", ticker_list_path="./utils/tickers.json", variables=None):
"""
Takes in the path to a database with stock information. Preferably an absolute path.
"""
self.database = db_path
self.account = account
self.connection = None
self.cursor = None
self.variables = variables
self.latest_prices = {}
self.ticker_list_path = ticker_list_path
# loading up the ticker list created from generating the database. The path must be correct.
try:
self.ticker_list = open(self.ticker_list_path).read()
self.ticker_list = json.loads(self.ticker_list)
except:
raise IOError("The tickers.json file could not be loaded. Please make sure that you have generated the database with the initialize script, and that you are running PyAlgosim from its original location.")
# verify that database is not empty (wrong path may have created a new database)
if os.path.isfile(self.database) != True:
raise Exception("The path entered is not the correct database file. Please make sure to run initialize.py to create the stock database, and point to the correct location.")
if not isinstance(self.account, PyBank.Account):
raise TypeError("Account is not a PyBank account. Please read the documentation.")
def __str__(self):
"""
Simply prints out the configuration of the object. Shouldn't really be needed.
"""
config = ""
config += "DB path: " + str(self.database) + "\n"
config += "Optional variables: " + str(self.variables) + "\n"
return config
def _connect_DB(self):
"""
Helper function that connects to the DB. Adds a connection object and cursor object to the PyAlgosim object.
"""
self.connection = sqlite3.connect(self.database)
self.cursor = self.connection.cursor()
def _disconnect_DB(self):
"""
Helper function that closes the connection to the DB.
"""
if self.connection and self.cursor:
self.connection.close()
self.connection = None
self.cursor = None
else:
raise IOError("There is no current active connection to the database.")
def _iterate_algorithm(self, algorithm, data, ticker):
"""
Given data returned by an SQL query call, iterates through it and applies the algorithm passed. Used to apply algorithm to each stock.
This function also takes care of keeping the Account object up to date with the latest prices, and making sure it executes trailing stops, etc.
"""
# copying initial state of the optional variables for each stock
if self.variables:
variables_copy = copy.deepcopy(self.variables)
else:
variables_copy = None
# each item in the "data" list will contain a tuple structured as (date, open_price, close_price, high, low, volume).
for stock_record in data:
# these variables are kept outside of the stock data (not passed directly on) but are passed on one at a time later
open_price = stock_record[1]
close_price = stock_record[2]
stock_data = {
"date" : stock_record[0],
"price" : open_price,
"ticker" : ticker
}
# updating internal prices of trading account to reflect actual selling and buying values
self.latest_prices[stock_data["ticker"]] = open_price
self.account.update(self.latest_prices, stock_data["ticker"])
algorithm(stock_data, self.account, variables_copy)
self.latest_prices[stock_data["ticker"]] = close_price
self.account.update(self.latest_prices, stock_data["ticker"])
stock_data["price"] = close_price
algorithm(stock_data, self.account, variables_copy)
def simulate(self, algorithm, time_start=None, time_end=None):
"""
Function that simulates the algorithm on data. Takes in an algorithm (function) that it will run, and optional times to start and end (timeframes within the data's range).
The algorithm should take in an object (which contains the date, open price, close price, high, low, and volume), an "account" variable which will be the PyBank account passed in to the PyAlgosim object, and an "optional variables" object (which will be the initial object passed in to the PyAlgosim object)
"""
# connecting to database
self._connect_DB()
# normalizing the dates given (if any)
if time_start:
time_start = normalize_date(time_start)
else:
# setting it to zero
time_start = 0
if time_end:
time_end = normalize_date(time_end)
else:
# setting to really big number because I'm not sure how SQL handles the Python "infinity"
time_end = 1000000000000000000
# querying database for each ticker file
for ticker in self.ticker_list:
query = "SELECT * FROM \"" + str(ticker) + "\" WHERE date >= " + str(time_start) + " AND date <=" + str(time_end) + ";"
records = self.cursor.execute(query)
# backtesting individual tickers
print "Backtesting", ticker, "..."
self._iterate_algorithm(algorithm, records, ticker)
print "Backtesting done"