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.

 


Discover more from George Pruitt

Subscribe to get the latest posts sent to your email.

Leave a Reply