def runBacktest(self, path, longWindow, shortWindow, display): dataframe = pd.read_csv(path) count = len(dataframe) for i in range(1, count): heuristicResult = self.heuristic(dataframe, longWindow, shortWindow, i) lastPrice = dataframe.iloc[i]["close"] date = dataframe.iloc[i]["timestamp"] if heuristicResult == 1 and not self.isLong(): """ Executing ORDINARY BUY order """ self.balance, self.unitsAvailable, self.stopLoss, self.takeProfit = tradeUtil.ordinaryBuyMax(dataframe.iloc[i]["timestamp"], self.balance, dataframe.iloc[i]["close"], self.unitsAvailable, display) self.lastBuyIndex = i self.buyDates.append(pd.to_datetime(date)) elif self.isLong() and (lastPrice < self.stopLoss or lastPrice > self.takeProfit): if lastPrice < self.stopLoss: """ Executing STOP LOSS SELL order """ self.balance, self.unitsAvailable = tradeUtil.stopLossSell(dataframe.iloc[i]["timestamp"], self.balance, self.stopLoss, self.unitsAvailable, display) diff1 = dataframe.iloc[i-1]["close"] - dataframe.iloc[i]["close"] diff2 = dataframe.iloc[i-1]["close"] - self.stopLoss frac = (diff2/diff1)*24 self.sellDates.append(pd.to_datetime(date)-timedelta(hours=24-frac)) elif lastPrice > self.takeProfit: """ Executing TAKE PROFIT SELL order """ self.balance, self.unitsAvailable = tradeUtil.takeProfitSell(dataframe.iloc[i]["timestamp"], self.balance, dataframe.iloc[i]["close"], self.unitsAvailable, display) self.sellDates.append(pd.to_datetime(date)) """ Transferring excess funds from balance to savings """ if self.balance > self.startingBalance: self.saved += self.balance - self.startingBalance self.balance = self.startingBalance """ If last BUY order has no corresponding SELL order, do not include it in profit/loss calculations. """ if self.isLong(): if display: print("NOTE: Last BUY entry printed above was not closed, and is not factored in Profit/Loss calculation.") self.buyDates = self.buyDates[:-1] self.balance += dataframe.iloc[self.lastBuyIndex]["close"] * self.unitsAvailable self.balance = self.balance + self.saved returnPercentage = 100*((self.balance-self.startingBalance)/self.startingBalance) period = util.periods(path)/365 avgReturnPercentage = round(returnPercentage/period, 2) period = round(period, 2) returnPercentage = round(returnPercentage, 2) self.balance = round(self.balance, 2) print("\nReturn: " + str(returnPercentage) + "% over a period of " + str(period) + " years (" + str(avgReturnPercentage) + "% per annum)") sys.stdout.flush() return self.buyDates, self.sellDates
def smaCrossover(path, window): smaColumn = "sma" + str(window) dataframe = pd.read_csv(path) count = len(dataframe) startingBalance = 1000000 balance = startingBalance unitsAvailable = 0 long = False for i in range(0, count): if (dataframe.iloc[i]["close"] > dataframe.iloc[i][smaColumn]) and (not long): qty = math.floor(balance / dataframe.iloc[i]["close"]) print( str(dataframe.iloc[i]["timestamp"]) + ": BUY " + str(qty) + " units at $" + str(dataframe.iloc[i]["close"])) long = True balance -= dataframe.iloc[i]["close"] * qty unitsAvailable += qty elif (dataframe.iloc[i]["close"] < dataframe.iloc[i][smaColumn]) and (long): print( str(dataframe.iloc[i]["timestamp"]) + ": SELL " + str(unitsAvailable) + " at $" + str(dataframe.iloc[i]["close"])) long = False balance += dataframe.iloc[i]["close"] * unitsAvailable unitsAvailable = 0 if unitsAvailable > 0: balance += dataframe.iloc[0]["close"] * unitsAvailable returnPercentage = 100 * ((balance - startingBalance) / startingBalance) period = util.periods(dataframe) / 365 avgReturnPercentage = round(returnPercentage / period, 2) period = round(period, 2) returnPercentage = round(returnPercentage, 2) balance = round(balance, 2) print("\nBalance: " + str(balance) + "\nReturn Percentage: " + str(returnPercentage) + "% over a period of " + str(period) + " years.") print("Average Yearly Return Percentage: " + str(avgReturnPercentage) + "%") print("Buy and Hold Return Percentage would have been: " + str( round( 100 * (dataframe.iloc[count - 1]['close'] - dataframe.iloc[0]['close']) / dataframe.iloc[0]['close'], 2)) + "%") print("I.e. Bought at $" + str(dataframe.iloc[0]['close']) + " on " + str(dataframe.iloc[0]['timestamp'] + " and sold at $" + str(dataframe.iloc[count - 1]['close']) + " on " + str(dataframe.iloc[count - 1]['timestamp']))) print("--------------------") sys.stdout.flush()
def runBacktest(self, path, window): dataframe = pd.read_csv(path) count = len(dataframe) for i in range(0, count): heuristicResult = self.heuristic(dataframe, window, i) if heuristicResult == 1 and not self.isLong(): self.balance, self.unitsAvailable, self.stopLoss = tradeUtil.ordinaryBuyMax( dataframe.iloc[i]["timestamp"], self.balance, dataframe.iloc[i]["close"], self.unitsAvailable) self.lastBuyIndex = i elif heuristicResult == 0 and self.isLong(): self.balance, self.unitsAvailable = tradeUtil.ordinarySellMax( dataframe.iloc[i]["timestamp"], self.balance, dataframe.iloc[i]["close"], self.unitsAvailable) # Make sure last buy was closed --- fix this if self.isLong(): print( "NOTE: Last BUY entry printed above is invalid. Please ignore." ) self.balance += dataframe.iloc[ self.lastBuyIndex]["close"] * self.unitsAvailable returnPercentage = 100 * ( (self.balance - self.startingBalance) / self.startingBalance) period = util.periods(dataframe) / 365 avgReturnPercentage = round(returnPercentage / period, 2) period = round(period, 2) returnPercentage = round(returnPercentage, 2) self.balance = round(self.balance, 2) print("\nReturn: " + str(returnPercentage) + "% over a period of " + str(period) + " years (" + str(avgReturnPercentage) + "% per annum)") print("--------------------") sys.stdout.flush()
if forcePull and os.path.isfile(rawPath): os.unlink(rawPath) if os.path.isfile(enrichedPath): os.unlink(enrichedPath) """ Pull and/or Enrich pricing data """ pullAndEnrich.pullAndEnrich(ticker, "full", rawPath, enrichedPath, indicators) """ Backtest Random strategy """ util.printPartition() print("Simulating Random Strategy " + str(randomStrategyCount) + " times.") randomReturn = randomStrategy.runMultipleRandoms(enrichedPath, randomStrategyCount) print("\nRandom strategy would have returned: " + str(randomReturn) + "% over a period of " + str(round(util.periods(enrichedPath)/365, 2)) + " years (on average).") sys.stdout.flush() """ Backtest each strategy requested """ for strategy in strategies: if "smac" in strategy: horizon = strategy[4:] util.printPartition() print("Simulating SMA Crossover Strategy with time horizon: " + horizon) sys.stdout.flush() buys, sells = SMACrossover.SMACrossover().runBacktest(enrichedPath, int(horizon), display) if plotBacktests: simplePlt.plot(ticker, enrichedPath, buys, sells, "SMA Crossover (" + horizon + " day) Strategy Backtest")
def smaLongShortCrossover(path, longWindow, shortWindow): longSMAColumn = "sma" + str(longWindow) shortSMAColumn = "sma" + str(shortWindow) dataframe = pd.read_csv(path) count = len(dataframe) print("--------------------") print("Running SMA " + str(longWindow) + "/" + str(shortWindow) + " Long/Short Crossover Strategy.") print("--------------------") startingBalance = 1000000 balance = startingBalance unitsAvailable = 0 long = False lastPrice = 0 stoploss = -1 entry = -1 stoplossPCT = .25 for i in range(1, count): if long and dataframe.iloc[i]['close'] <= stoploss: qty = math.floor(unitsAvailable * stoplossPCT) print( str(dataframe.iloc[i]["timestamp"]) + ": **PARTIAL STOP LOSS SELL** " + str(qty) + " at $" + str(dataframe.iloc[i]['close'])) sys.stdout.flush() balance += dataframe.iloc[i]['close'] * qty unitsAvailable -= qty if unitsAvailable == 0: long = False stoploss = stoploss - 0.05 * stoploss continue """ if long and dataframe.iloc[i]['close'] >= 1.1*entry: print (str(dataframe.iloc[i]["timestamp"]) + ": **PROFIT SELL** " + str(unitsAvailable) + " at $" + str(dataframe.iloc[i]['close'])) sys.stdout.flush() long = False balance += dataframe.iloc[i]['close'] * unitsAvailable unitsAvailable = 0 continue """ if (dataframe.iloc[i][shortSMAColumn] > dataframe.iloc[i][longSMAColumn]) and ( not long) and (dataframe.iloc[i - 1][shortSMAColumn] < dataframe.iloc[i - 1][longSMAColumn]): qty = math.floor(balance / dataframe.iloc[i]["close"]) print( str(dataframe.iloc[i]["timestamp"]) + ": **BUY** " + str(qty) + " units at $" + str(dataframe.iloc[i]["close"])) sys.stdout.flush() long = True stoploss = dataframe.iloc[i][ "close"] - 0.05 * dataframe.iloc[i]["close"] entry = dataframe.iloc[i]["close"] #print("Setting STOP LOSS at " + str(stoploss)) sys.stdout.flush() balance -= dataframe.iloc[i]["close"] * qty unitsAvailable += qty lastPrice = dataframe.iloc[i]["close"] elif (dataframe.iloc[i][shortSMAColumn] < dataframe.iloc[i][longSMAColumn]) and ( long) and (dataframe.iloc[i - 1][shortSMAColumn] > dataframe.iloc[i - 1][longSMAColumn] ): # and dataframe.iloc[i]["close"] > lastPrice: print( str(dataframe.iloc[i]["timestamp"]) + ": **SELL** " + str(unitsAvailable) + " at $" + str(dataframe.iloc[i]["close"])) sys.stdout.flush() long = False balance += dataframe.iloc[i]["close"] * unitsAvailable unitsAvailable = 0 if unitsAvailable > 0: balance += dataframe.iloc[i]["close"] * unitsAvailable print( str(dataframe.iloc[i]["timestamp"]) + ": **FORCE SELL** " + str(unitsAvailable) + " at $" + str(dataframe.iloc[i]["close"])) returnPercentage = 100 * ((balance - startingBalance) / startingBalance) period = util.periods(dataframe) / 365 avgReturnPercentage = round(returnPercentage / period, 2) period = round(period, 2) returnPercentage = round(returnPercentage, 2) balance = round(balance, 2) print("\nBalance: " + str(balance) + "\nReturn Percentage: " + str(returnPercentage) + "% over a period of " + str(period) + " years.") #print("Average Yearly Return Percentage: " + str(avgReturnPercentage) + "%") print("Buy and Hold Return Percentage would have been: " + str( round( 100 * (dataframe.iloc[count - 1]['close'] - dataframe.iloc[0]['close']) / dataframe.iloc[0]['close'], 2)) + "%") print("I.e. Bought at $" + str(dataframe.iloc[0]['close']) + " on " + str(dataframe.iloc[0]['timestamp'] + " and sold at $" + str(dataframe.iloc[count - 1]['close']) + " on " + str(dataframe.iloc[count - 1]['timestamp']))) print("--------------------\n\n") sys.stdout.flush()