Category Archives: Python

The EasyLanguage Function: A Thing of Beauty

The concept of Scope when it comes to a programming language can get very confusing very quickly. If you are new to programming and you think Scope is a mouthwash, no worries here. I will give a full explanation of Scope in terms of programming with the help of ChatGPT. Its response is italicized.

I recently chatted with ChatGPT about Scope in terms of the Python programing language.

In Python, the scope of a variable refers to the region of the program where that variable can be accessed or referenced. There are two types of scopes in Python: global scope and local scope.

Here’s an example that demonstrates the concept of scope in Python:

# global variable
x = 10

def my_func():
# local variable
y = 5
print("x inside my_func:", x) # x is accessed from the global scope
print("y inside my_func:", y) # y is accessed from the local scope

my_func()

print("x outside my_func:", x) # x is accessed from the global scope
print("y outside my_func:") # y is not defined in the global scope, so this will raise a NameError
Example of Scope as demonstrated by Chat-GPT

In this example, we define a global variable x with a value of 10.

We then define a function my_func that has a local variable y with a value of 5. Inside the function, we print the values of x and y. The variable x is accessed from the global scope, which means it can be referenced from anywhere in the program.

The variable y, on the other hand, is only accessible within the function, which is the local scope. Outside the function, we print the value of x again, which is still accessible from the global scope. However, when we try to print the value of y, we get a NameError because y is not defined in the global scope.

Okay that was pretty formal. In simpler terms, a variable declared inside a function can only be seen inside that function, unless it is declared globally. Another thing about Scope, is that the variables have amnesia. Once you step out of the function the variable forgets what it was, and you can’t refer to its prior value upon return to the function.

Unless you are an EasyLanguage function. Scope is still constrained by an EasyLanguage function, because it is a separate piece of code held within the library of Analysis Techniques. Most of the time you can’t see what’s in the function unless you open it with the ELEditor. However, the variables that are defined inside the function do not suffer from amnesia. If you need to refer to a prior value of a locally declared variable, you can. This type of function is what EasyLanguage calls a Serial function. The only downside to this function is it slows processing down quite a bit.

Okay. To make a long story short I wanted to show the magic of EasyLanguage function that I have been working with on a project. This project includes some of Ehlers’ cycle analysis functions. The one I am going to discuss today is the HighRoof function – don’t worry I am not going to go into detail of what this function does. If you want to know just GOOGLE it or ask ChatGPT. I developed a strategy that used the function on the last 25 days of closing price data. I then turned around and fed the output of the first pass of the HighRoof function right back into the HighRoof function. Something similar to embedding functions.

doubleSmooth = average(average(c,20),20);

Sort of like a double smoothed moving average. After I did this, I started thinking does the function remember the data from its respective call? The first pass used closing price data, so its variables and their history should be in terms of price data. The second pass used the cyclical movements data that was output by the initial call to the HighRoof function. Everything turned out fine, the function remembered the correct data. Or seemed like it did. This is how you learn about any programming language – pull out your SandBox and do some testing. First off, here is my conversion of Ehlers’ HighRoof function in EasyLanguage.

//Ehlers HiRoof
Inputs: dataSeries(numericseries),cutPeriod(Numeric);

Vars: a1(0), b1(0), c1(0), c2(0), c3(0), Filt(0), Filt2(0),
alpha1(0),oneMinusAlpha1(0), highPass(0),myhp(0),degrees(0);
Vars: numTimesCalled(0);

//Highpass filter cyclic components whose periods are shorter than 48 bars

numTimesCalled = numTimesCalled + 1;

print(d," numTimesCalled ",numTimesCalled," highPass[1] ",highPass[1]," highPass[2] ",highPass[2]," highPass[3] ",highPass[3]);
degrees = .707*360 / CutPeriod;

alpha1 = (Cosine(degrees) + Sine(degrees) - 1) / Cosine(degrees);

oneMinusAlpha1 = 1-alpha1;

highPass = square(oneMinusAlpha1/2)*(dataSeries-2*dataSeries[1]+dataSeries[2]) +
2*(oneMinusAlpha1)*highPass[1]-square(oneMinusAlpha1)*highPass[2];



EhlersHighRoof=highPass;
Ehlers High Roof Function

This function requires just two inputs – the data (with a history) and a simple length or cut period. The first input is of type numericSeries and the second input is of type numericSimple. You will see the following line of code

 print(d," numTimesCalled ",numTimesCalled," highPass[1] ",highPass[1]," highPass[2] ",highPass[2]," highPass[3] ",highPass[3]);

This code prints out the last three historic values of the HighPass variable for each function call. I am calling the function twice for each bar of data in the Crude Oil futures continuous contract.

1230206.00 numTimesCalled  494.00 highPass[1]   -0.78 highPass[2]   -0.51 highPass[3]   -0.60
1230206.00 numTimesCalled 494.00 highPass[1] -0.05 highPass[2] -0.02 highPass[3] -0.06
1230207.00 numTimesCalled 495.00 highPass[1] -0.38 highPass[2] -0.78 highPass[3] -0.51
1230207.00 numTimesCalled 495.00 highPass[1] 0.04 highPass[2] -0.05 highPass[3] -0.02
1230208.00 numTimesCalled 496.00 highPass[1] 0.31 highPass[2] -0.38 highPass[3] -0.78
1230208.00 numTimesCalled 496.00 highPass[1] 0.16 highPass[2] 0.04 highPass[3] -0.05
1230209.00 numTimesCalled 497.00 highPass[1] 0.49 highPass[2] 0.31 highPass[3] -0.38
1230209.00 numTimesCalled 497.00 highPass[1] 0.15 highPass[2] 0.16 highPass[3] 0.04
1230210.00 numTimesCalled 498.00 highPass[1] 0.30 highPass[2] 0.49 highPass[3] 0.31
1230210.00 numTimesCalled 498.00 highPass[1] 0.07 highPass[2] 0.15 highPass[3] 0.16
1230213.00 numTimesCalled 499.00 highPass[1] 0.52 highPass[2] 0.30 highPass[3] 0.49
1230213.00 numTimesCalled 499.00 highPass[1] 0.08 highPass[2] 0.07 highPass[3] 0.15
1230214.00 numTimesCalled 500.00 highPass[1] 0.44 highPass[2] 0.52 highPass[3] 0.30
1230214.00 numTimesCalled 500.00 highPass[1] 0.04 highPass[2] 0.08 highPass[3] 0.07
Output of calling HighRoof twice per bar

Starting at the top of the output you will see that on 1230206 the function was called twice with two different sets of data. As you can see the output of the first two lines is of a different magnitude. The first line is approximately an order or magnitude of 10 of the second line. If you go to lines 3 and 4 you will see the highPass[1] of lines 1 and 2 moves to highPass[2] and then onto highPass[3]. I think what happens internally is for every call on per bar basis, the variables for each function call are pushed into a queue in memory. The queue continues to grow for whatever length is necessary and then either maintained or truncated at some later time.

Why Is This So Cool?

In many languages the encapsulation of data with the function requires additional programming. The EasyLanguage function could be seen as an “object” like in object-oriented programming. You just don’t know you are doing it. EasyLanguage takes care of a lot of the behind-the-scenes data management. To do the same thing in Python you would need to create a class of Ehlers Roof that maintain historic data in class members and the calculations would be accomplished by a class method. In the case of calling the function twice, you would instantiate two classes from the template and each class would act independent of each other.

Here is my SandBox for Indicator


Value1 = EhlersHighRoof(close,25);
plot1(Value1,"EhlersHiRoof");
Value2 = EhlersHighRoof(value1,25);
plot2(Value2,"EhlersHiRoof2");
Sandbox Playground for Ehlers Function

 

One last nugget of information. If you are going to be working with trigonometric functions such as Cosine, Sine or Tangent, make sure your arguments are in degrees not radians. In Python, you must use radians.

Trading the Equity Curve – Part 1 of N?

Only Trade the Best Segments of the Equity Curve – Cut Out Drawdown and Take Advantage of Run Ups! Really?

Equity curve feedback has been around for many years and seems highly logical, but one can’t get an industry-wide agreement on its benefit.  The main problem is to know when to turn trading off and then back on as you track the equity curve.  The most popular approach is to use a moving average of the equity curve to signal system participation.   When the equity curve moves below 30, 60, or 90 period-moving average of equity, then just turn it off and wait until the curve crosses back above the average.  This approach will be investigated in Part 2 of this series.  Another approach is to stop trading once the curve enters a drawdown that exceeds a certain level and then start back up once the equity curve recovers.  In this post, this method will be investigated.

Programmers Perspective

How do you go about programming this tool to start with.  There are probably multiple ways of accomplishing this task, but the two I have most often observed were the two pass process and the inline simultaneous tracking of the synthetic and actual equity curves.  The two pass process generates an unadulterated equity curve and stores the equity and trades either in memory or in a file.  The second part of the process monitors the external equity curve along with the external trades synchronously and while trading is turned on, the trades are executed as they occur chronologically.  When trading is turned off, the synthetic equity curve and trades are processed along the way.   The second method is to create, which I have coined (maybe others too!), a synthetic equity curve and synthetic trades.  I have done this in my TradingSimula_18 software by creating a SynthTrade Class.  This class contains all the properties of every trade and in turn can use this information to create a synthetic equity curve.  The synthetic equity curve and trades are untouched by the real time trading.

Start Simple

The creation of an equity curve monitor and processor is best started using a very simple system.  One market algorithm that enters and exits on different dates, where pyramiding and scaling in or out are not allowed.  The first algorithm that I tested was a mean reversion system where you buy after two consecutive down closes followed by an up close and then waiting one day.  Since I tested the ES over the past 10 years you can assume the trend is up. I must admit that the day delay was a mistake on my behalf.  I was experimenting with a four bar pattern and somehow forgot to look at the prior day’s action.  Since this is an experiment it is OK!

if marketPosition <> 1 and 
(c[2] < c[3] and c[3] < c[4] and c[1]  > =  c[2]) then
buy next bar at open;

//The exit is just as simple -
//get out after four days (includeing entry bar) on the next bars open - no stops or profit objectives.  

If barsSinceEntry > 2 then sell next bar at open;
Simple Strategy to test Synthetic Trading Engine

Here is the unadulterated equity curve using $0 for execution costs.

Non adjusted equity curve of our simple mean reversion system. Wait for a pull back and then a rally before entering.

The Retrace and Recover Method

In this initial experiment, trading is suspended once you reach a draw down of 10% from the peak of the equity curve and then resumes trading once a rally of 15% of the subsequent valley.  Here is an intriguing graphic.

Green means ON. Red means OFF. The lower curve is the resultant curve.

I did this analysis by hand with Excel and it is best case scenario.  Meaning that when trading is turned back on any current synthetic position is immediately executed in the real world.  This experiment resulted in nearly the same drawdown but a large drop in overall equity curve growth – $75K.

Put the Synthetic Equity Curve Engine to the Test

Now that I had the confirmed results of the experiment, I used them as the benchmark against my TS-18 Synthetic Trade Engine.  But before I installed the Equity Curve algorithm, I needed to make sure my synthetic trades lined up exactly with the real equity curve.  The synthetic curve should align 100% with the real equity curve.  If it doesn’t, then there is a problem.  This is another reason to start with a simple trading strategy.

Take a look here where I print out the Synthetic Equity curve on  a daily basis and compare it with the end result of the analysis.

Synth. matches Reality

Now let’s see if it worked.

Testing with Synth. Equity Curve Trading Turned ON!

The equity curves are very similar.  However, there is a difference and this is caused by how one re-enters after trading is turned back on.  In this version I tested waiting for a new trade signal which might take a few days.  You could re-enter in three different ways:

  1. Automatically enter synthetic position on the next bar’s open
  2. Wait for a new trade signal
  3. Enter immediately if you can get in at a better price

Using the 10% Ret. and 15% Rec. algorithm didn’t help at all.  What if we test 10% and 10%.

10% Ret. and 10% Rec.

Now that performed better – more profit and less draw down.  Now that I have the synthetic engine working on simple algorithms we can do all sorts of equity curve analysis.  In the next installment in this series I will make sure the TS-18 Synthetic Engine can handle more complicated entry and exit algorithms.  I have already tested a simple longer term trend following strategy on a medium sized portfolio and the synthetic engine worked fine.  The retracement/recovery algorithm at 10%/15% did not work and I will go into the “whys” in my next post.

Another Good Year For Trend Following

Take a Look at the Last Two Years

Simple Donchian on a one contract basis.  $100 Commission/slippage.  Tested from 2000 thru December 31, 2021.  Do you see why most trend followers failed after the 2008 monstrous year.   Many funds caught the 2008 move and more funds were added soon thereafter.  Promises of similar performance came to fruition in 2011.  This kept much of the “new money” on the board.  However, reality set in and weak handed funds left for greener pastures.  Those that stuck it out were rewarded in 2014.  The trend drought of 2014 -2019 eroded most of the confidence in managed futures.  The rationalization that limited resources would eventually rise in price sounded good initially, but then fell on deaf ears after months of draw down.  Well known CTAs and hedge funds shut their doors forever.   The long awaited promise of 2008 came in the form of a pandemic – but it was too late.   Maybe now the deluge that ended the drought will persevere (hopefully not in the form of a pandemic) into the future.  Prices do not need to rise endlessly, but they need to move one direction or another without many hiccups.   

Simple Donchian Caught Most of the Commodities Up Moves

Which Sectors Pushed this Curve through the Roof

These reports were generated by my Python based Trading Simula-18 using Pinnacle continuous data – rollover triggered by date.  This is my new sector analysis report where I graph the last four years performance.  The tabular data is for the entire 21 year history.  The best sectors were energy, grains, financials and metals.  Lumber was extraordinary

Sector Analysis Report
################################################
Currency -------------------------------------
BN -28012 44681
SN -26925 55337
AN 6560 34350
DX 16284 24387
FN 67463 31737
JN -22212 50362
CN -25355 44110
------------------------------------------------
Totals: -12198 141445
------------------------------------------------
Currency Last 4 Years ---------------------
|
||| |
||||||
| ||||||||
| |||||||||
| ||||||||||
|||||||||||||
||||||||||||||
|||||||||||||| |
|||||||||||||||||||
||||||||||||||||||||| |
|||||||||||||||||||||| | |
|||||||||||||||||||||| | |
|||||||||||||||||||||| | || | | |
------------------------------------------------ 0
||||||||||||||||||||||||| ||| |||| ||| |
||||||||||||||||||||||||| |||| |||||||||||
||||||||||||||||||||||||| | |||| ||||||||||||
||||||||||||||||||||||||||| |||||||||||||||||
|||||||||||||||||||||||||||| ||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||
2018--------2019--------2020--------2021--------
################################################
Energies -------------------------------------
ZU 180750 38330
ZH 155696 85541
ZN 70630 74400
ZB 131874 66651
------------------------------------------------
Totals: 538951 154434
------------------------------------------------
Energies Last 4 Years ---------------------
| |
|| || ||
||| || |||
| ||||||||||
| |||||||||||
||| |||||||||||
|||| | ||||||||||||
||||||| ||||||||||||
|||||||||||||||||||||
|||||||||||||||||||||
|||||||||||||||||||||
|||||||||||||||||||||
| || |||||||||||||||||||||
|| || |||||||||||||||||||||
|| |||| |||||||||||||||||||||
||||||||| ||||||||||||||||||||||
| |||||||||| |||||||||||||||||||||||
|| || |||||||||||||||||||||||||||||||||||||
|||||| |||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||
2018--------2019--------2020--------2021--------
################################################
Metals -------------------------------------
ZG -17070 43540
ZI 68395 146885
ZK 101888 29475
ZP 82885 27600
ZA 174955 83910
------------------------------------------------
Totals: 411052 166703
------------------------------------------------
Metals Last 4 Years ---------------------
|
| | |
|| || |
|| |||| |||
| ||| | |||||||||
|| |||||||||||||||||
|| |||||||||||||||||||
|||||||||||||||||||||||
|||||||||||||||||||||||
|||||||||||||||||||||||
| | ||||||||||||||||||||||||
||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||
|| |||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||
|| |||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||
2018--------2019--------2020--------2021--------
################################################
Grains -------------------------------------
ZS 79175 20312
ZW -43438 51975
ZC 5238 26688
ZL 13248 24588
ZM 29860 28810
------------------------------------------------
Totals: 84083 88850
------------------------------------------------
Grains Last 4 Years ---------------------
|
|
||
||
|||||||||
|||||||||||
|||||||||||
|||||||||||
||||||||||||
| ||||||||||||
| | ||||||||||||
| || ||| ||||||||||||
||||| ||||| |||||||||||||
|||||||||||| |||||||||||||
|||||||||||| ||| | |||||||||||||
||||||||||||| ||||||| || ||||||||||||||
|||||||||||||||||||||||||||| ||||||||||||||
|||||||||||||||||||||||||||||| |||||||||||||||
||||||||||||||||||||||||||||||| |||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||
2018--------2019--------2020--------2021--------
################################################
Financials -------------------------------------
US 35991 24959
TY -350 29175
TU 1473 23969
EC 4700 9650
------------------------------------------------
Totals: 41813 56453
------------------------------------------------
Financials Last 4 Years ---------------------
| | ||||
||| | |||||
||| | || ||||||
||||| ||||||||||
|||||| ||||||||||
|||||||| ||||||||||
|||||||| ||||||||||
||||||||| |||||||||||
||||||||||||||||||||||
| ||||||||||||||||||||||
| ||||||||||||||||||||||
| | ||||||||||||||||||||||
| | || ||||||||||||||||||||||
|| |||||| |||||||||||||||||||||||
||| |||||||||||||||||||||||||||||||
|||| || | |||||||||||||||||||||||||||||||
||||| |||| |||||||||||||||||||||||||||||||
||||||| ||||||||||||||||||||||||||||||||||||
|||||||| ||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||
2018--------2019--------2020--------2021--------
################################################
Softs -------------------------------------
SB 25927 15035
KC -49775 94069
CC -72140 76660
CT 16785 45470
Lumber 218513 51745
JO 2588 15760
------------------------------------------------
Totals: 141898 128540
------------------------------------------------
Softs Last 4 Years ---------------------
| ||
||||
|||||
|||||
||||||
| ||||||
||||||||
||||||||
||||||||
||||||||
------------------------------------------------ 0
| | |||||||||
| | ||||||||||
||||||||||||||||
||||||||||||||||
|||||||||||||||||
|||||||||||||||||
|| | ||||||||||||||||||
| ||||||||||| | |||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||
2018--------2019--------2020--------2021--------
################################################
Meats -------------------------------------
ZT -29940 57680
ZZ 38480 15080
ZF 18413 57550
------------------------------------------------
Totals: 26952 66515
------------------------------------------------
Meats Last 4 Years ---------------------
| ||
| || ||
|| | || ||
||| | || ||
||||||||| ||||
| |||||||||| ||||
| | ||||||||||| |||||
| | ||||||||||| |||||| |
| || |||||||||||||||||| |
| ||| | |||||||||||||||||||||
|| ||| | |||||||||||||||||||||
|||||| || | ||||||||||||||||||||| |
|||||| || |||||||||||||||||||||||| |
|||||| || ||||||||||||||||||||||||||
|||||||| | || ||||||||||||||||||||||||||
|||||||| | ||| ||||||||||||||||||||||||||
|||||||||| | |||||||||||||||||||||||||||||||
||||||||||| | |||||||||||||||||||||||||||||||
|||||||||||| || ||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||
2018--------2019--------2020--------2021--------

How Do You Program this in Python

Here is the module for TS-18.  There is a little extra code to keep track of sectors in case you want to limit sector exposure.  However, this code takes every trade on a one contract basis.  This code reflects my latest version of TS-18, which will be released shortly.

#  Define Long, Short, ExitLong and ExitShort Levels - mind your indentations
buyLevel = highest(myHigh,40,curBar,1)
shortLevel = lowest(myLow,40,curBar,1)
longExit = lowest(myLow,20,curBar,1)
shortExit = highest(myHigh,20,curBar,1)
ATR = sAverage(myTrueRange,30,curBar,1)
stopAmt = 2000/myBPV

ATR = sAverage(myTrueRange,30,curBar,1)

posSize = 1
mmLxit = 99999999
mmSxit = -99999999
if mp == 1 : mmLxit = entryPrice[-1] - stopAmt
if mp ==-1 : mmSxit = entryPrice[-1] + stopAmt



# Long Exit
if mp == 1 and myLow[curBar] <= mmLxit and mmLxit > longExit and barsSinceEntry > 1:
price = min(myOpen[curBar],mmLxit)
tradeName = "LxitMM"
numShares = curShares
exitPosition(price, curShares, tradeName, sysMarkDict)
unPackDict(sysMarkDict)
# Long Exit
if mp == 1 and myLow[curBar] <= longExit and barsSinceEntry > 1:
price = min(myOpen[curBar],longExit)
tradeName = "Lxit"
numShares = curShares
exitPosition(price, curShares, tradeName, sysMarkDict)
unPackDict(sysMarkDict)
# Short Exit
if mp == -1 and myHigh[curBar] >= shortExit and barsSinceEntry > 1:
price = max(myOpen[curBar],shortExit)
tradeName = "Sxit"
numShares = curShares
exitPosition(price, curShares, tradeName, sysMarkDict)
unPackDict(sysMarkDict)
# Short Exit
if mp == -1 and myHigh[curBar] >= entryPrice[-1] + stopAmt and barsSinceEntry > 1:
price = max(myOpen[curBar],entryPrice[-1] + stopAmt)
tradeName = "SxitMM"
numShares = curShares
exitPosition(price, curShares, tradeName,sysMarkDict)
unPackDict(sysMarkDict)
# Long Entry
if myHigh[curBar] >= buyLevel and mp !=1:
price = max(myOpen[curBar],buyLevel)
tradeName = "Simple Buy"
numShares = posSize
enterLongPosition(price,numShares,tradeName,sysMarkDict)
unPackDict(sysMarkDict)
# Short Entry
if myLow[curBar] <= shortLevel and mp !=-1 :
price = min(myOpen[curBar],shortLevel)
if mp == 0 : sectorTradesTodayList[curSector] +=1
tradeName = "Simple Sell"
numShares = posSize
enterShortPosition(price, numShares, tradeName, sysMarkDict)
unPackDict(sysMarkDict)
Python within Trading Simula-18

Turn of the Month Trading Strategy [Stock Indices Only]

The System

This system has been around for several years.  Its based on the belief that fund managers start pouring money into the market near the end of the month and this creates momentum that lasts for just a few days.  The original system states to enter the market on the close of the last bar of the day if the its above a certain moving average value.  In the Jaekle and Tomasini book, the authors describe such a trading system.  Its quite simple, enter on the close of the month if its greater than X-Day moving average and exit either 4 days later or if during the trade the closing price drops below the X-Day moving average.

EasyLanguage or Multi-Charts Version

Determining the end of the month should be quite easy -right?  Well if you want to use EasyLanguage on TradeStation and I think on Multi-Charts you can’t sneak a peek at the next bar’s open to determine if the current bar is the last bar of the month.  You can try, but you will receive an error message that you can’t mix this bar on close with next bar.  In other words you can’t take action on today’s close if tomorrow’s bar is the first day of the month.  This is designed, I think, to prevent from future leak or cheating.  In TradeStation the shift from backtesting to trading is designed to be a no brainer, but this does provide some obstacles when you only want to do a backtest.

LDOM function – last day of month for past 15 years or so

So I had to create a LastDayOfMonth function.  At first I thought if the day of the month is the 31st then it is definitely the last bar of the month.  And this is the case no matter what.  And if its the 30th then its the last day of the month too if the month is April, June, Sept, and November.  But what happens if the last day of the month falls on a weekend.  Then if its the 28th and its a Friday and the month is blah, blah, blah.  What about February?  To save time here is the code:

Inputs: movAvgPeriods(50);
vars: endOfMonth(false),theDayOfWeek(0),theMonth(0),theDayOfMonth(0),isLeapYear(False);

endOfMonth = false;
theDayOfWeek = dayOfWeek(date);
theMonth = month(date);
theDayOfMonth = dayOfMonth(date);
isLeapYear = mod(year(d),4) = 0;

// 29th of the month and a Friday
if theDayOfMonth = 29 and theDayOfWeek = 5 then
endOfMonth = True;
// 30th of the month and a Friday
if theDayOfMonth = 30 and theDayOfWeek = 5 then
endOfMonth = True;
// 31st of the month
if theDayOfMonth = 31 then
endOfMonth = True;
// 30th of the month and April, June, Sept, or Nov
if theDayOfMonth = 30 and (theMonth=4 or theMonth=6 or theMonth=9 or theMonth=11) then
endOfMonth = True;
// 28th of the month and February and not leap year
if theDayOfMonth = 28 and theMonth = 2 and not(isLeapYear) then
endOfMonth = True;
// 29th of the month and February and a leap year or 28th, 27th and a Friday
if theMonth = 2 and isLeapYear then
Begin
If theDayOfMonth = 29 or ((theDayOfMonth = 28 or theDayOfMonth = 27) and theDayOfWeek = 5) then
endOfMonth = True;
end;
// 28th of the month and Friday and April, June, Sept, or Nov
if theDayOfMonth = 28 and (theMonth = 4 or theMonth = 6 or
theMonth = 9 or theMonth =11) and theDayOfWeek = 5 then
endOfMonth = True;
// 27th, 28th of Feb and Friday
if theMonth = 2 and theDayOfWeek = 5 and theDayOfMonth = 27 then
endOfMonth = True;
// 26th of Feb and Friday and not LeapYear
if theMonth = 2 and theDayOfWeek = 5 and theDayOfMonth = 26 and not(isLeapYear) then
endOfMonth = True;
// Memorial day adjustment
If theMonth = 5 and theDayOfWeek = 5 and theDayOfMonth = 28 then
endOfMonth = True;
//Easter 2013 adjustment
If theMonth = 3 and year(d) = 113 and theDayOfMonth = 28 then
endOfMonth = True;
//Easter 2018 adjustment
If theMonth = 3 and year(d) = 118 and theDayOfMonth = 29 then
endOfMonth = True;

if endOfMonth and c > average(c,movAvgPeriods) then
Buy("BuyDay") this bar on close;

If C <average(c,movAvgPeriods) then
Sell("MovAvgExit") this bar on close;
If BarsSinceEntry=4 then
Sell("4days") this bar on close;
Last Day Of Month Function and Strategy

All the code is generic except for the hard code for days that are a consequence of Good Friday.

All this code because I couldn’t sneak a peek at the date of tomorrow.  Here are the results of trading the ES futures sans execution costs for the past 15 years.

Last Day Of Month Buy If C > 50 Day Mavg

What if it did the easy way and executed the open of the first bar of the month.

If c > average(c,50) and month(d) <> month(d of tomorrow) then 
buy next bar at open;

If barsSinceEntry >=3 then
sell next bar at open;

If marketPosition = 1 and c < average(c,50) then
sell next bar at open;
Buy First Day Of Month

First Day of Month If C > 50 Day Mavg

The results aren’t as good but it sure was easier to program.

TradingSimula-18 Version

Since you can use daily bars we can test this with my TradingSimula-18 Python platform.  And we will execute on the close of the month.  Here is the snippet of code that you have to concern yourself with.  Here I am using Sublime Text and utilizing their text collapsing tool to hide non-user code:

Small Snippet of TS-18 Code

This was easy to program in TS-18 because I do allow Future Leak – in other words I will let you sneak a peek at tomorrow’s values and make a decision today.  Now many people might say this is a huge boo-boo, but with great power comes great responsibility.  If you go in with eyes wide open, then you will only use the data to make things easier or even doable, but without cheating.  Because you are only going to cheat yourself.  Its in your best interest do follow the rules.  Here is the line that let’s you leak into the future.

If isNewMonth(myDate[curBar+1])

The curBar is today and curBar+1 is tomorrow.  So I am saying if tomorrow is the first day of the month then buy today’s close.  Here you are leaking into the future but not taking advantage of it.  We all know if today is the last day of the month, but try explaining that to a computer.  You saw the EasyLanguage code.  So things are made easier with future leak, but not taking advantage of .

Here is a quick video of running the TS-18 Module of 4 different markets.

 

A Quant’s ToolBox: Beautiful Soup, Python, Excel and EasyLanguage

Many Times It Takes Multiple Tools to Get the Job Done

Just like a mechanic, a Quant needs tools to accomplish many programming tasks.  In this post, I use a toolbox to construct an EasyLanguage function that will test a date and determine if it is considered a Holiday in the eyes of the NYSE.

Why a Holiday Function?

TradeStation will pump holiday data into a chart and then later go back and take it out of the database.  Many times the data will only be removed from the daily database, but still persist in the intraday database.  Many mechanical day traders don’t want to trade on a shortened holiday session or use the data for indicator/signal calculations.  Here is an example of a gold chart reflecting President’s Day data in the intra-day data and not in the daily.

Holiday Data Throws A Monkey Wrench Into the Works

This affects many stock index day traders.  Especially if automation is turned on.  At the end of this post I provide a link to my youTube channel for a complete tutorial on the use of these tools to accomplish this task.  It goes along with this post.

First Get The Data

I searched the web for a list of historical holiday dates and came across this:

Historic List of Holidays and Their Dates

You might be able to find this in a easier to use format, but this was perfect for this post.

Extract Data with Beautiful Soup

Here is where Python and the plethora of its libraries come in handy.  I used pip to install the requests and the bs4 libraries.  If this sounds like Latin to you drop me an email and I will shoot you some instructions on how to install these libraries.  If you have Python, then you have the download/install tool known as pip.

Here is the Python code.  Don’t worry it is quite short.

# Created:     24/02/2020
# Copyright: (c) George 2020
# Licence: <your licence>
#-------------------------------------------------------------------------------

import requests
from bs4 import BeautifulSoup

url = 'http://www.market-holidays.com/'
page = requests.get(url)
soup = BeautifulSoup(page.text,'html.parser')
print(soup.title.text)
all_tables = soup.findAll('table')
#print (all_tables)
print (len(all_tables))
#print (all_tables[0])
print("***")
a = list()
b = list()
c = list()
#print(all_tables[0].find_all('tr')[0].text)
for numTables in range(len(all_tables)-1):
for rows in all_tables[numTables].find_all('tr'):
a.append(rows.find_all('td')[0].text)
b.append(rows.find_all('td')[1].text)

for j in range(len(a)-1):
print(a[j],"-",b[j])
Using Beautiful Soup to Extract Table Data

As you can see this is very simple code.  First I set the variable url to the website where the holidays are located.  I Googled on how to do this – another cool thing about Python – tons of users.  I pulled the data from the website and stuffed it into the page object.  The page object has several attributes (properties) and one of them  is a text representation of the entire page.  I pass this text to the BeautifulSoup library and inform it to parse it with the html.parser.  In other words, prepare to extract certain values based on html tags.  All_tables contains all of the tables that were parsed from the text file using Soup.  Don’t worry how this works, as its not important, just use it as a tool.  In my younger days as a programmer I would have delved into how this works, but it wouldn’t be worth the time because I just need the data to carry out my objective; this is one of the reasons classically trained programmers never pick up the object concept.  Now that I have all the tables in a list I can loop through each row in each table.  It looked liker there were 9 rows and 2 columns in the different sections of the website, but I didn’t know for sure so I just let the library figure this out for me.  So I played around with the code and found out that the first two columns of the table contained the name of the holiday and the date of the holiday.  So, I simply stuffed the text values of these columns in two lists:  a and b.  Finally I print out the contents of the two lists, separated by a hyphen, into the Interpreter window.  At this point I could simply carry on with Python and create the EasyLanguage statements and fill in the data I need.  But I wanted to play around with Excel in case readers didn’t want to go the Python route.  I could have used a powerful editor such as NotePad++ to extract the data from the website in place of Python.  GREP could have done this.  GREP is an editor tool to find and replace expressions in a text file.

Use Excel to Create Actual EasyLanguage – Really!

I created a new spreadsheet.  I used Excel, but you could use any spreadsheet software.   I first created a prototype of the code I would need to encapsulate the data into array structures.  Here is what I want the code to look like:

Arrays: holidayName[300](""),holidayDate[300](0);

holidayName[1]="New Year's Day "; holidayDate[1]=19900101;
Code Prototype

This is just the first few lines of the function prototype.  But you can notice a repetitive pattern.  The array names stay the same – the only values that change are the array elements and the array indices.  Computers love repetitiveness.  I can use this information a build a spreadsheet – take a look.

Type EasyLanguage Into the Columns and Fill Down!

I haven’t copied the data that I got out of Python just yet.  That will be step 2.  Column A has the first array name holidayName (notice I put the left square [ bracket in the column as well).  Column B will contain the array index and this is a formula.  Column C contains ]=”.  Column D will contain the actual holiday name and Column E contains theThese columns will build the holidayName array.  Columns G throuh K will build the holidayDates array.    Notice column  H  equals column B.  So whatever we do to column B (Index) will be reflected in Column H (Index).  So we have basically put all the parts of the EasyLanguage into  Columns A thru K. 

Excel provides tools for manipulating strings and text.  I will use the Concat function to build my EasyLanguageBut before I can use Concat all the stuff I want to string together must be in a string or text format.  The only column in the first five that is not a string is Column B.  So the first thing I have to do is convert it to text.  First copy the column and paste special as values.  Then go to your Data Tab and select Text To Columns. 

Text To Columns

It will ask if fixed width or delimited – I don’t think it matters which you pick.  On step 3 select text.

Text To Columns – A Powerful Tool

The Text To Columns button will solve 90% of your formatting issues in Excel.    Once you do this you will notice the numbers will be left justified – this signifies a text format.  Now lets select another sheet in the workbook and past the holiday data.

Copy Holiday Data Into Another Spreadsheet

New Year's Day - January 1, 2021
Martin Luther King, Jr. Day - January 18, 2021
Washington's Birthday (Presidents' Day) - February 15, 2021
Good Friday - April 2, 2021
Memorial Day - May 31, 2021
Independence Day - July 5, 2021
Labor Day - September 6, 2021
Thanksgiving - November 25, 2021
Christmas - December 24, 2021
New Year's Day - January 1, 2020
Martin Luther King, Jr. Day - January 20, 2020
Washington's Birthday (Presidents' Day) - February 17, 2020
Good Friday - April 10, 2020
Memorial Day - May 25, 2020
Holiday Output

 

Data Is In Column A

Text To Columns to the rescue.  Here I will separate the data with the “-” as delimiter and tell Excel to import the second column in Date format as MDY.  

Text To Columns with “-” as the delimiter and MDY as Column B Format

Now once the data is split accordingly into two columns with the correct format – we need to convert the date column into a string.

Convert Date to a String

Now the last couple of steps are really easy.  Once you have converted the date to a string, copy Column A and past into Column D from the first spreadsheet.  Since this is text, you can simply copy and then paste.  Now go back to Sheet 2 and copy Column C and paste special [values] in Column J on Sheet 1.  All we need to do now is concatenate the strings in Columns A thru E for the EasyLanguage for the holidayName array.  Columns G thru K will be concatenated for the holidayDate array.  Take a look.

Concatenate all the strings to create the EasyLanguage

Now create a function in the EasyLanguage editor and name it IsHoliday and have it return a boolean value.  Then all you need to do is copy/paste Columns F and L and the data from the website will now be available for you use.   Here is a portion of the function code.  Notice I declare the holidayNameStr as a stringRef?  I did this so I could change the variable in the function and pass it back to the calling routine.

Inputs : testDate(numericSeries),holidayNameStr(stringRef);

Arrays: holidayName[300](""),holidayDate[300](0);

holidayNameStr = "";

holidayName[1]="New Year's Day "; holidayDate[1]=19900101;
holidayName[2]="Martin Luther King, Jr. Day "; holidayDate[2]=19900115;
holidayName[3]="Washington's Birthday (Presidents' Day) "; holidayDate[3]=19900219;
holidayName[4]="Good Friday "; holidayDate[4]=19900413;
holidayName[5]="Memorial Day "; holidayDate[5]=19900528;
holidayName[6]="Independence Day "; holidayDate[6]=19900704;
holidayName[7]="Labor Day "; holidayDate[7]=19900903;
holidayName[8]="Thanksgiving "; holidayDate[8]=19901122;
holidayName[9]="New Year's Day "; holidayDate[9]=19910101;
holidayName[10]="Martin Luther King, Jr. Day "; holidayDate[10]=19910121;
holidayName[11]="Washington's Birthday (Presidents' Day) "; holidayDate[11]=19910218;

// There are 287 holiays in the database.
// Here is the looping mechanism to compare the data that is passed
// to the database

vars: j(0);
IsHoliday = False;
For j=1 to 287
Begin
If testDate = holidayDate[j] - 19000000 then
Begin
holidayNameStr = holidayName[j] + " " + numToStr(holidayDate[j],0);
IsHoliday = True;
end;
end;
A Snippet Of The Function - Including Header and Looping Mechanism

This was a pretty long tutorial and might be difficult to follow along.  If you want to watch my video, then go to this link.

I created this post to demonstrate the need to have several tools at your disposal if you really want to become a Quant programmer.  How you use those tools is up to you.  Also you will be able to take bits and pieces out of this post and use in other ways to get the data you really need.  I could have skipped the entire Excel portion of the post and just did everything in Python.  But I know a lot of Quants that just love spreadsheets.  You have to continually hone your craft in this business.   And you can’t let one software application limit your creativity.  If you have a problem always be on the lookout for alternative platforms and/or languages to help you solve it.

 

 

The Cure for the Common Trend Follower – SOTF Part 2

Clenow’s algorithm is definitely an indicator for the current State of Trend Following (SOTF).  However, the 3 X ATR trailing stop mechanism actually dampens the profit/draw down ratio.  Take a look at this chart.

Battle of Titans

All Trend Following mechanisms have a very common thread in their entry mechanisms.   The thing that separates them is the preemptive exit.  Do you allow the the algorithm to exit on a purely market defined method or do you overlay trade management?  Here the best approach was to let the Bollinger Band system run unfettered; even though it seems somewhat illogical.  Many times  trade management actually increases draw down.   Is there a solution?  What about this – keep risk down by trading a small, yet diverse portfolio of high volume markets and overlay it with a stock index mean reversion algo.  Take a look.

Bollinger Marries ES Reversion

 

Should’ve, Would’ve , Could’ve.

This could be scaled up.  The mean reversion helped lift  the chart out of the flat and draw down periods of late.  However, the smaller portfolio did OK during this time period too!  Can four or five high volume markets replicate a much larger portfolio?  All tests were carried out with TradingSimula18 – the software that comes with my latest book.

State of Trend Following – Part 1

Clenow’s Trend Following System

Its a new decade! Time to see what’s up with Trend Following.

I am a huge fan of Andreas Clenow’s books, and how he demonstrated that a typical trader could replicate the performance of most large Trend Following CTAs and not pay the 2% / 20% management/incentive combo fees.  So. I felt the system that he described in his book would be a great representation of The State of Trend Following.  At the same time I am going to demonstrate TradingSimula18 (the software included in my latest book).

System Description

Take a look at my last post.  I provide the EasyLanguage and a pretty good description of Clenow’s strategy.

TradingSimula18 Code [Python]

#---------------------------------------------------------------------------------------------------
# Start programming your great trading ideas below here - don't touch stuff above
#---------------------------------------------------------------------------------------------------
# Define Long, Short, ExitLong and ExitShort Levels - mind your indentations
ATR = sAverage(myTrueRange,30,curBar,1)
posSize = 2000/(ATR*myBPV)
posSize = max(int(posSize),1)
posSize = min(posSize,20)
avg1 = xAverage(myClose,marketVal5[curMarket],50,curBar,1)
avg2 = xAverage(myClose,marketVal6[curMarket],100,curBar,1)
marketVal5[curMarket] = avg1
marketVal6[curMarket] = avg2
donchHi = highest(myHigh,50,curBar,1)
donchLo = lowest(myLow,50,curBar,1)

if mp == 1 : marketVal1[curMarket] = max(marketVal1[curMarket],myHigh[curBar-1]- 3 * ATR)
if mp ==-1 : marketVal2[curMarket] = min(marketVal2[curMarket],myLow[curBar-1]+ 3 * ATR)
# Long Entry
if avg1 > avg2 and myHigh[curBar-1] == donchHi and mp !=1:
price = myOpen[curBar]
tradeName = "TFClenowB";numShares = posSize
marketVal1[curMarket] = price - 3 * ATR
if mp <= -1:
profit,curShares,trades = bookTrade(entry,buy,price,myDate[curBar],tradeName,numShares)
barsSinceEntry = 1
marketMonitorList[curMarket].setSysMarkTrackingInfo(tradeName,cumuProfit,mp,barsSinceEntry,curShares,trades)
# Long Exit
if mp == 1 and myClose[curBar-1] <= marketVal1[curMarket] and barsSinceEntry > 1:
price = myOpen[curBar]
tradeName = "Lxit";numShares = curShares
profit,curShares,trades = bookTrade(exit,ignore,price,myDate[curBar],tradeName,numShares)
todaysCTE = profit;barsSinceEntry = 0
marketMonitorList[curMarket].setSysMarkTrackingInfo(tradeName,cumuProfit,mp,barsSinceEntry,curShares,trades)
# Short Entry
if avg1 < avg2 and myLow[curBar-1] == donchLo and mp !=-1:
price = myOpen[curBar];numShares = posSize
marketVal2[curMarket] = price + 3 * ATR
if mp >= 1:
tradeName = "TFClenowS"
profit,curShares,trades = bookTrade(entry,sell,price,myDate[curBar],tradeName,numShares)
barsSinceEntry = 1
marketMonitorList[curMarket].setSysMarkTrackingInfo(tradeName,cumuProfit,mp,barsSinceEntry,curShares,trades)
# Short Exit
if mp == -1 and myClose[curBar-1] >= marketVal2[curMarket] and barsSinceEntry > 1:
price = myOpen[curBar]
tradeName = "Sxit"; numShares = curShares
profit,curShares,trades = bookTrade(exit,ignore,price,myDate[curBar],tradeName,numShares)
todaysCTE = profit;barsSinceEntry = 0
marketMonitorList[curMarket].setSysMarkTrackingInfo(tradeName,cumuProfit,mp,barsSinceEntry,curShares,trades)
#----------------------------------------------------------------------------------------------------------------------------
# - Do not change code below - trade, portfolio accounting - our great idea should stop here
#----------------------------------------------------------------------------------------------------------------------------
TradingSimula18 Python System Testing Environment

I am going to go over this very briefly.   I know that many of the readers of my blog have attempted to use Python and the various packages out there and have given up on it.  Quantopia and QuantConnect are great websites, but I feel they approach back-testing with a programmer in mind.  This was the main reason I created TS-18 – don’t get me wrong its not a walk in the park either, but it doesn’t rely on external libraries to get the job done.  All the reports I show here are generated from the data created solely by TS-18.  Plus it is very modular – Step 1 leads to Step2 and on and on.   Referring to the code I calculate the ATR (average true range) by calling the simple average function sAverage.  I pass it myTrueRanges, 30, curBar and 1.   I am looking for the average true range over the last 30 days.  I then move onto my position sizing – posSize = $2,000 / ATR in $s.  PosSize must fit between 1 and 20 contracts.  The ATR calculation can get rather small for some markets and the posSize can get rather large.  Avg1 and Avg2 are exponential moving averages of length 50 and 100DonchHi and donchLo are the highest high and lowest low of the past 50 days.   If mp == 1 (long position) then a trailing stop (marketVal1) is set to whichever is higher – the current marketVal1 or the yesterday’s High – 3 X ATR;  the trailing stop tracks new intra-trade highs.  The trailing stop for the short side, marketVal2 is calculated in a similar manner, but low prices are used as well as a positive offset of 3 X ATR.  

Now the next section of code is quite a bit different than say EasyLanguage, but parallels some of the online Python paradigms. Here you must test the current bar’s extremes against the donchHi if you are flat and marketVal1 (the trailing stop variable) if you are long.  If flat you also test the low of the bar against donchLo.  The relationship between avg1 and avg2 are also examined.  If the testing criteria is true, then its up to you to assign the correct price, posSize and tradeName.  So you have four independent if-then constructs:

  • Long Entry – if flat test to see if a long position should be initiated
  • Long Exit – if Long then test to see if a liquidation should be initiated
  • Short Entry – if flat test to see if a short position should be initiated
  • Short Exit – if Short then test to see if a liquidation should be initiated

That’s it – all of the other things are handled by TS-18.  Now that I have completely bored you out of your mind, let’s move onto some results.

Results from 2000 – risking $2,000 per trade:

Roller Coaster Ride for most CTAs, Last one out turn off the lights!

Sector Performance from 2000

Sector Performance from 2000

From this chart it doesn’t make much sense to trade MEATS, SOFTS or GRAINS with a Trend Following approach or does it?

In the next post, I will go over the results with more in depth and possibly propose some ideas that might or might not help.  Stay Tuned!

 

Get My Latest Book-TrendFollowing Systems: A DIY Project – Batteries Included

Just wanted to let you know that my latest book has just been published.

Trend Following Systems: A DIY Project – Batteries Included: Can You Reboot and Fix Yesterday’s Algorithms to Work with Today’s Markets? 

Trend Following Systems: A DIY Project – Batteries Included

This book introduces my new Python back-tester, TradingSimula-18.  It is completely and I mean completely self contained.  All you need is the latest version of Python and you will be up and running trading systems in less than 5 minutes.  Fifteen years of data on 30 futures is included (data from Quandl).  I have included more than 20 scripts that you can test and build on.   This back-tester is different than the one I published in the Ultimate Algorithmic Trading System Toolbox.  It utilizes what I call the horizontal portfolio spanning paradigm.  Instead of sequentially testing different markets in a portfoio:

It process data in the following manner:

This form of testing allows for decisions to be made on a portfolio basis at the end of any historic bar.   Things like inputting portfolio performance into an allocation formula is super simple.  However, this paradigm opens up a lot of different “what-if” scenarios.

  1. What If I Limit 2 Markets Per Sector
  2. What If I Turn Off A Certain Sector
  3. What If I Liquidate The Largest OTE loser
  4. What If I Liquidate The Largest OTE winner
  5. What If I Only Trade The Ten Markets With The Highest ADX Values

All the data and market performance and portfolio performance is right at your fingertips.  Your testing is only limited by your creativity.

The best part is you get to learn raw Python without having to install complicated libraries like SciKit, Numpy or Pandas.  You don’t even need to install distributions of commercial products – like Anaconda.  Don’t get me wrong I think Anaconda is awesome but many times it is overkill.  If you want to do machine learning then that is the way to go.  If you want to test simple Trend Following algorithms and make portfolio level decisions you don’t need a data science application.

There isn’t a complicated interface to learn.  Its all command line driven from Python’s IDLE.  90% of the source code is revealed for the back-testing software.  Its like one of those see-thru calculators.  You see all the circuits and semiconductors, but in Python.  So you will need to flow through the code to get to the sections that pertain to your test.  Here is a small sample of how you set up the testing parameters for a Donchian Script.

 

#--------------------------------------------------------------------------------
# If you want to ignore a bunch of non-eseential stuff then
# S C R O L L A L M O S T H A L F W A Y D O W N
#--------------------------------------------------------------------------------
#TradingSimula18.py - programmed by George Pruitt
#Built on the code and ideas from "The Ultimate Algorithmic Tradins System T-Box"
#Code is broken into sections
#Most sections can and should be ignored
#Each trading algorithm must be programmed with this template
#This is the main entry into the platform
#--------------------------------------------------------------------------------
#Import Section - inlcude functions, classes, variables from external modules
#--------------------------------------------------------------------------------
# --- Do not change below here
from getData import getData
from equityDataClass import equityClass
from tradeClass import tradeInfo
from systemMarket import systemMarketClass
from indicators import highest,lowest,rsiClass,stochClass,sAverage,bollingerBands
from indicators import highest,lowest,rsiClass,stochClass,sAverage,bollingerBands,\
adxClass,sAverage2
from portfolio import portfolioClass
from systemAnalytics import calcSystemResults
from utilityFunctions import getDataAtribs,getDataLists,roundToNearestTick,calcTodaysOTE
from utilityFunctions import setDataLists,removeDuplicates
from portManager import portManagerClass,systemMarkTrackerClass
from positionMatrixClass import positionMatrixClass
from barCountCalc import barCountCalc

from sectorClass import sectorClass, parseSectors, numPosCurrentSector,getCurrentSector
#-------------------------------------------------------------------------------------------------
# Pay no attention to these two functions - unless you want to
#-------------------------------------------------------------------------------------------------
def exitPos(myExitPrice,myExitDate,tempName,myCurShares):
global tradeName,entryPrice,entryQuant,exitPrice,numShares,myBPV,cumuProfit
if mp < 0:
trades = tradeInfo('liqShort',myExitDate,tempName,myExitPrice,myCurShares,0)
profit = trades.calcTradeProfit('liqShort',mp,entryPrice,myExitPrice,entryQuant,myCurShares) * myBPV
profit = profit - myCurShares *commission;trades.tradeProfit = profit;cumuProfit += profit
trades.cumuProfit = cumuProfit
if mp > 0:
trades = tradeInfo('liqLong',myExitDate,tempName,myExitPrice,myCurShares,0)
profit = trades.calcTradeProfit('liqLong',mp,entryPrice,myExitPrice,entryQuant,myCurShares) * myBPV
profit = profit - myCurShares * commission;trades.tradeProfit = profit;cumuProfit += profit
trades.cumuProfit = cumuProfit
curShares = 0
for remShares in range(0,len(entryQuant)):curShares += entryQuant[remShares]
return (profit,trades,curShares)

def bookTrade(entryOrExit,lOrS,price,date,tradeName,shares):
global mp,commission,totProfit,curShares,barsSinceEntry,listOfTrades
global entryPrice,entryQuant,exitPrice,numShares,myBPV,cumuProfit
if entryOrExit == -1:
profit,trades,curShares = exitPos(price,date,tradeName,shares);mp = 0
else:
profit = 0;curShares = curShares + shares;barsSinceEntry = 1;entryPrice.append(price);entryQuant.append(shares)
if lOrS == 1:mp += 1;trades = tradeInfo('buy',date,tradeName,entryPrice[-1],shares,1)
if lOrS ==-1:mp -= 1;trades = tradeInfo('sell',date,tradeName,entryPrice[-1],shares,1)
return(profit,curShares,trades)

dataClassList = list()

marketMonitorList,masterDateList,masterDateGlob,entryPrice = ([] for i in range(4))
buy = entry = 1; sell = exit = -1; ignore = 0;
entryQuant,exitQuant,trueRanges,myBPVList = ([] for i in range(4))
myComNameList,myMinMoveList,systemMarketList = ([] for i in range(3))
cond1,cond2,cond3,cond4 = ([] for i in range(4))
marketVal1,marketVal2,marketVal3,marketVal4 = ([] for i in range(4))
portManager = portManagerClass();marketList = getData();portfolio = portfolioClass()
numMarkets = len(marketList);positionMatrix = positionMatrixClass();positionMatrix.numMarkets = numMarkets
firstMarketLoop = True

#----------------------------------------------------------------------------------
# Set up algo parameters here
#----------------------------------------------------------------------------------
startTestDate = 20100101 #must be in yyyymmdd
stopTestDate = 20190228 #must be in yyyymmdd
rampUp = 100 # need this minimum of bars to calculate indicators
sysName = 'Donch-MAX2NSect' #System Name here
initCapital = 500000
commission = 100
Ignore Most Of This Code

Everything is batched processed: set up, pick market or portfolio, run.  Then examine all of the reports.  Here is an example of the sector analysis report.

          Total Profit  Max DrawDown
Currency -------------------------------
BP -14800 19062
SF -8600 53575
AD 4670 11480
DX 10180 10279
EC -9000 16775
JY 10025 18913
CD -19720 21830
-------------------------------------------
Totals: -27245 69223
-------------------------------------------
Energies -------------------------------
CL -40400 55830
HO 80197 23382
NG -14870 28920
RB -45429 61419
-------------------------------------------
Totals: -20502 75957
-------------------------------------------
Metals -------------------------------
GC 27210 36610
SI -1848 2389
HG -2402 2438
PL -16750 25030
PA 27230 38615
-------------------------------------------
Totals: 33440 61472
-------------------------------------------
Grains -------------------------------
S_ 27312 9088
W_ -25538 32600
C_ -1838 12212
BO -8460 9544
SM 390 11250
RR -1390 12060
-------------------------------------------
Totals: -9523 34135
-------------------------------------------
Financials -------------------------------
US 29488 18375
TY 969 12678
TU -2020 3397
FV -2616 4531
ED -4519 4869
-------------------------------------------
Totals: 21302 30178
-------------------------------------------
Softs -------------------------------
SB -1716 19717
KC 15475 44413
CC 540 8090
CT -8705 35660
LB 22269 16586
OJ 4720 8262
-------------------------------------------
Totals: 32583 57976
-------------------------------------------
Meats -------------------------------
LC -18910 24020
LH -31270 35640
FC 14600 25737
-------------------------------------------
Totals: -35580 59550
-------------------------------------------
Sector Analysis

Plus I include EasyLanguage for the majority of the scripts.  Of course without the portfolio level management.  I am working on a new website that will support the new book at TrendFollowingSystems.com.

Please take a look at my latest book – it would make an awesome Christmas present.

 

Python Script To Import List of Trades into TradeStation’s EasyLanguage – Sort of

Converting A List of Trades, Dates and Prices Into EasyLanguage Arrays:

As the old saying goes “a picture is worth a thousand words!”  Have you ever been given a list of trades like this:

Sell Short,20010622,1178.50 
Buy to Cover,20010626,1159.75
Sell Short,20010801,1150.00
Buy to Cover,20010807,1139.75
Sell Short,20010814,1129.00
Buy to Cover,20010816,1117.25
Sell Short,20011001,976.75
Buy to Cover,20011004,1016.75
Sell Short,20011107,1053.00
Buy to Cover,20011123,1069.50
Sell Short,20011219,1076.25
Buy to Cover,20020102,1075.00
Sell Short,20020129,1067.25
Buy to Cover,20020131,1046.75
Sell Short,20020131,1046.75
Buy to Cover,20020205,1026.75
Sell Short,20020520,1033.25
Buy to Cover,20020522,1011.50
Sell Short,20020731,832.00
Buy to Cover,20020805,792.50
Sell Short,20020812,834.00
Buy to Cover,20020814,811.75
Sell Short,20020911,838.50
Buy to Cover,20020913,816.75
List of Trades : Order, Date, Price

But really wanted to see this:

I have created a small Python script that will take a list of trades like those listed in table above and create the following EasyLanguage:

arrays: DateArr[500](0),TradeArr[500](""),PriceArr[500](0);
DateArr[0]=1010622;TradeArr[0]="SS";PriceArr[0]=1178.5;
DateArr[1]=1010626;TradeArr[1]="SX";PriceArr[1]=1159.75;
DateArr[2]=1010801;TradeArr[2]="SS";PriceArr[2]=1150.0;
DateArr[3]=1010807;TradeArr[3]="SX";PriceArr[3]=1139.75;
DateArr[4]=1010814;TradeArr[4]="SS";PriceArr[4]=1129.0;
DateArr[5]=1010816;TradeArr[5]="SX";PriceArr[5]=1117.25;
DateArr[6]=1011001;TradeArr[6]="SS";PriceArr[6]=976.75;
DateArr[7]=1011004;TradeArr[7]="SX";PriceArr[7]=1016.75;
DateArr[8]=1011107;TradeArr[8]="SS";PriceArr[8]=1053.0;
DateArr[9]=1011123;TradeArr[9]="SX";PriceArr[9]=1069.5;
DateArr[10]=1011219;TradeArr[10]="SS";PriceArr[10]=1076.25;
DateArr[11]=1020102;TradeArr[11]="SX";PriceArr[11]=1075.0;
DateArr[12]=1020129;TradeArr[12]="SS";PriceArr[12]=1067.25;
DateArr[13]=1020131;TradeArr[13]="SX";PriceArr[13]=1046.75;
DateArr[14]=1020131;TradeArr[14]="SS";PriceArr[14]=1046.75;
DateArr[15]=1020205;TradeArr[15]="SX";PriceArr[15]=1026.75;
DateArr[16]=1020520;TradeArr[16]="SS";PriceArr[16]=1033.25;
DateArr[17]=1020522;TradeArr[17]="SX";PriceArr[17]=1011.5;
Converting list of trades to EasyLanguage

This just creates the arrays that you can use to graph the trades on a chart.  If you are using exact prices you got to make sure your data aligns with the prices in the list of trades.  If you are only entering on the open or the close of the bar then the price array isn’t necessary.

The following Python script will also be helpful if you want to learn how to open a file in csv format, read it into lists, convert it and then save the output to a file.

#-------------------------------------------------------------------------------
# Name: Read csv file via askOpen and save txt file via askSave
# Purpose: Read the trade metrics from a TradeStation csv format
# and build arrays from the information to display on charts in
# TradeStation
# Author: georg
#
# Created: 29/08/2018
# Copyright: (c) georg 2018
#-------------------------------------------------------------------------------
import csv
import tkinter as tk
import os.path
from tkinter.filedialog import askopenfilenames
from tkinter.filedialog import asksaveasfilename

tradeType = list()
tradeDate = list()
tradePrice = list()

def main():
root = tk.Tk()
root.withdraw()
files = askopenfilenames(filetypes=(('CSV files', '*.csv'),
('TXT files', '*.txt')),
title='Select CSV format only!')
fileList = root.tk.splitlist(files)
fileListLen = len(fileList)


# make sure you know the format ahead of time
# I know "Buy",20180828,2745.75
#
cnt = 0
for files in range(0,fileListLen):
head,tail = os.path.split(fileList[files])
with open(fileList[files]) as f:
f_csv = csv.reader(f)
for row in f_csv:
numCols = len(row)
tradeType.append(row[0])
tradeDate.append(int(row[1]))
tradePrice.append(float(row[2]))
cnt += 1
f.close


filename = asksaveasfilename(title="Will Save File with '.txt'",defaultextension=".txt")
# filename = filename + '.txt'
target1 = open(filename,'w')
outString = 'arrays: DateArr[500](0),TradeArr[500](0),PriceArr[500](0);\n'
target1.write(outString)
for x in range(0,cnt):
if tradeType[x] == "Sell Short": tradeType[x] = "SS"
if tradeType[x] == "Buy": tradeType[x] = "B"
if tradeType[x] == "Buy to Cover": tradeType[x] = "SX"
if tradeType[x] == "Sell": tradeType[x] = "LX"
outString = 'DateArr['+str(x)+']='+str(tradeDate[x]-19000000)+';TradeArr['+str(x)+']="'+tradeType[x]+'";PriceArr['+str(x)+']='+str(tradePrice[x])+';\n'
target1.write(outString)
target1.close


if __name__ == '__main__':
main()
Python Script Open, Read, Convert and Write A File Using TK Dialogs

And here is the EasyLanguage code that will step through the arrays and place the trades accordingly.  I noticed that sometimes two trades could occur on the same bar, but only two and you will notice in the code where I programmed this occurrence.

vars: cnt(0);

If date of tomorrow = DateArr[cnt] then
Begin
print("Inside: ",d," ",dateArr[cnt]);
If tradeArr[cnt] = "B" then
begin
buy next bar at PriceArr[cnt] stop;
end;
If tradeArr[cnt] = "LX" then
begin
sell next bar at PriceArr[cnt] stop;
end;
If tradeArr[cnt] = "SS" then
begin
sellShort next bar at PriceArr[cnt] stop;
end;
If tradeArr[cnt] = "SX" then
begin
buyToCover next bar at PriceArr[cnt] stop;
end;
cnt = cnt + 1;
If DateArr[cnt] = DateArr[cnt-1] then
Begin
print("two trades same day ",d," ",dateArr[cnt]);
If tradeArr[cnt] = "B" then
begin
buy next bar at PriceArr[cnt] stop;
end;
If tradeArr[cnt] = "LX" then
begin
sell next bar at PriceArr[cnt] stop;
end;
If tradeArr[cnt] = "SS" then
begin
print("looking to go short at ",PriceArr[cnt]);
sellShort next bar at PriceArr[cnt] stop;
end;
If tradeArr[cnt] = "SX" then
begin
buyToCover next bar at PriceArr[cnt] stop;
end;
cnt = cnt + 1;
end;
end;
EasyLanguage Snippet To Execute Trades Stored in Arrays