Have you ever wanted to know exactly how long the different sections of code in your studies take to execute? Now you can easily time many different sections of code by calling a few simple EasyLanguage functions. You can get a summary of the total time and the average time per bar for each section of code, and you can also get the times for each bar for any of the sections. This utility times your code at a resolution in the microsecond range (1 / 1,000,000 second) so that you can get accurate and meaningful results.
Once you know where the time is being spent in your code, you can focus your efforts on improving performance where it counts the most. Then you can re-time the code to see the effects of your changes.
The following functions are provided:
TimerEnable — Enable the timing functions for a study
TimerBeginBar — Call at the beginning of a study’s code
TimerEndBar — Call at the end of a study’s code (but before TimerWriteResults)
TimerStart(Description) — Start the timer for a section of code
TimerStop(Description) — Stop the timer for a section of code
TimerStartDetail(Description, Bar) — Start timer and keep bar-by-bar detail
TimerStopDetail(Description, Bar) — Stop timer and keep bar-by-bar detail
TimerWriteResults(FileName, IncludeBarTimes) — Write the timing results to a text file
TimerError — Return a description of the last timer error
Call TimerEnable to enable the timing functions. If this function is not called, the other timing functions do nothing. You can call TimerEnable conditionally based on an input (e.g. UseTimer) to your study. This makes it easy to turn the timing functions on or off. This function will typically be called on the first bar (CurrentBar = 1). See the sample indicator below for an example.
You should call TimerBeginBar before any other code in your study (but after TimerEnable). You should call TimerEndBar after all the other code in your study (but before TimerWriteResults). These functions are required, since they calculate the total time for the study and the total time between bars.
The TimerStart/TimerStop pair allows you to time between any two points in your study, including across multiple bars. For example, you can use conditions to call TimerStart on one bar and TimerStop on a subsequent bar. You can also call TimerStart and TimerStop on every bar (or even multiple times per bar). The timer will add up all the times for a section (which is indicated by the description you pass to the functions), and it will report the total time for that section in the results file. It will also report the section time as a percentage of overall time.
The TimerStartDetail/TimerStopDetail pair allows you to time a section of code on every bar (or a subset of bars). The timer keeps track of the total time for each bar, which allows it to generate additional statistics for the results file, such as the average time per bar, the standard deviation, the minimum and maximum, and the 10th to 90th percentiles of the bar times. You also have the option to include the individual bar times in the report. If you wish, you can use conditions to call TimerStartDetail and TimerStopDetail only on certain bars (e.g. bars 1000 – 2000), in which case only these bars will be included in the report. The only requirement is that if you call TimerStartDetail on a specific bar, you must call TimerStopDetail on that bar as well.
In general, it is recommended that you use TimerStartDetail/TimerStopDetail rather than TimerStart/TimerStop, since the Detail functions generate much more information for the report. The TimerStart/TimerStop calls are provided primarily for extra flexibility, since they allow you to time between any two points in the execution of your study (including between-the-bars time).
The TimerWriteResults function writes the timing results to the specified text file. If the IncludeBarTimes argument is True, the function will also report individual bar times for any sections where you used the TimerStartDetail/TimerStopDetail functions. This function is typically called on the last bar of a chart, and it should be called after TimerEndBar. If you wish, you can also call TimerEnable on a bar other than the first bar and TimerWriteResults on a bar other than the last bar. In that case, the timer will report only on the bars between the TimerEnable call and the TimerWriteResults call. (You can think of these functions as turning the other timer functions on and off.)
Each of the functions returns true when successful, or false if there was an error (e.g. if you called TimerStop without first calling TimerStart). Be sure that the Description is the same for the TimerStart/TimerStop or TimerStartDetail/TimerStopDetail calls that surround a section of code. If a function returns false, you can call the TimerError function to get a description of the error.
To install the utility, copy the ELPerfTimer.dll file to your TradeStation program directory (probably C:\Program Files\TradeStation\Program). Then import the provided ELD file, which contains the EasyLanguage functions and a sample indicator (“Timer Test”) which uses the timing functions. The indicator has an input called UseTimer (true by default) which controls the timing functions, and an input called IncludeBarTimes (false by default) which indicates whether to report individual bar times. It is highly recommended that you study the example indicator to see how to use the functions and perform error checking. Try applying the indicator to a 120 day chart with 5 min bars. (The indicator uses 500 bars of history by default.)
The tool has a timing resolution of 0.28 microseconds on my machine. I think this might vary depending on processor speed (not sure about this), but it should be adequate on any fast machine. The timing resolution for your machine is included in the timing report.
Here’s a sample of how you would use the functions:
Inputs: Length(500), UseTimer(true), IncludeBarTimes(false); Vars: Result1(0), Result2(0); if UseTimer and CurrentBar = 1 then Condition1 = TimerEnable; if TimerBeginBar = false then Print(TimerError); if TimerStart("Fast Method") = false then Print(TimerError); Result1 = AverageFC(Close, Length); if TimerStop("Fast Method") = false then Print(TimerError); if TimerStartDetail("Slow Method", CurrentBar) = false then Print(TimerError); Result2 = Average(Close, Length); if TimerStopDetail("Slow Method", CurrentBar) = false then Print(TimerError); Plot1(Result1, "Result1"); Plot2(Result2, "Result2"); if TimerEndBar = false then Print(TimerError); if LastBarOnChart then begin if TimerWriteResults("C:\Temp\Timing " + GetSymbolName + ".txt", IncludeBarTimes) = false then Print(TimerError); end;
This might produce the following timing log:
Overall Time: 1.019986 seconds Resolution: 0.28 microseconds Between Bars: 0.190208 seconds % of Overall: 18.65% Fast Method Section Time: 0.056616 seconds % of Overall: 5.55% Slow Method Section Time: 0.591611 seconds % of Overall: 58.00% Avg per Bar: 0.000069 seconds Standard Dev: 0.000032 seconds Select Avg: 0.000067 seconds Select StDev: 0.000002 seconds Percentiles: Min: 0.000065 seconds 10%: 0.000066 seconds 20%: 0.000066 seconds 30%: 0.000066 seconds 40%: 0.000067 seconds 50%: 0.000067 seconds 60%: 0.000067 seconds 70%: 0.000067 seconds 80%: 0.000068 seconds 90%: 0.000069 seconds Max: 0.001970 seconds
A quick note here about the “Select Avg” and “Select StDev” statistics. For some reason, a typical timing run will usually have a few bar times that are unusually large. This probably happens because of CPU time being used by other processes, or it may happen because of behind-the-scenes overhead in TradeStation. In any case, these outlier times distort the statistics, since they usually do not indicate the actual time that the code takes to execute. The “Select Avg” and “Select StDev” statistics are calculated with the top 2% of the bar times removed. This will generally give a more accurate picture of the bar times distribution.
Important: Be sure to call TimerWriteResults (usually on the last bar) if you call any of the other timer functions. Also, be sure that it is called at the end of the study (after TimerEndBar). In addition to writing the results to a file, this function cleans up the memory used by the timing functions. If you fail to call this function, the memory will not be freed until the ELPerfTimer DLL is unloaded.
Here is the Timer DLL and ELD:
For those interested in the Visual C++ code for the DLL, here it is. Please note that no knowledge of C++ or DLLs is necessary in order to use this utility! This code is simply provided for the curious.