Unlocking a Gann-Based Pivot System with My “Intelligent Stack” Design

A Wheel Within a Wheel: Recording Market Events Beyond the Daily Bar

Dust flew as I opened my trading system vault. With more than 1,000 systems tucked away, why pull out Gann Pivot? Two reasons: its entry rules make an excellent coding example, and they give me a practical way to introduce my Intelligent Stack library.

In my opinion, Bruce Babcock’s choice of the Gann name served two purposes: recognition among the many “Gannsters” studying his work, and a connection to Gann’s interest in swings and market turning points. That is my interpretation, rather than a documented statement of Babcock’s intent. Despite the name, this system does not require astrology, biblical interpretation, or elaborate geometric calculations. Its rules focus on price pivots, the trading days between them, and risk management. I would describe it as Gann-inspired swing trading, rather than an attempt to reproduce his more esoteric methods. There is an irony here: Bruce Babcock criticized Gann in his own writing, yet attached Gann’s name to this system. Perhaps the distinction was between Gann’s more esoteric explanations and the practical value of swing-based trading rules. Whatever his reasoning, the system gives us something concrete to define, code, and test.

The “wheel within a wheel” idea resurfaced during a recent session with a client. We were using a two-dimensional EasyLanguage array to store information whenever certain market events occurred. Explaining how to move and retrieve those values required more array gymnastics than I wanted. My client is exceptionally sharp and caught on quickly, but I kept thinking: the interface should make the trading logic easier to understand, without making the array bookkeeping part of the lesson.

Years ago, I built structures in Fortran to handle similar bookkeeping. For this example, I wanted to bring that approach into EasyLanguage through a simple stack interface.

A conventional stack is a last-in, first-out collection: you push new items onto the top and pop them off. My Intelligent Stack borrows the newest-at-the-top organization but serves a different purpose. We do not need to pop our pivot history. Instead, we push new observations, inspect older entries with seek, and let the library handle the underlying array bookkeeping. When the stack reaches its capacity, the oldest observation drops out of the bottom.

Gann has also been on my mind lately. My friend and colleague, Mike Chalek, explores Gann’s cycle analysis and price geometry through his work at AstroTimeCycles.com. Although some of these ideas fall outside my usual approach, nearly 40 years of research have taught me not to dismiss a trading method simply because it sounds esoteric. If its rules can be defined, coded, and tested, I am willing to give it a fair examination.

Bruce Babcock published the basis of this system over 30 years ago. He was part of the first generation of system vendors. He published a pioneering book on trading system design – Babcock, B., & Williams, L. R. (1989). The Dow Jones-Irwin guide to trading systems. Dow Jones-Irwin. Foreword by Larry Williams.

The rules from the outside seem simple and reasonable:

Long Entry Rules

Evaluate the following conditions after each trading day’s close:

  1. Identify a new high pivot. Yesterday’s high must be strictly higher than both the preceding day’s high and today’s high:
    High[1] > High[2] and High[1] > High[0]

    Yesterday is the pivot day; today’s completed bar confirms it.

  2. Find the most recent earlier, higher high pivot. Search backward through the stored high pivots until you find one whose price exceeds the newly confirmed pivot high. If none is found within the stored history, there is no new long setup.
  3. Find the lowest intervening low pivot. Between these two high pivots, locate the low pivot with the lowest price. A low pivot is a day whose low is strictly lower than the lows of the preceding and following days.
  4. Measure the time from that low to the recent high. Count trading bars beginning with the day immediately before the recent high pivot and ending with the intervening low pivot day. The count must be at least 20 bars.
  5. Place the entry order. If the setup qualifies, place a buy stop for the next trading day one tick above the recent high pivot.

Timing clarification: The 20-bar requirement applies to the distance between the intervening low pivot and the recent high pivot—not between the two high pivots

Short Entry Rules: 

  1. Just reverse the long entry rules.

Exit Rules:

These are the rules that were published way back in the early 1990s.  So, the dollar values would need to be adjusted.  However, you will get the general idea.

A simple $1000 fixed stop from entry.  Imagine trying to trade gold or crude with this tight of a stop.

A Ratcheting Percent Trailing Stop protects a growing share of a trade’s profit. It activates once closing profit or maximum position profit exceeds $1,600 per contract. Each additional $1,500 reduces the permitted giveback by five percentage points.

Highest Closing Profit Giveback Allowed Profit Protected
$1,600* 70% $480
$3,100 65% $1,085
$4,600 60% $1,840

The first row illustrates the starting level; activation occurs just above $1,600.

The stop tightens as the trade profit increases and never loosens when profit declines. I’ll explain the calculations and implementation in a later post.

 

Doesn’t EasyLanguage OOEL already include a Stack object?

Yes. TradeStation’s object-oriented EasyLanguage library includes a conventional stack with Push, Pop, and Peek operations. TradeStation documentation

My Intelligent Stack serves a different purpose. Built with ordinary EasyLanguage arrays and functions, it stores a fixed number of observations with the newest in slot zero. When it fills, the oldest observation drops out of the bottom.

For pivot analysis, we need to inspect earlier entries without removing them. My seek function provides that access, while other library functions calculate averages, standard deviations, and more. There is no pop operation because this application needs a searchable history rather than a collection we consume one item at a time.

Think of it as a bounded, newest-first history collection with the array bookkeeping tucked behind a simple interface.

Why My Intelligent Stack Design?

First off, you don’t need to enter the world of objects to use my

libraries.  I teach EasyLanguage to traders, so I try to keep things as simple as possible.  Introducing a two-dimensional array can quickly cause confusion.  My stack structure is based on synchronized single dimension-ed arrays.  And I use the word stack in all my functions.

Stack declaration

Arrays:
HPvt_stack[200](0),
LPvt_stack[200](0),
HPvtBN_stack[200](0),
LPvtBN_stack[200](0);

Here I create 4 stacks using Array declaration.  I dimension them to 200 and set the initial elements to zero.  Remember arrays are just lists that can be indexed.  The HPvt stacks are to be synchronized when using them.  In the same manner so is the LPvt stacks.

Pushing data onto a stack

    Value1 = Stack.Push(H[1],HPvt_stack,200);
Value1 = Stack.Push(barNumber-1,HPvtBN_stack,200);

When I discover a pivot high I push the price and the barNumber-1 onto their respective stacks:  HPvt_stack and HPvtBN_stack .

After pushing all four pivots:

Array Index HPvt_stack HPvtBN_stack Meaning
[0] 4 4 Number of stored pivots
[1] 110.00 138 Newest pivot
[2] 106.75 125 Previous pivot
[3] 108.50 112 Older pivot
[4] 105.25 100 Oldest pivot

Each new push places the pivot’s price and bar number at index [1], shifts older entries down, and updates the count at [0]. Matching indexes in the two stacks always describe the same pivot.

The library handles the shifting and count updates, so users can focus on the trading logic. They only need to declare the arrays with enough capacity; the stack functions manage the contents.

EasyLanguage arrays are zero-based. My implementation reserves that first element, [0], for the count and stores observations beginning at [1].

Why Do I Call Them Intelligent Stacks?

I built a collection of functions that do more than store and retrieve values. They can calculate averages, standard deviations, and other statistics over an entire stack or a selected portion. For example:

value1 = Stack.Average(chanWidthStack,20);

This calculates the average of the 20 most recent stored observations. All library functions use the Stack. prefix.

Some functions come in two forms:

value1 = Stack.Highest(HPvt_stack);
value2 = Stack.HighestIndex(HPvt_stack);

value1 contains the highest price in the stack. value2 contains its location, or index.

Why return the location? Because our price and bar-number stacks hold related information at matching indexes. If the highest price occupies slot [3] in HPvt_stack, its bar number occupies slot [3] in HPvtBN_stack. We retrieve it with:

highestPivotBar = Stack.Seek(HPvtBN_stack,value2);

The index connects the two stacks, telling us both what the highest pivot price was and when it occurred.

Now let’s put these functions to work. Each time a new pivot high is confirmed, we search the stored pivots for the most recent higher pivot high. Its bar number defines our search boundary. We then locate the lowest intervening pivot low and check whether it occurred at least 20 bars before the new pivot high. Notice how the index-returning functions connect prices with their corresponding bar numbers.

if HighPivotFormed(1,0) <> -999999 then
begin
currentHPvtBN = BarNumber - 1;
priorHPvtIndex = -1;
endIndex = 0;
interLowIndex = -1;

{ Find the most recent higher pivot high. }
if Stack.Size(HPvt_stack) > 0 then
priorHPvtIndex = Stack.GreaterIndex(HPvt_stack,H[1],1);

{ Bound the low-pivot search using that pivot's bar number. }
if priorHPvtIndex <> -1 then
begin
priorHPvtBN = Stack.Seek(HPvtBN_stack,priorHPvtIndex);
endIndex = Stack.LesserIndex(LPvtBN_stack,priorHPvtBN,1) - 1;
end;

{ Find the lowest pivot low within the search range. }
if endIndex >= 1 then
interLowIndex = Stack.LowestRangeIndex(LPvt_stack,1,endIndex);

{ A qualifying low establishes or replaces the pending buy order. }
if interLowIndex <> -1 then
begin
interLowPivotBN = Stack.Seek(LPvtBN_stack,interLowIndex);
interLowCount = currentHPvtBN - interLowPivotBN;
if interLowCount >= interveningPvtTrigger then
begin
buyPrice = H[1] + tickOffset * MinMove / PriceScale;
buyOrderBN = BarNumber;
end;
end;
Finding an intervening lowest pivot low between pivot high boundaries

Are you Bewildered?

I know this code looks a little bit on the gross side.  I can tell you that it is better than the loopy version and we now have a nice library of functions that we can use with our stacks.  Let’s break it down.

    { Find the most recent higher pivot high. }
if Stack.Size(HPvt_stack) > 0 then
priorHPvtIndex = Stack.GreaterIndex(HPvt_stack,H[1],1);

{ Bound the low-pivot search using that pivot's bar number. }
if priorHPvtIndex <> -1 then
begin
priorHPvtBN = Stack.Seek(HPvtBN_stack,priorHPvtIndex);
endIndex = Stack.LesserIndex(LPvtBN_stack,priorHPvtBN,1) - 1;
end;
First two steps explained

Once a new pivot high is confirmed at H[1], the first if checks whether HPvt_stack contains any earlier pivot-high prices. If it does, Stack.GreaterIndex(HPvt_stack,H[1],1) searches for the first price greater than H[1]. Since the newest entry is stored at [1], this finds the most recent qualifying pivot high. The function returns its location in the stack, or -1 if none is found.

The second if proceeds only when a match exists. We use its index with Stack.Seek to read the corresponding bar number from HPvtBN_stack. Remember, the price and bar-number stacks are paired: matching indexes describe the same pivot.

That bar number establishes the older boundary for our next search. LPvtBN_stack contains the bar numbers of stored pivot lows, also ordered newest first. Stack.LesserIndex finds the first low pivot that occurred before our qualifying high pivot. Subtracting one from that index excludes this older low and everything below it. The resulting endIndex bounds the portion of LPvt_stack we will search for the lowest intervening pivot low.

In other words, we have located the bar number of the prior qualifying pivot high and use it as the boundary for our backward search. We now need to find the lowest pivot low between that boundary and the newly confirmed pivot high, along with its bar number. The stack functions handle the searching internally, so our strategy code does not need its own search loops.

   { Find the lowest pivot low within the search range. }
if endIndex >= 1 then
interLowIndex = Stack.LowestRangeIndex(LPvt_stack,1,endIndex);

{ A qualifying low establishes or replaces the pending buy order. }
if interLowIndex <> -1 then
begin
interLowPivotBN = Stack.Seek(LPvtBN_stack,interLowIndex);
interLowCount = currentHPvtBN - interLowPivotBN;
if interLowCount >= interveningPvtTrigger then
begin
buyPrice = H[1] + tickOffset * MinMove / PriceScale;
buyOrderBN = BarNumber;
end;
end;
Find the intervening lowest low pivot and validate the current pivot high

Working within our stack environment, we use Stack.LowestRangeIndex to locate the lowest pivot low within the search range in LPvt_stack. We then use that index to retrieve its corresponding bar number from LPvtBN_stack. If the newly confirmed pivot high occurred at least 20 bars after that low, we have a qualifying setup. We establish a fresh buyPrice one tick above the new pivot high.

The trickiest part of this example is using an index to connect a pivot’s price in one stack with its bar number in another. Most single-stack tasks are simpler: call a function to retrieve a value, find an extreme, or calculate an average. Here, the paired stacks let us answer both what happened and when it happened.

A Wheel within a Wheel

Our Intelligent Stack separates the storage of observations from EasyLanguage’s bar-by-bar history. Instead of recording a value on every bar, we push a value only when an event occurs, such as a newly confirmed pivot. We retrieve earlier events through stack functions rather than ordinary series indexing. This creates a second history inside the daily chart: one measured in market events instead of bars. That is our “wheel within a wheel.”

Original Results over the past 20 Years – no execution fees charged

This material is for educational purposes only and does not constitute investment advice. Futures and options trading involves substantial risk of loss and is not suitable for every investor. Any performance data shown is hypothetical and may not reflect actual results. You may lose more than your initial investment, and losses can occur quickly. Do not trade with money you cannot afford to lose. The information provided is not a solicitation or offer to buy or sell any financial instrument, and no representation is made that any account will achieve similar results. Past performance is not indicative of future results. You are responsible for your own trading decisions and should seek independent financial advice before trading

These results show why 30-year-old dollar values do not keep up with the times.

Updated Dollar Values and Intervening Bar Count:

Email me for full source code and access to the limited Intelligent Stack functions discussed in this post.

Patience, Grasshopper—the prompt is not yet the Master.

AI will not replace the seasoned programmer, well not yet.

Can AI solve 10 lines of EasyLanguage code and fix the subtle order-execution flaws hiding inside? EasyLanguage’s small footprint makes it a known weak spot for LLMs, which rely on sheer volume to learn edge cases. That weakness shows up clearly when entry and exit criteria are triggered at the same time. The strategy exits and re-enters at the exact same price and time. While trade management stays intact on paper, this simultaneous exit and re-entry cannot be truly realized in real-world trading, leaving you paying extra execution costs and commissions for phantom backtest trades.

Exit after N bars

Many predictive models utilize an “exit after N bars” rule to determine objective function fitness.

The problem with testing a “no matter what happens, you exit after N bars” strategy in TradeStation is that the engine will immediately re-enter the same trade at the exact same exit price and bar if the entry criteria remain true.

Above we had four occurrences of this problem.  In reality this is just one trade.

inputs:exitBars(7),stoploss$(2000),profitTarg$(3500);
if close > close[1] and close > average(close,20) Then
buy("simple buy") next bar at open;
if barsSinceEntry >= exitBars then
begin
if close > close[1] then sell("xitBars") next bar at market;
sell("xitBars-ll") next bar at lowest(l,2) stop;
end;
setStopLoss(stoploss$);
setProfitTarget(profitTarg$);
Simple system that allows entry and exit conditions to exist simulteneously!

The AI Response

I fed all of this into an LLM. Before going completely down the rabbit hole of a seemingly infinite prompt, test, feedback loop, I programmed the fix myself.

Then, I showed the LLM my code and asked a simple question: If we had stayed in that loop, would you have ever derived this solution?

Its answer was a clear “no.”

Here is why the AI got stuck, and why real trading and programming experience still matters.

The EasyLanguage Challenge

EasyLanguage is a specialized, niche script. Mainstream languages like Python or JavaScript have massive amounts of code online for LLMs to train on, but EasyLanguage has a much smaller footprint.

Because of that limited training data, AI models rely on broad programming rules. They simply do not understand how TradeStation’s state machine processes order fills, bar updates, and variable evaluation line-by-line during an execution pass.

How I Solved It with a PreLaunch Paradigm

Instead of trying to force built-in engine stops that kept breaking, I stepped back and built a custom execution flow using three practical techniques:

  • The Hybrid Stop Switch (setStopLoss(-1)): Standard AI models treat native engine tools and custom order logic as an either-or choice. By passing -1 into setStopLoss, I actively suppressed the native stop engine the moment my custom price targets took over, stopping order conflicts in their tracks.
  • State Priming (barsInTrade = -1): An LLM looks at setting a counter to -1 as a mistake and tries to “correct” it to 0. But because TradeStation increments variables at the very bottom of the script pass, priming barsInTrade to -1 ensured the trade clock started at exactly 0 when the next bar opened.
  • Forward-Looking Execution (open of next bar): By evaluating open of next bar inside the end-of-pass calculation, the code figures out theoretical fill prices (theoEntryPrice) and re-establishes profit targets before the new bar starts printing. That handles simultaneous exit-and-reentry collisions cleanly in a single pass.

The Bottom Line

AI has a strong bias toward fixing what is already on the screen. It tries to stretch built-in functions past their limits instead of stepping back and rebuilding the architecture.

AI writes code based on how a system is supposed to work on paper. It still takes a seasoned programmer to write code based on how execution engines actually behave in the real world.

It admitted that generative models have a strong “fix what’s there” bias. They try to bend standard built-in functions to their limit rather than stepping back, discarding broken abstractions, and manually rebuilding the architecture.

It takes a seasoned programmer; someone who truly understands platform execution loops, bar-evaluation timing, and manual state overrides to see past the standard toolkit and engineer a real solution.

My code grew from ten lines to 40.  Platform nuances and misbehavior usually needs much more code

 

 

Day Trading the 30-Minute Breakout, Again

The 30-Minute Breakout: A Classic Strategy Built on Simplicity

It seems like it is a good time, based on performance, to take another look at this simple model.  Just like bell bottom jeans, this simple approach flows in and out of fashion.  I revisited this strategy in my 2023 “Easing Into EasyLanguage – Day Trading Edition“ book.

Most day traders spend hours glued to their monitors, constantly scanning charts and second-guessing every tick. But some of the most effective strategies take the opposite approach: one trade per day, simple execution, and fixed risk.

Back in 1998, I was sitting in a crowded ballroom at a trading convention in Orlando. The keynote speaker stepped up to the stage, pulled up a PowerPoint presentation, and unveiled a strategy that was already considered an “oldie” back then.  We had actually been trading derivatives of this approach for more than 15 years at this point.

“Oldie” is probably an appropriate word to use—the core concepts of the Opening Range Breakout (ORB) and the Opening Range Breakout with Pattern (ORBP) had already been floating around the industry for over two decades.

In fact, one of the most exhaustive works on the subject was published back in 1990 by Toby Crabel and released by my friend Ed Dobson at Traders Press: Day Trading with Short Term Price Patterns and Opening Range Breakout. Following the multi-billion-dollar success of Crabel’s firm, Crabel Capital Management, that single out-of-print hardcover became a true holy grail item—regularly fetching anywhere from $500 to over $1,000 on the secondary market.

You can even watch the team at Crabel Capital discuss how those core quantitative principles scaled up in this J.P. Morgan featured interview.

Getting back to 1998 – Looking around the room, people were transfixed. Slide after slide showed successful breakout after successful breakout.

Even today, breakout trading remains one of the most widely used entry techniques across the market. Why? Because this specific approach checks almost every box a trader could want:

  • One trade per day — Essential for preventing revenge trading, though modern market microstructure often causes early breakouts to fail. Hard-coding a rule to fade that initial failed move can actually turn a false breakout into a high-alpha opportunity.

  • No screen-staring — You don’t need to sit at your desk all session.

  • Defined risk — Built-in protection and zero overnight risk.

  • Easy automation — Follows the Keep It Simple, Stupid (KISS) principle to a T.

Here is how the classic setup works:

  1. Wait for the Setup: At 9:55 AM Eastern, look at your 5-minute chart. Wait for that 9:50–9:55 AM bar to close (completing the first 30 minutes of the trading day).

  2. Set Your Orders: Place a buy stop order one or two ticks above the highest high of those first six 5-minute bars, and a sell short order one or two ticks below the lowest low.

  3. Execute: Wait for one side to trigger. As soon as you get filled on one side, cancel the opposing entry order and convert it into your liquidation/stop-loss order.

  4. Manage Risk: Early market volatility can sometimes create a range that is too wide for your risk parameters. If the dollar risk between the channel high and low is greater than your personal risk tolerance, use a pre-set disaster stop or simply skip the trade.

Trading doesn’t have to be complex to be effective. Sometimes, stepping back and letting a classic rule-based strategy do the heavy lifting is the smartest move you can make.

Step 1: Fire Up the Code Editor

When a strategy with this kind of mileage crosses your desk and catches your eye, your first job isn’t to start placing trades—it’s to translate it into code.

Now, since the rules fit on the back of a napkin, you’d naturally assume writing the script would be a piece of cake. You can even prompt your favorite AI to whip up the code, and to its credit, it’ll get you about 80% of the way there. But as any veteran EasyLanguage programmer knows, the devil is always in the details. There’s almost always a sneaky little hiccup lurking in the logic—whether it’s an off-by-one error in your bar counts, a weird multi-data alignment issue, or an order that fills when it shouldn’t.

More importantly, build parameterization into the code from the beginning. The goal is not to curve-fit the past into a fragile illusion of perfection, but to find parameter ranges that place the strategy in the right ballpark for today’s electronic markets. Risk and reward characteristics evolve as stock indexes reach new highs and volatility changes with them.

Email me at george.p.pruitt@gmail.com to get the EasyLanguage source code.  

Here are the key variables I always like to isolate and test:

  • The Breakout Window: The 30-minute opening range is a classic that’s been around since the floor-trading days. But is 30 minutes still the magic number? Opening up a search space for 15, 30, or 45-minute ranges lets the data tell us how modern volatility behaves.

  • Capital & Trade Risk: On fast-moving contracts like the ES or NQ, a wide 30-minute opening bar can yield a stop-loss distance that’ll blow right past your account’s risk budget. We need a disaster stop override.

  • Profit Targets: Riding a trade to the final bell sounds great in theory, but late-day momentum can turn on a dime. Testing defined profit objectives helps us figure out if taking money off the table beats hoping for a strong closing bell.

  • Break-Even Triggers: Let’s face it—false breakouts happen, and in today’s algorithmic environment, they happen a lot. Introducing a break-even stop once a trade moves a certain number of ticks or dollars into the green can salvage capital when the initial push was purely manifested by stop runners.

  • Adding the Pattern to the ORB:  If you allow this simple strategy free reign it will almost certainly trade every day.  In this simple test we could utilize some of Toby Crabels volatility constraints.
    • NR-7:  Yesterday must be the narrowest range of the past seven days
    • NR-4:  Yesterday must be the narrowest range of the past four days.
    • TR vs. ATR:  Compression or expansion.  Was yesterday’s True Range greater than or less than the 20-Day Average True Range.

Here is the analysis from July 2018 through July 2023.  I picked this period because this is when I published the book plus five years prior.  Tested on @NQ.D without commission and slippage.

  1. Optimize risk versus reward while ignoring break even and range filters:
    1. profit objective from $3,000 to $9,000 by $1000
    2. stop loss: $1500 to $3000 by $500
  2. Optime risk versus reward versus breakeven ignoring range filters
    1. profit objective from $3,000 to $9,000 by $1000
    2. stop loss: $1500 to $3000 by $500
    3. break even stop from $1500 to $2000 by $250
  3. Optimize range filters
    1. NR4
    2. NR7
    3. Range compression and expansion

3-D Visualizations and Discussion

Optimzation 1:

Objective function:  Net Profit

Reward.  Nice results across a nice high and level plateau.  The strategy wants a lower risk per trade, but it wants the profits to run.

Objective Function:  Maximum Draw down

Risk.  Well, we are dealing with AI and Mag7 volatility here.  Very few results with less than a $30K draw down.  We are bumping up against the wall risking $1000 per trade.  We could push through the wall but I personally don’ think it is worth the effort.

Optimzation 2:

Objective function:  Net Profit

Plotting two parameters on a 3D chart is straightforward. Adding a third requires an extra step. We can still plot a result at each X–Y coordinate, but multiple results may now share that same coordinate because of the third parameter. To create one surface, we accumulate those results and plot their average at each unique X–Y location.

Reward.  Nice results across a nice high and level plateau, again.  The introduction of the Break-Even optimization changed the surface.  Remember we are looking at aggregate statistic at each X and Y not just one point.

Objective Function:  Maximum Draw down

Risk.  This was somewhat surprising.  The Break-Even addition returned all values (when averaged) at each X and Y between -$20K and -$30K.

Heat Map of Risk (3 parameters).  A chart like this is hard to see unless you rotate them.  I like to look at a Heat Map as well.  The following map shows a lower draw down values at lower stop loss and profit objectives.  Logical, right?

Heat Map of Risk (2 parameters).  What does the 2 parameter optimization heat map look like?

I think we can conclude the application of a Break-Even trade as a plus for the system.  Very few X and Y show desirable results.

Trade Filtering – Good or Bad:  Neither NR4 or NR7 were productive.  However, comparing yesterday’s True Range with the ATR produced some surprising results.

This demonstrates we need volatility but not too much volatility.  Trade filtering goes hand in hand with the amount you want to risk.  Filtering trades diminishes executions and therefore reduces exposure.

Final System:

Risk: $2000 – Reward: $9000 – BreakEven: $1500 – Volatility TR < 1.5 X ATR

Walk It Forward

Walked forward from August 2023 – this was the endpoint we used in the optimization process. A good fellow with just a hint of a temper.

Too much money too quickly!

Incubation Assessment
Overall Assessment: Degraded
Risk Assessment: High Risk
Incubation Readiness Score: 4 / 8
Return delivery is running ahead of baseline: expected annual return is 318% versus actual annual return of 434%, and expected annual gain of $39,732 compares with actual annual gain of $54,197. However, that stronger return delivery has come with a less stable path and/or materially heavier risk than history would suggest. Risk is materially worse than the historical profile: actual worst drawdown of $35,005 is 1.720 times the historical drawdown of $20,355. Risk conditions are in the High Risk range. The realized monthly path is no longer tightly aligned with the baseline, based on monthly equity correlation of 0.960, projection RMSE of $55,147, normalized RMSE of 1.388, and path wander ratio of 0.476.

Correlation still shows directional similarity, but the path wander ratio indicates noticeable drift away from the projected path over the same window. Monte Carlo context is cautionary: actual forward equity is $162,590, gain percentile is 78% (in the upper quartile), and drawdown percentile is 84% (in the upper quartile for drawdown stress). Taken together, the system shows meaningful deterioration in incubation.

That flat period at the beginning of the test period looks a little suspect, right?

Monte Carlo It

Running 2500 simulations with $50K initial capital and then extracting a typical year out of the results you get this:

This is what you get when you perform a Monte Carlo analysis over a time period that shows exceptional results.  However, the very best and very worst trades were removed first before the shuffling.

Top left corner is most dense quadrant = GOOD!

Walk it Backward!  Bell Bottom Jeans!

The 30-minute rule during this period of time was dismal.  Just like Bell Bottom jeans in the 1980s.

From Generated Strategies to Engineered Systems: Why Good Numbers Are Only the Beginning

The Throwing-Spaghetti-Against-the-Wall Approach

We now have all sorts of platforms that can test seemingly innumerable combinations of patterns, indicators, trade management rules, markets, and time frames. We have seen systems work on 60-minute bars, but fail completely on 120-minute bars. Popular candlestick patterns are aggressively used to generate systems that pass whatever gauntlet has been defined as “robust.”

The no-coding-required algo generators only look at the metrics. If a system looks great on a 445-minute chart using a HAMMER or HANGING MAN candlestick pattern, and it passes the out-of-sample inline stress testing, then it becomes one of the noodles that sticks to the wall.

I am not being critical of this overall approach. Expanding the search universe by using a wide spectrum of multi-minute bars is somewhat newer, and in many ways, somewhat novel. Back in the day, we mostly stuck with daily bars or common intraday intervals such as 15-, 30-, and 60-minute bars. A 445-minute bar was not even considered. But with today’s tools, almost anything is possible.

What I am critical of is the tendency to take those results at face value without drilling down into the underlying logic. A system that passes a statistical test is not automatically a system that makes sense. And more importantly, it is not automatically a system that can actually be traded.

Enter the Engineer

Plotting the algorithm on the chart and examining the actual trades is Step #1.

Why do this, you may ask? The code is the code. The generator produced an impressive-looking algorithm. The metrics passed the test. The equity curve looked promising.

But what if the algorithm just got lucky? What if it is already drifting away from its foundational premise?

For example, suppose I create a strategy using momentum along with the Hammer and Hanging Man candlestick patterns. The results look promising. A hypothetical generator was fed the available data, churned through the combinations, and produced the following candidate system:

Candidate System

Time Frame: 445-minute bars
Market: @ES
Buy: Momentum is positive and Hammer pattern occurs
Sell Short: Momentum is negative and Hanging Man pattern occurs
Risk: $2,800 to make $5,600

From an engineer’s perspective, I kind of like it.

It has several attractive qualities:

  • It is symmetrical.
  • It is applied to a good, liquid market.
  • It uses a different time frame.
  • It involves time-tested candlestick patterns.
  • The risk-to-reward ratio is a clean 1-to-2.

But then the engineer asks the next question:

Can the bar interval itself be trusted?

You cannot slice a pie evenly using 30% slices.

Most trading days consist of 23 hours, or:

23 × 60 = 1,380 minutes

Now divide the trading day by the selected bar interval:

1,380 ÷ 445 = 3.10

Or stated another way:

445 + 445 + 445 = 1,335 minutes

That leaves a remainder of:

1,380 – 1,335 = 45 minutes

So, the chart does not really consist of four equal 445-minute bars. It consists of three large 445-minute bars and one small 45-minute bar.

That matters.

A Hammer or Hanging Man pattern detected on a 445-minute bar is not the same thing as a Hammer or Hanging Man pattern detected on a 45-minute cleanup bar at the end of the trading session. The pattern may have the same name, and the code may treat it the same way, but structurally it is not the same event.

This is where generated code becomes engineering work.

The generator saw a metric.
The engineer sees a structural problem

Small Piece at the EOD

But Can You Argue With the Numbers?

At this point, someone could reasonably say, “Yes, the final bar is shorter, but look at the performance report.”

And that is a fair objection.

The numbers appear impressive. The system produced a solid net profit, a respectable profit factor, and a reasonable percentage of winning trades. On the surface, the strategy looks like something worth pursuing.

But this is exactly where the engineer has to slow down.

Most candlestick patterns are built around the range, body, open, close, high, and low of the bar. In other words, the pattern is not just a label. It is a structural interpretation of price movement during a specific unit of time.

Here is the EasyLanguage definition of the Hammer and Hanging Man pattern:

BodyHi = MaxList( Close, Open ) ;
BodyLo = MinList( Close, Open ) ;
Body = BodyHi - BodyLo ;
PriceAvg = XAverage( Close, Length ) ;
BodyAvg = XAverage( Body, Length ) ;

oHammer = 0 ;
oHangingMan = 0 ;

if Body < BodyAvg
and Body > 0
and BodyLo > MedianPrice
and BodyLo - Low > Factor * Body
and High - BodyHi < Body

then
if Close < PriceAvg then
{ TREND IS down }
oHammer = 1
else if Close > PriceAvg then
{ TREND IS up }
oHangingMan = 1 ;

C_Hammer_HangingMan = 1 ;
Hammer/Hanging Man

The key element here is the current bar’s open-to-close body, along with the relationship of that body to the bar’s total range. That makes perfect sense when each bar represents the same amount of market time.

But do you really want to rely on a bar that always covers only 45 minutes when the other bars cover 445 minutes?

From an engineering standpoint, I do not believe the smaller bar provides enough information to fulfill the spirit of the complete pattern. The code may identify the shape, but the market structure behind that shape is not equivalent.

In this case, it appears that a malformed Hanging Man triggered a short sale. The 45-minute cleanup bar completed the pattern, and the system acted on it.

And yet, the numbers look good.

That is the trap.

A performance report can tell you that something worked historically. It cannot tell you whether the logic that produced those trades was structurally sound. That part still requires inspection, judgment, and engineering.

My goodness!

Do You Throw the Results Out Over a Technicality?

Not necessarily.

This is where engineering judgment comes into play. The fact that one of the trades was triggered by a malformed candlestick pattern does not automatically mean the entire idea is worthless. The performance report may still be pointing toward something useful. The combination of market, time frame, momentum filter, and trade management rules may contain real information.

But it does mean the results should not be accepted at face value.

The first question is not, “Did it make money?”

The first question is, “Did it make money for the reason I think it made money?”

That distinction matters.

If a strategy is supposed to exploit a Hammer or Hanging Man pattern, then the bars used to form those patterns should be structurally comparable. A 45-minute cleanup bar sitting next to three 445-minute bars may satisfy the code definition, but it does not necessarily satisfy the trading premise.

So I would not immediately throw the results away. I would move the candidate system into the engineering phase. That means isolating the questionable condition, removing or correcting the structural flaw, and then testing whether the core idea still holds up.

If the system falls apart after making those changes, then the original result was probably more fragile than it appeared. If the system survives, then you may have something more interesting.

That is the difference between throwing spaghetti against the wall and engineering a tradable system.

The generator found a candidate.

The engineer decides whether that candidate deserves to live.

Next Installment — Working Your Craft: Some Coding Required

Don’t worry — I do the heavy lifting for you.

But there is real value in sitting in front of the editor, debugging the code, flipping system components on and off, and examining the functions that drive the trades. That process is a win-win. Even if you do not grasp every minute detail, that is okay.

You do not have to become a full-time programmer to benefit from this exercise.

Understanding the why behind the code is often enough. Why did the trade trigger? Why did the pattern qualify? Why did the system enter there instead of somewhere else? Why did the performance report look good?

Those questions move you beyond blind acceptance of the results. They turn a generated candidate into something you can inspect, challenge, and eventually trust — or reject.

In the next installment, we will take the same strategy idea and make it more practical for real-time trading. The larger bar can still generate the signal, but the smaller bar can handle the execution. In TradeStation terms, Data2 can define the setup, while Data1 handles the trade.

Watching a system trade on a large bar can hide too much of the story. If a trade enters, gets stopped out, or takes a profit all within the same large bar, the report may tell you what happened, but it does not really show you how the trade unfolded.

That is why I like to watch the trade develop on a smaller execution bar. Seeing the five-minute bar crawl across the screen gives you a much better feel for the trade. You can see the entry, the follow-through, the hesitation, the stop pressure, and the eventual exit in a way that a large bar simply cannot reveal.

Maybe that is just me, but I find it much more satisfying.

And more importantly, it provides a clearer picture of whether the trade was realistically executable.

That is where the engineering really begins.

 

 

 

Is Your System Broken?

The Ultimate Test of Efficacy: Time

At Futures Truth, we focused on one thing: walk-forward results across hundreds of trading systems. Back then, the industry was unrecognizable compared to today—if you can even call what exists now an “industry.”

Just this morning, I was reminiscing about vendors who sold strategies for upwards of $5,000. Those packages usually consisted of a set of rules (disclosed or not) and a rudimentary DOS-based program to generate simple charts and next-day orders. This was well before the heyday of TradeStation; those days are gone forever, for better or worse.

Back then, if a system performed well in walk-forward analysis, it earned a ranking. In those days, a Futures Truth ranking meant something. Sometimes, however, that ranking felt like a curse. Critics even argued you could “fade” a top-ranked system and make more money by taking the opposite trade. The reality is that many of those legendary “Top Ten” systems—built for a different era—simply wouldn’t survive in today’s markets.

Side Bar:  There are still services such as Striker and The Collective that still monitor trading systems.  Striker only shows real execution, which is mostly a good thing – real execution costs are shown.  But so is broker error.  Heck, we are all human and we all make mistakes.  Striker is upfront with this, and they state they will do the best they can – execution can be a beast. 

Speaking of execution – The Sunday Evening Gap

A Traders Nightmare. Most likely Trend Followers were short during the huge gap here!

 Imagine, having a stop order in crude oil on a Sunday evening when the market opens thousands of dollars through your stop.  If a system derived position is short the broker’s only directive is to GET OUT – no matter what, at the first off ramp.  The above chart shows a single contract slip on crude futures at the genesis of the Iran War.

The Million-Dollar Question

The second most common question I was asked at Futures Truth was: “How do I know if the system I’m trading is broken?”

Can you guess the most common one? “If it were your money, which system would you trade?”

We never answered that one directly; there was never a simple black-and-white answer. But the second question—the one about “broken” systems—usually surfaced when a trader was deep in a drawdown.

Seasoned traders know that systems ebb and flow; drawdown is simply the “tax” we pay to play the game. Others, however, get “married” to a system and stick with it until “death do us part.” My standard response back then was always:

“Is your current performance still within the boundaries of the backtest?”

In other words, has the system exceeded its historical maximum drawdown by a meaningful margin? Does the current real-time performance look like other “rough patches” in the historical equity curve? If the answer was yes, the trader would usually give it a little more room.

Into Unexplored Waters

Those were simpler days, but the core problem remains: we cannot see the future. No forward analysis can tell you with certainty what a trading system will do next. What it can do is tell you when the system has moved into “unexplored waters.” Once you know that, the decision to stay, abandon, or pause becomes much easier.

All trading systems oscillate between success and failure. If a system has a genuine technical edge, that edge may eventually reassert itself—provided you have the time and capital to wait. But most traders don’t have unlimited resources.

Recently, the market activity surrounding the Iran conflict has pushed many systems into intense drawdowns. The same old questions have reared their heads again. This time, however, I wanted to provide a more empirically derived analysis. I’ve developed a disciplined approach to measuring risk and reward that moves beyond “gut feel.”

To illustrate, I pulled a system off my shelf that I originally designed for retail consumption back in June 2018. Here is how it’s currently holding up:

Hypothetical Results: Before and after development.

The Profit Mirage

This is a mean-reversion approach. I remember thinking back in 2018: How much longer can this bull market continue? With that in mind, I utilized a simple regime filter. After just a few weeks of trading later that year, I genuinely thought the system might be broken. The market spent a large portion of that fall and winter below its 200-day moving average, and shorting simply wasn’t working.

The system then went dormant for a long stretch, finally “waking up” at the height of the pandemic. Had you stuck with it, you would be up significantly today (though we stopped trading right at the onset of the pandemic).

But here is the catch: Profit, by itself, is not enough to determine if a system is broken. As long as they are making money, most traders never bother to peek below the surface. This system would likely be sitting near the top of the Futures Truth rankings today. But let’s dive in and see how it actually performed on its “test of time.”

Test 1 – Baseline Projection

Baseline projection based on in sample average monthly return.

Is Outperformance Always a Good Thing?

Looking at the chart above, you see a steep deviation below the baseline initially, followed by a rocket-ship move to the upside. By late 2025, the actual equity is sitting way above the red dashed expectation line.

Most traders would see this and think they’ve struck gold. “Isn’t this what we want—a strong positive deviation?” they’d ask.

In a world of simple “bottom-line” thinking, the answer is yes. But in the world of professional algorithmic trading, this chart is screaming a different story. To a seasoned developer, deviation is deviation. Whether it’s to the upside or downside, moving this far away from the “expected” path suggests that the strategy’s original statistical model is no longer in control.

When a system starts generating 190% of its expected return, it’s often because it has inadvertently stepped into a high-volatility regime it wasn’t designed to navigate. If the “upside” is this aggressive, you can bet the “downside” risk has scaled right along with it.

Test 2:  Monte Carlo Analysis on Walk Forward

Return and Drawdown sitting on the tails.

The Statistical Reality Check

To understand why I called this system “Degraded” despite the profits, we have to look at the Monte Carlo Walk Forward distributions. This is where we compare real-time performance against thousands of simulated “alternate realities” based on the system’s history.

1. The Equity Distribution (The Good News… or is it?)

In the top chart, our actual forward equity of $67,575 (the dashed red line) sits at the 97th percentile.

  • Interpretation: Out of 1,000 possible outcomes, the system performed better than 970 of them. While this looks great, being this far out on the “tail” of the distribution suggests we are no longer operating in a normal environment.

2. The Drawdown Distribution (The Warning)

The bottom chart is the real story. The actual worst drawdown reached $18,588, placing it in the 98th–99th percentile of severity.

  • The Comparison: The median expected drawdown was only $8,125.

  • The Verdict: We have blown past the 90th percentile of $13,611 and are deep into the “danger zone.”

The Bottom Line

When a system hits the 97th percentile for gains but simultaneously hits the 99th percentile for drawdown stress, the math is telling you that the character of the strategy has changed. You aren’t just trading a system in a “rough patch”—you are trading a system that has moved into a risk regime it was never built to survive. This is the “evidence-based framework” I mentioned earlier. Without these charts, you’re just guessing. With them, you have the data to justify stepping aside.

The Verdict: Test 3 – Overall Assessment

This is where the “gut feel” ends and empirical analysis begins. The following commentary is generated by my new software; a quasi-expert system designed to pair raw statistical results with descriptive, actionable interpretation.

Status: DEGRADED

Risk Assessment: CRITICAL RISK

Incubation/Trading Readiness Score: 4 / 8

Overall Assessment: Degraded
Risk Assessment: Critical Risk
Incubation/Trading Readiness Score: 4 / 8
Return delivery is running ahead of baseline: expected annual return is 18% versus actual annual return of 34% and expected annual gain of $4,477 compares with actual annual gain of $8,536. However, that stronger return delivery has come with a less stable path and/or materially heavier risk than history would suggest. Risk is materially worse than the historical profile: actual worst drawdown of $18,588 is 2.670 times the historical drawdown of $6,962. Risk conditions are now in the Critical Risk range. The realized monthly path is poorly aligned with the baseline, based on monthly equity correlation of 0.960, projection RMSE of $21,789, normalized RMSE of 4.867, and path wander ratio of 0.621. Correlation still describes directional similarity, but RMSE and path wander ratio show how far the realized path has wandered from projection over the same window. Monte Carlo context is cautionary: actual forward equity is $67,575, gain percentile is 97% (near the top of the simulated distribution), and drawdown percentile is 99% (in the worst decile for drawdown stress). Taken together, the system shows meaningful deterioration in incubation.

A readiness reading of 4 out 8 indicates this system right now has degraded to a point where caution and I mean extreme caution should be used in your decision to trade this strategy.  The major factors influencing rating are:

  • Return Attainment:   1.907 – — This indicates the system has achieved 190.7% of its historically expected return during the incubation/trading period. In practical terms, the strategy is generating returns at nearly twice the pace implied by its historical baseline.
  • Path Wander Ratio: 0.621 — This indicates that the actual equity path is deviating from the projected path by a meaningful amount relative to the total expected move over the incubation/trading period. Put simply, the system is not wildly off course, but it is no longer tracking the historical baseline tightly.
  • Drawdown Stress: 2.67 — This indicates that the actual worst drawdown has expanded to roughly 2.7 times the level suggested by the system’s historical baseline. Put simply, the strategy may still be generating return, but it is doing so while absorbing far more pain than its historical profile would justify.

Hindsight is always 20/20, and it is easy to say now that you should have stuck with the system. But with a large sample of out-of-sample trades showing this degree of deterioration, if I had to choose between staying with it, abandoning it entirely, or temporarily shutting it down, I would probably step aside and wait for conditions to settle down. Remember, not trading is an algorithm too.   Having the right tools to make this kind of decision is paramount, because they give you an evidence-based framework for explaining and defending your reasoning.

The Power of Incubation: Is Your Best System Collecting Dust?

How many systems do you have sitting on your shelf? I personally have at least a hundred, probably more. In this business, a little dust is not always a bad thing.

Figuratively speaking, the more “dust” a system has collected, the more real-time incubation it has endured. And that gives you something far more valuable than any backtest: pure, unadulterated out-of-sample evidence.

Seeds Waiting to Be Planted

Algorithmic trading development rarely produces just one system. It creates a trail of offshoots—versions that may have looked unremarkable or even mediocre during the initial build. Yet many of these forgotten systems are really just seeds waiting for the right environment.

When you revisit them months or even years later, you may find that a strategy which struggled in 2022 has bloomed into a powerhouse in 2026. Without a framework to measure that growth, you would never know.

Why You Need an Incubation Framework

Most traders revisit old systems by simply eyeballing an equity curve. An incubation framework goes much deeper by providing:

Historical Context: Does the dusty system’s recent performance still match its original DNA?

Regime Readiness: Has the market finally moved into the regime this offshoot was designed for?

The Go/No-Go Signal: A quantitative way to decide whether a seed is finally ready to be moved from the shelf to the server.

TS-SystemChecker software

TS-System Check Control Panel

A Tool Built for the Journey

I built TS-SystemChecker because, after decades at Futures Truth and years of developing my own strategies, I needed a better way to cut through the emotional fog that surrounds system evaluation.
I didn’t design TS-SystemChecker to be a black box or some kind of get-rich-quick shortcut. I built it because, after decades at Futures Truth and years of developing my own strategies, I wanted a better way to cut through the emotional fog that surrounds system evaluation.

Whether you are a retail trader focused on refining one core system, or a developer like me with a shelf full of offshoots, this framework was built for that journey.

For the specialist: If you have one system you live and die by, the deep-dive analysis helps define its boundaries of truth. You can begin to see whether a drawdown is simply part of the system’s normal character or evidence of something more structural.

For the portfolio manager: If you are tracking a library of ideas, the Batch Analysis feature helps you monitor many systems at once. Import the trade files, review the evidence, and identify which seeds may finally be ready to move from the shelf to a live account.

Looking Beneath the Surface

At the end of the day, this is why I built TS-SystemChecker. Traders need more than opinions, hope, or fear. They need a framework grounded in evidence.

That is the real purpose of this tool: not to make decisions for you, but to help you make better ones.

TS-SystemChecker Batch Mode

Generated Code Is Not the Same as Engineered Code

AI can write structure, but experienced programmers still supply the craft

The more we rely on generated code, the more disciplined we must become in questioning it.

AI and modern frameworks now provide valuable insights that, just a few years ago, would have required significant time and effort to obtain. However, while they offer tremendous macro-level leverage, they can also introduce subtle assumptions that lead to impossible scenarios and misleading downstream analysis. This is especially true in environments designed for rapid idea testing, where convenience can come at the expense of deeper, microscopic introspection.

For example, in my PatternSmasher framework, I use constructs like BarsSinceEntry to control trade duration and evaluate pattern efficacy. This makes it very easy to test thousands of ideas quickly. But that convenience comes with a responsibility. If you rely on these abstractions without thinking through the details, you can end up with behavior that looks perfectly valid in code but could never occur in the real world.

I have seen this problem in other frameworks and in AI-generated code as well. This is why it is so important to continue to hone your craft and take a deep dive into the results produced by generated code. In the quant world, the first step is to study the trades and isolate problems such as what I call simultaneous same-direction exit and reentry. Once you see it, the job is to fix it without changing the intent of the algorithm.

Let me show you exactly what I mean. The logic behind this example looks perfectly fine on the surface. But when you dig into the trades, you see the problem immediately. In this chart, the system exits a long position and then turns right around and buys again at the same time and price. That is not a reversal. It is a same direction exit and reentry that simply cannot happen in the real world, and it pollutes the back test with trades that should not exist.

Example of simultaneous same-direction exit and reentry at the same time and price—an impossible trade sequence that distorts backtest results – 640 minute bar on Gold – why 640?

We entered a long position, the trade expired, immediately re-entered on the next setup, that trade expired as well, and then entered again—only to get stopped out. That’s three round turns, each incurring commission and slippage.

// Simple code that is of course mean reversion.
// However, since we seem to be in this regime
// let's hone our craft to make this work as intended.

input:movAvgLen(50),consCloses(1),exitAfterNBars(5),stopLoss(3000);

value1 = countIF(c < c[1],consCloses);
value2 = countIF(c > c[1],consCloses);


if value1 = consCloses and close > average(c,movAvgLen) then
buy ("lentry") next bar at open;
if value2 = consCloses and close < average(c,movAvgLen) then
sellShort ("sentry") next bar at open;


if barsSinceEntry > exitAfterNBars then
Begin
sell("lx-exp") next bar at open;
buyToCover("sx-exp") next bar at open;
end;
setStopLoss(stopLoss);
Simple entry with expiration exit

The code that produced this looks pretty clean. You have your entry logic, a BarsSinceEntry exit, and a stop loss. On the surface, everything seems fine.

But you don’t find this kind of problem by staring at the code. You find it by looking at the trades.  This is the one thing AI or a framework doesn’t examine.  At first, the natural reaction is to slap a MarketPosition “gate” on the entry logic. The word “gate” may be a dead giveaway that AI has influenced the discussion. But I like it. It has been around since the early days of electrical circuits, and it is very appropriate here.  I’ve noticed that many of the words AI uses have started to creep into my own vocabulary. Funny how that happens.

Fix #1

input:movAvgLen(50),consCloses(1),exitAfterNBars(5),stopLoss(3000);

value1 = countIF(c < c[1],consCloses);
value2 = countIF(c > c[1],consCloses);


if marketPosition <> 1 and
value1 = consCloses and close > average(c,movAvgLen) then
buy ("lentry") next bar at open;

if marketPosition <> -1 and
value2 = consCloses and close < average(c,movAvgLen) then
sellShort ("sentry") next bar at open;


if barsSinceEntry > exitAfterNBars then
Begin
sell("lx-exp") next bar at open;
buyToCover("sx-exp") next bar at open;
end;
setStopLoss(stopLoss);
Fix #1 - solves the simultaneous exit and re-entry same direction glitch

So what does that MarketPosition “gate” actually do?

It fixes the symptom. The same-bar exit and reentry disappears, and the trades look cleaner.

But it also changes the algorithm in a much deeper way.

In the original design, a new long signal while already long reaffirmed the position and should have kept the trade alive. The gate removes that behavior. Now the strategy must exit first and then wait until the next bar to reenter.

And that delay matters.

By the time the next bar arrives, the setup may be gone. What should have been one continuous trade is now split into pieces—or missed entirely.

You didn’t just clean up the trades. You changed which trades exist.  The new strategy more or may not be more efficient, but just know the algorithm is now different.

Fix #2

We bought, suppressed the expiration exit due to a new buy setup—twice—and were ultimately stopped out at the level where the stop loss from the final trade that didn’t occur—but whose properties we were monitoring—would have been triggered.

Could most quants who aren’t programmers solve this riddle? Probably not. My 40 years of programming experience certainly played a role, and my familiarity with EasyLanguage—especially its limitations—helped guide me down the right path. But more importantly, I was able to recognize the nature of the problem, apply targeted fixes, and then analyze the resulting trades. I repeated this process—wash, rinse, repeat—until the issue was resolved.

Much of the knowledge I relied on has been documented by myself and others over the years. Investing time in books, videos, and webcasts specific to your programming language remains essential—it forms the foundation. But ultimately, refining your own skills and developing your craft is a time-consuming process that pays lasting dividends.

Groundwork for the Fix

Solving what initially appears to be a simple riddle requires recognizing several underlying behaviors. I was able to correct the issue because I could anticipate when a new trade was about to occur. When both the exit gate for an existing position and the entry gate for a new position in the same direction were simultaneously open, I prevented the transition by closing both gates.

However, simply blocking the transition was not enough. I had to simulate the trade that would have occurred. This meant marking the hypothetical entry price, resetting the stop-loss based on that price, and reinitializing my own bars-in-trade counter.

At this point, I could no longer rely on EasyLanguage’s built-in functions such as BarsSinceEntry or SetStopLoss. Those functions assume an actual executed trade and therefore could not reflect the internal state I needed to maintain. To solve the problem correctly, I had to take full control of trade state management and explicitly track these values myself.

input:movAvgLen(50),consCloses(1),exitAfterNBars(5),stopLoss(3000);

vars: mp(0),barsMult(1),barsIntrade(0),lStopLevel(0),sStopLevel(0),closedTrades(0);
vars: canGoLong(False),canGoShort(False);

canGoLong = countIF(c < c[1],consCloses) = consCloses and close > average(c,movAvgLen) ;
canGoShort = countIF(c > c[1],consCloses) = consCloses and close < average(c,movAvgLen);

mp = marketPosition;

//Exit Technology

closedTrades = totalTrades;
//long exit on bar after entry
if mp[1] <> mp and mp = 1 or (closedTrades > closedTrades[1]) Then
begin
barsInTrade = 0;
lStopLevel = open[0] - stopLoss/bigPointValue ;
end;

//short exit on bar after entry
if mp[1] <> mp and mp = -1 or (closedTrades > closedTrades[1]) Then
begin
barsInTrade = 0;
sStopLevel = open[0] + stopLoss/bigPointValue ;
end;

//long reentry stop reset
if mp = 1 and canGoLong and barsInTrade > exitAfterNBars Then
begin
lStopLevel = open of tomorrow - stopLoss/bigPointValue ;
// print(d," ",t," should exit and renter long tomorrow ",barsInTrade," ",barsSinceEntry," ",open of tomorrow);
barsInTrade = -1;
end;

//short reentry stop reset
if mp = -1 and canGoShort and barsInTrade > exitAfterNBars Then
begin
sStopLevel = open of tomorrow + stopLoss/bigPointValue ;
// print(d," ",t," should exit and renter short tomrorrow ",barsInTrade," ",barsSinceEntry," ",open of tomorrow);
barsInTrade = -1;
end;

if mp = 1 then
sell("lx-stopLoss") next bar at lStopLevel stop;

if mp = -1 then
buyToCover("sx-stopLoss") next bar at sStopLevel stop;

//Entry Logic
if canGoLong then
buy ("lentry") next bar at open;
if canGoShort then
sellShort ("sentry") next bar at open;


//Bars in trade expiration exit
if barsInTrade > exitAfterNBars then
Begin
sell("lx-exp") next bar at open;
buyToCover("sx-exp") next bar at open;
end;

//Day of entry protection
setStopLoss(stopLoss);
//Increment barsInTrade - mimic TradeStation here too!
if mp <> 0 then barsInTrade = barsInTrade + 1;
Fix #2 - difficult initially but reusable

This version fixes the problem by taking control of the trade state instead of relying on EasyLanguage’s built-in functions.

First, I define whether I can go long or short, independent of my current position. Then I track my own state variables—market position, bars in trade, stop levels, and trade count—so I know exactly what the system is doing at all times.

The key occurs when a same-direction signal appears after the trade has technically expired. Instead of allowing an exit and immediate reentry, I suppress both actions and simulate the renewed trade. I mark the hypothetical entry price, reset the stop based on that level, and restart my bars-in-trade counter.

Because of this, I can no longer rely on BarsSinceEntry or SetStopLoss—they depend on actual trades. I manage everything explicitly.

The result is a continuous position that preserves the original intent of the algorithm without introducing impossible trades into the backtest.

EasyLanguage also has its share of esoteric nuances. Code order can matter in some places and not in others, particularly with order execution. Even detecting position changes requires a bit of finesse. These details matter, but they are beyond the scope of this discussion.

This is where the difference between generated code and engineered code becomes clear.

A programmer who is not willing to put in the work—and instead relies on AI to solve the problem—will likely stop at the first acceptable fix. The code will run, the trades will look cleaner, and the issue will appear resolved. But the deeper problem remains: the structure has changed, trades may be missing, and the original intent of the algorithm has been compromised.

As we become more dependent on code generation through AI and frameworks, it becomes even more important to validate that the output is reasonable and reflects something that could occur in the real world. That responsibility does not go away—it increases. And it requires us to continue honing our craft.

AI can generate code and even suggest reasonable fixes, but it does not truly understand the nuances of the language, the sequencing of events, or the intent behind the strategy. It cannot look at a trade and say, “that shouldn’t have happened.” It does not debug by questioning reality—it follows patterns.

Arriving at the correct solution required recognizing the problem, iterating through possible fixes, examining the trades, and refining the logic until the behavior matched the intent. That process—wash, rinse, repeat—is the craft.

Generated code can get you started. Engineered code is what gets you to the truth.  Take a look at the two following reports.  Similar results, but look at the number of trades and those statistics tied to this number.

 

Monte Carlo: Garbage or Gold?

A quick history of Monte Carlo (the short version)

 

Monte Carlo Tool Link:  Read blog first:  Monte Carlo Trade Flight Simulator · Streamlit

Youtube Video on Monte Carlo Tool:  Youtube Monte Carlo Tool Video

Monte Carlo methods took off in the 1940s during wartime research at Los Alamos, when scientists needed a practical way to estimate outcomes for complex systems that couldn’t be solved with a single clean equation. Trading has the same problem: there’s no tidy formula that can tell you the order your wins and losses will arrive in—and that order is where luck lives.

So we do the next best thing: we add randomness on purpose. Monte Carlo repeatedly reshuffles the same trade outcomes to create many plausible equity paths, revealing how smooth (or brutal) the ride can be even when the system’s edge stays the same.

“No matter how sophisticated our choices, how good we are at dominating the odds, randomness will have the last word.” — Nassim Nicholas Taleb, Fooled by Randomness

What Monte Carlo does in trading

In trading system analysis, the “Monte Carlo move” is straightforward: you take your historical list of trade results (P/L per trade), then you randomly reshuffle (or resample) that list thousands of times. Each reshuffle produces a new, plausible equity curve built from the same underlying trade outcomes. From there, you measure the things that actually determine whether a system is tradeable—how deep the drawdowns get, how long the slumps can last, and how often the path gets ugly enough to force you to quit or cut size.

This doesn’t predict the future. It answers a different (and more practical) question:

Given the trade outcomes you’ve already seen, how good—or how bad—can the ride get due to sequencing alone?

That’s the value, or is it?

Why traders debate the value of Monte Carlo

Why some traders love it

  • It exposes fragility that a single backtest can hide.
    A backtest is one historical path—one specific order of wins and losses. Monte Carlo reshuffles that order to show other plausible paths. If a strategy only “works” when winners show up early, Monte Carlo will expose that quickly.

  • It turns vague fear into a measurable risk.
    Traders feel risk but struggle to quantify it. Monte Carlo lets you define a failure line (for example, “equity falls below 60% of starting capital”) and estimate how often that happens across thousands of simulated lives. You may still trade it—but now you’re choosing with a probability, not a gut feeling.

  • It helps you size the system rationally.
    Most blow-ups aren’t caused by a bad system—they’re caused by a decent system traded too big. By running the same trades under different starting capital (or leverage), Monte Carlo shows where the strategy becomes survivable. It often reveals a capital/size “threshold” where ruin risk drops and drawdowns become tolerable.

Why some traders hate it

  • It assumes the future behaves like the past.
    Monte Carlo can’t detect regime change. If your edge only works in certain “market moods” (trending vs choppy, low-vol vs high-vol), the simulation may look great right up until the market stops playing that game.

  • It assumes trades can be shuffled like a deck of cards.
    Many Monte Carlo runs treat each trade as an independent draw from the same bag of outcomes. Real systems aren’t that clean—markets come in streaks and clusters (volatility spikes, choppy stretches, correlation breaks), and those dependencies don’t always survive a simple reshuffle. Monte Carlo still helps measure sequence risk, but it isn’t a full market simulator.

  • It can punish good systems—and flatter lucky ones.
    A solid system can look worse if its history includes a few rare “tail” events—Monte Carlo will replay those tails in many sequences. Meanwhile, a strategy that enjoyed an unusually favorable historical run can look sturdier than it deserves, because the simulation is only as honest as the sample you feed it.

So, it’s not garbage… but it’s not gold automatically either.

Monte Carlo is a tool. Like any tool, it can be used well or used blindly.

The setup: how I ran these simulations

Monte Carlo Trade Flight Simulator · Streamlit

For these tests, I used my Streamlit-based Monte Carlo “Trade Flight Simulator.” You paste a column of trade P/L and the simulator generates:

  • Risk of Ruin based on a user-defined ruin line
  • Median drawdown across thousands of randomized equity paths
  • Worst-case outcomes (1st percentile)
  • Distribution visuals (“broom chart” equity fan + destination histogram)
  • Scaling table across start equity levels

Key settings used here

  • Ruin threshold: 60% of starting equity
  • Position size: 1 contract per trade
  • Execution costs: $40 per trade included in the trade list results you pasted
  • Horizon: number of trades pasted (the simulator runs “N trades” each life)
  • Optional CAGR is computed from first/last trade dates when provided

System #1: Mean Reversion on the Mini Nasdaq (MNQ)

~$40 execution costs, ~20 years

Start Equity Risk of Ruin Median DD Annual Return Worst Case (1st %)
$25,000 30% 42.9% 12.9% $85,019
$31,250 14% 36.2% 11.7% $80,747
$37,500 8% 32.2% 10.8% $93,072
$43,750 4% 30.1% 10.0% $78,308
$50,000 1% 26.4% 9.6% $81,352
$56,250 1% 25.2% 8.8% $93,765
$62,500 0% 24.4% 8.4% $77,907
$68,750 0% 23.0% 8.1% $81,076
$75,000 0% 20.6% 7.7% $81,108
$81,250 0% 19.1% 7.4% $89,028
$87,500 0% 18.9% 7.0% $81,982

System #1 — Mean Reversion (MNQ)

  • At $25,000 start equity
    • Risk of Ruin: 30%
    • Median Drawdown: 42.9%
    • Annual Return: 12.9%
    • Worst Case (1%): +$85,019
    • Prob > 0: 99.9%
  • At $50,000 start equity (“still not comfortable”)
    • Risk of Ruin: 1%
    • Median Drawdown: 26.4%
    • Annual Return: 9.6%
  • At $62,500 start equity (“stability zone”)
    • Risk of Ruin: 0%
    • Median Drawdown: 24.4%
    • Annual Return: 8.4%

What Monte Carlo reveals about this system

This is what a tradeable but under-capitalized system looks like.

The edge is real (the probability of finishing positive is essentially ~100%), but the sequence risk at low starting equity is still brutal:

  • A 30% risk of ruin at $25k (with a 60% ruin line) is not a rounding error.

  • Even at $31,250, ruin risk is still 14%.

  • The system doesn’t start to feel “professional” until you get into the $60k+ range, where ruin drops to 0% and median drawdowns settle into the mid-20% area.

Monte Carlo’s message:
If you want this system to behave like something you can actually stick with, you don’t optimize parameters — you capitalize it properly.

System #2: Trend Following on Crude Oil

~$40 execution costs, ~20 years

System #2 — Trend Following (Crude)

Start Equity Risk of Ruin Median DD Annual Return Worst Case (1%)
$25,000 68% 89.7% 8.7% -$99,928
$31,250 58% 80.7% 7.8% -$95,663
$37,500 47% 71.3% 7.1% -$100,991
$43,750 40% 64.1% 6.5% -$111,564
$50,000 31% 58.3% 6.2% -$98,846
$56,250 28% 57.0% 5.5% -$94,594
$62,500 22% 51.1% 5.3% -$100,349
$68,750 19% 49.9% 4.8% -$103,016
$75,000 16% 47.2% 4.5% -$112,968
$81,250 12% 42.3% 4.5% -$98,345
$87,500 11% 42.1% 4.0% -$98,228
  • At $25,000 start equity
    • Risk of Ruin: 68%
    • Median Drawdown: 89.7%
    • Annual Return: 8.7%
    • Worst Case (1%): -$99,928
    • Prob > 0: 87.3%
  • At $50,000 start equity
    • Risk of Ruin: 31%
    • Median Drawdown: 58.3%
    • Annual Return: 6.2%
    • Worst Case (1%): -$98,846
    • Prob > 0: 90.1%
  • At $81,250 start equity
    • Risk of Ruin: 12%
    • Median Drawdown: 42.3%
    • Annual Return: 4.5%
    • Worst Case (1%): -$98,345
    • Prob > 0: 86.4%
  • At $87,500 start equity
    • Risk of Ruin: 11%
    • Median Drawdown: 42.1%
    • Annual Return: 4.0%
    • Worst Case (1%): -$98,228
    • Prob > 0: 87.1%

What Monte Carlo reveals about this system

This is a classic crude trend-following signature: the system can be profitable over time, but the path can be violently unforgiving—especially when under-capitalized.

  • The probability of finishing positive is only in the mid-to-high 80% range, not “near-certain.”
  • At $25,000, the system is living on the edge: 68% risk of ruin with an 89.7% median drawdown.
  • Even after you scale up, the ride is still rough. At $81,250, ruin risk is still 12% with a 42.3% median drawdown.

The most important tell is the left tail: the 1% worst-case outcome is negative at every starting equity you tested (roughly –$94,594 to –$112,968). That means there are plausible sequences where the system not only suffers deep drawdowns, but ends the run down money—even with larger starting capital.

Monte Carlo’s message: This isn’t just a “start with more money” situation. Increasing capital helps, but the strategy’s tail risk remains severe. If you trade this, you need materially more capitalization, smaller sizing, or a risk overlay—because crude can deliver adverse sequences that this system does not comfortably absorb.

Which system is “superior” under your ruin rule?

You asked earlier: with a 60% ruin line and 1 contract per trade, does Monte Carlo reveal superiority?

Yes — because it reframes superiority as:

Which system survives at realistic starting equity levels with tolerable drawdowns?

Under-capitalized start: both are dangerous

At $25k, both systems are dangerous under the 60% ruin definition:

  • MNQ MR: 29% ruin, 41% median DD
  • Crude TF: 48% ruin, 55% median DD

So if someone insists on $25k and 1 contract, System #1 is clearly less fragile than System #2.

Once you move into realistic capital, System #1 stabilizes sooner

MNQ MR drops into “sane” ruin probabilities faster:

  • MNQ MR hits ~0% ruin by $56,250
  • Crude TF doesn’t really calm down until $75k–$81k

That’s not a judgment against trend following — it’s a reminder that instrument volatility matters and crude can be a different animal.

If you define “superior” as best risk-adjusted scaling

Based on your tables:

  • MNQ mean reversion looks easier to scale under your assumptions
  • Crude trend following can still be very viable, but it demands more capitalization to get into the same comfort zone

Monte Carlo didn’t make either system “good” or “bad.”
It made the capital requirements and sequence risk visible.

The visuals I include from the Streamlit app

System #1 Mean Reversion on the NQ

The Journey (“broom chart”)
Shows the median equity path with a confidence band.
Great for communicating “how rough can the ride get?”

Broom Chart

The Destination (ending equity histogram)
Think of each green bar as a bucket of endings. After 1,000 randomized runs, some endings cluster in the middle (the “typical” outcomes), while a smaller number land in the tails (the “lucky” and “unlucky” sequences). The dashed line marks your starting equity ($25,000). If the histogram sits mostly to the right, the system usually finishes positive. If a meaningful chunk sits to the left, that’s your “this can end down” reality—even with the same system and the same trades, just a different order.

Destination Histogram

Efficiency cloud (drawdown vs net profit scatter)

  • Each dot = one simulated run (“one life”).
  • Left to right (x-axis) = max drawdown during that run (more right = more pain).
  • Down to up (y-axis) = net profit at the end (higher = more gain).
  • The dotted lines mark the median drawdown and median profit, splitting the plot into four zones.
  • Best zone: upper-left (good profit with smaller drawdowns).
  • Worst zone: lower-right (big drawdowns with poor outcomes).

If most dots sit upper-left, the system is efficient. If the cloud spreads far right, the system’s edge may be real, but the ride can be brutal unless you reduce size or add capital.

Efficiency Cloud

Pros and cons of Monte Carlo (in plain English)

Pros

  • It highlights sequence risk that backtests hide
  • It gives you a practical scaling map
  • It converts drawdown fear into probability
  • It forces you to confront whether your system is truly robust or just lucky

Cons

  • Garbage in, garbage out (your trade list must be clean)
  • It assumes your future trade distribution resembles the past
  • It doesn’t simulate regime shifts (it’s not a market model)
  • It can create false confidence if you treat it as prophecy

Monte Carlo is not a crystal ball. It’s a stress test.

Conclusion: Garbage or Gold?

Monte Carlo is gold when it’s used as a risk lens.

It’s garbage only when people use it as a substitute for thinking — or when they treat it as a promise about the future.

For me, the biggest takeaway from these two systems is simple:

  • A profitable system can be untradeable if it’s under-capitalized.
  • Monte Carlo makes that obvious — quickly and brutally.
  • And it gives you something most trading metrics do not:
    a realistic map from “this looks good” to “this can survive.”

If you want to know what your system really feels like under stress, run it through my free Monte Carlo Trade Flight Simulator (Streamlit). Paste your trade list, set a starting equity, and it will generate a distribution of possible equity paths—so you can see the range of outcomes, not just the single backtest line. In a minute or two you’ll know whether your strategy is sturdy (most paths survive and grow) or fragile (too many paths crater early), and you’ll get practical numbers like “typical drawdown,” “worst-case runs,” and “probability of finishing above zero.”

Trades vs. Time: Two Monte Carlo Styles

Monte Carlo has to “shuffle” something: you can shuffle trades or you can shuffle time periods (daily/weekly/monthly returns). Trade-shuffling is great for a single system because it keeps each trade intact—entry and exit stay married—so you’re mainly testing how sensitive results are to the order trades arrive.

Devil’s Advocate: shuffling time can feel less “real,” because it breaks those trade narratives. A multi-day trade becomes a series of daily fragments, and once you reshuffle daily P/L you can build equity paths that no single set of trades could have produced exactly.

That’s the tradeoff TS-PortfolioMerge makes on purpose. It builds a daily mark-to-market equity curve (open positions are revalued each day), then resamples those daily equity changes so every system stays aligned to the same calendar. This isn’t about “reinvesting” or scaling up contracts—it’s about equity path risk: the way good and bad stretches of days create drawdowns, recovery difficulty, and survival pressure for a portfolio even when trade size stays constant.

 

Reverse-Engineering a Trading Indicator with AI

From Raw Wavelet Code to a Trading Tool with More Knobs Than Anyone Was Turning

The Indicator I Thought I Understood

A client sent me a trading indicator they had just started using.
It was short. Clean. About a page of code.

I’m not entirely sure where it originated, but it had the unmistakable feel of something machine-generated — technically sound, compact, and largely undocumented.

Their usage was simple:

  • Plot one line
  • Look at its slope compared to one bar ago
  • Go long or short accordingly
{---------------------------------------------------------
Causal True ? trous Wavelet Indicator
---------------------------------------------------------}
Inputs:
UseD1(true),
UseD2(false),
UseD3(false),
UseD4(false),
UseD5(false),
UseD6(false),
ColorBarsByTrend(true),
InvertTrendColorMap(false), // optional flip if colors look reversed
TrendColor(green), // used only if ColorBarsByTrend = false
DenoisedColor(white),
ResidualColor(red);
Vars:
Price(0),
c0(3.0/8.0),
c1(1.0/4.0),
c2(1.0/16.0),

...
...
...
// --- Step 1: Current price ---
Price = Close;
// --- Step 2: A0 is raw price ---
A0 = Price;
// --- Step 3: Causal ? trous B3-spline filter (past bars only) ---
A1 = c0*A0 2*c1*A0[1] 2*c2*A0[2];
A2 = c0*A1 2*c1*A1[2] 2*c2*A1[4];
A3 = c0*A2 2*c1*A2[4] 2*c2*A2[8];
A4 = c0*A3 2*c1*A3[8] 2*c2*A3[16];
A5 = c0*A4 2*c1*A4[16] 2*c2*A4[32];
A6 = c0*A5 2*c1*A5[32] 2*c2*A5[64];
// --- Step 4: Details ---
D1 = A0 - A1;
D2 = A1 - A2;
D3 = A2 - A3;
D4 = A3 - A4;
D5 = A4 - A5;
D6 = A5 - A6;
// --- Step 5: Trend ---
Trend = A6;
...
...
// --- Step 7: Residual ---
Residual = Price - Reconstructed;
// --- Step 8: Plot ---
Plot1(Trend, "Trend");
Plot2(Reconstructed, "Denoised");
Plot3(Residual, "Residual");
Wavelet a trous snippet

They were using a single configuration — effectively listening to just one component of the indicator: Trend. And to be fair, it mostly worked. The trouble only appeared when the Residual (what ever that is) was plotted alongside it. Because it lived on a very different scale, it crushed the display and made the indicator look unusable. See the section at the bottom of this post for how to fix that. Other than that, nothing was actually “broken.”

That behavior was also an early clue that the code itself was likely AI-generated. If you’ve worked with John Ehlers–style indicators, you may recognize the fingerprints of Digital Signal Processing here: fixed coefficients, repeated smoothing, and the output of one calculation feeding directly into the next in a cascading fashion. Those are classic DSP techniques — powerful, but easy to mislabel or oversimplify when dropped directly into a trading context.

In hindsight, the breadcrumbs were right in the header: wavelet and à trous. Even if you’ve never heard those terms, you can paste them into an AI chat and ask, “What does this mean?” That won’t instantly tell you how to trade it — but it will give you the vocabulary and the map so you’re not reverse-engineering in the dark. From there, the real work becomes translating the math into something a trader can actually see and use.

What is a wavelet à trous?

A wavelet à trous (“with holes”) method is a signal-processing technique that breaks a data series into multiple layers, each representing a different time scale. It does this by repeatedly smoothing the data while spacing the filter farther apart at each step, without downsampling the signal.

The result is a set of detail layers (short-term to long-term) plus a final smooth baseline. By recombining selected layers, you can emphasize noise, structure, or long-term movement — depending on what you want to study.

In other words, you define the underlying structure of the market and then decompose that structure into layers of different frequencies. If you want to emphasize noise, you limit the smoothing. If you want to emphasize trend, you add more layers. Many indicators require you to constantly adjust lookback lengths to achieve smoother results, but this approach—much like an audio equalizer—only requires adding or removing layers. That alone is an extremely nice feature.

What caught my attention wasn’t that the indicator failed—it was that the code itself clearly had more depth than how it was being used. There were multiple inputs, multiple layers, and multiple outputs, yet only a single switch was being flipped. That mismatch—between the richness of the code and the simplicity of its use—is what made me start pulling on the thread.

I Knew What the Code Was Doing — But Not What It Was

I understood the mechanics.
Repeated smoothing.
Differences between layers.
A clean reconstruction.

But the script was labeled with terms like wavelet and à trous — language most traders (myself included) don’t use day-to-day. The variable names didn’t help either. Everything technically worked, but nothing explained itself.

This wasn’t an exotic math problem.
It was a communication problem.

So I did what most of us do now when we want clarity: I brought AI into the conversation.

Using AI to Understand — Not to Predict

This is important.

I didn’t ask AI to:

  • optimize anything
  • generate a strategy
  • predict markets

I asked it questions I’d normally ask another developer:

  • What is this code actually doing conceptually?
  • Why does the reconstruction work so cleanly?
  • What is changing when different layers are included or excluded?

The first pass gave me structure.
The second pass gave me language.
The third pass gave me something unexpected: metaphors.

Not all of them worked.

When the Right Metaphor Finally Clicked

AI proposed several ways to think about the indicator — mechanical, mathematical, spatial. Some were accurate, but none quite matched how traders experience charts.

Then we circled around sound.

Filtering.
Layers.
Mixing.

That’s when it clicked.

This indicator wasn’t a “trend line.”
It was an equalizer.

Once I framed it that way, everything snapped into place:

  • The slowest layer wasn’t “trend” — it was the bass line
  • Faster layers weren’t noise — they were texture and rhythm
  • Turning components on and off wasn’t optimization — it was listening choice

The metaphor wasn’t decorative.
It became a tool.

From Cryptic Code to Wavelet Analog

With that framing, I cleaned up the code:

  • Renamed variables so they described what they felt like, not how they were computed
  • Grouped logic around intention, not math
  • Made the behavior readable on a chart

What emerged from this process was Wavelet Analog — an indicator that separates price into layers and lets the trader decide which ones to listen to.

So why describe it as analog?

When I first saw six True/False toggles as inputs, my refactoring instincts immediately kicked in. Why six switches? Why not a single input that lets the user pick a number from one to six and choose a single layer? After all, that’s how we usually simplify interfaces. And that’s exactly how my client was using it — with only UseD1 enabled.

That kind of refactor is clean. It’s digital. It reduces complexity.

But it also misses the big picture.

The original design wasn’t meant to select one layer — it was meant to let the user combine layers. One switch, or several. Fine detail alone, coarse structure alone, or anything in between. Layers could be stacked, blended, and cascaded.

That’s where the analog idea comes in. Instead of choosing a single, precise value—a digital decision—the original script let the trader feather the signal. Think of it like adjusting bands on an audio equalizer: you’re not flipping one switch on and everything else off; you’re shaping the mix.

Once I saw it that way, the six toggles stopped looking awkward and started looking intentional. Intentional—but also redundant. Imagine having to flip six separate switches on or off, in various combinations, all while keeping in mind that you may want to optimize how those layers interact. You could encode the toggles as 0s and 1s—false and true—and that would indeed open the door to optimization. It works, but it’s still clunky. Zeros and ones everywhere.

That naturally raises the question: can this be reduced to a simple binary pattern? If you’re familiar with my Pattern Smasher work, you already know the answer is yes—binary representations are compact, expressive, and highly optimizable. It’s an excellent approach. The downside is that it requires the user (and any downstream logic) to understand base-2 numbering, which isn’t a reasonable expectation for most traders.

So instead, we sidestep the binary scaffolding while keeping its power by leaning on EasyLanguage’s string-handling capabilities. Rather than six individual toggles, we represent them as a single string of six characters, each a 0 or 1. For example:

“110000”

This string simply means UseD1 and UseD2 are active. You don’t need to know—or care—what the decimal value of “110000” is. A 1 turns on the corresponding UseDX; a 0 turns it off. When more than one 1 appears in the string, the layers are cascaded automatically.

Same analog flexibility. Cleaner interface. Far less friction.

Parsing a string with one simple function: MidStr

Having a nice library of string manipulation functions enforces my prior post on why Quant languages should use the EasyLanguage model. I can easily extract the character at each location located in the string. The first location is represented by one and the last by six.

    if MidStr(Switchboard, 1, 1) = "1" then MasterOut = MasterOut   Band1_Hiss;
if MidStr(Switchboard, 2, 1) = "1" then MasterOut = MasterOut Band2_Treble;
if MidStr(Switchboard, 3, 1) = "1" then MasterOut = MasterOut Band3_Presence;
if MidStr(Switchboard, 4, 1) = "1" then MasterOut = MasterOut Band4_Mids;
if MidStr(Switchboard, 5, 1) = "1" then MasterOut = MasterOut Band5_Body;
if MidStr(Switchboard, 6, 1) = "1" then MasterOut = MasterOut Band6_Bass;
Using MidString to parse a String

Here the string is represented by Switchboard and is decomposed by the MidStr function. This function expects two arguments – starting postion and the number of characters to gather. As you can see by the code, we are stepping through each character in the string and extracting that particular character. Based on its value, we integrate that particular layer into the final calculation.

Same math.
Same structure.
Completely different understanding.

One Indicator, Multiple Trading Tempos

Here’s where the iceberg metaphor really matters.

The client had been trading the tip:

  • One layer
  • One tempo
  • One interpretation

But underneath that single line were multiple valid ways to trade:

  • Scalpers listening to fast detail
  • Swing traders listening to rhythm and rotation
  • Trend followers locking onto structure

Nothing was added.
Nothing was optimized.
We just stopped pretending the indicator was simpler than it really was.

The Real Lesson (and Why AI Matters Here)

AI didn’t invent anything in this process.

What it did was help surface alternative ways of thinking — some useful, some not — until the right framing emerged. The insight came from the interaction, not the output.

That’s the part of AI that excites me most for traders.

Not as a signal generator.
Not as a replacement for thinking.

But as a tool for understanding what we already have.

Closing Thought and Nex Steps

Most traders inherit indicators they never fully unpack.
They trade what’s visible and ignore what’s underneath.

Sometimes, the most valuable work isn’t finding something new —
it’s learning how to see what’s already there.

That’s what this exercise reminded me.

In the next installment, i will unpack this intriguing indicator and turn it into a complete trading system.

Final Code and Enhancements

{-------------------------------------------------------------------------------
Indicator Name: Wavelet Analog (Equalizer Naming)

Switchboard: "1 2 3 4 5 6"
1: Fine Grain Detail --- 6: Coarse Structural Detail
The Anchor (SubBass) is the permanent baseline track.
-------------------------------------------------------------------------------}
Inputs:
Switchboard("000000") [DisplayName = "Analog Switches (Bands 1-6)"],
ViewMode(0) [DisplayName = "0:Signal View, 1:Difference"];

Vars:
// "Tone Curve" Weights (fixed EQ kernel)
Tone0(0.375), Tone1(0.25), Tone2(0.0625),

// Tracks: Raw progressively stronger low-pass versions
RawTrack(0), LP1(0), LP2(0), LP3(0), LP4(0), LP5(0), SubBass(0),

// EQ Bands (detail layers)
Band1_Hiss(0), // Ultra-high: micro flicker / "hiss"
Band2_Treble(0),
Band3_Presence(0),
Band4_Mids(0),
Band5_Body(0),
Band6_Bass(0), // Low: macro structure / "bass"

// Outputs
MasterOut(0), Anchor(0), CutSignal(0);

Vars: j(0), ValidSwitches(True);

// --- Step 1: The "Analog Console" Smoothing Ladder ---
RawTrack = Close;
LP1 = Tone0*RawTrack 2*Tone1*RawTrack[1] 2*Tone2*RawTrack[2];
LP2 = Tone0*LP1 2*Tone1*LP1[2] 2*Tone2*LP1[4];
LP3 = Tone0*LP2 2*Tone1*LP2[4] 2*Tone2*LP2[8];
LP4 = Tone0*LP3 2*Tone1*LP3[8] 2*Tone2*LP3[16];
LP5 = Tone0*LP4 2*Tone1*LP4[16] 2*Tone2*LP4[32];
SubBass = Tone0*LP5 2*Tone1*LP5[32] 2*Tone2*LP5[64];

// --- Step 2: Split into EQ Bands (details between tracks) ---
Band1_Hiss = RawTrack - LP1;
Band2_Treble = LP1 - LP2;
Band3_Presence = LP2 - LP3;
Band4_Mids = LP3 - LP4;
Band5_Body = LP4 - LP5;
Band6_Bass = LP5 - SubBass;

// --- Step 3: Master bus anchor switchboard mix ---
Anchor = SubBass;
MasterOut = Anchor;

// --- Validate the switchboard ONCE ---
once
begin
if StrLen(Switchboard) > 6 then
ValidSwitches = false
else
begin
for j = 1 to 6
begin
if MidStr(Switchboard, j, 1) <> "0" and MidStr(Switchboard, j, 1) <> "1" then
begin
ValidSwitches = false;
break;
end;
end;
end;
end;

if ValidSwitches then
begin
if MidStr(Switchboard, 1, 1) = "1" then MasterOut = MasterOut Band1_Hiss;
if MidStr(Switchboard, 2, 1) = "1" then MasterOut = MasterOut Band2_Treble;
if MidStr(Switchboard, 3, 1) = "1" then MasterOut = MasterOut Band3_Presence;
if MidStr(Switchboard, 4, 1) = "1" then MasterOut = MasterOut Band4_Mids;
if MidStr(Switchboard, 5, 1) = "1" then MasterOut = MasterOut Band5_Body;
if MidStr(Switchboard, 6, 1) = "1" then MasterOut = MasterOut Band6_Bass;

// --- Step 4: What you CUT from the mix ---
CutSignal = Close - MasterOut;

// --- Step 5: Plotting ---
if CurrentBar > 130 then
begin
if ViewMode = 0 then
begin
Plot1(MasterOut, "MasterOut", White, default, 1);
Plot2(Anchor, "Anchor", DarkGreen, default, 1);
end
else
begin
Plot3(CutSignal, "CutSignal", Red, default, 1);
Plot4(0, "Zero", LightGray);
end;
end;
end;
Wavelet Analog

Examples

Three charts are shown with three different presets.

Plotting 2 Scales in TradeStation

You can’t plot a single multiple output indicator with different scales in the same chart in TradesStation (well not easily). You have to plot either one or the other and this can be accomplished by using a plot toggle. Here is the toggle in EasyLanguage.

If ViewMode = 0 then
begin
Plot1(MasterOut, "MasterOut", White);
Plot2(Anchor, "Anchor", DarkGreen);
end
else
begin
Plot3(CutSignal, "CutLine");
Plot4(0, "Zero");
Different Plot Scale Toggle

Why EasyLanguage Should Be the Blueprint for Quant Languages

When I first ran into EasyLanguage, I didn’t take it seriously.

I come to this with a bias: I’m a lifelong systems programmer, and I helped build a trading platform the old-fashioned way.

Years ago I co-created Excalibur, a Fortran-based trading and backtesting engine. In that world, everything is explicit. If you want rolling windows, you build them. If you want indicator “memory,” you write the storage. If you want speed, you earn it with careful code and a lot of scaffolding.

So when I first encountered EasyLanguage, I didn’t take it seriously. It looked too simple—almost like “training wheels” for people who didn’t want to program.

Then time did what time always does: it changed my opinion.

After decades of building systems, libraries, and tooling—and watching how often good ideas get buried under boilerplate—I started to see EasyLanguage differently. It’s not “cute.” It’s a purpose-built quant DSL with one superpower that most general-purpose languages don’t give you for free:

Native time-series semantics.

In other words, EasyLanguage starts you in a world where “one bar ago” is normal, rolling windows are natural, and stateful indicators can be expressed as simple algebra. If I were building a quant language today, I’d copy that blueprint: human-readable rules plus time-series semantics baked into the language.

To explain why, I like a metaphor: Flatland versus Spaceland.


Flatland versus Spaceland

Flatland is where most beginners start—especially if they come from C, Python, or Excel. In Flatland, a variable is simply “a value right now.” The world feels perfectly sensible, but it’s missing something. The moment you need yesterday, or the last 30 bars, you’re forced into extra machinery: arrays, indexing, loops, buffers, bookkeeping.

Then comes the EasyLanguage moment—the part that feels like science fiction the first time you truly get it.

In Spaceland, the “missing dimension” exists: time. Variables don’t just have a current value; they have a built-in past. Close naturally includes Close[1]. Your own variables remember prior values. Rolling functions like Average() and RSI() aren’t special libraries—they’re native operations on values that already extend through time.

So the breakthrough isn’t learning a new function. It’s realizing you’ve been thinking on a plane, and EasyLanguage is operating in a world with one more dimension.

(If you’ve never read Edwin Abbott’s novella Flatland, no worries—this post borrows the idea, not the geometry. Abbott’s missing dimension is spatial; mine is time.)


Scalar versus series (without the esoterica)

In most general-purpose languages, a variable is a scalar: one value right now. If you want the last 30 values, you must store them and manage the indexing yourself.

In EasyLanguage, variables behave like series: the current value plus an implicit history. That’s why these feel natural:

If Close > Close[1] then ...
value1 = Average( (High + Low) / 2, 30 )
value2 = Average( RSI(Close, 14), 30 )


The “series prep” tax in Python

EasyLanguage can do this in one line because it can treat the expression (High + Low)/2 as a time series automatically:

MidPointAvg = Average((High + Low)/2, 30)

In Python—even if high and low already exist as lists—you still have to manufacture the series you want to average. Before you can average midpoints, you must first create a new midpoint list for the last lookBack bars:

# Assume:
# - high and low are lists (oldest -> newest)
# - currentBar is the index of the bar we're on "right now"
# - lookBack is how many bars we want to include
lookBack = 30

# Step 1) Build a NEW series (midpoint) for the last lookBack bars
midpointSeries = []

for barsAgo in range(lookBack):
bar = currentBar - barsAgo
if bar < 0:
break # ran out of history

midpoint = (high[bar] + low[bar]) / 2.0
midpointSeries.append(midpoint)

# Step 2) Now we can feed that newly created series to the generic average
mid_avg = average(midpointSeries)

Same goal. Totally different assumptions.

  • Python is scalar-first: you build the series.

  • EasyLanguage is series-first: the platform quietly supplies the time dimension.

Why EasyLanguage is a great engineering-to-trading bridge

If you’re coming from DSP or any engineering intensive discipline, you already know what you want to test: filters with memory, rolling statistics, trigger lines, crossings, parameter tweaks you can validate visually. The last thing you want is to burn weeks building infrastructure—buffers, indexing rules, warm-up handling—before you ever test the idea. EasyLanguage skips that entire tax. It starts you in Spaceland: time-series semantics are native, history is built in, and writing a filter looks like writing the math.

The mind-meld example (Ehlers High Pass)

Here’s a (simplified) EasyLanguage high-pass filter. From a programmer’s perspective, it’s mind-bending because it reads like algebra, but behaves like a stateful filter:


//Ehlers HighPass function - from his website
//https://www.mesasoftware.com/papers/

Inputs: Price(NumericSeries), Period(NumericSimple);


Vars: a1(0),
b1(0),
c1(0),
c2(0),
c3(0);

a1 = ExpValue(-1.414*3.14159 / Period);
b1 = 2*a1*Cosine(1.414*180 / Period);
c2 = b1; c3 = -a1*a1;
c1 = (1 + c2 - c3) / 4;
If CurrentBar >= 4 Then
EhlersHighPass = c1*(Price - 2*Price[1] + Price[2]) +
c2*EhlersHighPass[1] + c3*EhlersHighPass[2];
If CurrentBar < 4 Then
EhlersHighPass = 0;

The “magic” is here:

c2*EhlersHighPass[1] + c3*EhlersHighPass[2]

In computer-science terms, this is not “recursion” (no function calls itself). In signal-processing terms, it’s feedback: today’s output uses prior output. EasyLanguage makes that look effortless because the platform runs once per bar and preserves the prior values automatically.


Brain Meld Squared

If you’re a programmer, you know what kind of scaffolding this should require:

value1 = EhlersHighPass(Close, 14);
value2 = EhlersHighPass(Close, 28);

Those are two independent filters. Each one needs its own private memory—its own prior outputs—yet EasyLanguage gives you two clean calls. No objects. No buffers. No state management. It just works.


Ultra special: chaining filters

And if you can do that, you can do this:

value1 = EhlersHighPass(EhlersHighPass(Close, 14), 20);

That single line implies two live filter instances with separate state, running bar-by-bar, with the outer filter consuming the inner filter’s output as a time series. That’s series semantics and object-like behavior showing up at the same time—without the programmer ever building the scaffolding.


Closing thought

If I were designing a quant language today, I’d copy EasyLanguage’s blueprint: human-readable rules plus native time-series semantics. It lowers the barrier for non-programmers and removes the infrastructure tax for engineers who just want to test ideas quickly—especially the DSP-to-trading crowd.

Mean Reversion in 5 lines of code:

input: mDay(0),nDay(1),stopLossAmt$(1750),profitTargAmt$(5000),tradeLife(5);

if close > average(close,100) and close mDay days ago < close mDay + nDay days ago then
buy next bar at market;
if barsSinceEntry > tradeLife then sell next bar at open;

setStopLoss(stopLossAmt$);
setProfitTarget(profitTargAmt$);
Could be written as 5 lines, right?

Results

Simple EasyLanguage Code

This POINT is AVERAGE of 66 Values

All points that start with the address 2, 4 were all positive.  There were 66 observations.

66 addresses @ MDAY = 2 AND NDAY = 4

Splicing away all but MDAY = 2!  Big BLOBS.  Some were good (green) and some were bad (purple!)

Volumetric SLICED @ MDAY = 2

Magnifying the blobs – they break away into 6 distinct values – 4 dimensions in 3D Space.

Entering the MATRIX 4 Parameters plotted in 3 Dimensional

These graphs demonstrate a certain level of robustness.   As long as we stay in a bull market to a certain degree.

Backtesting with [Trade Station,Python,AmiBroker, Excel]. Intended for informational and educational purposes only!