-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy paththeBullsSupplier.py.py
613 lines (449 loc) · 18.8 KB
/
theBullsSupplier.py.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
#theBrokeQuant
#Last Update: 4/30/2012
import os
import urllib
import sys
import random
import math
##USER DEFINED INPUT##
#Starting Date
StartDay = 1
StartMonth = 'January'
StartYear = 2012
#Ending Date
EndDay =31
EndMonth = 'December'
EndYear = 2012
TimeInterval = "Day"
#Starting Capital
startingValue = 10000
#cost per trade
flat_rate = 8.95
numTrials = 1
#percentage of minimum for past 10 days
percentOfMin = 1
stopLoss = 0
daysInMA= 50
daysInLongMA = 100
numDaysTrack = 5
numDaysHold = 10
isSP500 = True
isBatch = False
batchTest = True
batchList = ['']
performedBest = False
topPerformers = 10
##BEGINNING OF PROGRAM##
#import data from file
def importData(StartDay, StartMonth, StartYear, EndDay, EndMonth, EndYear, Ticker, TimeInterval):
masterL = []
Months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
#format begin date ex. 20120528 = 2012-05-28
beginDate = str(StartYear)
if len(beginDate) == 3:
beginDate = beginDate + '0'
if len(str(Months.index(StartMonth)+1)) >= 2:
beginDate = beginDate + str(Months.index(StartMonth)+1)
else:
beginDate = beginDate + "0"+ str(Months.index(StartMonth)+1)
if len(str(StartDay)) >= 2:
beginDate = beginDate + str(StartDay)
else:
beginDate = beginDate + "0"+ str(StartDay)
beginDate = int(beginDate)
#format end date using the same method as above
endDate = str(EndYear)
if len(endDate) == 3:
endDate = endDate + '0'
if len(str(Months.index(EndMonth)+1)) >= 2:
endDate = endDate + str(Months.index(EndMonth)+1)
else:
endDate = endDate + "0"+ str(Months.index(EndMonth)+1)
if len(str(EndDay)) >= 2:
endDate = endDate + str(EndDay)
else:
endDate = endDate + "0"+ str(EndDay)
endDate = int(endDate)
#open the ticker's respective file
filename = "Tickers\\" + Ticker + ".txt"
f = open(filename, "r")
line = f.readline()
grabbingDates = False
#collect all the dates within the specified date range
while line:
line = line.split()
#break if it's a header line
if line[0][0] == 'D':
break
#remove hyphen in data's date
numDate = ""
for char in line[0]:
if char != "-":
numDate = numDate + char
#stop collecting data when the current lines data <= specified
#end date
if int(numDate) > endDate:
f.close()
return masterL
if grabbingDates:
masterL.append(line)
if int(numDate) == endDate:
f.close()
return masterL
#begin collecting data if current line's data >= specified begin date
if int(numDate) >= beginDate and not(grabbingDates):
masterL.append(line)
grabbingDates = True
line = f.readline()
f.close()
return masterL
#find the previous X-days maximum high and minimum low
#works when list is sorted oldest to newest
def xDayLowHigh(L, days):
#reverse the list and take the x most recent days
tempList = []
i = len(L) - 1
while i >= 0:
tempList.append(L[i])
i = i - 1
L = tempList[0:days]
#find the minimum and maximum
maximum = 0
minimum = sys.maxint
for entry in L:
if float(entry[2]) > maximum:
maximum = float(entry[2])
if float(entry[3]) < minimum:
minimum = float(entry[3])
return [minimum, maximum]
#calculate the X-day simple moving average
#works when list is sorted oldest to newest
def movingAverage(L, days):
#reverse the list and take the x most recent days
tempList = []
i = len(L) - 1
while i >= 0:
tempList.append(L[i])
i = i - 1
L = tempList[0:days]
#calculate the average
average = 0
for entry in L:
average = average + float(entry[4])
return average/days
#backtest a single ticker in the specified period of time. the output is a list
#of every possible trade that would have occurred in the specified date range
#L is the output of importData()
def backtest(L, StartDay, StartMonth, StartYear):
returnsList = []
#find the index of the beginning date
Months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
for entry in L:
dateList = entry[0].split("-")
#if there is a problem parsing the html, return nothing
if len(dateList)!=3:
return returnsList
#otherwise continue
if int(dateList[0]) == StartYear and int(dateList[1]) == Months.index(StartMonth)+1 and int(dateList[2]) == StartDay:
index = L.index(entry)
break
elif (int(dateList[0]) == StartYear and int(dateList[1]) == Months.index(StartMonth)+1 and int(dateList[2]) > StartDay) or (int(dateList[0]) == StartYear and int(dateList[1]) > Months.index(StartMonth)+1 and int(dateList[2]) < StartDay) or (int(dateList[0]) > StartYear and int(dateList[1]) < Months.index(StartMonth)+1 and int(dateList[2]) < StartDay):
index = L.index(entry)
break
#if there is not enough data, return nothing
if len(L) < 400:
return returnsList
#CYCLING THROUGH DAYS
#begin cycling through data, starting at the specified begin date and walk
#to the specified end date
i = index
while i < len(L):
#calculate signals
#minimum, maximum, shortMA, long mA
minmaxList = xDayLowHigh(L[:i],numDaysTrack)
movingaverage = movingAverage(L[:i], daysInMA)
longMA = movingAverage(L[:i], daysInLongMA)
#PURCHASE STATEMENT
#if this a new minimum, price > than shortMA, and price > longMA
#purchase the security
if float(L[i][3]) < minmaxList[0]*percentOfMin and float(L[i][3]) > movingaverage and float(L[i][3]) > longMA:
purchase = True
purchaseDate = L[i][0]
beginDayIndex = i
#if the day opens above the minimum and crosses down, the purchase
#price will be at the 10D minimum
if float(L[i][1]) > minmaxList[0]*percentOfMin:
purchasePrice = minmaxList[0]*percentOfMin
#if the open price opens below the 10D minimum we will purchase
#at open
else:
purchasePrice = float(L[i][1])
#move onto the next day
i = i + 1
#for each day holding the security
while purchase:
#SELL STATEMENTS
#if we run out of dates to check sell at close and return
#you will enter this when you're still holding the security
#during your specified end date
if i > len(L) - 1:
salePrice = float(L[i-1][4])
returnsList.append([purchaseDate, salePrice/purchasePrice, purchaseDate, round(purchasePrice,2), L[i-1][0], salePrice, round(salePrice/purchasePrice,3)])
return returnsList
#if the day has a new xDay high, sell the security
#lowhighList = xDayLowHigh(L[:i], 10)
if purchase and float(L[i][2]) > minmaxList[1]:#lowhighList[1]:
#if the open is greater than the 10D high, we sell at open
if float(L[i][1]) > minmaxList[1]:#lowhighList[1]:
salePrice = float(L[i][1])
#if the open is lower than the 10D high, we sell the 10D high
else:
salePrice = minmaxList[1]#lowhighList[1]
returnsList.append([purchaseDate, salePrice/purchasePrice, purchaseDate, round(purchasePrice,2), L[i][0], salePrice, round(salePrice/purchasePrice,3)])
i = i + 1
purchase = False
#if the price drops below the short MA, sell the security
newmovingaverage = movingAverage(L[:i], daysInMA)
if purchase and float(L[i][3]) < newmovingaverage:
#if the open is less than the MA, we sell at the open
if float(L[i][1]) < minmaxList[1]:#lowhighList[1]:
salePrice = float(L[i][1])
#if the open is greater than the MA, we sell at the MA
else:
salePrice = newmovingaverage
returnsList.append([purchaseDate, salePrice/purchasePrice, purchaseDate, round(purchasePrice,2), L[i][0], salePrice, round(salePrice/purchasePrice,3)])
i = i + 1
purchase = False
#if the price drops below our stop loss, sell the security
minLoss = purchasePrice * stopLoss
if purchase and float(L[i][3]) < minLoss:
#if the open is lower than the stop loss, we sell the open
if float(L[i][1]) < minLoss:
salePrice = float(L[i][3])
#oterwise we sell the stop loss
else:
salePrice = minLoss
returnsList.append([purchaseDate, salePrice/purchasePrice, purchaseDate, round(purchasePrice,2), L[i][0], round(salePrice,2), round(salePrice/purchasePrice,3)])
i = i + 1
purchase = False
#if we've been holding for 10 days, sell at close
if purchase and i - (numDaysHold + 1) == beginDayIndex:
salePrice = float(L[i-1][4])
returnsList.append([purchaseDate, salePrice/purchasePrice, purchaseDate, round(purchasePrice,2), L[i-1][0], round(salePrice,2), round(salePrice/purchasePrice,3)])
i = i + 1
purchase = False
#if it's not a sale statement
if purchase:
i = i + 1
#if it's not a buy statement
else:
i = i + 1
return returnsList
#Sort date from oldest to newest
def sortList(L):
newL = []
for entry in L:
temp = []
string = ""
for i in entry[0].split("-"):
string = string + i
temp.extend([int(string)] + entry[1:8])
newL.append(temp)
masterSort = []
while len(newL)>0:
minValue = sys.maxint
minIndex = 0
i = 0
while i < len(newL):
if int(newL[i][0]) < minValue:
minValue = int(newL[i][0])
minIndex = i
i = i + 1
masterSort.append(newL[minIndex])
del newL[minIndex]
for entry in masterSort:
entry[0] = str(entry[0])[0:4] + "-" + str(entry[0])[4:6] + "-" + str(entry[0])[6:8]
return masterSort
#sort a list of numbers in descending order
def sortDescendingInts(L):
masterSort = []
while L:
maxValue = 0
minIndex = 0
i = 0
for entry in L:
if entry[0] > maxValue:
maxValue = entry[0]
maxIndex = i
i = i + 1
masterSort.append(L[maxIndex])
del L[maxIndex]
if not(L):
return masterSort
#Takes a list of tickers, backtests each one of them, collects all possible trades,
#puts them in a dictionary sorted by the purchase date, runs scenarios of possible
#outcomes, and calculates the average return
def backtestBatch(tickerList, numTrials):
MasterList = []
#get all possible trades that could have occured
for Ticker in tickerList:
try:
#print Ticker
L = importData(StartDay, StartMonth, StartYear - 2, EndDay, EndMonth, EndYear, Ticker, TimeInterval)
for entry in backtest(L, StartDay, StartMonth, StartYear):
MasterList.append(entry+[Ticker])
except:
print "Unknown error, skipped ticker", Ticker
#sorted list of all possible trades (sorted by date)
sortedList = sortList(MasterList)
#dictionary of all possible trades
purchaseDict = {}
#sequence of trades
tradesList = []
#place all trades occuring on the same day in a dictionary with key
#equal to the purchase date
tempList = []
listOfDates = []
i = 0
while i < len(sortedList):
if tempList:
if tempList[0][0] == sortedList[i][0]:
tempList.append(sortedList[i])
i = i + 1
else:
purchaseDict.update({tempList[0][0]:tempList})
listOfDates.append(tempList[0][0])
tempList = []
else:
tempList.append(sortedList[i])
i = i + 1
if tempList:
purchaseDict.update({tempList[0][0]: tempList})
listOfDates.append(tempList[0][0])
#Run an analysis looping through numTrials random combinations of trades
AverageReturns = []
loops = 0
while loops < numTrials and len(listOfDates) > 0:
i = 0
buyDate = listOfDates[0]
while buyDate:
#chose a random trade that occured on the curent purchase date
randomInt = random.randrange(len(purchaseDict[buyDate]))
tradesList.append(purchaseDict[buyDate][randomInt])
#retrieve the date that security would have been sold
saleDate = purchaseDict[buyDate][randomInt][4] #saleDate, ha. oops.
#get the index of the next possible trade (this would come
#after the sell date)
indexOfNextEntry = None
for entry in listOfDates:
#format possible purchase date
entryDate = ""
for char in entry:
if char!= "-":
entryDate = entryDate + char
entryDate = int(entryDate)
#format sell date
intsaleDate = ""
for char in saleDate:
if char!= "-":
intsaleDate = intsaleDate + char
intsaleDate = int(intsaleDate)
#next purchase date found
if intsaleDate < entryDate:
indexOfNextEntry = listOfDates.index(entry)
break
#if there are no more possible purchases then end the loop
if indexOfNextEntry == None:
buyDate = False
#otherwise repeat with the next possible purchase
else:
buyDate = listOfDates[indexOfNextEntry]
#if you wanted to see the sequence of purchses, the remove
#the hashtags from the following print statements
#print "Purchase Date, Price, Sale Date, Price, Return, Ticker"
returns = 1
for entry in tradesList:
returns = returns*(entry[1])-2*flat_rate
print entry[1:]
tradesList = []
AverageReturns.append(returns)
loops = loops + 1
#calculate the average return and stand deviation
if len(AverageReturns) > 0:
#average
ave = 0
for entry in AverageReturns:
ave = ave + entry
average = ave/len(AverageReturns)
#standard deviation
stdev = 0
if numTrials > 1:
for entry in AverageReturns:
#print entry
stdev = stdev + (entry - average)*(entry - average)
#print len(AverageReturns)
standardDev = math.sqrt(stdev/(len(AverageReturns)-1))
else:
standardDev = "N/A"
#will return false if no trades occurred
return [average, standardDev]
else:
return False
#test each ticker one by one and return list of best performers
def findBestPerformers(tickerList):
masterAverage = []
for Ticker in tickerList:
tickerAverage = backtestBatch([Ticker], numTrials)[0]
if tickerAverage:
masterAverage.append([tickerAverage, Ticker])
masterAverage = sortDescendingInts(masterAverage)
return masterAverage
#open SP500 list and import tickers
tickerList = []
f= open("SP500.txt","r")
lines = f.readline()
while lines:
tickerList.append((lines.split('\n'))[0])
lines = f.readline()
f.close()
#test group of tickers and get average return of numTrials
#possible combinations of trades
if batchTest:
if isSP500:
tickers = tickerList
if isBatch:
tickers = batchList
#i = 1
#while i >= .89:
#percentOfMin = i
batchtestresults = backtestBatch(tickers, numTrials)
if batchtestresults:
endingValue = startingValue * batchtestresults[0]
#print "Year:", entry
#print "Percent of Min:", i
print "Starting Value:", startingValue
if endingValue:
print "Ending Value:", round(endingValue,2)
print "Return:", round((endingValue/startingValue - 1)*100,3), "%"
print "Stdev:", batchtestresults[1]
print ""
else:
print "Ending Value:", startingValue
print "Return:", "0%"
else:
#print "Year:", entry
#print "Percent of Min:", i
print "No trades"
#i = i - .01
#test each ticker one by one and return list of best performers
if performedBest:
if isSP500:
tickers = tickerList
if isBatch:
tickers = batchList
bestPerformers = findBestPerformers(tickers)[:topPerformers+1]
bestList = []
for entry in bestPerformers:
bestList.append(entry[1])
print entry
print bestList