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:
- 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.
- 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.
- 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.
- 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.
- 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:
- 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
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
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.
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.
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.
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.
Discover more from George Pruitt
Subscribe to get the latest posts sent to your email.







