Happy New Year and some code tidbits

Dollar Cost Averaging Algorithm – Buy X amount every other Monday!

I am not going to get into this controversial approach to trading.  But in many cases, you have to do this because of the limitations of your retirement plan.  Hey if you get free matching dough, then what can you say.

Check this out!

Buy $1000 worth of shares every other Monday. INTC
if dayOfWeek(d of tomorrow)< dayOfWeek(d) Then
begin
toggle = not(toggle);
if toggle then buy dollarInvestment/close shares next bar at open;
end;
Toggle every other Monday ON

Here I use a Boolean typed variable – toggle.   Whenever it is the first day of the week, turn the toggle on or off.  Its new state becomes the opposite if its old state.  On-Off-On-Off – buy whenever the toggle is On or True.  See how I determined if it was the first day of the week; whenever tomorrow’s day of the week is less than today’s day of the week, we must be in a new week.  Monday = 1 and Friday = 5.

Allow partial liquidation on certain days of the year.

Here I use arrays to set up a few days to liquidate a fractional part of the entire holdings.

arrays: sellDates[20](0),sellAmounts[20](0);
//make sure you use valid dates
sellDates[0] = 20220103;sellAmounts[0] = 5000;
sellDates[1] = 20220601;sellAmounts[1] = 5000;
sellDates[2] = 20230104;sellAmounts[2] = 8000;
sellDates[3] = 20230601;sellAmounts[3] = 8000;

value1 = d + 19000000;
if sellDates[cnt] = value1 then
begin
sell sellAmounts[cnt]/close shares total next bar at open;
cnt = cnt + 1;
end;
Notice the word TOTAL in the order directive.

You can use this as reference on how to declare an array and assign the elements an initial value.  Initially, sellDates is an array that contains 20 zeros, and sellAmounts is an array that contains 20 zeros as well.  Load these arrays with the dates and the dollar amounts that want to execute a partial liquidation.  Be careful with using Easylanguage’s Date.  It is in the form YYYMMDD – todays date December 28, 2023, would be represented by 1231228.  All you need to do is add 19000000 to Date to get YYYYMMDD format.  You could use a function to help out here, but why.  When the d + 19000000 equals the first date in the sellDates[1] array, then a market sell order to sell sellAmounts[1]/close shares total is issued.  The array index cnt is incremented.  Notice the order directive.

sell X shares total next bar at market;

If you don’t use the keyword total, then all the shares will be liquidated.

To create a complete equity curve, you will want to liquidate all the shares at some date near the end of the chart.  This is used as input as well as the amount of dollars to invest each time.

//Demonstation of Dollar Cost Averaging
//Buy $1000 shares every two weeks
//Then liquidate a specific $amount on certain days
//of the year

vars: toggle(False),cnt(0);
inputs: settleDate(20231205),dollarInvestment(1000);
arrays: sellDates[20](0),sellAmounts[20](0);
//make sure you use valid dates
sellDates[0] = 20220103;sellAmounts[0] = 5000;
sellDates[1] = 20220601;sellAmounts[1] = 5000;
sellDates[2] = 20230104;sellAmounts[2] = 8000;
sellDates[3] = 20230601;sellAmounts[3] = 8000;

value1 = d + 19000000;
if sellDates[cnt] = value1 then
begin
sell sellAmounts[cnt]/close shares total next bar at open;
cnt = cnt + 1;
end;

if dayOfWeek(d of tomorrow)< dayOfWeek(d) Then
begin
toggle = not(toggle);
if toggle then buy dollarInvestment/close shares next bar at open;
end;

if d + 19000000 = settleDate Then
sell next bar at open;

A cool looking chart.

A chart with all the entries and exits.

Allow Pyramiding

Turn Pyramding On. You will want to allow up to X entries in the same direction regardless of the order directive.

Working with Data2

I work with many charts that have a minute bar chart as Data1 and a daily bar as Data2.  And always forget the difference between:

Close of Data2 and Close[1] of Data2

24 hour regular session used here
1231214 1705 first bar of day - close of data2 4774.00 close[1] of data2 4760.75
1231214 1710 second bar of day - close of data2 4774.00 close[1] of data2 4760.75
1231215 1555 next to last bar of day - close of data2 4774.00 close[1] of data2 4760.75
1231215 1600 last bar of day - close of data2 4768.00 close[1] of data2 4774.00

Up to the last bar of the current trading day the open, high, low, close of data2 will reflect the prior day’s values.  On the last bar of the trading day – these values will be updated with today’s values.

Hope these tidbits help you out.  Happy New Years!

Warmest regards,

George

A Timely Function in EasyLanguage

Learn how to constrain trading between a Start and End Time – not so “easy-peasy”

Why waste time on this?

Is Time > StartTime and Time <= EndTime then…  Right?

This is definitely valid when EndTime > StartTime.  But what happens when EndTime < StartTime.  Meaning that you start trading yesterday, prior to midnight, and end trading after midnight (today.)  Many readers of my blog know I have addressed this issue before and created some simple equations to help facilitate trading around midnight.  The lines of code I have presented work most of the time.  Remember when the ES used to close at 4:15 and re-open at 4:30 eastern?   As of late June 2021, this gap in time has been removed.  The ES now trades between 4:15 and 4:30 continuously.   I discovered a little bug in my code for this small gap when I was optimizing a “get out time.”   I wanted to create a user function that uses the latest session start and end times and build a small database of valid times for the 24-hour markets.  Close to 24 hours – most markets will close for an hour.  With this small database you can test your time to see if it is a valid time.  The construction of this database will require a little TIME math and require the use of arrays and loops.  It is a good tutorial.  However, it is not perfect.  If you optimize time and you want to get out at 4:20 in 2020 on the ES, then you still run into the problem of this time not being valid.  This requires a small workaround.  Going forward with automated trading, this function might be useful.  Most markets trade around the midnight hour – I think meats might be the exception.

Time Based Math

How many 5-minute bars are between 18:00 (prior day) and 17:00 (today)?  We can do this in our heads 23 hours X (60 minutes / 5 minutes) or 23 X 12 = 276 bars.  But we need to tell the computer how to do this and we also should allow users to use times that include minutes such as 18:55 to 14:25. Here’s the math – btw you may have a simpler approach.

Using startTime of 18:55 and endTime of 14:25.

  1. Calculate the difference in hours and minutes from startTime to midnight and then in terms of minutes only.
    1. timeDiffInHrsMins = 2360 – 1855 = 505 or 5 hours and 5 minutes.  We use a little short cut hear.  23 hours and 60 minutes is the same as 2400 or midnight.
    2. timeDiffInMinutes = intPortion(timeDiffInHrsMins/100) * 60 + mod(timeDiffInHrsMins,100).  This looks much more complicated than it really is because we are using two helper functions – intPortion and mod:
      1. ) intPortion – returns the whole number from a fraction.  If we divide 505/100 we get 5.05 and if we truncate the decimal we get 5 hours.
      2. ) mod – returns the modulus or remainder from a division operation.  I use this function a lot.  Mod(505/100) gives 5 minutes.
      3. ) Five hours * 60 minutes + Five minutes = 305 minutes.
  2. Calculate the difference in hours and minutes from midnight to endTime and then in terms of minutes only.
    1. timeDiffInHrsMins = endTime – 0 = 1425 or 14 hours and 25 minutes.  We don’t need to use our little, short cut here since we are simply subtracting zero.  I left the zero in the calculation to denote midnight.
    2. timeDiffInMinutes = timeDiffInMinutes + intPortion(timeDiffInHrsMins/100) * 60 + mod(timeDiffInHrsMins,100).  This is the same calculation as before, but we are adding the result to the number of minutes derived from the startTime to midnight.  
      1. ) intPortion – returns the whole number from a fraction.  If we divide 1425/100, we get 14.05 and if we truncate the decimal, we get 14.
      2. ) mod – returns the modulus or remainder from a division operation.  I use this function a lot.  Mod(1425/100) gives 25.
      3. ) 14* 60 + 25 = 865 minutes.
      4. ) Now add 305 minutes to 865.  This gives us a total of 1165 minutes between the start and end times.
    3. Now divide the timeDiffInMinutes by the barInterval.  This gives 1165 minutes/5 minutes or 233 five-minute bars.

Build Database of all potential time stamps between start and end time

We now have all the ingredients to build are simple array-based database.  Don’t let the word array scare you away.  Follow the logic and you will see how easy it is to use them.   First, we will create the database of all the time stamps between the regular session start and end times of the data on the chart.  We will use the same time-based math (and a little more) to create this benchmark database.  Check out the following code.

// You could use static arrays
// reserve enough room for 24 hours of minute bars
// 24 * 60 = 1440
// arrays: theoTimes[1440](0),validTimes[1440](0);
// syntax - arrayName[size](0) - the zero sets all elements to zero
// this seems like over kill because we don't know what
// bar interval or time span the user will be using

// these arrays are dynamic
// we dimension or reserve space for just what we need
arrays: theoTimes[](0),validTimes[](0);

// Create a database of all times stamps that potentiall could
// occur

numBarsInCompleteSession = timeDiffInMinutes/barInterval;

// Now set the dimension of the array by using the following
// function and the number of bars we calculated for the entire
// regular session
Array_setmaxindex(theoTimes,numBarsInCompleteSession);
// Load the array from start time to end time
// We know the start time and we know the number of X-min bars
// loop from 1 to numBarsInCompleteSession and
// use timeSum as the each and every time stamp
// To get to the end of our journey we must use Time Based Math again.
timeSum = startTime;
for arrayIndex = 1 to numBarsInCompleteSession
Begin
timeSum = timeSum + barInterval;
if mod(timeSum,100) = 60 Then
timeSum = timeSum - 60 + 100; // 1860 - becomes 1900
if timeSum = 2400 Then
timeSum = 0; // 2400 becomes 0000
theoTimes[arrayIndex] = timeSum;

print(d," theo time",arrayIndex," ",theoTimes[arrayIndex]);
end;
Create a dynamic array with all possible time stamps

This is a simple looping mechanism that continually adds the barInterval to timeSum until numBarsInCompleteSession are exhausted.  Reade about the difference between static and dynamic arrays in the code, please.  Here’s how it works with a session start time of 1800:

theoTimes[01] = 1800 + 5 = 1805
theoTimes[02] = 1805 + 5 = 1810
theoTimes[04] = 1810 + 5 = 1815
theoTimes[05] = 1815 + 5 = 1820
theoTimes[06] = 1820 + 5 = 1830
...
//whoops - need more time based math 1860 is not valid
theoTimes[12] = 1855 + 5 = 1860
Insert bar stamps into our theoTimes array

More time-based math

Our loop hit a snag when we came up with 1860 as a valid time.  We all know that 1860 is really 1900.  We need to intervene when this occurs.  All we need to do is use our modulus function again to extract the minutes from our time.

If mod(timeSum,100) = 60 then timeSum = timeSum – 60 + 100.  Her we remove the sixty minutes from the time and add an hour to it.

1860 – 60 + 100 = 1900 // a valid time stamp

That should fix everything right?  What about this:

theoTimes[69] = 2340 + 5 = 2345
theoTimes[70] = 2345 + 5 = 2350
theoTimes[71] = 2350 + 5 = 2355
theoTimes[72] = 2355 + 5 = 2400 // whoops
2400 is okay in Military Time but not in TradeStation

This is a simple fix with.  All we need to do is check to see if timeSum = 2400 and if so, just simply reset to zero.

Build a database on our custom time frame.

Basically, do the same thing, but use the user’s choice of start and end times.

	//calculate the number of barInterval bars in the
//user defined session
numBarsInSession = timeDiffInMinutes/barInterval;

Array_setmaxindex(validTimes,numBarsInSession);

startTimeStamp = calcTime(startTime,barInterval);

timeSum = startTime;
for arrayIndex = 1 to numBarsInSession
Begin
timeSum = timeSum + barInterval;
if mod(timeSum,100) = 60 Then
timeSum = timeSum - 60 + 100;
if timeSum = 2400 Then
timeSum = 0;
validTimes[arrayIndex] = timeSum;
// print(d," valid times ",arrayIndex," ",validTimes[arrayIndex]," ",numBarsInSession);
end;
Create another database using the time frame chose by the user

Don’t allow weird times!

Good programmers don’t allow extraneous values to bomb their functions.  TRY and CATCH the erroneous input before proceeding.  If we have a database of all possible time stamps, shouldn’t we use it to validate the user entry?  Of course, we should.

//Are the users startTime and endTime valid
//bar time stamps? Loop through all the times
//and validate the times.

for arrayIndex = 1 to numBarsInCompleteSession
begin
if startTimeStamp = theoTimes[arrayIndex] then
validStartTime = True;
if endTime = theoTimes[arrayIndex] Then
validEndTime = True;
end;
Validate user's input.

Once we determine if both time inputs are valid, then we can determine if the any bar’s time stamp during a back-test is a valid time.

if validStartTime = false or validEndTime = false Then
error = True;


//Okay to check for bar time stamps against our
//database - only go through the loop until we
//validate the time - break out when time is found
//in database. CanTradeThisTime is the name of the function.
//It returns either True or False

if error = False Then
Begin
for arrayIndex = 1 to numBarsInSession
Begin
if t = validTimes[arrayIndex] Then
begin
CanTradeThisTime = True;
break;
end;
end;
end;
This portion of the code is executed on every bar of the back-test.

Once and only Once!

The code that creates the theoretical and user defined time stamp database is only done on the very first bar of the chart.  Also, the validation of the user’s input in only done once as well.  This is accomplished by encasing this code inside a Once – begin – end.

Now this code will test any time stamp against the current regular session.  If you run a test prior to June 2021, you will get a theoretical database that includes a 4:20, 4:25, and 4:30 on the ES futures.  However, in actuality these bar stamps did not exist in the data.  This might cause a problem when working with a start or end time prior to June 2021, that falls in this range.

Function Name:  CanTradeThisTime

Complete code:

//  Function to determine if time is in acceptable
// set of times
inputs: startTime(numericSimple),endTime(numericSimple);

vars: sessStartTime(0),sessEndTime(0),
startTimeStamp(0),timeSum(0),timeDiffInHrsMins(0),timeDiffInMinutes(0),
validStartTime(False), validEndTime(False);

vars: error(False),arrayIndex(0),
numBarsInSession(0),numBarsInCompleteSession(0);

arrays: theoTimes[](0),validTimes[](0);
vars: arrCnt(0),seed(0);

canTradeThisTime = false;

once
Begin

sessStartTime = sessionStartTime(0,1);
sessEndTime = sessionEndTime(0,1);

if sessStartTime > sessEndTime Then
Begin
timeDiffInHrsMins = 2360 - sessStartTime;
timeDiffInMinutes = intPortion(timeDiffInHrsMins/100) * 60 + mod(timeDiffInHrsMins,100);

timeDiffInHrsMins = sessEndTime - 0;
timeDiffInMinutes += intPortion(timeDiffInHrsMins/100) * 60 + mod(timeDiffInHrsMins,100);
end;

if sessStartTime <= sessEndTime Then
Begin
timeDiffInHrsMins = (intPortion(sessEndTime/100) - 1)*100 + mod(sessEndTime,100) + 60 - sessEndTime;
timeDiffInMinutes = intPortion(timeDiffInHrsMins/100) * 60 + mod(timeDiffInHrsMins,100);
end;

numBarsInCompleteSession = timeDiffInMinutes/barInterval;

Array_setmaxindex(theoTimes,numBarsInCompleteSession);

timeSum = startTime;
for arrayIndex = 1 to numBarsInCompleteSession
Begin
timeSum = timeSum + barInterval;
if mod(timeSum,100) = 60 Then
timeSum = timeSum - 60 + 100;
if timeSum = 2400 Then
timeSum = 0;
theoTimes[arrayIndex] = timeSum;

print(d," theo time",arrayIndex," ",theoTimes[arrayIndex]);
end;

if startTime > endTime Then
Begin
timeDiffInHrsMins = 2360 - startTime;
timeDiffInMinutes = intPortion(timeDiffInHrsMins/100) * 60 + mod(timeDiffInHrsMins,100);
timeDiffInHrsMins = endTime - 0;
timeDiffInMinutes += intPortion(timeDiffInHrsMins/100) * 60 + mod(timeDiffInHrsMins,100);
end;

if startTime <= endTime Then
Begin
timeDiffInHrsMins = (intPortion(endTime/100) - 1)*100 + mod(endTime,100) + 60 - startTime;
timeDiffInMinutes = intPortion(timeDiffInHrsMins/100) * 60 + mod(timeDiffInHrsMins,100);
end;

numBarsInSession = timeDiffInMinutes/barInterval;

Array_setmaxindex(validTimes,numBarsInSession);

startTimeStamp = calcTime(startTime,barInterval);

timeSum = startTime;
for arrayIndex = 1 to numBarsInSession
Begin
timeSum = timeSum + barInterval;
if mod(timeSum,100) = 60 Then
timeSum = timeSum - 60 + 100;
if timeSum = 2400 Then
timeSum = 0;
validTimes[arrayIndex] = timeSum;
print(d," valid times ",arrayIndex," ",validTimes[arrayIndex]," ",numBarsInSession);
end;
for arrayIndex = 1 to numBarsInCompleteSession
begin
if startTimeStamp = theoTimes[arrayIndex] then
validStartTime = True;
if endTime = theoTimes[arrayIndex] Then
validEndTime = True;
end;
end;

if validStartTime = False or validEndTime = false Then
error = True;

if error = False Then
Begin
for arrayIndex = 1 to numBarsInSession
Begin
if t = validTimes[arrayIndex] Then
begin
CanTradeThisTime = True;
break;
end;
end;
end;
Complete CanTradeThisTime function code

Sandbox Strategy function driver

inputs: startTime(1800),endTime(1500);

if canTradeThisTime(startTime,endTime) Then
if d = 1231206 or d = 1231207 then
print(d," ",t," can trade this time");

I hope you find this useful.  Remember to purchase by Easing into EasyLanguage books at amazon.com.  The DayTrade edition is still on sale.  Email me with any question or suggestions or bugs or anything else.