Category Archives: Python

Dr. ChatGPT, or How I Learned to Stop Worrying and Love AI.

Embracing AI: The Journey from Skepticism to Synergy with ChatGPT.

Using TradeStation XML output, Python and ChatGPT to create a commercial level Portfolio Optimizer.

As a young and curious child my parents would buy me RadioShack Science Fair Kits, chemistry sets, a microscope and rockets.  I learned enough chemistry to make rotten egg gas.  I grew protozoa to delve into the microscopic world.  I scared my mom and wowed my cousins with the Estes Der Big Red rocket.  But it wasn’t until one Christmas morning I opened the Digital Computer Kit.  Or course you had to put it together before you could even use it – just like the model rockets.  Hey, you got an education assembling this stuff.  Here is a picture of a glorified circuit board.

My first computer.

This really wasn’t a computer, but more of an education in circuit design, but you could get it to solve simple problems “Crossing the River”, decimal to binary, calculating the cube root and 97 other small projects.  These problems were solved by following the various wiring diagrams.  I loved how the small panels would light up with the right answers, but grew frustrated because I couldn’t get beyond the preprogrammed wiring schema.  I had all these problems I wanted to solve but could not figure out the wiring.  Of course, there were real computers out there such as the HAL 9000.  Just kidding.  I would go to the local Radio Shack and stare at the all the computers.  Hoping one day I would have one sitting on my desk.  My Dad was an aircraft electrician (avionics) in the U.S. Navy with a specialty in Inertial Navigation Systems.  He would always want to talk about switches, gyros, some dude named Georg Ohm and oscilloscopes.  I had my mind stuck in space, you know “3-D Space Battle” – the Apple II game, to listen or to learn from his vast knowledge.    A couple of years later and a paper route I had a proper 16K computer, the TI-99-4A.  During this time, I dreamed of a supercomputer that could answer all my questions and solve all my problems.  I thought the internet was the manifestation of this dream, but in fact it was the Large Language Models such as ChatGPT.

Friend or Foe

From a programmer’s perspective AI can be scary, because you might just find yourself out of a job.  From this experiment, I think we are a few years away from this possibility.  Quantum computing, whenever it arrives, might be a viable replacement, but for now I think we are okay.

The Tools You Will Need

You will need a full installation of Python along with Numpy and Pandas installed on your computer if you want ChatGPT to do some serious coding for you.  Python and its associated libraries are simply awesome.   And Chat loves to use these tools to solve a problem.  I pay $20 a month for Chat so I don’t know if you could get the same code as I did if you have the free version.  You should try before signing up.

The Project:  A Portfolio Optimizer using an Exhaustive Search Engine

About ten years ago, I collaborated with Mike Chalek to develop a parser that analyzes the XML files TradeStation generates when saving strategy performance data. Trading a trend-following system often requires significant capital to manage a large portfolio effectively. However, many smaller traders operate with limited resources and opt to trade a subset of the full portfolio.

This approach introduces a critical challenge: determining which markets to include in the subset to produce the most efficient equity curve. For instance, suppose you have the capital to trade only four markets out of a possible portfolio of twenty. How do you decide which four to include? Do you choose the markets with the highest individual profits? Or do you select the ones that provide the best profit-to-drawdown ratio?

For smaller traders, the latter approach—prioritizing the profit-to-drawdown ratio—is typically the smarter choice. This metric accounts for both returns and risk, making it essential for those who need to manage capital conservatively. By focusing on risk-adjusted performance, you can achieve a more stable equity curve and better protect your account from significant drawdowns.

I enhanced Mike’s parser by integrating an exhaustive search engine capable of evaluating every combination of N markets taken n at a time. This approach allowed for a complete analysis of all possible subsets within a portfolio. However, as the size of the portfolio increased, the number of combinations grew exponentially, making the computations increasingly intensive. For example, in a portfolio of 20 markets, sampling 4 markets at a time results in 4,845 unique combinations to evaluate.

Calculating the number of combinations.

Using the formula above you get 4,845 combinations.  If you estimate each combination to take once second, then you are talking about 21 minutes.  N/2 will produce the most combinations.  Sampling 10 out of 20 will take 51.32 hours.

  • 1 out of 20: 20 combinations
  • 2 out of 20: 190 combinations
  • 3 out of 20: 1,140 combinations
  • 4 out of 20: 4,845 combinations
  • 5 out of 20: 15,504 combinations
  • 6 out of 20: 38,760 combinations
  • 7 out of 20: 77,520 combinations
  • 8 out of 20: 125,970 combinations
  • 9 out of 20: 167,960 combinations
  • 10 out of 20: 184,756 combinations
  • 11 out of 20: 167,960 combinations

The exhaustive method is not the best way to go when trying to find the optimal portfolios across a broad search space.  This is where a genetic optimizer comes in handy.  I played around with that too.  However, for this experiment I stuck with a portfolio of eleven markets.  I used the Andromeda-like strategy that I published in my latest installment of my Easing Into EasyLanguage series.

Here is the output of the project when sampling four markets out of a total of eleven.  All this was produced with Python and its libraries and ChatGPT.

Tabular Format

The best 4-Market portfolio out of 11 possibilities.

Graphic Format

ChatGPT, Python, Matplotlib, oh my!

Step 1 – Creating an XML Parser script

You can save your strategy performance report in RINA XML format.  The ability to save in this format seems to come and go, but my latest version of TradeStation provides this capability.  The XML files are ASCII files that contain every bar of data, market and strategy properties and trades.  However, they are extremely large as each piece of data has prefix and suffix tag.

<StrategyPerformance>
<Market>
<Name>SPY</Name>
<Bars>
<Bar>
<Date>2024-12-01</Date>
<Open>450.25</Open>
<High>455.30</High>
<Low>449.85</Low>
<Close>452.10</Close>
</Bar>
<Bar>
<Date>2024-12-02</Date>
<Open>452.15</Open>
<High>458.40</High>
<Low>451.50</Low>
<Close>457.75</Close>
</Bar>
</Bars>
</Market>
<Trades>
<Trade>
<Type>Buy</Type>
<Date>2024-12-01</Date>
<Price>450.50</Price>
<Quantity>100</Quantity>
</Trade>
<Trade>
<Type>Sell</Type>
<Date>2024-12-02</Date>
<Price>457.50</Price>
<Quantity>100</Quantity>
</Trade>
</Trades>
<PerformanceMetrics>
<NetProfit>700.00</NetProfit>
<Drawdown>50.00</Drawdown>
<ProfitFactor>2.5</ProfitFactor>
</PerformanceMetrics>
</StrategyPerformance>
Small example of XML file

​Imagine the size of the XML when working with one or even five-minute bars.  

Getting ChatGPT to create an XML parser for the specific TradeStation output

  • The first thing I did was save the performance, in XML format, from a workspace in TradeStation that used an Andromeda-like strategy on eleven different daily bar charts.
  • I asked Chat to analyze the XML file that I attached to a new chat.  I started a new chart for this project.  I discovered the chat session is also known as a “workflow”.  This term emphasizes:
    • Collaboration: We work as a team to tackle challenges.
    • Iteration: We revisit and improve upon earlier steps as needed.
    • Focus: Each session builds upon the previous ones to move closer to a defined goal.
  • Once it understood the mapping of the XML, I asked Chat to extract the bar data and output it to a csv file.  And it did it without a hitch.  When I say it did it, I mean it created a Python script that I loaded into my PyScripter IDE and executed.
  • I then asked for an output of the trade-by-trade report, and it did it without a hitch.  Notice these tasks do not require much in the way of reasoning.

Getting ChatGPT to combine the bar and trade data to produce a daily equity stream.

This is where Python and its number crunching libraries came in handy.  Chat pulled in the following libraries:

  • xml.etree
  • pandas
  • tkinter
  • datetime

I love Python, but the level of abstraction with its libraries can make you dizzy.  It is not important to fully understand the panda’s data frame to utilize it.   Heck, I didn’t really know how it mapped and extracted the data from the XML file.  I prompted chat with the following:

[My prompts are bold and italicized.]

With the bar data and the trade data and the bigpointvlaue, can you create an equity curve that shows a combination of open trade equity and closed trade equity?  Remember the top part of the xml file contains bar data and the lower part contains all the trade data.

It produced a script that hung up.  I informed chat that the script hung up and that the script wasn’t working.  It found the error and fixed it.  Think about what Chat was doing. It was able to align the data so that open trades produced open trade equity and closed out trades produced closed trade equity.  Well, initially it had a small problem.    It knew what Buy and Sell meant, and the math involved with calculating the two forms of equity, open and closed.  I didn’t inform Chat of any of this.  But the equity data did not look exactly right.  It looked like the closed trade equity was being calculated improperly.

Is the Script checking for LExit and SExit to calculate when a trade closes out?

Once it figured out that the equity stream must contain open and closed trade equity and learned the terms, LExit and SExit, a new script was created that nearly replicated the equity curve from the TradeStation report.  When Chat starts creating lengthy scripts it will open a side bar window call the “Canvas” and put the script in there.  This makes it easier to copy and paste the code.  I eventually noticed that the equity curve did not include commission and slippage charges.

Please extract the slippage and commission values and deduct this amount from each trade.

At this point the workflow remembered the mapping of the XML file and was able incorporate these two values into the trade processing.  I wanted the user to be able to select multiple XML files and have the script process these files and produce the three output files, bar_data, trade_data, and equity_data.    I did have to explain that the execution costs must be applied to all the entries and exits.

I would like the user to select multiple xml files and create the data and trade and equity files incorporating the system name and market name and their naming scheme.

A new library was imported, TKinter and a file open dialog was used for the user to select all the XML files they wanted to process.  These few chores required very little interaction between Chat and me.  I thought, wow this is going to be a breeze.  I moved onto the next phase of the project by asking Chat the following:

Can you create a script that will use Tkinter to open the equity files from the prior script and allow the user to choose N out of the total files selected to create all the combined equity curves using an exhaustive search method.

I knew this was a BIG ASK!  But it swallowed this big pill without a hitch.  Maybe us programmers will be replaced sooner than later.  I wanted to fine tune the script.

Can you keep track of maximum draw down for each combination and then sort the combinations by the profit to draw down ratio. For the best combination can you create a csv file with the daily returns so i can plot them in Excel.

On a quick scan of the results, I did not initially notice that the maximum draw-down metric was not right.  So, I pushed on the fine tuning.

This works great. In the output files can we delete the path name to the csv files. For the output I would just like to have the system name and the symbol for each combination.

Script created.

I had the following error message. NameError: name ‘all_combinations’ is not defined. I also asked Chat if it can you add “Date” to the header for the combination daily files?

Many times, Chat will just recreate the part of the code that needs to be modified.  This makes copying and pasting difficult, because you have to make sure you find the right location in the code to replace.  I always ask Chat to recreate the entire script with the latest changes.  

Hey Chat, the following error message was created:  system, symbol = filename.replace(“.csv”, “”).split(“_”) ValueError: too many values to unpack (expected 2)

The error was fixed, but I finally noticed the maximum draw down calculations were not right.

I don’t think the maximum draw down metric is being calculated properly. “Andromeda-Like_@SB, Andromeda-Like_@EC, Andromeda-Like_@S, Andromeda-Like_@JY”,
PL:  92141.8499999999,
DD:  92985.59999999992,
Ratio:  0.990926014350609,144.
The draw down metric is similar to the total profit metric.
Script created.

The draw down is still not calculated properly. I think there is an error in your reasoning. I calculate draw down by iterating through the equity curve and keeping track of peaks and subsequent troughs. The largest distance between the peaks and its associated trough is the max draw down.

Script created but looked a little primitive.  So, I asked.

Is this the most efficient method?

Well, I should have kept my mouth shut.  I felt like the Python libraries would be able to do this much quicker with list comprehension or with some other magic.   And Chat agreed with me.  Script created.

The draw down is still incorrect.

Script created.

It was still wrong and instead of going back and using the original draw down script I tried to nudge Chat into the right direction.

The equity curve will have multiple peaks and multiple troughs. You will need to keep track of all of the peaks and troughs and then find the maximum distance between the sets of peaks and troughs.

Script created and was still wrong.  This is where you start going down a rabbit hole.  Chat will produce debugging script to try and figure out where the error resides.  But this is very time consuming, so I kept pushing Chat to do it right.

Each peak must be tied to its respective trough. Each distance between each respective peak and drawdown can then be measured for the largest value.

Script is wrong.

This still not working. I don’t think you are calculating the difference between peaks and subsequent troughs properly.

Script is wrong.

This is still not working. We should be able to do this in a single pass. Iterate through the daily values and track the highest level at that point in the series and then the subsequent deepest trough. Store the first difference. Continue iterating looking for a new peak and the subsequent deepest trough. Compare this value to the prior and take the largest of the two. Store this value and continue iterating.

Script from original request is generated.  Finally got back to the starting point.  What a waste of time.  I will be more careful with my suggestions.  However, the script is still wrong – arrgh!  I decide to run the debugger on the draw down function and the code is right, but the data is wrong.

The problem lies in the equity series. It should contain the combined equity for the combinations. There should be a master date and each combination populates the master date. If there is a missing date in the combinations, then the master date should copy the prior combinations combined value.

Warning message:  se obj.ffill() or obj.bfill() instead.  df_aligned = df.reindex(master_date_index).fillna(method=”ffill”).fillna(0)

Chat created some deprecated code.   This was an easy fix, I just had to replace one line of code.  However, every iteration following this still had the same damn deprecated code.

Error or warning: FutureWarning: Series.getitem treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use ser.iloc[pos] peak = equity_series[0] # Initialize the first value as the peak

Script updated.

Can we drop the “_equity_curve” from the name of the system and symbol in the Perfomance metrics file. Will excel accept the combination names as a single column or will each symbol have its own column because of the comma. I would like for each combination in the Performance_Metrics file to occupy just one column.

Script created and this is what it should look like.  Notice it was remembering the wrong maximum draw down values I fed it earlier.  Don’t worry it was right in the script.

Combination Total Profit Max Drawdown Profit-to-Drawdown Ratio Combination Number
“Andromeda-Like_@SB, Andromeda-Like_@EC” 92141.85 92985.60 0.99 1
“Andromeda-Like_@S, Andromeda-Like_@JY” 82450.55 70530.45 1.17 2

Conclusion

I could have done the same thing ChatGPT did for me, but I wouldn’t have used the Numpy or Pandas libraries simply because I’m not familiar with them. These libraries make the exhaustive search manageable and incredibly efficient. They handle tasks much faster than pure Python alone.

To get ChatGPT to generate the code you need, being a programmer is essential. You’ll need to guide it through debugging, steer it in the right direction, and test the scripts it produces. It’s a back-and-forth process—running the script, identifying warnings or errors, and pointing out incorrect outputs. Sometimes, your programming insights and suggestions might inadvertently lead ChatGPT down a rabbit hole. Features that worked in earlier versions may stop working in subsequent iterations as modifications are applied to address earlier issues.

ChatGPT can also slow down at times, and its Canvas tool has a line limit, which can result in incomplete scripts. As a programmer, it’s easy to spot these issues—you’ll need to inform ChatGPT, and it will adjust by splitting the script into parts, some appearing in the Canvas and the rest in the chat window.

The collaboration between ChatGPT and me was powerful enough to replicate, in just one day, software that Mike Chalek and I spent weeks developing a decade ago. The original version had a cleaner GUI, but it was significantly slower compared to what we’ve achieved here.

If you’re a programmer, have Python installed with its libraries, and work with ChatGPT, the possibilities are endless. But there’s no magic—success requires thoughtful feedback and precise prompting.

Email me if you would like to have the Python scripts that accomplish the following tasks.  If you are not familiar with pandas or xml processing, the code, even being Python savvy, will look a little foreign.  No worries – it just works.

  1. XML Parser – creates data, trades and equity files in .csv format.
  2. TradeStationExhaustiveCombos – creates all the combos when sampling n out of N markets.
  3. The simple Tkinter GUI and Matplotlib graphing tool to plot the combos.

There is a total of three scripts.  Remember you will need to have Python, pandas, matplotlib already installed on your computer.  If you have any questions on how to install these just let me know.

Is your Big Point Value right?

Thinking of using futures data from a vendor such as CSI, Pinnacle, Norgate…?

You would be surprised how many quants still use Excel or they own homegrown back-testing software.  Determining trade execution is simple.  On a stop order, all you need to do is see if today’s bar is higher than the prior bars calculated trade signal – right.  And if it is then a buy stop order is executed and the calculated signal price.  Store the entry price and when the exit occurs do the math to calculate the profit or loss.  Before you can do this, you must know two very important contract specs on the futures data you are testing:

  1. big point value – I will explain how to calculate this a little later
  2. minimum tick move

That’s it.  You don’t need to know anything beyond this to properly calculate trade signals and trade P and L.  As long as you store this information for each futures/commodity market you are testing, then you will be good to go.  Many testing platforms store more information such as contract size, trading hours, expiration date and several other contract specs.  You don’t need to know this information if you all you are interested is proper signal placement and accurate trade accounting.  Once you set the big point value and minimum tick move you are good to go, right?  Yes, if you stick with the same data vendor.  I kid you not.  Data vendors often quote the same exact market with different decimal places.  If you look up a sugar quote at the CME website, you will see it quoted like 0.1908.  If you look it up on TradeStation it is quoted as 19.08.  It’s the same size contract 112,000 pounds, but if you use the big point value at the CME, it will not produce accurate calculations on the TradeStation quote.  You can’t use the contract size of 112,000 pounds to help with the math either.  You have to delve into how each data vendor quotes their data and this will impact the big point move and minimum tick move.  Most vendors are gracious enough to provide a futures dictionary with these specs.  But it is wise to know how to do this by hand as well.

If you had to take a test, could you calculate the profit or loss from a trade in soybeans, in sugar or in euro currency?

Are you kidding me this is super simple.  Just program the strategy in TradeStation or Amibroker or Multicharts or TradingSimula-18.  I took the NFA Series 3 test more than two decades ago, and this type of question was on the test.  The following information was given:

  • contract size – 5000 bushels
  • minimum tick or move – 1/4th of a cent or penny
  • long entry price 8.32’2 and sell exit price 8.48’6 (per bushel)

The entry and exit price may look a little funny.  The entry price is eight dollars and 32 1/4 cents.   Many still quote soybeans in 1/8ths.  So, 2 X 1/8th = 1/4th.  For fun let’s calculate the notional value of the entry and exit prices.  The notional value of a soybean contract at the entry price is $8.3225 * 5000 = $41,612.50.  As you know futures are highly leveraged and you can control this quantity of soybeans for a small percentage of the notional value (margin.)  Now the exit price is $8.4875 (6*1/8th = 3/4th of a cent) and the notional value of the contract at exit is $8.4875 * 5000 = $42,437.5.  The result of the trade was $42,437.50 – $41,612.50 = $825.  Or you could simply multiple the difference between entry and exit by 5000.  This would $0.165 * 5000.  This makes perfect sense, but as a broker it was difficult to keep the contract size of a market and its minimum tick move in your memory.  Well, if you did it long enough it wasn’t that difficult.  You can reduce the math down to one easy to remember value and quickly do the math in your head.  The concept of the big point move allows for this.  If you download your data from Pinnacle or CSI the price of soybeans is usually quoted like this:  848.75.  This would be eight hundred and 48.75 cents.  The big point move is the amount of dollars required to lift (or drop) the first digit to the left of the decimal by one.  The first digit to the left of the decimal in beans is a penny or one cent.  A one cent move in beans is 5000 * $0.01 or $50.  Going back to our trade example 848.75 – 832.25 = 16.5 – multiply this by $50 you get $825.  You can also derive the minimum move dollar value too.  If the minimum move is 1/4th of a cent, then you can multiply 5000 * $.0025 and this yields $12.5.

Why is this important to know?

You should know this if you are trading the market, period.  Also, if you are a quant and are using some back testing software that requires you to set up the database outside the purview of the back-tester, then these values must be known, and must also be accurate.  Since TradeStation integrates its own data, you don’t have to worry about this.  But if you want to use a database from Pinnacle, BarChart, CSI or Norgate, then you have to take the time to set this up, right off the bat.  I feel like the leading data vendors, provide accurate data.  But there are differences from one vendor to another.  Not all data vendors use the same number of decimal places in their market quotes, so you must be able to determine the big point value and minimum move from the data.   You can do all the math to determine profit and loss from the big point value.  Here is a snapshot of a few markets in the TS-18 dataMasterPinnacle and the TS-18 dataMasterCSI text files. Like I said, all vendors will provide this information for you, so you can set your database properly.  Some back-testing platforms require more contract specs, but TS-18 just needs this information to carry out accurate calculations.  Here TS-18 wants to know the symbol, big point value, minimum tick move, and market name.

Pinnacle Data
AN,1000,0.01,Aus.Dol
ZL,600,0.01,BeanOil
BN,625,0.01,B.Pound
ZU,1000,0.01,Crude
ZC,50,0.25,Corn
CC,10,1,Cocoa
CL,1000,0.01,Crude
CN,1000,0.01,Can.Dol
CT,500,0.01,Cotton
FN,1250,0.005,EuroCur


CSI Data
AD,100000,0.0001,Aus.Dol
BO,600,0.01,BeanOil
BP,62500,0.0001,B.Pound
CL,1000,0.01,Crude
C2,50,0.25,Corn
C_,50,0.25,Corn
CC,10,1,Cocoa
CD,100000,0.0001,Can.Dol
CL,1000,0.01,Crude
CT,500,0.01,Cotton
CU,125000,0.00005,EuroCur
EC,125000,0.00005,EuroCur

Other than the symbol names the values for each symbol are very similar.  Most data vendors, including CSI quote the Euro currency like this:

1.08735 and a move to 1.08740 = 0.00005 *$125,000 = $6.25

But Pinnacle data quotes it like this:

108.735 and a move to 108.740 = 0.005 * $1,250 = $6.25

The size of the contract isn’t different here.  It is the quote that is different.  The big point value is tied to the contract size and the format of the quote.  This is why knowing the big point value is so important.  If you don’t set up the contract specifications correctly, then you will receive inaccurate results.

One more test in sugar – from the CME website

  • contract size – 112,000 pounds
  • minimum tick or move – $0.0001
  • long entry price $0.1910 and sell exit price $0.1980 (per pound)
  • big point value = $112,000
  • $0.0070 * 112,000 = $784

From TradeStation

  • contract size – 112,000 pounds
  • minimum tick or move – $0.01
  • long entry price $19.10 and sell exit price $19.80 (per pound??)
  • big point value = $1,120
  • $0.70 * 1,120 = $784

Most fractional quotes are delivered in decimal format and the also very important minimum tick move.

The interest rate futures have minimum tick moves ranging from 1/256ths to 1/32nds.  Most vendors will give you a quote like 120.03125 for the bonds.  If you test in TradersStudio or Excel or any other platform where the data is not integrated and are using CSI (or any other vendor), then you must force your calculated prices to fall on an even tick.  Assume you do a calculation to buy a bond future at 120.022365895.  If you don’t take the minimum tick move into considerations, you might be filled at this theoretical price.  In reality you should be filled on an even tick at 120.03125.  This is worse but realistic fill. You could create what you think is a really awesome strategy where you are shaving off the price on every trade – getting a better fill on every trade.

Thinking of purchasing and using back-testing software or Excel and data from a data vendor?

Become your own quant and do this.  You will learn a lot about algorithm testing and development.  But first things first.  Get your database set up according to your data vendor.  Once this chore is complete, testing becomes smooth sailing.  I provide the databases for Pinnacle and CSI with TS-18. Other software provides these as well, and a way to create your own databases.

Trading Before and After Holidays

Morgan Stanley Research Idea from 2018

In May of 2018 Morgan Stanley presented a report from their research department.  The report was written by Tony Small and Matthew Hornbach.  The information presented validated the idea of seasonality in the markets.  The writers/researchers gathered all the data going back to 1984 on the ten-year futures and all the federal holidays going back over the same span of time.  They then analyzed either going long or short M days prior to the holiday and exiting N days before the holidays.  They also did the same thing after the holiday – entering M days after and exiting N days after.  The boundary was set to five days before and after.  This report was sent to me by one of my clients.  The conclusion of the article was that there seemed to be a seasonality component to the days prior and after a major holiday.  And that it seemed this component did indeed provide a technical advantage (on the surface.)  Here is their conclusion.

“Given the risk of data mining, investors should accept this analysis with some degree of caution,” Small and Hornbach said. “There is no guarantee that historical seasonality and past price performance will continue in the future.”

How would one test this idea on their own?

Tools needed:

  1. Python with NumPy installed.
  2. An AI chatbot such as ChatGPT.
  3. Access to TY futures data going back to 1984 (actual non-Panama adjusted data would be preferable.)
  4. A testing platform that allows future leak such as George’s own TradingSimula-18.  Excel would work too but would require a bunch of work.

Some programming knowledge.

  1. Create output for all of the federal holidays going back to January 1984.  This list must also include Good Fridays.  This list should have two fields – the date and the name of the holiday.
  2. TS-18 came in handy because I read the file that was created from step 1 and whenever I came across or spanned a holiday date, I created a loop that calculated the ROC for the prior five days and also for the five days that followed.   I created a nomenclature to quickly recognize the ROC span.
    • B54 – ROC between 5 days before and 4 days before – 1 day
    • B53 – ROC between 5 days before and 3 days before – 2 days
    • B52 – ROC between 5 days before and 2 days before – 3 days
    • B51 – ROC between 5 days before and 1 days before – 4 days
    • B43 – ROC between 4 days before and 3 days before – 1 day
    • B42 – ROC between 4 days before and 2 days before – 2 days
    • B41 – ROC between 4 days before and 1 days before – 3 days
    • B32 – ROC between 3 days before and 2 days before – 1 day
    • B31 – ROC between 3 days before and 1 days before – 2 days
    • B21 – ROC between 2 days before and 1 days before – 1 day
    • A12 – ROC between 1 days after and 2 days after – 1 day
    • A13 – ROC between 1 days after and 3 days after – 2 days
    • A14 – ROC between 1 days after and 4 days after – 3 days
    • A15 – ROC between 1 days after and 5 days after – 4 days
    • A23 – ROC between 2 days after and 3 days after – 1 day
    • A24 – ROC between 2 days after and 4 days after – 2 days
    • A25 – ROC between 2 days after and 5 days after – 3 days
    • A34 – ROC between 3 days after and 4 days after – 1 day
    • A35 – ROC between 3 days after and 5 days after – 2 days
    • A45 – ROC between 4 days after and 5 days after – 1 day
  3. This process created a file that looked like this:
    • Output of ROC with nested for loops
  4. With this data I summed up each ROC calculation (B54 thru A45).  From this you could pick an entry and exit day combination.  The best results were wrapped around Memorial Day.  Here was where I ran into a small but highly aggravating scenario.  Most of the holidays had similar numbers, but there were a few that had some really strange values.  I knew I couldn’t use regular adjusted futures data, because many times these values can go below zero and when doing division with these price adjustments, the values are simply not accurate.  So, I used continuous non adjusted data, but ran into a problem with the rollover gap.  Some of the holidays occurred really close to rollover dates.  The calculations that bridged a rollover was not accurate.  I then remembered Pinnacle data also includes Ratio Adjusted Data or RAD.  I applied the looping algorithm to this data, and it SEEMED to fix the problem.
  5. I was able to pick a B(n,m) or A(m,n) for each market.  If the magnitude of ROC was sufficiently negative, you could use the dates to short the market.

ChatGPT and Python and Numpy to the rescue

I asked ChatGPT to provide python code to generate a list of all federal holidays from 1984.

from pandas.tseries.holiday import USFederalHolidayCalendar
from datetime import datetime,timedelta
import numpy as np
# Create a calendar for U.S. federal holidays
cal = USFederalHolidayCalendar()

# Get the holidays for the year 2014
holidays_2014 = cal.holidays(start='1984-01-01', end='2024-12-31').to_pydatetime()

federalHolidays = []
date_list = holidays_2014.astype(datetime).tolist()
for n in range(len(date_list)):
dateString = f'{date_list[n]:%Y%m%d}'
federalHolidays.append(int(dateString))
Create a list of federal holidays

This worked great, but I had to convert the NumPy datetime construct to a string.  I like to keep things simple.  You will see this in the code.  I started to get to work on the second part of the task when I discovered there were no dates for GOOD FRIDAYS – argh!  ChatGPT to rescue once again.

def good_friday_dates(start_year, end_year):
good_friday_dates = []
for year in range(start_year, end_year + 1):
# Calculate the date of Easter Sunday for the given year
easter_sunday = calculate_easter_date(year)

# Good Friday is two days before Easter Sunday
good_friday = easter_sunday - timedelta(days=2)

# Add the Good Friday date to the list
good_friday_dates.append(good_friday)

return good_friday_dates

def calculate_easter_date(year):
# Algorithm to calculate the date of Easter Sunday
a = year % 19
b = year // 100
c = year % 100
d = b // 4
e = b % 4
f = (b + 8) // 25
g = (b - f + 1) // 3
h = (19 * a + b - d - g + 15) % 30
i = c // 4
k = c % 4
l = (32 + 2 * e + 2 * i - h - k) % 7
m = (a + 11 * h + 22 * l) // 451
month = (h + l - 7 * m + 114) // 31
day = ((h + l - 7 * m + 114) % 31) + 1
easter_date = datetime(year, month, day)
return easter_date

# Generate Good Friday dates from 1984 to 2024
good_friday_dates_1984_to_2024 = good_friday_dates(1984, 2024)
Hope this code is right for Good Fridays

Good Friday, as Easter, is all over the map.  Tomorrow is Good Friday.  The code looks pretty gross, so I just accepted it.  It may not be 100% accurate, but if ChatGPT says so….  Once I had a list of all holidays, I was able to start cooking (MLK was added in 1986 and Juneteenth in 2021.)  Since Juneteenth is relatively new it did not offer a statistically significant number of observations.

Future Leak comes in Handy.

Every quant needs some software that will look through data and do some calculations.  I did this project with EasyLanguage, but it was a little difficult.  I had to do a two-pass process (well I did the same with Python too.)  I first created an indicator that imported the holiday dates and then outputted the close (or open of next bar) for close[5], close[4], close[3], close[2], close[1] and then waited 1, 2, 3, 4 and 5 bars after the date and printed out those respective closing prices.  I did not do this step in EasyLanguage.  I actually create EasyLanguage as my output.  Here is what it looked like:

holidayName[1]="MemorialDay";
fiveBeforeArr[1]=1010518;
fourBeforeArr[1]=1010521;
threeBeforeArr[1]=1010522;
twoBeforeArr[1]=1010523;
oneBeforeArr[1]=1010524;
oneAfterArr[1]=1010529;
twoAfterArr[1]=1010530;
threeAfterArr[1]=1010531;
fourAfterArr[1]=1010601;
fiveAfterArr[1]=1010604;
holidayName[2]="July4th";
fiveBeforeArr[2]=1010626;
fourBeforeArr[2]=1010627;
threeBeforeArr[2]=1010628;
twoBeforeArr[2]=1010629;
oneBeforeArr[2]=1010702;
oneAfterArr[2]=1010705;
twoAfterArr[2]=1010706;
threeAfterArr[2]=1010709;
fourAfterArr[2]=1010710;
fiveAfterArr[2]=1010711;
holidayName[3]="LaborDay";
fiveBeforeArr[3]=1010824;
fourBeforeArr[3]=1010827;
threeBeforeArr[3]=1010828;
twoBeforeArr[3]=1010829;
oneBeforeArr[3]=1010830;
oneAfterArr[3]=1010904;
twoAfterArr[3]=1010905;
threeAfterArr[3]=1010906;
fourAfterArr[3]=1010907;
fiveAfterArr[3]=1010910;
All key dates are stored in arrays.

Future leak allows you to peak into the future and this comes in super handy when doing research like this.  Here is some python code in TS-18 that does all these calculations in one sweep.


# The date of the holiday and the holiday name are stored in lists
# Each Holiday has its own date and 20 ROC calculations
# These calculations are stored in lists as well
tempDate = int(extraData[extraCnt][0]) #first field in the csv file
whichHoliday = extraData[extraCnt][1] #second field in the csv file
if date[D] == tempDate or (date[D] > tempDate and date[D1] < tempDate):
foundJ = 99
for j in range(12):
if whichHoliday == listOfHolidayClass[j].holidayName:
foundJ = j
listOfHolidayClass[foundJ].holidayDates.append(tempDate);
for j in range(-5,-1): #Before Holiday
for k in range(j+1,0):
denom = close[D+j]
listOfHolidayClass[foundJ].rocList.append((close[D+k] - close[D+j])/denom)
for j in range(0,4): #After Holiday - here is the future leak
for k in range(j+1,5):
denom = close[D+j]
listOfHolidayClass[foundJ].rocList.append((close[D+k] - close[D+j])/denom)
extraCnt = extraCnt + 1
Python code using future leak to calculate the 20 ROC from before and after holiday

I then printed this out to a csv file and imported into Excel (see above) and found out the best combination of days for each holiday.  Now that I had the B(n,m) or the A(m,n) values, I need a process to tell TS-18 which day to buy/short and exit.  Here is some code that, again uses future leak, to print out the five dates before the holiday and the five dates after the holiday.

tempDate = int(extraData[extraCnt][0])
whichHoliday = extraData[extraCnt][1]
if date[D] == tempDate or (date[D] > tempDate and date[D1] < tempDate):
for j in range(12):
if whichHoliday == listOfHolidayClass[j].holidayName:
foundJ = j
print(date[D],end=',')
print(whichHoliday,end=',')
for j in range(-5,6):
print(date[D+j],end=',')
print("",end = '\n')
extraCnt = extraCnt + 1

# Here is what the file looked like
19840103,NewYear,19831223,19831227,19831228,19831229,19831230,19840103,19840104,19840105,19840106,19840109,19840110,
19840221,PresidentsDay,19840213,19840214,19840215,19840216,19840217,19840221,19840222,19840223,19840224,19840227,19840228,
19840423,GoodFriday,19840413,19840416,19840417,19840418,19840419,19840423,19840424,19840425,19840426,19840427,19840430,
19840529,MemorialDay,19840521,19840522,19840523,19840524,19840525,19840529,19840530,19840531,19840601,19840604,19840605,
19840705,July4th,19840627,19840628,19840629,19840702,19840703,19840705,19840706,19840709,19840710,19840711,19840712,
19840904,LaborDay,19840827,19840828,19840829,19840830,19840831,19840904,19840905,19840906,19840907,19840910,19840911,
19841008,ColumbusDay,19841001,19841002,19841003,19841004,19841005,19841008,19841009,19841010,19841011,19841012,19841015,
19841112,VeteransDay,19841105,19841106,19841107,19841108,19841109,19841112,19841113,19841114,19841115,19841116,19841119,
19841123,Thanksgiving,19841115,19841116,19841119,19841120,19841121,19841123,19841126,19841127,19841128,19841129,19841130,
Create the dates that occur in the date B4 and after holiday

Now read the data in and pick how many days before to b/s and before to exit or after to b/s and to exit

holidayBuyDates = []
holidayXitDates = []
gfCnt = 0

#structure of holidayDatesData list - all strings
# 0.) Holiday Date
# 1.) Holiday Name
# 2.) 5-Before
# 3.) 4-Before
# 4.) 3-Before
# 5.) 2-Before
# 6.) 1-Before
# 7.) 0-Before/After
# 8.) 1-After
# 9.) 2-After
# 10.) 3-After
# 11.) 4-After
# 12.) 5-After
#Below I pick 1 day after to buy and 4 days after to exit [8,11]
for j in range(numHolidayDates):
if holidayDatesData[j][1] == "MemorialDay":
holidayBuyDates.append(int(holidayDatesData[j][8]))
holidayXitDates.append(int(holidayDatesData[j][11]))



# Okay Let's put in some logic to create a long position
if not(long) and canTrade and date[D] == holidayBuyDates[gfCnt]:
price = close[D]
tradeName = "Hol-A1Buy"
posSize = 1
tradeTicket.append([buy,tradeName,posSize,price,mkt])
# Long Exits
if long and canTrade and date[D] == holidayXitDates[gfCnt]:
price = longExit
tradeName = "Hol-A4Xit"
tradeTicket.append([exitLong,tradeName,curShares,price,mkt])
gfCnt = gfCnt + 1
TradingSimual_-18 Code to execute buys and exits
No execution fees – executing on close of the day
Memorial Day Example -----------------------------------------
Sys Name : TS-18HolidayTest
Mkt Symb : TY Mkt Name : 10YNote
19840530 Hol-A1Buy 1 -35.90625 0.00 0.00
19840604 Hol-A4Xit 1 -34.12500 1731.25 1731.25
19850529 Hol-A1Buy 1 -18.43750 0.00 0.00
19850603 Hol-A4Xit 1 -16.96875 1418.75 3150.00
19860528 Hol-A1Buy 1 -0.40625 0.00 0.00
19860602 Hol-A4Xit 1 -2.87500 -2518.75 631.25
19870527 Hol-A1Buy 1 -1.09375 0.00 0.00
19870601 Hol-A4Xit 1 -0.28125 762.50 1393.75
19880601 Hol-A1Buy 1 -1.87500 0.00 0.00
19880606 Hol-A4Xit 1 -0.56250 1262.50 2656.25
19890531 Hol-A1Buy 1 2.68750 0.00 0.00
19890605 Hol-A4Xit 1 4.46875 1731.25 4387.50
19900530 Hol-A1Buy 1 2.50000 0.00 0.00
19900604 Hol-A4Xit 1 3.68750 1137.50 5525.00
19910529 Hol-A1Buy 1 6.59375 0.00 0.00
19910603 Hol-A4Xit 1 6.65625 12.50 5537.50
19920527 Hol-A1Buy 1 13.71875 0.00 0.00
19920601 Hol-A4Xit 1 14.50000 731.25 6268.75
19930602 Hol-A1Buy 1 28.21875 0.00 0.00
19930607 Hol-A4Xit 1 27.96875 -300.00 5968.75
19940601 Hol-A1Buy 1 24.62500 0.00 0.00
19940606 Hol-A4Xit 1 25.93750 1262.50 7231.25
19950531 Hol-A1Buy 1 33.34375 0.00 0.00
19950605 Hol-A4Xit 1 34.59375 1200.00 8431.25
19960529 Hol-A1Buy 1 32.09375 0.00 0.00
19960603 Hol-A4Xit 1 30.93750 -1206.25 7225.00
19970528 Hol-A1Buy 1 32.93750 0.00 0.00
19970602 Hol-A4Xit 1 33.75000 762.50 7987.50
19980527 Hol-A1Buy 1 40.00000 0.00 0.00
19980601 Hol-A4Xit 1 40.31250 262.50 8250.00
19990602 Hol-A1Buy 1 37.62500 0.00 0.00
19990607 Hol-A4Xit 1 37.81250 137.50 8387.50
20000531 Hol-A1Buy 1 36.10938 0.00 0.00
20000605 Hol-A4Xit 1 37.43750 1278.12 9665.62
20010530 Hol-A1Buy 1 42.92188 0.00 0.00
20010604 Hol-A4Xit 1 44.06250 1090.62 10756.25
20020529 Hol-A1Buy 1 50.14062 0.00 0.00
20020603 Hol-A4Xit 1 50.68750 496.88 11253.12
20030528 Hol-A1Buy 1 68.06250 0.00 0.00
20030602 Hol-A4Xit 1 68.34375 231.25 11484.38
20040602 Hol-A1Buy 1 64.43750 0.00 0.00
20040607 Hol-A4Xit 1 63.78125 -706.25 10778.12
20050601 Hol-A1Buy 1 72.32812 0.00 0.00
20050606 Hol-A4Xit 1 72.29688 -81.25 10696.88
20060531 Hol-A1Buy 1 65.46875 0.00 0.00
20060605 Hol-A4Xit 1 66.07812 559.38 11256.25
20070530 Hol-A1Buy 1 66.57812 0.00 0.00
20070604 Hol-A4Xit 1 66.06250 -565.62 10690.62
20080528 Hol-A1Buy 1 77.21875 0.00 0.00
20080602 Hol-A4Xit 1 76.10938 -1159.38 9531.25
20090527 Hol-A1Buy 1 87.12500 0.00 0.00
20090601 Hol-A4Xit 1 87.14062 -34.38 9496.88
20100602 Hol-A1Buy 1 95.48438 0.00 0.00
20100607 Hol-A4Xit 1 96.18750 653.12 10150.00
20110601 Hol-A1Buy 1 102.23438 0.00 0.00
20110606 Hol-A4Xit 1 103.12500 840.62 10990.62
20120530 Hol-A1Buy 1 115.32812 0.00 0.00
20120604 Hol-A4Xit 1 117.37500 1996.88 12987.50
20130529 Hol-A1Buy 1 115.51562 0.00 0.00
20130603 Hol-A4Xit 1 115.59375 28.12 13015.62
20140528 Hol-A1Buy 1 116.31250 0.00 0.00
20140602 Hol-A4Xit 1 116.56250 200.00 13215.62
20150527 Hol-A1Buy 1 121.20312 0.00 0.00
20150601 Hol-A4Xit 1 121.42188 168.75 13384.38
20160601 Hol-A1Buy 1 125.07812 0.00 0.00
20160606 Hol-A4Xit 1 126.50000 1371.88 14756.25
20170531 Hol-A1Buy 1 124.17188 0.00 0.00
20170605 Hol-A4Xit 1 124.65625 434.38 15190.62
20180530 Hol-A1Buy 1 120.35938 0.00 0.00
20180604 Hol-A4Xit 1 119.21875 -1190.62 14000.00
20190529 Hol-A1Buy 1 124.76562 0.00 0.00
20190603 Hol-A4Xit 1 125.76562 950.00 14950.00
20200527 Hol-A1Buy 1 137.43750 0.00 0.00
20200601 Hol-A4Xit 1 137.84375 356.25 15306.25
20210602 Hol-A1Buy 1 133.03125 0.00 0.00
20210607 Hol-A4Xit 1 133.42188 340.62 15646.88
20220601 Hol-A1Buy 1 122.48438 0.00 0.00
20220606 Hol-A4Xit 1 121.70312 -831.25 14815.62
20230531 Hol-A1Buy 1 115.40625 0.00 0.00
20230605 Hol-A4Xit 1 115.10938 -346.88 14468.75

Here are the results for the Morgan Stanley analysis in 2018

Best combos for entry and exit for each holiday.

My analysis covered the same period plus all of 2018, 2019, 2020, 2021, 2022, and 2023.  My combos were very similar in most cases, but the last few years moved the needle a little bit.  However, the results still showed a technical edge.

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!