├── Strategy.h ├── tools.h ├── histTextLoad.h ├── MomentumStrat3.h ├── MomentumStrat3.cpp ├── README.rst ├── histTextLoad.cpp ├── stratEval.h └── main.cpp /Strategy.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Strategy.h 3 | * AutoTrade 4 | * 5 | * 6 | */ 7 | 8 | #ifndef STRATEGY_H 9 | #define STRATEGY_H 1 10 | 11 | #include "histTextLoad.h" 12 | 13 | class Strategy 14 | { 15 | public: 16 | Strategy() { } // to be used later 17 | virtual ~Strategy() { } // to be used later 18 | 19 | virtual int execute(timeSlice currentBar) = 0; 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /tools.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tools.h 3 | * Momentum 4 | * 5 | * Created by Peter Harrington on 6/28/11. 6 | * Copyright 2011 Clean Micro, LLC. All rights reserved. 7 | * 8 | * This file contains general purpose tools. 9 | * 10 | */ 11 | 12 | //general debugging print macro 13 | #ifndef DEBUG 14 | #define PRINTD(arg) 15 | #else 16 | #define PRINTD(arg) printf(arg) 17 | #endif 18 | -------------------------------------------------------------------------------- /histTextLoad.h: -------------------------------------------------------------------------------- 1 | /* 2 | * histTextLoad.h 3 | * AutoTrade 4 | * 5 | * Created by Peter Harrington on 1/15/11. 6 | * Copyright 2011 Clean Micro, LLC. All rights reserved. 7 | * 8 | */ 9 | 10 | #ifndef HIST_TEXT_LOAD_H 11 | #define HIST_TEXT_LOAD_H 1 12 | 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | 18 | struct timeSlice 19 | { 20 | long int date; 21 | double close,high,low,open; 22 | int volume; 23 | bool validBar; 24 | }; 25 | 26 | vector histTextLoad (string fileName); 27 | timeSlice parseLine(string inputLine); 28 | string splitCSVline(string inputLine, size_t *commLoc, size_t *prevCommaLoc); 29 | bool timeSliceSortPred(timeSlice t1, timeSlice t2); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /MomentumStrat3.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MomentumStrat3.h 3 | * Momentum 4 | * 5 | * Created by Peter Harrington on 6/28/11. 6 | * Copyright 2011 Clean Micro, LLC. All rights reserved. 7 | * 8 | */ 9 | #ifndef MOMENT3_STRATEGY_H 10 | #define MOMENT3_STRATEGY_H 1 11 | 12 | #include 13 | #include "Strategy.h" 14 | #include 15 | #include 16 | 17 | class MomentumStrat3 : public Strategy 18 | { 19 | int barsSeen; //stores how many bars we have seen 20 | int currPos; //stores current position -1, 0, +1 21 | double prevClose, entryPrice, entryThresh, stopLoss, profitObj; 22 | 23 | public: 24 | MomentumStrat3(double entryThreshIn, double stopLossIn, double ratioIn); 25 | virtual ~MomentumStrat3() {} 26 | virtual int execute(timeSlice currentBar); 27 | }; 28 | #endif 29 | -------------------------------------------------------------------------------- /MomentumStrat3.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MomentumStrat3.cpp 3 | * Momentum 4 | * 5 | * Created by Peter Harrington on 6/28/11. 6 | * Copyright 2011 Clean Micro, LLC. All rights reserved. 7 | * 8 | */ 9 | //#define DEBUG 10 | 11 | #include "MomentumStrat3.h" 12 | #include "tools.h" 13 | 14 | const double ENTRY_THRESH = 0.0005; 15 | const double STOP_LOSS = 0.0015; 16 | const double PROFIT_OBJ = 2*STOP_LOSS; 17 | 18 | MomentumStrat3::MomentumStrat3(double entryThreshIn, double stopLossIn, double ratioIn){ 19 | currPos = 0; 20 | barsSeen = 0; 21 | entryThresh = entryThreshIn; 22 | stopLoss = stopLossIn; 23 | profitObj = ratioIn * stopLossIn; 24 | } 25 | 26 | int MomentumStrat3::execute(timeSlice currentBar) 27 | { 28 | if (barsSeen > 0){ 29 | if (currPos == 0){ //see if we should enter a trade 30 | double deltaClose = currentBar.close - prevClose; 31 | if (deltaClose > entryThresh){ //go long 32 | currPos = 1; 33 | entryPrice = currentBar.close; 34 | PRINTD("found a trade to enter long\n"); 35 | } 36 | else if (deltaClose < -1*entryThresh){ //short 37 | currPos = -1; 38 | entryPrice = currentBar.close; 39 | PRINTD("found a trade to enter short\n"); 40 | } 41 | } 42 | else { //see if we should exit the trade 43 | double priceMovement = currentBar.close - entryPrice; 44 | if ((currPos * priceMovement) > profitObj){ 45 | currPos = 0; //profit obj hit 46 | PRINTD("hit the profit obj\n"); 47 | } 48 | if ((currPos * priceMovement) < -1*stopLoss){ 49 | currPos = 0; //hit the stop loss 50 | PRINTD("hit the stop loss\n"); 51 | } 52 | } 53 | } 54 | prevClose = currentBar.close; 55 | barsSeen++; 56 | 57 | return currPos; 58 | } -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Devistator Backtesting Platform 2 | ===== 3 | I built this backtesting platform after outgrowing platforms in Matlab and Python. I chose C++ because: 4 | * it's fast 5 | * I can trade in C++ 6 | It is very fast. On a two year old machine this code rips through two years of one minute data in less than a second. You can make some mistakes with C++ and kill its perfomance. Execptions are a good example. Care was taken to avoid the performance killing features of C++, and keep the code closer to pure C. 7 | The second point may not seem relevant but once you develop a strategy that looks good in backtesting you will want to start to paper trade it. If papertrading goes bad, then you should ask "why is my trading different from my backtesting?" If you trade in one language but backtested in another language you ported things over, and in doing so you may have made errors. 8 | 9 | Features 10 | ============ 11 | A good backtest platform does it's best to remove unwanted bias, some forms of bias cannot be avoided in the code. Look-ahead bias can be avoided with proper coding. This code demonstrates how to create a strategy class and then evaluate the strategy. Bars are fed to the strategy class, this prevents the strategy class from having access to future price movement, therby removing look ahead bias. 12 | The stratEval function calculates a number of performance metrics for the given strategy, and the given historical data. The following are displayed: 13 | * Sharpe's Ratio 14 | * Max Draw Down 15 | * Number of Trades 16 | * Percentage Time in the Market 17 | * Returns After Transaction Costs 18 | 19 | The Strategy Class 20 | ============ 21 | In order to make this as modular as possible, an abstract class: Strategy was created (see Strategy.h). This does nothing and never gets executed. However all real strategies will inherit from this class. Every class that inherts from this must implement the execute() method, which requires a timeSlice as input. This allows the use of any time scale (tick data is ok). 22 | The idea here is that you write different strategies, and easily swap them in and out. The strategy can do anything you want, maintain state etc. The strategy cannot see future bars, and this prevents look-ahead bias. If you want to look at multiple securities at one time, you can do that by redefining histTextLoad.cpp. This allows things like trading baskets of securities for stat. arb. 23 | One example strategy class MomentumStrat2 is included, with transaction costs included this is wildly unprofitable. (If it were profitable I wouldn't be posting it.) 24 | 25 | Execution 26 | ============ 27 | This default main.cpp I have opens a bunch of text files, parses them, and then runs a given strategy on the historical data. To execute this after you have built the code enter: 28 | ``../Momentum 2.0`` 29 | It is assumed that you are in a directory with a bunch of text files containing historical data. 30 | -------------------------------------------------------------------------------- /histTextLoad.cpp: -------------------------------------------------------------------------------- 1 | // This file parses a comma-delimited text file into a vector of time slices 2 | // 3 | // To do: 1. have the number of fields be variable/user selectable so we do not waste memory 4 | 5 | #include 6 | #include 7 | #include "histTextLoad.h" 8 | 9 | #define DEBUG false 10 | #define DELIMITER '\t' 11 | 12 | vector histTextLoad (string fileName) 13 | { 14 | timeSlice currentBar; 15 | vector returnVector; 16 | string line; 17 | ifstream myfile; 18 | myfile.open(fileName.c_str()); 19 | if (myfile.is_open()) 20 | { 21 | while (myfile.good())//good is true until EOF hit 22 | { 23 | getline(myfile,line); 24 | currentBar = parseLine(line); 25 | if (currentBar.validBar == false) 26 | { 27 | #if DEBUG 28 | cout<<"bad data line: "< positionVec); 28 | void calcReturns(double returnsArr[], vector histData); 29 | void sumCumReturns(stratResu* retStruct, double pnlArr[], int arrSize, maxDDStore* maxDDPass); 30 | double calcPerentMkt(vector positionVec); 31 | int calcNumTrades(vector positionVec); 32 | 33 | stratResu stratEval(Strategy * stratUnderTest, vector testData, maxDDStore* maxDDPass) 34 | { 35 | vector positionVec; //position vector to be used in step 4 36 | positionVec.push_back(0); //initial position is 0 37 | int tradingSignal = 0; 38 | for (vector::const_iterator citer=testData.begin(); citer!=testData.end()-1; ++citer) 39 | { 40 | //get trading signal for each timeSlice 41 | tradingSignal = stratUnderTest->execute(*citer); 42 | positionVec.push_back(tradingSignal); 43 | } 44 | 45 | //calculate the returns: price(t)-price(t-1) 46 | double returnsArr[testData.size()-1]; 47 | calcReturns(returnsArr, testData); 48 | 49 | //calc pnl: position(t)*returns(t) 50 | double pnlArr[testData.size()-1]; 51 | calcPNL(pnlArr, returnsArr, positionVec); 52 | 53 | stratResu retResults; 54 | sumCumReturns(&retResults, pnlArr, positionVec.size(), maxDDPass); 55 | retResults.percInMkt = calcPerentMkt(positionVec); 56 | 57 | retResults.numberTrades = calcNumTrades(positionVec); 58 | 59 | return retResults; 60 | } 61 | void sumCumReturns(stratResu* retStruct, double pnlArr[], int arrSize, maxDDStore* maxDDPass){ 62 | retStruct->totalRet = 0.0; //init data structure 63 | 64 | double cumSum = 0.0; 65 | double cumSumSq = 0.0; 66 | double peakCumSum = maxDDPass->drawDown; //value to hold the peak for maxDD calc 67 | int maxDDtime = maxDDPass->maxDDtime; 68 | int ddTime = maxDDPass->ddTime; 69 | double drawDown = 0.0; 70 | double maxDrawDown = maxDDPass->maxDrawDown; 71 | 72 | for (int i=0; i peakCumSum){ 76 | peakCumSum = cumSum; 77 | ddTime = 0; 78 | } 79 | else { 80 | drawDown = peakCumSum - cumSum; 81 | if (drawDown > maxDrawDown) maxDrawDown = drawDown; 82 | ddTime++; 83 | if (ddTime > maxDDtime) maxDDtime = ddTime; 84 | } 85 | } 86 | retStruct->totalRet = cumSum; 87 | 88 | retStruct->partStatN = arrSize; 89 | retStruct->partStatSumSq = cumSumSq; 90 | 91 | maxDDPass->drawDown = drawDown; 92 | maxDDPass->ddTime = ddTime; 93 | maxDDPass->maxDDtime = maxDDtime; 94 | maxDDPass->maxDrawDown = maxDrawDown; 95 | } 96 | void initDDstore(maxDDStore* maxDDPass){ 97 | maxDDPass->ddTime = 0; 98 | maxDDPass->maxDDtime =0; 99 | maxDDPass->drawDown = 0.0; 100 | maxDDPass->maxDrawDown = 0.0; 101 | } 102 | double calcPerentMkt(vector positionVec){ //count the % of time in the market 103 | int minsInPos = 0; 104 | for (int i=0; i positionVec){ //calc the number of times the position changes 110 | int totalTrades = 0; 111 | int lastEl = positionVec.size()-1; 112 | for (int i=1; i positionVec){ 119 | for (int i=0; i histData){ 125 | //float returnsArr[histData.size()-1]; 126 | for (int i=1; i 9 | #include 10 | #include 11 | #include 12 | #include "Strategy.h" 13 | #include "MomentumStrat3.h" 14 | #include "histTextLoad.h" 15 | #include "stratEval.h" 16 | 17 | using namespace std; 18 | const int NUM_HOLDOUT_ITER = 1; 19 | 20 | int getDir (string dir, vector &files); 21 | stratResu stratEval(Strategy * stratUnderTest, vector testData, maxDDStore* maxDDPass); 22 | double calcStd(double mean, double dailyRets[], int numDays); 23 | double calcStdDist(double totalReturns, double partStatSumSq, int numMins); 24 | 25 | 26 | int main (int argc, char * const argv[]) 27 | { 28 | double sumReturns = 0; 29 | for (int j=0; j < NUM_HOLDOUT_ITER; j++) 30 | { 31 | 32 | vector trainingSet = vector (); 33 | int numFiles = getDir(".", trainingSet); 34 | 35 | //split up the files into training and test sets 36 | vector testSet = vector (); 37 | srand(time(NULL)); //seed the RNG 38 | int randn; 39 | for (int p=0; p < numFiles/10; p++) 40 | { 41 | randn = rand() % trainingSet.size(); //get a random number randn [0, trainingSet.size()] 42 | testSet.push_back(trainingSet[randn]); //add element at index randn, to testSet 43 | trainingSet.erase(trainingSet.begin() + randn); //remove element at index randn from trainingSet 44 | } 45 | 46 | //loop over training set 47 | MomentumStrat3 * myStrat; 48 | stratResu myStratResults; 49 | double retAfterTransCost, totalReturns = 0.0; 50 | double returnArr[numFiles], mean, standDev, sharpeRatio, timeInMktSum = 0.0; 51 | maxDDStore maxDDPass; 52 | initDDstore(&maxDDPass); 53 | int partStatN = 0.0; 54 | double partStatSumSq = 0.0; 55 | int posDays = 0; 56 | int negDays = 0; 57 | int totalTrades = 0; 58 | int numTradingDays = trainingSet.size(); 59 | for (int k=0; k < numTradingDays; k++) 60 | { 61 | //First step load data into vector 62 | vector histData = histTextLoad(trainingSet[k]); 63 | if (histData.size() == 0) return 0; //stop the program if no data was returned 64 | 65 | //Second step: sort vector by date (may not be needed depending on input file) 66 | sort(histData.begin(), histData.end(), timeSliceSortPred); 67 | 68 | //Third step: feed stream to strategy class 69 | myStrat = new MomentumStrat3(atof(argv[1]), atof(argv[2]), atof(argv[3])); 70 | myStratResults = stratEval(myStrat, histData, &maxDDPass); 71 | 72 | partStatN += myStratResults.partStatN; 73 | partStatSumSq += myStratResults.partStatSumSq; 74 | 75 | totalReturns += myStratResults.totalRet; 76 | returnArr[k] = myStratResults.totalRet; 77 | if (myStratResults.totalRet >= 0) posDays++; 78 | else negDays++; 79 | 80 | delete myStrat; 81 | 82 | totalTrades += myStratResults.numberTrades; 83 | timeInMktSum += myStratResults.percInMkt; 84 | } 85 | printf("*******$$$$$$$*******$$$$$$$*******\n"); 86 | retAfterTransCost = totalReturns - (2.5/40000.0)*totalTrades; 87 | printf("totalReturns: %5f, returns after transaction costs: %f\n", totalReturns, retAfterTransCost); 88 | mean = totalReturns/numTradingDays; 89 | standDev = calcStd(mean, returnArr, numTradingDays); 90 | mean = retAfterTransCost/numTradingDays; 91 | //standDev = calcStdDist(totalReturns, partStatSumSq, partStatN); 92 | sharpeRatio = 15.8745 * mean / standDev; //this is anualized Sharpe's ratio 15.875 = sqrt(252) 93 | printf("mean (daily) return is: %f, the return (daily) std is: %f\n", mean, standDev); 94 | printf("anualized Sharpe's ratio: %f\n", sharpeRatio); 95 | printf("percentage time in the mkt: %3f\n", timeInMktSum/numTradingDays); 96 | printf("max draw down: %5f, maxDD time (min): %d\n", maxDDPass.maxDrawDown, maxDDPass.maxDDtime);//this is only the latest 97 | printf("number of trades: %d\n", totalTrades); 98 | printf("total positive days: %d total negative days: %d\n", posDays, negDays); 99 | sumReturns += retAfterTransCost; 100 | } 101 | printf("after %d iterations the avg return is: %f\n",NUM_HOLDOUT_ITER, sumReturns/NUM_HOLDOUT_ITER); 102 | return 0; 103 | } 104 | double calcStdDist(double totalReturns, double partStatSumSq, int numMins){ 105 | double mean, varSum; //this calcs the minute std from partial sums 106 | mean = totalReturns/(double)numMins; 107 | varSum = partStatSumSq - 2 * mean * totalReturns + (double)numMins * mean * mean; 108 | return sqrt(varSum/numMins); 109 | } 110 | 111 | //function for calculating standard deviation 112 | double calcStd(double mean, double dailyRets[], int numDays){ 113 | double var; 114 | double varSum = 0.0; 115 | for (int i=0; i &files){ 125 | DIR *dp; 126 | struct dirent *dirp; 127 | if ((dp = opendir(dir.c_str())) == NULL ){ 128 | cout << "error opening directory: "<< dir <d_name); 134 | if ((strcmp(fileStr.c_str(),".") != 0) && (strcmp(fileStr.c_str(),"..") != 0)) files.push_back(fileStr); 135 | } 136 | closedir(dp); 137 | return files.size(); //return the number of files in the directory 138 | } 139 | 140 | --------------------------------------------------------------------------------