An Initial Evaluation of Buy on Proper Order and Sell on Dynamic Stop Loss Orders

A well-constructed buy-sell strategy can help you buy securities when their prices are highly likely to continue rising until they don’t rise any more.  Self-directed security traders sometimes call this a “buy high, sell higher” strategy.  One way of implementing this kind of strategy is to buy a security when its price is rising, and sell the security through a sell order that protects accumulated returns from an imminent reversal or price gradually drifting lower from its entry for a position.  Another critical feature of the strategy is that sell order prices can be dynamic and rise in response to gains in the underlying security’s price.

There are many ways to implement this kind of strategy.  This post implements the buy high element of the strategy based on the relationships between close prices and two or more exponential moving averages (EMAs) with different period lengths.  So long as a security’s close price is greater than the EMA with the shortest period length, and the other EMAs with progressively longer period lengths have successively lower values, the close price and its associated EMAs are said to be in proper order.  When a security’s close price and its associated EMAs no longer have successively declining values for a trading day, then security’s prices are no longer in proper order.

Readers familiar with a structured query language (SQL), such as T-SQL, will likely find this reference to be good one for calculating and learning more about EMAs.  Readers who prefer a chart-based approach with numerical values will likely find this alternative reference more helpful for learning more about EMAs in security trading.  In any event, all code samples within this post for tasks such as implementing the buy-sell model and analyzing model results are in T-SQL.

This post implements the “buy higher” element of buy-sell model with an algorithm implemented by a T-SQL code segment.  The code segment determines when a security is eligible for trading after the algorithm assesses that a security’s close price and two EMAs with different period lengths are in proper order.  Next, another T-SQL code segment implements the “sell higher” model element; a security’s price will no longer be rising at this point.  One consequence of this process is to sell the security and continue searching iteratively until eventually encountering the end of the evaluation period for the buy-sell model.

There are four key sections to this post.

  • This post begins with a more detailed summary of the buy-sell model design.

  • The second post section demonstrates how to process a sample dataset for four tickers from the first trading day in 2023 through the final trading day in February, 2026 as well as how to calculate EMAs with the help of a T-SQL stored procedure documented at MSSQLTips.com.  The  goal of this second section is to provide code samples and a reference that equips readers to process any set of tickers they choose in a similar fashion to those analyzed in this post.  To implement the model, you’ll need source data with open and close values for each ticker across all trading days in your evaluation period.

  • The third section illustrates how to create and populate a trading days log based on the buy-sell model’s algorithm.  The code behind the trading days log reveals the daily price action resulting from a simplified backtest of the buy-sell model with the historical data described in the second section.  Additionally, a trades log is populated with buy and sell dates and prices for each trade.  The trades log is derived from the trading days log. 

  • The fourth section features a brief data analysis of the trades log that highlights selected key benefits of the buy-sell model, including the ability to specify profitable trades from volatile price series through dynamically determined sell dates.

An Overview of the Buy-Sell Model

This section presents an overview for the buy and sell components of the buy‑sell model implemented in this post.  The model tracks price changes across a success of buy-sell cycles.  The model’s buy component is based on open and close prices as well as EMAs for close prices with both 21‑day period lengths (ema_21) and 100‑day period lengths (ema_100).

  •  An open price is the initial price for a security, and a close price is the final price at the end of a trading day.  As simple as these definitions may appear, their determination involves reconciling all buys and sells that occur over the duration of a trading day.

  • The close carries more informational weight than any intraday price — it reflects where buyers and sellers were finally willing to agree after all volatility, news flow, corporate actions, and order flow had been absorbed.  Similarly, the open price must mesh perfectly with post‑market price action from the preceding trading day as well as pre‑market price action for the current trading day.

  • The ema_21 and ema_100 values in this post are the exponentially weighted averages of the close prices across successive trading days.  The weights place the largest relative weight on the close price for the current trading day and progressively smaller weights on successive prior trading days.  Additionally, EMAs are calculated based seed values that reflect the arithmetic average of the first 21 and 100 days in a price series.

The model permits a buy to occur on the day after a proper order for close, ema_21, and ema_100 values.  Recall that a proper order is determined by the following relationship: close > ema_21 > ema_100.

  • The proper order can be observed either on the first day of a price series  or

  • on any subsequent trading day in a price series where close, ema_21, and ema_100 for the preceding trading day are not in proper order.

  • The date and open value on the day after a fresh proper order relationship defines date and open value for a buy.

After the buy price is determined for a buy-sell cycle, the model computes lower and upper boundary price values.  The initial lower boundary price is ninety-seven percent of the buy price, and the initial upper boundary price is six percent more than the buy price.  Additionally, a stop loss order is placed with your brokerage firm at the lower boundary price.  The execution of the stop loss order ends the current buy-sell cycle.  The relationships between boundary values and prices during subsequent trading days within a buy-sell cycle causes one of three outcomes.

  • No Action: So long as the closing price of a trading day remains below the upper boundary value and above the lower boundary value, no action is taken.

  • Step-Up: If the closing price of a trading day rises to or above the current upper boundary value, both the upper and lower boundary values are revised for the start of the next session:

    • A new upper boundary value is set at six percent larger than the previous upper boundary value.

    • The new lower boundary value is adjusted to ninety-seven percent of that same previous upper boundary value, and the stop-loss order is updated accordingly.

  • Exit: If the closing price of a trading day falls to or below the current lower boundary value, the stop-loss order closes the position. This "End-of-Day" sell marks the completion of the buy-sell cycle.

In addition to share exchanges during a regular trading day, security shares can also be bought and sold during the after-hours trading period for the current trading day and the pre-market period before the start of trading during regular trading hours on the next trading day.  Stop loss orders are not typically operational during extended-hour times outside of the normal trading day.  Also, the model is assumed not to be operational during extended-hour times.  As a result, share prices can have gaps relative to upper and lower boundary values amid a buy-sell cycle at the start of regular trading hours on a new trading day.  Price gaps relative to upper and lower boundary values are processed at the start of a new trading day, just like the response to prices at the upper boundary or at the lower boundary values during a trading day.  In addition, stop loss orders should be updated, if required, at the start of a new trading day.

As you can see, the model implements sells exclusively through stop loss orders.  When the price never increases to an upper boundary, this results in an account balance debit.  When the price reaches or exceeds the upper boundary multiple times within a buy-sell cycle, this leads to an account balance credit.  By using an upper boundary value that is twice as far away from the break-even value and by starting a trade from a proper order for close and EMA values, the buy-sell model aims to grow the balance of accounts following its buy and sell prescriptions.


Steps for Compiling a Source Dataset for the Model

As indicated in the preceding section, the dataset for the model presented and evaluated in this post requires open and close prices as well as ema_21 and ema_100 values for a set of tickers.  The four tickers examined in this post are GOOGL, MU, SNDK, and SPY.  This section describes how the data for this post were assembled.  The dataset assembly descriptions in this section are offered as guidelines when you are assembling datasets for testing the model with different tickers and timeframes in your own computing environment.

  •  Before you can implement and evaluate the model, you need to download historical open and close values.  This goal was achieved in a prior post titled “How to Populate a Trading Database with Refinitiv, Excel, and SQL Server”.  This section briefly reviews the prior post to highlight key lessons learned, such as how to download historical prices with Excel’s STOCKHISTORY function in a format that can be saved as csv files that are imported into a SQL Server table.

  • After downloading the historical prices, you next need to calculate the ema_21 and ema_100 values for the tickers you want to model.  You can accomplish this task with the help of a MSSQLTips.com article  titled “Calculate Exponential Moving Average in SQL Server”.  The article presents a T-SQL stored procedure for calculating EMA values with different period lengths, such as ema_21 and ema_100.

  • The final resource presented in this section is a custom T-SQL script that illustrates how to join calculated EMA values with their underlying historical price data into a SQL Server table suitable for running the model described in the preceding section.

The “How to Populate a Trading Database with Refinitiv, Excel, and SQL Server” post illustrates a step-by-step approach for creating and populating a SQL Server table with values from Excel’s STOCKHISTORY function.  This function can readily download historical open, high, low, and close prices to an Excel workbook so that each worksheet tab in the workbook file contains the data for one ticker.  After populating each worksheet with data for one ticker, you can invoke the File Save command to store the contents of each worksheet in a separate csv file.  The post concludes by presenting and showing how to use a T-SQL script for importing all the csv files exported from Excel into a SQL Server table.  This sequence of steps can be adapted to many different trading analytics tasks besides implementing the model that is the focus of this post.

After you populate a SQL Server table, you can compute EMA values for the close values with a stored procedure from the “Calculate Exponential Moving Average in SQL Server” article at MSSQLTips.com.  The article section titled “Stored Procedure for Calculating Emas Based on a SMA Seed Value” shows how to compute EMA values in a way that is common for close price analyses.  The calculation strategy follows guidelines from this widely referenced article at StockCharts.com.

The following custom script in bold font pulls data from multiple sources and populates a destination SQL Server table.  In between the pulling and the populating, the script

  • makes a backup copy of a clean stock history table (dbo.stockhistory_clean) from its prior use,

  • the dbo.stockhistory_clean table is then freshly populated for its current use,

  • excerpts from the dbo.stockhistory_clean are used to populate the dbo.ticker_date_prices table, which is processed by a stored procedure (dbo.usp_calculate_ema_from_sma_seed),

  • the  dbo.usp_calculate_ema_from_sma_seed stored procedure is run twice to compute EMA values – once to return ema_21 values and a second time to return ema_100 values; the ema return values are saved to a tall table named dbo.ticker_ema_history_from_ema_seed,

  • in the final step, the destination table (dbo.stockhistory_with_emas) is populated based on dbo.stockhistory_clean, and two derived tables from dbo.ticker_ema_history_from_ema_seed – one with ema_21 values and another with ema_100 values.

The following script initially sets the default database name to SecurityTradingAnalytics, but you can change the database name to any other name for which you have access so long as the referenced tables and store procedure are available from that database.



 

use SecurityTradingAnalytics

go

 

-- use in coordination with

-- Stored Procedure for Calculating Emas Based on a SMA Seed Value section

-- from Calculate Exponential Moving Average in SQL Server in MSSQLTips.com

-- at https://www.mssqltips.com/sqlservertip/11544/exponential-moving-average-in-sql-server/

 

/*

-- backup dbo.ticker_date_prices

select *

into dbo.original_ticker_date_prices_for_usp_calculate_ema_from_sma_seed

from dbo.ticker_date_prices

*/

 

-- truncate dbo.ticker_date_prices

-- load dbo.stock_history_clean

-- from How to Populate a Trading Database with Refinitiv, Excel, and SQL Server

-- at

truncate table dbo.ticker_date_prices

insert into dbo.ticker_date_prices

select ticker, date, [close]

from [dbo].[stockhistory_clean]

 

 

-- Execute to calculate 21-day and 100-day emas

-- for dbo.ticker_date_prices

-- based on [dbo].[stockhistory_clean]

truncate table dbo.ticker_ema_history_from_sma_seed

exec dbo.usp_calculate_ema_from_sma_seed @period = 21;

exec dbo.usp_calculate_ema_from_sma_seed @period = 100;

 

 

-- optionally display calculated emas for [dbo].[stockhistory_clean]

/*

select *

from dbo.ticker_ema_history_from_sma_seed

order by ticker, period, date

 

-- display 21-day emas

select *

from dbo.ticker_ema_history_from_sma_seed

where period = 21

order by ticker, date

 

-- display 100-day emas

select *

from dbo.ticker_ema_history_from_sma_seed

where period = 100

order by ticker, date

*/

 

-- drop the table if it already exists to allow for re-runs

if object_id('dbo.stockhistory_with_emas', 'u') is not null

drop table dbo.stockhistory_with_emas;

 

-- create the new table by joining the data

-- from dbo.stockhistory_with_emas

-- with data from dbo.stockhistory_clean

select

s.ticker,

s.date,

             s.[open],

s.[close],

ema21.ema as ema_21,

ema100.ema as ema_100

into dbo.stockhistory_with_emas

from dbo.stockhistory_clean s

left join (

select ticker, date, ema

from dbo.ticker_ema_history_from_sma_seed

where period = 21

) ema21

on s.ticker = ema21.ticker

and s.date = ema21.date

left join (

select ticker, date, ema

from dbo.ticker_ema_history_from_sma_seed

where period = 100

) ema100

on s.ticker = ema100.ticker

and s.date = ema100.date;

 

-- optionally verify the results

/*

select * from dbo.stockhistory_with_emas

order by ticker, date desc;

*/

 

-- display dbo.stockhistory_with_emas

select * from dbo.stockhistory_with_emas


Here is what the first 28 rows looks like from the dbo.stockhistory_with_emas table.  The first row has a date column of 2023-01-03, the first trading date in January, 2023.  Notice that the first 20 rows in the ema_21 column are null.  The twenty-first row in that column contains the arithmetic average of the first 21 rows in the close column, which is the seed value for the GOOGL ema_21 time series.  All the GOOGL rows for the ema_100 column in the following screenshot are null because the ema_100 time series does not start until the one hundredth row.

 

  

Here is another screenshot showing what 28 rows looks like that include a before/after view of GOOGL rows for the start of non-null ema_100 column values from the dbo.stockhistory_with_emas table.  The hundredth row shows the seed value for ema_100 values; this value is the arithmetic average of the first 100 GOOGL close column values.  Row 100 in the first row in the result set for which a proper order can be assessed for close, eam_21, and ema_100 column values.



The following screenshot displays the last 28 rows in the result set from the preceding script.  These rows are for the SPY ticker, and the final date column value is 2026-02-27, the last trading date February 2026.  In between the start of the SPY rows with a date value of 2023-01-03 and the end of the GOOGL rows are result set rows for the MU and SNDK tickers.


 

An Overview of the Code for the Daily Trading Log and the Trades Log

This following script initializes the working environment, rebuilds the logging tables, and then processes historical price and EMA data to generate a complete daily activity log and a trades log.  It begins by selecting a default database.  The script then drops and recreates the daily_trading_log and trades_log tables to ensure a fresh, consistent structure for each run.

After the tables are prepared, the script declares all variables required for cursor‑driven processing, including price fields, EMA values, trade state flags, step‑up boundaries, and cumulative return tracking.  The ticker_cursor temporary object iterates through the dataset in the stockhistory_with_emas table in ticker/date order, allowing the script to detect trend conditions, generate buy signals, manage active trades, apply stop‑loss and step‑up logic, and record each day’s trading state based on the logic described in the “An Overview of the Buy-Sell Model” post section.  For example, during a trade, the script updates lower and upper boundaries, logs daily activity, and records completed trades with percentage return and cumulative return.  When a new ticker begins, all trade‑state variables are reset to ensure clean separation between tickers.

At the end of processing, the script outputs the full contents of both daily_trading_log and trades_log, ordered for readability.  These tables provide a complete audit trail of every trading decision, boundary adjustment, and realized return generated by the model.


-- set the default database for the script

use SecurityTradingAnalytics

go

 

-- suppresses display of "rows affected" messages from

-- insert, update, delete, and merge statements

set nocount on;

 

-- 1. Setup Table Structures

if object_id('dbo.daily_trading_log', 'u') is not null

             drop table dbo.daily_trading_log;

go -- This ensures the drop is finished

 

create table dbo.daily_trading_log (

ticker varchar(10),

trade_date date,

[close] decimal(18,4),

    [lower_boundary] decimal(18,4),

    [upper_boundary] decimal(18,4),

in_trade_flag bit,

signal varchar(20)

);

go -- This ensures the table exists before we move on

 

if object_id('dbo.trades_log', 'u') is not null

             drop table dbo.trades_log;

go

            

create table dbo.trades_log (

ticker varchar(10),

buy_date date,

buy_price decimal(18,4),

sell_date date,

sell_price decimal(18,4),

step_ups int,

pct_return decimal(18,4),

cum_pct_return decimal(18,4)

);

go

 

-- 2. Processing Logic

declare @ticker varchar(10), @date date, @open decimal(18,4), @close decimal(18,4),

                             @e21 decimal(18,4), @e100 decimal(18,4);

declare @prev_ticker varchar(10) = '';

declare @prev_e21 decimal(18,4), @prev_e100 decimal(18,4), @prev_close decimal(18,4);

declare @in_trade bit = 0, @buy_price decimal(18,4), @lower decimal(18,4),

                             @upper decimal(18,4), @steps int = 0, @buy_date date;

declare @current_signal varchar(20);

declare @trade_ret decimal(18,4), @cum_ret decimal(18,4) = 0;

 

declare ticker_cursor cursor for

select ticker, date, [open], [close], ema_21, ema_100

from dbo.stockhistory_with_emas order by ticker, date;

 

open ticker_cursor;

fetch next from ticker_cursor into @ticker, @date, @open, @close, @e21, @e100;

 

while @@fetch_status = 0

begin

if @ticker <> @prev_ticker

begin

     set @in_trade = 0;

     set @prev_close = null;

     set @prev_e21 = null;

     set @prev_e100 = null;

                             set @cum_ret = 0;

end

 

set @current_signal = 'hold';

 

-- Check for Entry

             if @in_trade = 0 and @prev_close is not null

    and @prev_close > @prev_e21 and @prev_e21 > @prev_e100

begin

     set @in_trade = 1;

     set @buy_price = @open;

     set @buy_date = @date;

     set @lower = @buy_price * 0.97;

     set @upper = @buy_price * 1.06;

     set @steps = 0;

     set @current_signal = 'BUY';

 

     insert into dbo.daily_trading_log (ticker, trade_date, [close],

                                             [lower_boundary], [upper_boundary], in_trade_flag, signal)

     values (@ticker, @date, @close, @lower, @upper, 1, @current_signal);

 

     -- Move to next record immediately to avoid double-processing this date

     set @prev_ticker = @ticker;

     set @prev_close = @close;

     set @prev_e21 = @e21;

     set @prev_e100 = @e100;

     fetch next from ticker_cursor into @ticker, @date, @open, @close, @e21, @e100;

     continue;

end

 

-- Process Active Trade

else if @in_trade = 1

begin

     if @close <= @lower

     begin

         set @current_signal = 'SELL (STOP LOSS)';

         set @trade_ret = ((@close - @buy_price) / @buy_price) * 100;

         set @cum_ret = @cum_ret + @trade_ret;

 

         -- Explicit Insert for Daily Log

         insert into dbo.daily_trading_log (ticker, trade_date, [close],

                                                             [lower_boundary], [upper_boundary], in_trade_flag, signal)

         values (@ticker, @date, @close, @lower, @upper, 1, @current_signal);

 

         -- Explicit Insert for Trades Log

         insert into dbo.trades_log (ticker, buy_date, buy_price, sell_date,

                                                             sell_price, step_ups, pct_return, cum_pct_return)

         values (@ticker, @buy_date, @buy_price, @date, @close, @steps,

                                                             @trade_ret, @cum_ret);

        

         set @in_trade = 0;

     end

     else if @close >= @upper

     begin

         set @current_signal = 'STEP-UP';

         insert into dbo.daily_trading_log (ticker, trade_date, [close],

                                                             [lower_boundary], [upper_boundary], in_trade_flag, signal)

         values (@ticker, @date, @close, @lower, @upper, 1, @current_signal);

        

         set @lower = @upper * 0.97;

         set @upper = @upper * 1.06;

         set @steps = @steps + 1;

     end

     else

     begin

         insert into dbo.daily_trading_log (ticker, trade_date, [close],

                                                             [lower_boundary], [upper_boundary], in_trade_flag, signal)

         values (@ticker, @date, @close, @lower, @upper, 1, @current_signal);

     end

end

 

set @prev_ticker = @ticker;

set @prev_close = @close;

set @prev_e21 = @e21;

set @prev_e100 = @e100;

fetch next from ticker_cursor into @ticker, @date, @open, @close, @e21, @e100;

end

 

close ticker_cursor; deallocate ticker_cursor;

 

-- display trading_log and trades_log table

select * from dbo.daily_trading_log order by ticker, trade_date;

select * from dbo.trades_log order by ticker, buy_date;

 

Here is an excerpt from the daily_trading_log table for the first GOOGL trade starting on trade_date 2023-05-26 with a BUY value in the signal column through the close date for the trade on 2023-06-26 with a SELL (STOP LOSS) in the signal column.  All rows in the trade have an in_trade_flag value of 1.  Because the script only executes an INSERT command when the model is active, the log automatically suppresses the 'noise' of the days spent in cash. If a date falls outside of a trade window, it simply isn't recorded in this table, ensuring the final report only shows actionable data.


 

The following screen excerpt shows a block of rows from the second GOOGL trade.  Notice a BUY value appears in row 21, which has a trade_date of 2023-07-06.  The code excludes trading days after last date in the first trade, and it starts again with first trading day in the subsequent trade.  As you can see, the trade doesn’t end on row 35.  Instead, it gets ready to update boundary values in row 36 because the close value on 2023-07-26 exceeds the current upper value.  The signal value for row 35 is STEP-UP.


 

The next two screenshots show two additional blocks of trading days.  The first block of trading days end on row 60, which has another STEP-UP value in its signal column.  The second block of trading days end on row 75 with a SELL (STOP LOSS) value in its signal column.  This marks the end of the second GOOGL trade on 2023-09-21.




The next set of screenshots are from the final select statement in the preceding script.   Each screenshot shows the rows in the trades_log table for one of the four tickers tracked in this post – GOOGL, MU, SNDK, and SPY.

  •  Row numbers in the first column of each screenshot are for sequential trades in the trades log; the trades are sorted by ticker and buy_date.

  • The columns to the right of the column for row numbers have column headers.

    • The ticker column value denotes the ticker to which the other column values belong.

    • The next four columns denote the buy date and buy price as well as the sell date and sell price.  The values in these columns denote when each trade was started and ended as well as the entry and exit price for each trade.

  • The step_ups column value reflects the number of step-ups within a trade.  A step-up occurs when the close price on a trading day equals or exceeds the current upper boundary value within a trade.  The upper and lower boundary values are increased for the trading session following each step up.

  • On the day a step-up is triggered, the boundaries are immediately recalculated for the following session so that

    • The new upper boundary is 1.06 times the former upper boundary,

    • The new lower boundary is .97 time the former upper boundary.

  • There can be zero, one, or more step ups within a trade.

    • If a trade has zero step_ups that means the trade closed at a lower boundary value that never exceeded its initial buy setting.

    • If a trade has one step-up that means one daily close value exceeded the upper boundary value during that trade.  Therefore, the trade closed at a lower boundary value that was increased once from its initial buy setting.

    • If a trade’s  step_ups  column value is greater than one that means the lower boundary was increased more than once from its initial buy setting.

  • The pct_return column value for a trade is the sell_price less the buy_price  difference divided by the buy_price.  The greater the number of step ups in a trade, the greater the chance for the pct_return value to grow during a trade.

    • Notice that for the following four screenshots the pct_return value is always negative when a trade’s step_ups column value is zero.  This outcome is because the pct_return value is dependent on the sell_price less and the buy_price, and the sell_price is dependent is dependent on the lower boundary value which is always less than the buy price.

    • The greater the number of step ups during a trade, the greater the pct_return for the trade.  This is because the lower boundary value on which the sell_price depends grows with each step up during a trade.

  • The cum_pct_return is simply the running total of pct_return column values for a ticker.  The initial cum_pct_return resets to the pct_return value for each tickers first trade.


 

 


Summary Analysis of the Trades Log Results

The following table displays top-line results of the model implemented in this post for the four tickers examined in this post.  There are three metrics for each ticker.  A "Winning Trade" is defined as any trade where the sell price is greater than the buy price.

  • The model designates three trades for SPY, but all three trades were winning trades.  The final cumulative percentage return for SPY was dramatically smaller than for the other tickers.

  • The ticker with the largest final cumulative percentage return was SNDK.  This is despite the fact that the total number of designated trades was the same as for GOOGL and slightly less than half of the trades for MU.

  • The final cumulative percentage return for MU was less than for SNDK, but more than for GOOGL.

  • GOOGL ranked third in terms of the four tracked tickers on the final cumulative percentage metric.

The main take-away for the model is that it showed positive cumulative percentage returns for all four tickers.  Additionally, a large percentage of the trades were positive for all four tickers.



 

The following performance chart from Finviz.com provides additional insight about the performance of the tickers tracked in this post.  The most striking aspect of the performance chart is that SNDK returned a substantially higher percentage gain than any of the four tracked tickers.  This is in spite of the fact that its performance chart did not start until mid-February, 2025, while the other three tickers were in full operation over the evaluation period from the first trading day in 2023 through the end of February in 2025.


 

Next Steps

This first evaluation of the buy-sell model suggests that locking in gains as a trade progresses can produce respectable results, but there is certainly room for further refinement.

Future versions of this model could explore using shorter period lengths for exponential moving averages. By adjusting these parameters, we might find a way to enter trades earlier in a price series. For example, relaxing the requirement that the price must exceed the 100-day average could allow the model to capture more of the initial momentum that occurs before a long-term trend is fully established.

Another area for improvement involves the timing of the exit. In a live trading environment, a stop loss order is often triggered the moment a price touches a specific boundary. This implementation currently relies on daily data, but a more responsive model could be designed to close a position whenever a price boundary is breached during the session. If the lower boundary value is higher than the lowest price reached during the day, the model should ideally reflect an exit at that boundary rather than waiting for the final close.

Ultimately, the goal is to develop a mechanism for getting out of a position sooner when a trend begins to fade. If you have thoughts on how to refine these exit triggers or improve the timing of the initial buy, please share your ideas via a comment for this post. I would be happy to give credit to any suggestions that help improve the model's performance in future evaluations.



What I Learned from This Post

  • The Power of Proper Order. I found that price and multiple EMAs (21-day and 100-day) can act as an effective filter for timing trade entry.
  • Dynamic vs. Static Risk Management. I learned how to successfully simulate dynamic stop loss orders within a T-SQL backtest environment.
  • Handling the "Gap" Reality. I learned that special code is required to account for extended-hours price gaps to keep backtest results accurate.
  • State Management via Cursors. I learned how to use a cursor-driven approach to track whether the model was "in-trade" or "in-cash" day-by-day based on the close price.
  • SQL as a Backtesting Engine. While many traders and analysts use Python, I discovered that T-SQL can serve as a robust and efficient environment for backtesting.
  • My Next Steps: I am inspired to adapt the code samples in this post to a set of tickers that I routinely follow.

Comments

Popular posts from this blog