Evaluating POGL Trades Against Buy-and-Hold Across Eras
Should you trade in and out of positions, or should you buy and hold? If an active strategy truly offers an edge, it shouldn’t just rely on theory—it must be backed by quantitative models evaluated across multiple decades and a diverse basket of tickers. Welcome to the Security Trading Analytics blog, where we present and backtest active position management models to let the empirical data speak for themselves.
This is the third post in a multi-part series on tracking ticker performance over multiple decades across three different regimes. The regimes are labelled: expansion, contraction, and shock. During an expansion regime, ticker prices are generally rising. In contrast, contraction and shock regimes denote periods of generally declining prices. Contraction regimes have durations of months or years, and shock regimes have durations of days or months.
- The first post in this series presents spreadsheet-based techniques for deriving historical prices for six tickers going back as far as the early 1970s through July 31, 2026. Then, the spreadsheet derived historical prices are deposited in a SQL Server database for downstream processing and backtesting the Proper Order Gain Lock-in (POGL) model.
- The second post in the series examines when different era-regime pairs start and stop, and if price trends during regimes follow the pattern suggested by their name.
- This third post reviews and compares the POGL model with a buy-and-hold investment strategy across the regimes defined and validated in the second post. In this way, the current post attempts to answer the question asked at the beginning of this introduction.
The Eras and Regimes Tracked in this Post
The following table lists 21 eras defined for use in the multi-part series to which this post belongs. Each era has an associated regime. The eras appear in chronological order (sequence 1 through 21), along with start and end dates and durations of each era.
- The first era is associated with the 1973 OPEC oil embargo.
- The fourteenth era reflects the impact of the COVID-19 pandemic.
- The twentieth era reflects market disruptions occurring during the intensive 2026 U.S./Israel bombing of Iran.
- The twenty-first era is open as of when the following table was compiled. Notice the duration of this era is 3.8+ months. This is to signify that the era was not closed when the table was compiled. Because an open era lacks a settled conclusion, including it would distort duration-normalized metrics. Therefore, while 21 eras are itemized in the following table, only the 20 closed eras (Eras 1–20) are evaluated in this post.
The POGL Model Definition
The POGL model is based on a trend-following system. The implementation relies on two distinct criteria sets — one set for entering a trade and a second set for exiting a trade.
- Entry Goal: Enter a trade when trend indicators signal a period of potential price growth.
- Exit Goal: Exit a trade when a rising price trend reverses to close strictly below an active trailing stop floor, or when an initial position closes strictly below the maximum risk floor.
The signal for a trade entry depends on a proper order
relationship between the current trading day's close price and a pair of
exponential moving averages: a shorter-period EMA and a longer-period EMA. The
proper order relationship requires the close price to be greater than the
shorter-period EMA value, and the shorter-period EMA value to be greater than
the longer-period EMA value. For the baseline implementation in this post, the
short and long periods are set to 21 days (ema_21) and 63 days (ema_63), respectively.
Position entry executes on the first trading session after a
buy signal (T+1). The opening price of that next session serves as the initial
entry price for the trade.
At trade entry, two dynamic boundary prices are established:
- Ratchet Base Price: Set equal to the entry open price. Multiplying this base price by 1.20 establishes the initial upper boundary target (20 percent above entry).
- Stop Loss Price (Lower Boundary): Anchored 10 percent below the entry open price, establishing the initial risk floor for the trade.
When a trading day's close price matches or exceeds the active upper boundary target, the dynamic profit ratchet triggers:
- The ratchet base price advances by 20 percent relative to the former base price.
- The stop loss price (lower boundary) steps up to 10 percent below the newly advanced ratchet base price.
A trade is exited when the session close price falls
strictly below the active stop loss price. This exit action functions to either
lock in accumulated prior gains relative to the stepped-up stop-loss floor or
cut initial trade losses once the close price drops past the 10 percent risk
trigger floor used in this post.
The Backtest POGL TSQL Script
The backtest for the POGL model depends on a daily trading log and a POGL trade ledger that is populated based on POGL model rules described in the preceding section. The following TSQL script shows how to run the model in four steps.
- Step 1 creates a the dbo.daily_trading_log table to store the daily trade data, which consists of ticker, trade date, daily open and close values, as well as two EMA value sets. Logid is a surrogate key for the table.
- Step2 populates the dbo.daily_trading_log table from dbo.stockhistory_with_emas table.
- Step 3 creates the dbo.pogl_trade_ledger table, which has a separate row for each trade based on POGL model rules.
- Step 4 is the POGL backtest engine for populating the ledger table based on the contents of the dbo.daily_trading_log table and the POGL model rules. This step contains a pair of nested cursors.
- The outer ticker_cursor iterates through the six tickers tracked in this post (CAT, JPM, LLY, MSFT, NVDA, SHW).
- The inner open price cursor iterates through the trading days to implement the POGL rules and populate the ledger table with a succession of trades.
-- step 1: initialize staging table for ticker price
histories and moving averages
use securitytradinganalytics;
go
drop table if exists dbo.daily_trading_log;
go
create table dbo.daily_trading_log (
[log_id] int identity(1,1) not null,
[ticker] varchar(10) not null,
[trade_date] date not null,
[open] numeric(10,4) not null,
[close] numeric(10,4) not null,
[ema_21] numeric(10,4) null,
[ema_63] numeric(10,4) null
constraint
[PK_daily_trading_log] primary key clustered ([log_id] asc)
);
go
-- step 2: populate staging log from repository
insert into dbo.daily_trading_log (
[ticker],
[trade_date],
[open],
[close],
[ema_21],
[ema_63]
)
select
[ticker],
[date] as
[trade_date],
[open],
[close],
[ema_21],
[ema_63]
from dbo.stockhistory_with_emas
where [ticker] in ('CAT', 'JPM', 'LLY', 'MSFT', 'NVDA',
'SHW')
order by [ticker], [date] asc;
go
-- step 3: initialize trade ledger target table
drop table if exists dbo.pogl_trade_ledger;
go
create table dbo.pogl_trade_ledger (
[trade_id] int identity(1,1) not null,
[ticker] varchar(10) not null,
[entry_date] date not null,
[entry_price]
numeric(10,4) not null,
[exit_date] date null,
[exit_price]
numeric(10,4) null,
[holding_period_bars] int null,
[realized_return_pct]
numeric(10,4) null,
constraint
[PK_pogl_trade_ledger] primary key clustered ([trade_id] asc)
);
go
-- step 4: execute backtest engine across six tickers
declare @ticker varchar(10);
declare ticker_cursor cursor local fast_forward for
select distinct
[ticker]
from
dbo.daily_trading_log
where [ticker]
in ('CAT', 'JPM', 'LLY', 'MSFT', 'NVDA', 'SHW')
order by
[ticker];
open ticker_cursor;
fetch next from ticker_cursor into @ticker;
while @@fetch_status = 0
begin
declare
@trade_active bit = 0;
declare
@entry_date date;
declare
@entry_price numeric(10,4);
declare
@stop_loss_price numeric(10,4);
declare
@ratchet_base_price numeric(10,4);
declare
@holding_bars int = 0;
declare
@curr_date date;
declare
@curr_open numeric(10,4);
declare
@curr_close numeric(10,4);
declare
@curr_ema21 numeric(10,4);
declare
@curr_ema63 numeric(10,4);
declare
@entry_signal_pending bit = 0;
declare
price_cursor cursor local fast_forward for
select
[trade_date],
[open],
[close],
[ema_21],
[ema_63]
from
dbo.daily_trading_log
where
[ticker] = @ticker
order by
[trade_date] asc;
open
price_cursor;
fetch next from
price_cursor into
@curr_date,
@curr_open, @curr_close, @curr_ema21, @curr_ema63;
while
@@fetch_status = 0
begin
-- 1.
execute pending entry at t+1 open
if
@entry_signal_pending = 1 and @trade_active = 0
begin
set
@trade_active = 1;
set
@entry_date = @curr_date;
set
@entry_price = @curr_open;
set
@ratchet_base_price = @curr_open;
set
@stop_loss_price = round(@curr_open * 0.90, 4);
set
@holding_bars = 1;
set
@entry_signal_pending = 0;
end
else if
@trade_active = 1
begin
set
@holding_bars = @holding_bars + 1;
-- 2.
evaluate trailing stop exit on session close
if
@curr_close < @stop_loss_price
begin
insert into dbo.pogl_trade_ledger (
[ticker],
[entry_date],
[entry_price],
[exit_date],
[exit_price],
[holding_period_bars],
[realized_return_pct]
)
values (
@ticker,
@entry_date,
@entry_price,
@curr_date,
@curr_close,
@holding_bars,
round(((@curr_close - @entry_price) / @entry_price) * 100.0, 4)
);
set
@trade_active = 0;
set
@entry_date = null;
set
@entry_price = null;
set
@stop_loss_price = null;
set
@ratchet_base_price = null;
set
@holding_bars = 0;
end
else
begin
--
3. evaluate 20% dynamic profit ratchet on session close
if
@curr_close >= round(@ratchet_base_price * 1.20, 4)
begin
set @ratchet_base_price = round(@ratchet_base_price * 1.20, 4);
set @stop_loss_price = round(@ratchet_base_price * 0.90, 4);
end
end
end
-- 4.
evaluate entry signal on session close
if
@trade_active = 0 and @entry_signal_pending = 0
begin
if
@curr_ema21 > @curr_ema63 and @curr_close > @curr_ema21
begin
set
@entry_signal_pending = 1;
end
end
fetch next
from price_cursor into
@curr_date, @curr_open, @curr_close, @curr_ema21, @curr_ema63;
end;
close
price_cursor;
deallocate
price_cursor;
fetch next from
ticker_cursor into @ticker;
end;
close ticker_cursor;
deallocate ticker_cursor;
go
Contrasting POGL Trade Performance Versus Buy-and-Hold Investing
So now you have learned about the six tickers tracked in this post from the early 1970s through July 31, 2026. You also received an introduction to the defining properties of the POGL model as well as a TSQL script, which automates the choosing of entry and exit for a succession of trades based price action for individual tickers. This section presents selected results and commentary that compares the POGL model to a buy-and-hold investment strategy for the six tickers tracked over the 20 evaluation eras tracked in this post.
The following screenshot compares the POGL model to a buy-and-hold strategy based on the percent return mean for each of the six tickers individually as well as the collection of all six tickers. These results include ticker-era pairs where POGL did not specify at least one trade. On the other hand, the buy-and-hold strategy was in the market for all tick-era pairs even when did not specify any trades for a ticker-era pair. The far right column displays percentage spread values between the pogl_era_pct_return_mean column values less the bh_overall_pct_return_mean. The spread values clearly indicate that the buy-and-hold strategy massively outperforms the POGL model for all individual tickers as well as for the set of all tickers.
JPM is widely regarded as a premiere, well-capitalized financial ticker whose price movement largely reflects broad market volatility. Additionally, JPM returns in this post a bh_overall_pct_return_mean value of 58.42 %. However, buy-and-hold investors with JPM shares would have experienced a bumpy ride on their way to that 58.42 % across the 20 eras tracked in this post.
The following result set shows the bh_overall_pct_return values by era for the JPM ticker. There are a total of 7 of 20 eras with a bear market decline of 20% or more. Furthermore, 2 of those 7 declines (-71.06% and -66.11%) could easily encourage some investors to dispose of their JPM shares at depressed prices before reaching their 58.42% for holding their shares across the 20 era set. This is an example of one type of challenge that buy-and-hold investors must face.
This section’s last screenshot compares the POGL model to a buy-and-hold strategy based on the number of trading days during which capital was at risk. By definition, the buy-and-hold strategy was available for all trading days in each era. In contrast, the POGL model had capital at risk for the duration of each trade. If no trades were specified in an era, then no capital was at risk in that era. On the other hand, trade accounting for the POGL model can span days from multiple eras when its entry date is in one era, but its exit date is in another era. Basically, this second approach compares the buy-and-hold strategy to the POGL model on how efficiently capital is used when each approach is in the market.
The buy-and-hold strategy still outperforms across the full set of all tickers, but on an individual ticker basis the POGL model has a more favorable spread for 3 of 6 tickers. Because the POGL model returned inferior performance is 6 out of 6 individual ticker comparisons based on the percent return mean for individual tickers, this second set of comparisons shows the POGL model more efficient at returning relatively improved performance for the JPM, MSFT, and SHW tickers.
Concluding Comments
Based on the quantitative backtest data across 20 market regimes and 374 POGL trade lifecycles, the performance metrics for the six tickers tracked over the evaluation timeframe indicate that a buy-and-hold strategy outperformed POGL, which is generally representative of other trend-following models. However, before applying this conclusion to your own trading practice, ask whether the results reported here apply to the range of stocks you consider as investment candidates. Traders utilizing an active trading model like POGL will likely not restrict their holdings to stocks with large market capitalizations and multi-decade resilience, such as the six tickers tracked in this post.
Choosing an appropriate sample of securities for evaluating investment strategies is a very challenging assignment. For example, I recently read from the Ask Gemini feature in my Chrome browser that between 15 and 50 companies per year, depending on the era, are dropped from the S&P 500 index. Therefore, it is likely that well over 100 companies out of the S&P 500 members did not survive the timeframe of the stocks included in this study. These historical facts are presented to encourage you to carefully consider the stocks tracked in your own investment evaluation studies.
Another major consideration for investment strategy evaluation projects is on which indicators do you base your investment strategies. The POGL model relies on a combination of exponential moving averages, close prices, dynamic stop and limit orders, and daily price action. Some automated investment strategies have simpler mechanisms, such as just moving average crossovers, Relative Strength reversals, or MACD indicator crossovers. To definitively determine whether a buy-and-hold investment strategy is better or worse than automated trading models generally requires considering a very wide range of models.
This blog views automated models for selecting entry and exit dates for security trades as a fruitful endeavor. This post is our initial comparison of the POGL model versus a buy-and-hold strategy. The POGL model can be tested with other parameters as well as against other benchmarks besides the six-ticker collection used in this post. In the near-term future, look forward to evaluations of Heikin-Ashi candles as a basis for investment models.
Appendix A: Code for the first image in the Contrasting POGL Trade Performance Versus Buy-and-Hold Investing Section
Appendix B: Code for the second image in the Contrasting POGL Trade
Performance Versus Buy-and-Hold Investing Section
-- 20 JPM-era pairs
select
ticker,
era_id,
era_name,
regime,
overall_pct_return as bh_overall_pct_return
from dbo.regime_ticker_metrics
where ticker = 'JPM'
and era_id
<> 21 -- keep consistent
with the series' open-era exclusion
order by era_id;
Appendix C: Code for the third image in the Contrasting POGL Trade
Performance Versus Buy-and-Hold Investing Section
--
============================================================================
-- POGL vs. Buy-and-Hold -- Capital-at-Risk Adjusted
Return by Era
--
============================================================================
-- Part 2 of a two-script pair supporting the
"Contrasting POGL Trade
-- Performance Versus Buy-and-Hold Investing" post
section. Companion to
-- pogl_vs_bh_mean_pct_return.sql, which answers
"which approach
-- accumulated more return over the full era." This
script asks a
-- different question: when POGL's capital was actually
deployed, how did
-- its rate of return compare to buy-and-hold's
continuous exposure?
--
-- WHY EACH SIDE GETS ITS OWN DENOMINATOR:
-- Buy-and-hold is invested for an era's entire span, so
that era's own
-- trading_days figure IS buy-and-hold's true
days-at-risk -- dividing its
-- era return by trading_days gives a real per-day rate
for buy-and-hold.
--
-- POGL is not the same shape. A closed trade's return is
credited
-- entirely to its EXIT era (realization accounting --
see the attribution
-- rule below), but the trade may have been open far
longer than that
-- era's own span. Confirmed example from this series: a
trade with 1,103
-- days of exposure (holding_period_bars) closing inside
a 23-trading-day
-- era. Dividing that trade's return by the exit era's 23
trading days
-- would overstate its daily rate roughly 48x relative to
dividing by the
-- 1,103 days the capital was actually at risk. So POGL's
rate here
-- divides by pogl_exposure_days -- the summed
holding_period_bars of
-- whichever trades closed in that era -- not by the
era's own
-- trading_days.
--
-- This keeps both sides on realization accounting
throughout (no
-- mark-to-market, no reinvestment/compounding assumption
across trades)
-- while giving each side a denominator that reflects its
own actual days
-- at risk, not a denominator borrowed from the other
strategy.
--
-- POPULATION: unlike the mean-pct-return script, this
comparison can only
-- be computed for eras where POGL held at least one
position --
-- pogl_exposure_days is 0 in a zero-trade era, and a
return-per-day-at-
-- risk figure is undefined when no capital was at risk.
So this script
-- uses active_era_count (matching the convention from
the earlier
-- active-eras-only rollup), not eligible_era_count --
the sample sizes
-- here and in the mean-pct-return table will differ, and
that's expected,
-- not an error.
--
-- Same table sources and exclusions as prior scripts in
this series:
--
dbo.market_regime_ledger, dbo.regime_ticker_metrics,
dbo.pogl_trade_ledger
-- Era 21
excluded; MSFT era 4 excluded; NVDA era 7 excluded (IPO partial eras)
--
============================================================================
use SecurityTradingAnalytics;
go
--
---------------------------------------------------------------------------
-- Step 1: attribute each closed POGL trade to its exit
era, aggregate to
-- one row per (era_id, ticker) -- summed realized return
AND summed
-- exposure days (holding_period_bars) for whatever
trades closed there.
-- Because this sums both a trade's gain and its own
days-held BEFORE
-- dividing, pogl_return_per_exposure_day below (Step 2)
works out to a
-- pooled ratio -- total gain / total days -- rather than
an average of
-- separate per-trade rates, so a quick trade and a
long-held trade
-- closing in the same era aren't treated as
equally-weighted data points.
--
---------------------------------------------------------------------------
if object_id('tempdb..#pogl_era_agg') is not null drop
table #pogl_era_agg;
select
m.era_id,
t.ticker,
count(*) as
pogl_closed_trade_count,
sum(t.realized_return_pct)
as pogl_era_pct_return,
sum(t.holding_period_bars)
as pogl_exposure_days
into #pogl_era_agg
from dbo.pogl_trade_ledger t
inner join dbo.market_regime_ledger m
on t.exit_date
>= m.start_date
and t.exit_date
<= m.end_date
where t.exit_date is not null -- closed trades only
group by m.era_id, t.ticker;
go
--
---------------------------------------------------------------------------
-- Step 2: join to buy-and-hold era metrics, apply the
exclusion list,
-- compute each side's own per-day rate -- bh divided by
the era's
-- trading_days (bh's real exposure), pogl divided by
pogl_exposure_days
-- (pogl's real exposure).
--
---------------------------------------------------------------------------
if object_id('tempdb..#era_comparison') is not null drop
table #era_comparison;
select
rtm.ticker,
rtm.era_id,
rtm.trading_days,
isnull(p.pogl_closed_trade_count, 0)
as pogl_closed_trade_count,
isnull(p.pogl_exposure_days, 0)
as pogl_exposure_days,
cast(rtm.overall_pct_return / nullif(rtm.trading_days, 0) as
numeric(10,4))
as
bh_return_per_trading_day,
cast(isnull(p.pogl_era_pct_return, 0) / nullif(p.pogl_exposure_days, 0)
as numeric(10,4))
as
pogl_return_per_exposure_day
into #era_comparison
from dbo.regime_ticker_metrics rtm
left join #pogl_era_agg p
on p.era_id =
rtm.era_id
and p.ticker =
rtm.ticker
where rtm.era_id <> 21 -- open era,
all tickers
and not
(rtm.ticker = 'MSFT' and rtm.era_id = 4)
-- MSFT IPO partial era
and not
(rtm.ticker = 'NVDA' and rtm.era_id = 7);
-- NVDA IPO partial era
go
--
---------------------------------------------------------------------------
-- Result set 1: per-ticker capital-at-risk adjusted mean
return, active
-- eras only (pogl_closed_trade_count > 0 -- required
for
-- pogl_return_per_exposure_day to be defined; see
population note above).
--
---------------------------------------------------------------------------
select
ticker,
count(*)
as active_era_count,
cast(avg(bh_return_per_trading_day) as numeric(10,4)) as
bh_return_per_trading_day_mean,
cast(avg(pogl_return_per_exposure_day) as numeric(10,4)) as
pogl_return_per_exposure_day_mean,
cast(avg(pogl_return_per_exposure_day) - avg(bh_return_per_trading_day)
as numeric(10,4))
as capital_at_risk_spread
from #era_comparison
where pogl_closed_trade_count > 0
group by ticker
order by ticker;
go
--
---------------------------------------------------------------------------
-- Result set 2: pooled "ALL TICKERS"
capital-at-risk adjusted mean return
-- -- all active era-ticker rows across all six tickers
combined into one
-- set (not an average of the six per-ticker means).
--
---------------------------------------------------------------------------
select
'ALL
TICKERS'
as ticker,
count(*)
as active_era_count,
cast(avg(bh_return_per_trading_day) as numeric(10,4)) as
bh_return_per_trading_day_mean,
cast(avg(pogl_return_per_exposure_day) as numeric(10,4)) as
pogl_return_per_exposure_day_mean,
cast(avg(pogl_return_per_exposure_day) - avg(bh_return_per_trading_day)
as numeric(10,4))
as capital_at_risk_spread
from #era_comparison
where pogl_closed_trade_count > 0;
go
Comments
Post a Comment