Backtesting Heikin Ashi Candle Colors to Buy Dips and Exit When Runs End

Daily OHLC candles reflect in an unfiltered way the components of a daily trading day based on the open, high, low, and close prices during a trading day.  Traders benefit from this data because today’s close price less yesterday’s close price can confirm if and by how much prices rose or fell today versus yesterday.  Similarly, the open price from today less close price from yesterday can confirm if and by how much a security’s prices changed at the market open today relative to yesterday’s close price.

While OHLC candles facilitate compiling a log of daily price movements, they can also reflect significant visual noise and false momentum signals from routine daily price action within and across trading days.  Heikin Ashi candles offer a powerful alternative by applying a set of averaging formulas across adjacent sessions to smooth day-to-day volatility and reduce the effect of random price fluctuations.  A trading model based on Heikin Ashi candles leverages these benefits to distinguish a true directional shift from a transient counter-trend move and establish objective, repeatable rules for entering on sustained dips and exiting when momentum runs genuinely exhaust.

This post’s main objective is to introduce and demonstrate the operation of a trading model based on Heikin Ashi candles.  The post tracks trade outcomes for a set of eight tickers and a benchmark ticker.  The eight non-benchmark tickers reflect trading instruments that are likely to attract traders interested in exploiting relatively short-run trends, such as those detected by Heikin Ashi candles.  The post also includes a brief introductory section on Heikin Ashi candles because this is the blog’s initial post on Heikin Ashi candles.

The Nine Tickers Tracked in This Post

This section lists and describes the nine tickers tracked in this post (AAPL, AMD, DELL, GOOGL, MSFT, MU, NVDA, VLO, and SPY).    SPY serves as a broad-market benchmark that does not have the same volatility over the evaluation period as the remaining eight tickers.  With the exception of SPY, these tickers appeal to active traders basing trade entry and exit  dates on Heikin Ashi candle colors.  SPY targets investors who seek safety in broad diversification but also prefer capital appreciation from securities that can be traded.  

The following image is an excerpt from Finviz that provides a topline summary of the tickers tracked in this post.  For example, the tickers are from the Technology, Communication Services, Energy, and Financial sectors, with the Technology sector being overrepresented.  The tickers are more diverse at the industry level.  All the tickers are for US companies.




This post focuses primarily on trade percentage returns when trade entry and exit dates are selected based on Heikin Ashi candle colors.  The evaluation period is from the first trading day in 2017 through September 30, 2026.  The historical price data for this post is derived from GOOGLEFINANCE functions in Google Sheets.

Before presenting highlights of how the GOOGLEFINANCE function in Google Sheets is used in this post, I want to inform readers that you can browse the data and supporting GOOGLEFINANCE expression for downloading historical prices from this url (https://docs.google.com/spreadsheets/d/1nBxDJD4spgv3h6cvbPVcJtjI4GV9HmMwI_9hlHrsEK4/edit?usp=sharing).  There is no paywall, no need to have a Google browser, or even a Google account.  The only feature that requires a free Google account is making an automated full-workbook copy straight into Google Drive.

Here is an excerpt from the DELL tab in the First_HA_Ticker_Set workbook that you can access from the preceding url reference.  The cursor rests in cell A1, and a truncated view of the GOOGLEFINANCE expression appears in the formula bar.  The worksheet shows the initial four rows of historical dates and prices.  You can verify that the first trading day in January, 2017 is the third day of the month.



The next workbook excerpt shows the bottom five rows of historical dates and prices in the DELL tab.  The cursor rests in the final row of the worksheet with a Date value of 9/30/2026.  If you examine the preceding  workbook excerpt, you can see that the fourth parameter for the GOOGLEFINANCE expression contains this date.  By selecting any of the other eight tabs, you can change the data being displayed in the workbook.



While many US tickers like DELL or SPY pull price data cleanly in the GOOGLEFINANCE function without an exchange code, certain equities or international stocks require explicit prefixes (e.g., NASDAQ:AAPL or TSE:SHOP). If you are unsure whether a specific ticker requires an exchange prefix, you can ask an AI assistant (like Google Gemini or Microsoft Copilot): "What is the correct Google Finance exchange prefix for ticker [XYZ]?"

After you extract historical prices with the GOOGLEFINANCE function for whatever tickers you are tracking, you can download historical prices from each tab to your default Downloads directory with the Google Sheets File → Download → Comma Separated Values (.csv) command.  Then, you can migrate the csv files to whatever target directory you are using to store the historical prices for your ticker set.  This post uses the “C:\Users\User\Documents\First_HA_Ticker_Set\” folder for storing csv files with historical data for each ticker.

A SQL Server Dynamic Ingestion Script for Downloaded CSV Files

Ingestion of csv files into a SQL Server table can be a routine task.  Here is a T-SQL script that dynamically ingests the csv files for the nine tickers tracked in this post from the "C:\Users\User\Documents\First_HA_Ticker_Set" folder.

As you can see, the first four steps transfer csv files from the source directory of csv files from GOOGLEFINANCE worksheets for each of the tickers tracked in this post.

  • Step 1 creates a staging table for the csv files that correspond to each ticker.
  • Step 2 creates a clean table for storing the data for all nine tickers.
  • Step 3 configures a folder’s path for the source csv files as well as a temporary table of tickers.  When you are adapting this process to other tickers in different folder paths, you will need to update @folder_path local variable and the #tickers temp table contents.
  • Step 4 uses the file_cursor cursor and a dynamic sql string (@sql) to populate the dbo.stockhistory_clean_gs table with the csv file contents.
  • Step 5 returns a result set that compares all nine tickers in terms of row count, min_date, and max_date.


/* dynamic ingestion for googlefinance csv files into a tall sql table ver_4.4

   date: 10/3/2026

   authors: rick dobson & Google Gemini

   purpose: load 5-column googlefinance csv exports with full OHLC price data,

            convert date/numeric fields directly (decimal 18,6),

            populate dbo.stockhistory_clean_gs,

            and verify row counts as well as first and last dates per ticker.

*/

 

use securitytradinganalytics;

go

 

---------------------------------------------------------

-- 1. recreate staging table (5 columns matching CSV layout)

---------------------------------------------------------

if object_id('dbo.stockhistory_stage', 'U') is not null

    drop table dbo.stockhistory_stage;

 

create table dbo.stockhistory_stage

(

    [date]   varchar(50),

    [open]   varchar(50),

    [high]   varchar(50),

    [low]    varchar(50),

    [close]  varchar(50)

);

 

---------------------------------------------------------

-- 2. recreate clean table for google finance (_gs)

---------------------------------------------------------

if object_id('dbo.stockhistory_clean_gs', 'U') is not null

    drop table dbo.stockhistory_clean_gs;

 

create table dbo.stockhistory_clean_gs

(

    [date]   date          not null,

    [open]   decimal(18,6) not null,

    [high]   decimal(18,6) not null,

    [low]    decimal(18,6) not null,

    [close]  decimal(18,6) not null,

    [ticker] varchar(20)   not null,

    constraint pk_stockhistory_clean_gs primary key clustered ([ticker], [date])

);

go

 

---------------------------------------------------------

-- 3. configure folder path and tickers

---------------------------------------------------------

declare @folder_path varchar(255);

set @folder_path = 'C:\Users\User\Documents\First_HA_Ticker_Set\';

 

if object_id('tempdb..#tickers') is not null

    drop table #tickers;

 

create table #tickers

(

    ticker varchar(20) not null

);

 

insert into #tickers (ticker)

values

    ('AAPL'), ('AMD'), ('DELL'), ('GOOGL'), ('MSFT'),

    ('MU'), ('NVDA'), ('VLO'), ('SPY');

 

---------------------------------------------------------

-- 4. iterate through cursor and process files

---------------------------------------------------------

declare @ticker varchar(20);

declare @file_path varchar(500);

declare @sql nvarchar(max);

 

if cursor_status('global', 'file_cursor') >= -1

begin

    close file_cursor;

    deallocate file_cursor;

end;

 

declare file_cursor cursor for

select ticker from #tickers;

 

open file_cursor;

fetch next from file_cursor into @ticker;

 

while @@fetch_status = 0

begin

    truncate table dbo.stockhistory_stage;

 

    set @file_path = @folder_path + @ticker + '.csv';

 

    set @sql = '

    bulk insert dbo.stockhistory_stage

    from ' + nchar(39) + @file_path + nchar(39) + '

    with

    (

        firstrow = 2,

        fieldterminator = '','',

        rowterminator = ''\n''

    );';

 

    exec sp_executesql @sql;

 

    ---------------------------------------------------------

    -- direct insertion of clean rows with injected @ticker

    ---------------------------------------------------------

    insert into dbo.stockhistory_clean_gs ([date], [open], [high], [low], [close], [ticker])

    select

        try_convert(date, s.[date]) as [date],

        try_convert(decimal(18,6), s.[open]) as [open],

        try_convert(decimal(18,6), s.[high]) as [high],

        try_convert(decimal(18,6), s.[low]) as [low],

        try_convert(decimal(18,6), s.[close]) as [close],

        @ticker as [ticker]

    from dbo.stockhistory_stage s

    where try_convert(date, s.[date]) is not null

      and try_convert(decimal(18,6), s.[open]) is not null

      and try_convert(decimal(18,6), s.[high]) is not null

      and try_convert(decimal(18,6), s.[low]) is not null

      and try_convert(decimal(18,6), s.[close]) is not null;

 

    fetch next from file_cursor into @ticker;

end;

 

close file_cursor;

deallocate file_cursor;

go

 

---------------------------------------------------------

-- 5. verification check

---------------------------------------------------------

select

    ticker,

    count(*) as row_count,

    min([date]) as min_date,

    max([date]) as max_date

from dbo.stockhistory_clean_gs

group by ticker

order by ticker;


Here is the result set from the preceding script.  During DELL's re-listing transition in late December 2018, GOOGLEFINANCE omitted a single trading day (12/28/2018). As a consequence, DELL's dataset contains 2,448 rows compared to 2,449 rows for the other eight tickers.



Here is a relative price performance chart from Finviz for the full set of nine tickers tracked in this post, spanning Jan 3, 2017 through September 30, 2026. Finviz provides an excellent external cross-check confirming that all nine securities—including DELL—share an identical multi-year evaluation window. As the chart shows, DELL achieved a massive 3,415.60% return across the period, with a substantial portion of that surge occurring between the second half of 2025 and September 30, 2026. 

This strong multi-year trend is precisely the type of price movement that attracts active traders using Heikin Ashi candles to isolate momentum and filter out noise. When evaluating trading models, it is essential to include strongly trending assets to evaluate how effectively model entry and exit rules for trades capture extended price expansions. 



A Quick Tutorial for Heikin Ashi Candles

From an analytical perspective, Heikin Ashi candles differ from standard OHLC candles in how their core values are calculated across adjacent sessions.

  • OHLC candles are derived from open, high, low, and close prices for the current trading period, which is often a single trading day.
  • Heikin Ashi candle values, except for the first period in a time series, are based on two periods: the current period and the preceding period. The Heikin Ashi close is the average of the current period's open, high, low, and close prices. The Heikin Ashi open is the average of the preceding period's Heikin Ashi open and Heikin Ashi close. Because the preceding Heikin Ashi open was itself computed from the period before it, the Heikin Ashi open acts as a running average of prices through the preceding period.
  • The initial period in a Heikin Ashi candle series does not have a preceding period.  Therefore, this period requires special expressions to compute its Heikin Ashi candle values.

  • The specific expressions used in this post to compute the full set of Heikin Ashi candles appear in the “A Backtest for Entry/Exit Values Based on Heikin Ashi Candle Colors” section.
The following pair of screenshots show a Heikin Ashi candle chart above a standard OHLC candle chart for Micron (MU).  Both charts show candles for trading days from 11/11/2025 through 4/22/2026.

  • In the standard OHLC chart, green candles represent trading days when the close price is above the open price, and red candles represent trading days when the close price is not above the open price.
  • In the Heikin Ashi chart, green candles represent trading days when the Heikin Ashi close is above the Heikin Ashi open, that is, when the day's average price is above the running average price through the prior day. Red candles represent trading days where ha_close falls below ha_open.
  • The key point to observe is that the runs of green candles in the top chart of Heikin Ashi candles are longer than in the bottom chart of standard OHLC candles.
  • This post evaluates the performance of two relatively simple rules for entering and exiting trades based on Heikin Ashi candle colors for each of the tickers tracked in this post.



A Script for Computing Heikin Ashi Candles and Candle Colors

The code for this section demonstrates how to compute and verify Heikin Ashi candle values and their associated colors in five basic steps.  An optional sixth step displays selected column values from the first and last three rows for each ticker that verify the determination of candle colors.  If you are going to use Heikin Ashi candles as described in this post, it is imperative that you understand how to assign Heikin Ashi candle colors.

The specific Heikin Ashi values computed in this section are named after their corresponding source candle values – namely, daily open, high, low, and close values.  The corresponding Heikin Ashi candle values are named ha_open, ha_high, ha_low, and ha_close.  The script populates the dbo.First_HA_Dataset table with ticker and date column values, and open, high, low, and close values from dbo.stockhistory_clean_gs, and calculated ha_open, ha_high, ha_low, ha_close, and ha_color (from ha_open and ha_close) values.  The #with_dates temporary table facilitates the implementation of script mechanics.

Here is an overview of the five steps in this script.

  • Step1 removes the dbo.First_HA_Dataset table and the #with_dates temporary table so that each run of the script can freshly populate these two tables.
  • Steps 2 and 3 work together to populate the dbo.First_HA_Dataset table.
  • Step 4 adds a natural key based on ticker and date to the dbo.First_HA_Dataset table.
  • Steps 5 and 6 are two verification checks.
    • Step 5 is an aggregate verification result set by ticker in which
      • source_row_count should match ha_row_count
      • flat_bars per is the count of rows where ha_close equals ha_open
      • the sum of green_bars and red_bars should equal ha_row_count if ha_open and ha_close never equal each other for unique ticker-date pairs
    • Step 6 displays the first and last three rows for each ticker-date
      • ha_color should be red whenever ha_open is greater than ha_close
      • ha_color should be green whenever ha_open is less than ha_close

/* heikin ashi candle computation with a layered cte chain ver_1.3

   date: 10/6/2026

   authors: rick dobson & Claude

   purpose: compute heikin ashi (ha) candle values and colors for every
            ticker in dbo.stockhistory_clean_gs and save them in
            dbo.First_HA_Dataset.
*/

use securitytradinganalytics;
go

---------------------------------------------------------
-- 1. drop tables left from a prior run (if there is one)
---------------------------------------------------------
drop table if exists dbo.First_HA_Dataset;
drop table if exists #with_dates;

---------------------------------------------------------
-- 2. prepare each bar for the ha calculation and save the
--    result in a temp table (#with_dates)
---------------------------------------------------------
-- layer 1: every source row plus ha close
with source_rows as
(
    select
        ticker,
        [date],
        [open],
        [close],
        cast(([open] + [high] + [low] + [close]) / 4.0 as decimal(18,6)) as ha_close
    from dbo.stockhistory_clean_gs
)
-- layer 2: prior and next trading dates within each ticker
select
    ticker,
    [date],
    [open],
    [close],
    ha_close,
    lag([date])  over (partition by ticker order by [date]) as prev_date,
    lead([date]) over (partition by ticker order by [date]) as next_date
into #with_dates
from source_rows;

-- natural key on the temp table; this is the index the recursive join seeks on
alter table #with_dates
    add constraint pk_with_dates primary key clustered (ticker, [date]);

---------------------------------------------------------
-- 3. recursive ha open and final materialization
---------------------------------------------------------
with

-- layer 3: recursive ha open
-- anchor: each ticker's first bar seeds ha open as (open + close) / 2
-- recursive step: ha open = (prior ha open + prior ha close) / 2
ha_chain as
(
    -- anchor member
    select
        w.ticker,
        w.[date],
        w.next_date,
        cast((w.[open] + w.[close]) / 2.0 as decimal(18,6)) as ha_open,
        w.ha_close
    from #with_dates w
    where w.prev_date is null

    union all

    -- recursive member
    select
        n.ticker,
        n.[date],
        n.next_date,
        cast((p.ha_open + p.ha_close) / 2.0 as decimal(18,6)) as ha_open,
        n.ha_close
    from ha_chain p
    join #with_dates n
        on  n.ticker = p.ticker
        and n.[date] = p.next_date
)

-- final layer: join ha values to daily ohlc by ticker and date,
-- add ha high, ha low, and candle color, and materialize the result
select
    d.ticker,
    d.[date],
    d.[open],
    d.[high],
    d.[low],
    d.[close],
    h.ha_open,
    (select max(v) from (values (d.[high]), (h.ha_open), (h.ha_close)) as t(v)) as ha_high,
    (select min(v) from (values (d.[low]),  (h.ha_open), (h.ha_close)) as t(v)) as ha_low,
    h.ha_close,
    case
        when h.ha_close > h.ha_open then 'green'
        else 'red'
    end as ha_color
into dbo.First_HA_Dataset
from ha_chain h
join dbo.stockhistory_clean_gs d
    on  d.ticker = h.ticker
    and d.[date] = h.[date]
option (maxrecursion 0);

---------------------------------------------------------
-- 4. natural primary key on (ticker, date)
---------------------------------------------------------
alter table dbo.First_HA_Dataset
    add constraint pk_First_HA_Dataset primary key clustered (ticker, [date]);
go

---------------------------------------------------------
-- 5. verification check
--    ha_row_count should equal source_row_count for every ticker;
--    green_bars, red_bars, and flat_bars count bars where ha close is
--    above, below, or exactly equal to ha open, so the three counts
--    sum to ha_row_count; a flat_bars count of zero confirms every
--    bar is clearly green or red
---------------------------------------------------------
select
    f.ticker,
    count(*) as ha_row_count,
    src.source_row_count,
    sum(case when f.ha_close > f.ha_open then 1 else 0 end) as green_bars,
    sum(case when f.ha_close < f.ha_open then 1 else 0 end) as red_bars,
    sum(case when f.ha_close = f.ha_open then 1 else 0 end) as flat_bars
from dbo.First_HA_Dataset f
join
(
    select ticker, count(*) as source_row_count
    from dbo.stockhistory_clean_gs
    group by ticker
) src
    on src.ticker = f.ticker
group by f.ticker, src.source_row_count
order by f.ticker;

--------------------------------------------------------------------------------
-- 6. optional: display colors for the first and last three rows for each ticker
--------------------------------------------------------------------------------
with numbered as
(
    select
        *,
        row_number() over (partition by ticker order by [date])      as from_first,
        row_number() over (partition by ticker order by [date] desc) as from_last
    from dbo.First_HA_Dataset
)
select
    ticker, [date], [open], [high], [low], [close],
    ha_open, ha_high, ha_low, ha_close, ha_color
from numbered
where from_first <= 3
   or from_last  <= 3
order by ticker, [date];

Here is the result set from step 5.



Here is the result set from step 6 copied to and formatted in an Excel worksheet. If you have difficulty viewing the values in the display below, consider zooming your view.



A Backtest for Entry/Exit Values Based on Heikin Ashi Candle Colors

This section looks at sequences of ha colors based on ha opens and closes across consecutive trading days to identify trade entry and exit dates and prices.  The ha values are calculated from the first trading day in January 2017, and trades are taken from February 1, 2017 through the last trading day in September 2026, so the seed used for the first ha open has faded before any trade.

The trading strategy over this timeframe depends on entry and exit signals.

  • The entry signal is defined by a  dip over at least three consecutive trading days with red ha color values followed by a single-day recovery (with a green ha color).
  • The exit signal is defined by a run of two consecutive trading days with red ha color values.  The two-day period can start on or after entry date and before the  next entry signal.

Trade entry and exit dates are defined by entry and exit signals.
  • Trade entry dates and prices are, respectively, the first trading date after the entry signal and its open price.
  • Trade exit dates and prices are, respectively, the first trading date after the exit signal and its open price.

This strategy is applied separately to eight actively traded stocks (AAPL, AMD, DELL, GOOGL, MSFT, MU, NVDA and VLO) and to SPY as a benchmark, with results measured as percent change per trade.

The following script implements the preceding modeling description and returns two result sets.

  • The first result set contains a row for individual trades specified by the model in the dbo.First_HA_Trades table.  There is a separate row in this table for each trade designated by model.  Column names in the table include ticker, entry_date, entry_price, exit_date, exit_price, trade_pct for percentage return from a trade, and cum_pct for cumulative percentage return for a ticker through the close of that trade.  The cum_pct column values reset at the beginning of each ticker to trade_pct value for first trade within a ticker via a sum function’s partition by ticker setting.
  • The second result set contains a row for each ticker.  The rows are listed in alphabetical order for the eight tickers that traders typically buy and sell on an active basis as well as for the SPY benchmark ticker that investors sometimes buy and hold.  By applying the same model to all nine tickers, the benchmark ticker can be compared to the remaining eight tickers and the remaining eight tickers can be compared among one another on an apples-to-apples basis.  The results in the second table reflect the outcome from the 3 reds + 1 green in, 2 reds out trading strategy across all nine tickers in terms of their trades count and their final cumulative percentage value. 

The code inside the following script consists of five layers.  The first four layers are part of a layered CTE.

  • Layer 1 populates the bars CTE with a separate row for each row in the dbo.First_HA_Dataset.  The bars CTE has columns that facilitate the  tracking of entry and  exit signals along with their associated trade entry and exit dates and prices.
    • The first three columns identify the ticker, date, and ha_color of the current row. 
    • The next three columns store the ha_color values for each of the three prior rows.
    • The next column stores the date of the previous row.
    • The last two columns store the date and the open price for the next row.
  • Layer 2 filters the bars CTE to extract just rows that match the entry signal criteria and  then populates the entry_signals CTE entry date and entry price values from the bars CTE.
  • Layer 3 filters the bars CTE to extract each row whose ha_color is red and whose prior row’s ha_color is also red.  Each extracted row represents one pair of consecutive red days, with the dates of the first and second red days stored in the first_red_date and exit_signal_date columns. Additionally, the associated exit date and exit price values are extracted from the bars CTE.
  • Layer 4 populates the trades CTE with the entry date and price as well as the exit date and price for each trade within each ticker.  Each entry date is matched with the first exit signal for the same ticker whose two reds both fall on or after the entry date.  This layer also enforces the rule of accepting trades that start on or after February 1, 2017.  By default, if an entry date does not have a matching exit date that is on or before September 30, 2026, then the trade has null values for exit date and price.
  • The final layer updates the rowset from trades CTE by adding trade_pct and cum_pct columns and materializes the outcome as the dbo.First_HA_Trades table.
  • Before running the select statements for the two result sets from the following script, a primary key constraint on ticker and entry_date is added to the dbo.First_HA_Trades table.

/* ha color entry/exit trades: 3 reds + 1 green in, 2 reds out ver_1_2

   date: 10/10/2026

   authors: rick dobson & Claude

 

   purpose: list trades for every ticker in dbo.First_HA_Dataset based on

            ha candle colors, save them in dbo.First_HA_Trades, and

            summarize the closed trades for each ticker.

 

   rules:   entry signal: a green ha bar immediately preceded by

                          three consecutive red ha bars

            entry:        open price on the trading day after the entry signal

            exit signal:  the second of two consecutive red ha bars,

                          both on or after the entry date

            exit:         open price on the trading day after the exit signal

            scope:        trades entered on or after 2/1/2017 (@start_date),

                          a few weeks after the data starts, so the ha open

                          seed has faded before any trade

*/

 

use securitytradinganalytics;

go

 

-- price data starts 2017-01-03; starting trades a few weeks later lets the

-- ha open seed (first bar's (open + close) / 2) fade before any trade

declare @start_date date = '2017-02-01';

 

drop table if exists dbo.First_HA_Trades;

 

with

 

-- layer 1: each bar with the colors of the prior bars,

--          plus the prior date and the next trading day's date and open,

--          all computed within each ticker

bars as

(

    select

        ticker,

        [date],

        ha_color,

        lag(ha_color, 1) over (partition by ticker order by [date]) as color_1_back,

        lag(ha_color, 2) over (partition by ticker order by [date]) as color_2_back,

        lag(ha_color, 3) over (partition by ticker order by [date]) as color_3_back,

        lag([date])      over (partition by ticker order by [date]) as prev_date,

        lead([date])     over (partition by ticker order by [date]) as next_date,

        lead([open])     over (partition by ticker order by [date]) as next_open

    from dbo.First_HA_Dataset

),

 

-- layer 2: entry signals (red, red, red, green);

--          the trade enters at the next trading day's open

--          (a signal on a ticker's last bar has no next day and is skipped)

entry_signals as

(

    select

        ticker,

        next_date as entry_date,

        next_open as entry_price

    from bars

    where ha_color     = 'green'

      and color_1_back = 'red'

      and color_2_back = 'red'

      and color_3_back = 'red'

      and next_date is not null

),

 

-- layer 3: exit signals (red, red); the pair's first red is prev_date,

--          and the trade exits at the next trading day's open

exit_signals as

(

    select

        ticker,

        prev_date as first_red_date,

        [date]    as exit_signal_date,

        next_date as exit_date,

        next_open as exit_price

    from bars

    where ha_color     = 'red'

      and color_1_back = 'red'

),

 

-- layer 4: pair each entry with the first exit signal for the same ticker

--          whose two reds both fall on or after the entry date

trades as

(

    select

        e.ticker,

        e.entry_date,

        e.entry_price,

        x.exit_date,

        x.exit_price

    from entry_signals e

    outer apply

    (

        select top 1 xs.exit_date, xs.exit_price

        from exit_signals xs

        where xs.ticker = e.ticker

          and xs.first_red_date >= e.entry_date

        order by xs.exit_signal_date

    ) x

    where e.entry_date >= @start_date

)

 

-- final layer: per-trade and cumulative percent results, materialized

-- (a trade still open at the end of the data shows null exit values

--  and does not change the cumulative total)

select

    ticker,

    entry_date,

    entry_price,

    exit_date,

    exit_price,

    cast((exit_price - entry_price) / entry_price * 100 as decimal(18,4)) as trade_pct,

    cast(sum((exit_price - entry_price) / entry_price * 100)

             over (partition by ticker order by entry_date

                   rows unbounded preceding) as decimal(18,4)) as cum_pct

into dbo.First_HA_Trades

from trades;

 

-- natural primary key on (ticker, entry_date)

alter table dbo.First_HA_Trades alter column ticker     varchar(20) not null;

alter table dbo.First_HA_Trades alter column entry_date date        not null;

 

alter table dbo.First_HA_Trades

    add constraint pk_First_HA_Trades primary key clustered (ticker, entry_date);

go

 

-- display the trades

select *

from dbo.First_HA_Trades

order by ticker, entry_date;

 

---------------------------------------------------------

-- summary by ticker

--    trades counts closed trades only; count(trade_pct) skips the

--    null trade_pct of a trade still open at the end of the data;

--    total_pct_change sums the percent change of the closed trades

--    and matches each ticker's last cum_pct value;

--    spy is listed last as the benchmark; the other tickers are

--    listed alphabetically

---------------------------------------------------------

select

    ticker,

    count(trade_pct) as trades,

    cast(sum(trade_pct) as decimal(10,2)) as total_pct_change

from dbo.First_HA_Trades

group by ticker

order by case when ticker = 'SPY' then 1 else 0 end, ticker;


The next pair of screenshots show the first and last five rows from the first result set for the DELL ticker.  Recall that the first result set is a trades ledger with ticker, entry date, entry price, exit date, and exit price along trade percentage return and cumulative percentage return.  This pair of screenshots makes it easy to determine

  • the initial entry date and price for the DELL trade was on March 9, 2017 for a price of $17.73, and
  • the final exit date and price for the DELL trade was on September 30, 2026 for a price of $546.48.




The following pair of screenshots show comparable results for the SPY ticker, which is the benchmark for the other eight tickers tracked in this post.




The final screenshot in this post shows the trade count and total percentage change for each ticker covered in this post.

  • All eight comparison tickers have a larger total percentage change than the SPY benchmark.  The percentage values in the following display show percentage change achieved by the model.
    • Also, four of the comparison tickers (AMD, DELL, MU, and NVDA) have total percentage change values in excess of 200 percentage points versus a total percentage change value of just 31.69 for the SPY ticker
    • The relative performance chart at the end of the “A SQL Server Dynamic Ingestion Script for Downloaded CSV Files” section shows buy-and-hold price changes for the same tickers and timeframe. Those values are not directly comparable to the model’s totals, because buy-and-hold compounds one position held throughout, while the model sums the percent changes of separate fixed-amount trades. Even so, SPY ranks last under both measures.
  • From the trades column below, you can also see that the 3 reds + 1 green in, 2 reds out model generates fewer trades for the SPY ticker than any of the remaining eight tickers.  While the differences are not nearly as large as for total percentage change values, the results still show a tendency for the SPY ticker, which is a widely followed benchmark ticker, to be more unresponsive to the model.


Concluding Comments

This post demonstrates the practical application of Heikin Ashi candle colors for identifying trend reversals across a nearly ten-year evaluation window spanning from January 2017 through September 2026.  The total percentage change finding for the 3 reds + 1 green in, 2 reds out model returned distinctly different total percentage return values for the eight actively traded tickers than the SPY benchmark ticker.  A similar kind of distinction was also found for total trades by ticker and even for the rank order of buy-and-hold percentage change by ticker.

The 3 reds + 1 green in, 2 reds out model examined in this post illustrates only one of the many ways of using Heikin Ashi candle values to estimate worthy entry and exit trade points for a set of tickers.  This model focuses on an adaptation of the popular “buy the dip” model that is configured for implementation with Heikin Ashi candles and underlying  OHLC values.

If you decide that you wish to experiment with your own customized Heikin Ashi model or even just reuse the model covered in this post with a different set of tickers, you are likely to find some of the scripts in this post helpful.  For that reason, all scripts referenced in this post appear in the body of the post.

I close by inviting you to leave comments with your suggestions for using Heikin Ashi candles for modeling entry and exit dates and prices for stock tickers in different ways than covered in this post.

Comments

Popular posts from this blog