Petra on Programming: Short-Term Candle Patterns

Japanese rice merchants invented candle patterns in the eighteenth century. Some traders believe that those patterns are still valid today. But alas, it seems no one yet got rich with them. Still, trading book authors are all the time praising patterns and inventing new ones, in hope to find one pattern that is really superior to randomly entering positions. In the Stocks & Commodities January 2021 issue, Perry Kaufman presented several new candle patterns. Let’s repeat his pattern tests with major US stocks and indices, and with or without an additional trend filter.

Kaufman’s 6 patterns were named Key Reversal, Island Reversal, Outside Days, Wide Ranging days, 3-Day Compression, and Gap opening. All are calculated from the previous 3 or 4 candles or price ranges. All but one are symmetric, meaning they deliver bullish and bearish signals. And because I’m a bit on the evil side, I added a 7th pattern that I just invented: TotalRandom.

Here’s how the patterns look as indicators in C:

// a higher high followed by a lower close. 
var cdlKeyReversal()
{
  if(priceHigh(0) > priceHigh(1) && priceLow(0) < priceLow(1)) {
    if(priceClose(0) < priceLow(1)) return -100; // sell
    if(priceClose(0) > priceHigh(1)) return 100; // buy
  } 
  return 0;
}


// recent 3 days each have a true range smaller than the 4th previous day.
var cdl3DayCompression()
{
  vars TRs = series(TrueRange(),4);
  if(TRs[0] < TRs[3] && TRs[1] < TRs[3] && TRs[2] < TRs[3])
    return 100;
  else
    return 0;
}

// a gap higher followed by a lower close, but not filling the gap. 
var cdlIslandReversal()
{
  if(priceLow(0) > priceHigh(1) && priceClose(0) < priceOpen(0)) 
    return -100; // sell
  if(priceLow(1) > priceHigh(0) && priceClose(0) > priceOpen(0)) 
    return 100; // buy
  else return 0;
}

// a higher high and a lower low, close in upper or lower 25% of the range.
var cdlOutsideDay()
{
  if(priceHigh(0) > priceHigh(1) && priceLow(0) < priceLow(1)) {
    if(priceClose(0) < 0.75*priceLow(0) + 0.25*priceHigh(0)) 
      return -100; // sell
    if(priceClose(0) > 0.25*priceLow(0) + 0.75*priceHigh(0)) 
      return 100; // buy
  } 
  return 0; 
}

// same as outside days, but true range must exceed 1.5 ร— 20-day ATR
var cdlWideRangeDays()
{
  if(TrueRange() > 1.5*ATR(20))
    return cdlOutsideDay();
  else
    return 0;
}

// gap must be larger than 0.5 ร— 20-day ATR.
var cdlGapOpening()
{
  var Ratio = (priceOpen(0) - priceClose(1))/ATR(20);
  if(Ratio >= 0.5) return 100;
  if(Ratio <= -0.5) return -100;
  return 0; 
}

// just enter a trade when you feel lucky
var cdlTotalRandom()
{
  int Pattern = random(100);
  if(Pattern > 90) return 100;
  if(Pattern < 10) return -100;
  return 0;
}

Following the convention of the classic candle pattern indicators from the TA library, the pattern functions return +100 for a bullish pattern, -100 for a bearish pattern, and 0 for no pattern.

We’re now putting them to theย  test. We want to trade the patterns with IWM, AAPL, AMZN, GE, WMT, and TSLA stocks, with SPY and QQQ index ETFs, and with and without an additional trend filter. For any detected pattern, we’re entering a long or short position depending on if it’s bullish or bearish. We always invest the same amount, regardless of the price of the stock. The position is closed after 1, 2, 3, 4, or 5 days. For any asset and any exit type we’ll export the number of trades and the total win and loss to a table in HTML format. That is convenient for me because I can paste it directly in this blog article.

The test script:

var pattern();

function run()
{
  pattern = cdlKeyReversal;
  bool WithTrend = 0;
  string Name = "KeyRev";

  BarPeriod = 1440;
  StartDate = 20100101; // TSLA went public in 2010
  EndDate = 2020; // test until 2020
  var Investment = 10000;
  TradesPerBar = 40; // 8 assets * 5 algos

  char File[100];
  if(is(EXITRUN)) {
    sprintf(File,"Log\\%s.htm",strf("%s%s",
      Name,ifelse(WithTrend,"WithTrend","NoTrend")));
    printf("\nStoring results in %s",File);
    file_write(File,strf("<p>%s:</p>\n<table><tr>",File),0);
// write the table header
    file_append(File,"<td>Asset</td>",0);
    file_append(File,"<td>Long/Short</td>",0);
    file_append(File,"<td>Day 1</td>",0);
    file_append(File,"<td>Day 2</td>",0);
    file_append(File,"<td>Day 3</td>",0);
    file_append(File,"<td>Day 4</td>",0);
    file_append(File,"<td>Day 5</td></tr>",0);
  }

  assetAdd("SPY","STOOQ:*.US"); // load price history from Stooq
  assetAdd("QQQ","STOOQ:*.US");
  assetAdd("IWM","STOOQ:*.US");
  assetAdd("AAPL","STOOQ:*.US");
  assetAdd("AMZN","STOOQ:*.US");
  assetAdd("GE","STOOQ:*.US");
  assetAdd("WMT","STOOQ:*.US");
  assetAdd("TSLA","STOOQ:*.US");

  while(asset(loop("SPY","QQQ","IWM","AAPL","AMZN","GE","WMT","TSLA"))) {
  vars Trend = series(SMA(seriesC(),80));
  while(algo(loop("Day1","Day2","Day3","Day4","Day5"))) {

  Lots = Investment/priceClose();
  LifeTime = Itor2+1; // life time in days
  int Signal = pattern();
  if(Signal > 0 && (!WithTrend || rising(Trend)))
    enterLong();
  else if(Signal < 0 && (!WithTrend || falling(Trend)))
    enterShort();
  if(is(EXITRUN)) {
    if(Algo == "Day1") // first loop run
      file_append(File,strf("\n<tr><td>%s</td><td>%i/%i</td>",
        Asset,NumWinLong+NumLossLong,NumWinShort+NumLossShort),0);
    string Color = ifelse(WinLong-LossLong+WinShort-LossShort > 0,
      "bgcolor=\"lime\"","bgcolor=\"red\"");
    file_append(File,strf("<td %s>%.0f/%.0f</td>",
      Color,WinLong-LossLong,WinShort-LossShort),0);
    if(Algo == "Day5")
      file_append(File,"</tr>",0);
  }

  }} // loops
  if(is(EXITRUN)) {
    file_append(File,"</table>",0);
    file_append(File,strf("<p>Total Profit: %.0f (%.0f%%)</p>",
      WinTotal-LossTotal,100*(WinTotal-LossTotal)/Investment),0);
  }
}

Some explanations about the code. At the begin of the run() function the pattern and the trend mode are set up. For convenience we’re using a function pointer that is set to the pattern function to be tested. Afterwards, when it’s the last run, the table header is written into the HTM file. A char array (File) and the sprintf() function is used for the file name instead of the more convenient strf(), because we need to keep the file name for later writing into the table.

The test uses two nested loops, first for selecting the stocks, second for selecting the trade life time to get the results from a 1-day trade up to a 5-days trade. The predefined Itor2 variable is the iterator of the second loop and runs from 0 to 4, so we just use it to set the LifeTime variable for the subsequent trade. The results are printed in the EXITRUN at the last day of the simulation.

The numbers in the tables are the results from long/short trades. A winning pattern is displayed in green, a losing pattern in red. Transaction costs are considered.

KeyRev, No Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 54/73 379/-74 787/-1126 1276/-1753 1203/-2587 986/-3444
QQQ 66/80 1028/-2222 1842/-1958 2042/-2242 2008/-2681 1179/-4133
IWM 63/84 19/-300 756/-488 1298/-502 1386/-900 2063/-2429
AAPL 59/61 -1346/-7827 -947/-9158 -559/-13563 -1237/-11751 356/-11040
AMZN 69/58 142/-1259 2013/-2446 1252/-2293 2208/-4940 3134/-7322
GE 66/61 -4510/-2948 -7995/-6381 -8232/-6583 -7267/-8451 -7646/-10219
WMT 62/69 -337/-177 228/-100 1709/-821 1612/-128 1472/-983
TSLA 65/52 5480/2377 11321/814 11299/-206 9784/-1291 5640/-5471

Total Profit: -103182 (-1032%)

KeyRev, With Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 44/13 208/472 276/260 744/198 96/-236 345/-855
QQQ 53/12 448/-490 1350/-427 1763/-488 1806/-1524 985/-1860
IWM 43/22 563/557 831/-150 81/-773 225/-1174 -55/-2342
AAPL 46/10 -842/-475 55/-926 955/-1562 -149/-1882 603/-2556
AMZN 58/16 314/-661 1254/-897 -727/-566 -680/-1734 -27/-1628
GE 36/29 -1967/-645 -2990/-2871 -1527/-3644 -262/-4542 -1106/-6970
WMT 42/26 -438/215 -379/676 610/-9 312/706 1050/-76
TSLA 41/17 5530/-1068 11484/-1301 10308/-3273 10240/-3127 7723/-3471

Total Profit: -2109 (-21%)

Compression, No Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 699/0 6795/0 11511/0 17027/0 18998/0 23941/0
QQQ 694/0 13941/0 22562/0 28398/0 33369/0 35845/0
IWM 719/0 9964/0 15613/0 19472/0 22317/0 22088/0
AAPL 740/0 -32636/0 -19330/0 -12002/0 -2776/0 2230/0
AMZN 733/0 25188/0 37365/0 49592/0 52783/0 62537/0
GE 728/0 -48784/0 -45754/0 -40932/0 -41726/0 -44097/0
WMT 743/0 4060/0 9378/0 19676/0 19954/0 19714/0
TSLA 701/0 34085/0 39053/0 50163/0 74096/0 93178/0

Total Profit: 606857 (6069%)

Compression With Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 561/0 5212/0 8658/0 9792/0 10530/0 12164/0
QQQ 571/0 10895/0 18052/0 20473/0 24411/0 26917/0
IWM 525/0 6363/0 9300/0 9143/0 14086/0 15742/0
AAPL 547/0 -20085/0 -4174/0 -1241/0 5801/0 5899/0
AMZN 557/0 19877/0 28991/0 34042/0 32737/0 38519/0
GE 385/0 -24387/0 -22127/0 -16754/0 -16487/0 -19714/0
WMT 501/0 4228/0 9813/0 18114/0 21492/0 20353/0
TSLA 443/0 29320/0 34921/0 42957/0 58827/0 66305/0

Total Profit: 548966 (5490%)

Island, No Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 19/32 547/696 540/314 -262/1357 -282/1186 -517/133
QQQ 24/26 300/-283 -684/-452 -1190/360 -2194/-65 -1274/-920
IWM 10/27 921/-15 1587/-112 1610/1583 1936/918 1698/924
AAPL 30/33 -2304/-1850 -2055/-2128 -3958/-2921 -4046/-3311 -5085/-3696
AMZN 20/25 498/-1995 454/-1575 -28/-1469 -150/-2013 -214/-2048
GE 20/28 24/-1885 -1181/-221 -719/1343 2468/106 682/-1594
WMT 23/17 1099/972 2056/605 2508/697 3300/1188 3094/782
TSLA 25/28 1029/-5539 1732/-7774 3167/-5777 1278/-6200 -2787/-10091

Total Profit: -47171 (-472%)

Island With Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 11/5 348/391 -19/444 104/841 -69/910 133/411
QQQ 15/4 -43/376 -274/407 -1069/948 -1588/1139 -1544/557
IWM 5/7 47/173 461/234 562/1312 539/1506 643/1364
AAPL 24/4 -1095/-337 -1625/104 -3931/26 -4541/175 -5013/115
AMZN 12/3 90/-377 -1049/-311 -1364/-3 -2070/214 -1720/-32
GE 10/15 -407/-829 -2018/34 -2323/1562 -1673/767 -2358/-155
WMT 13/3 813/58 1138/-257 1269/185 1484/365 1389/-15
TSLA 13/8 -12/-1216 1134/-1731 1574/-1913 -41/-2357 -5443/-2904

Total Profit: -27380 (-274%)

Outside, No Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 77/77 888/-1105 1410/-1913 2295/-2788 1849/-3487 1555/-3798
QQQ 86/86 919/-1641 1510/-2001 1638/-1562 2132/-2919 1198/-3920
IWM 89/83 -17/-740 205/216 15/155 885/-739 419/-1453
AAPL 65/65 2441/-3824 3324/-5405 4000/-7484 4439/-7002 6148/-7471
AMZN 85/68 489/-561 2895/-1176 2629/-2024 2498/-3505 4767/-6733
GE 74/73 -239/-4137 -2582/-6322 -3096/-7272 -3567/-8987 -3550/-11511
WMT 72/84 -491/1036 461/361 1678/-514 1991/-700 879/-2724
TSLA 74/59 6927/1382 11200/0 12701/-1546 11173/-5879 6769/-6498

Total Profit: -35405 (-354%)

Outside With Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 63/13 664/123 977/-243 1430/-396 596/-887 830/-1168
QQQ 69/11 214/-123 371/-70 481/-8 919/-998 -43/-1275
IWM 59/23 918/-59 1629/-481 1346/-1381 1447/-2182 1339/-2496
AAPL 51/11 2210/-377 3673/-635 4927/-799 5244/-1175 6062/-2042
AMZN 67/14 979/-563 1886/-906 89/-377 316/-1515 1795/-1141
GE 42/34 677/-2682 -353/-4345 1801/-5183 1663/-6603 -732/-10460
WMT 49/31 -746/1081 -482/1016 1165/267 644/244 1183/-976
TSLA 49/23 6834/-1174 11638/333 10403/-871 10899/-1795 8143/-2696

Total Profit: 38018 (380%)

Range, NoTrend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 18/27 205/-520 206/-1392 179/-1244 -10/-1337 -254/-2635
QQQ 18/30 -64/-214 274/-741 412/161 839/72 558/-1528
IWM 19/25 -392/-382 -873/-88 -711/1284 -1083/1207 -886/456
AAPL 9/13 404/-293 318/-951 1193/-420 1187/-576 980/-1500
AMZN 13/17 1100/-1315 1785/-843 938/612 1702/526 2577/-667
GE 17/23 -1281/-181 -1833/-2128 -3415/-2288 -4833/-1910 -5257/-3787
WMT 12/14 -142/399 -433/915 -1078/801 -1232/998 -1746/697
TSLA 21/17 849/33 3714/-1998 3761/-3474 2735/-7781 386/-7093

Total Profit: -38348 (-383%)

Range With Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 15/4 -245/51 -193/283 11/413 -409/105 -522/-507
QQQ 13/5 -585/-99 -374/151 -293/514 -138/86 -539/-216
IWM 11/5 393/-230 304/-222 212/-246 -74/-9 77/-157
AAPL 8/2 517/295 254/21 1049/448 1092/335 840/103
AMZN 11/4 -34/-1248 252/-1603 -759/-364 -603/-1203 -141/-1438
GE 9/10 906/319 496/-1302 389/-1897 -343/-1786 -1874/-3716
WMT 6/4 -262/154 -545/604 -328/404 -704/424 -558/61
TSLA 13/3 386/-191 4201/309 1815/-35 1290/-206 1/-597

Total Profit: -7235 (-72%)

Gap, No Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 349/281 1843/-2647 5590/-3626 7139/-5568 8563/-8215 9461/-9622
QQQ 307/249 -436/-2679 947/-5712 3275/-8531 7805/-12092 9842/-13431
IWM 234/209 2551/-84 3639/-418 5118/-2119 6055/-3499 9321/-5574
AAPL 270/192 6959/-1638 7870/-5676 9847/-7260 13533/-5428 16900/-8041
AMZN 195/135 1427/-2939 2777/-4051 4995/-4568 8998/-6259 13534/-6271
GE 241/195 112/371 -4831/5254 -3762/3531 -1410/5945 1695/4319
WMT 125/180 -1507/-7044 329/-7500 448/-6792 24/-6722 876/-7916
TSLA 197/154 21127/-6332 36571/-12591 46932/-11143 58537/-9760 71862/-9286

Total Profit: 192941 (1929%)

Gap With Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 282/82 2592/-488 5160/166 7869/-2794 8352/-3565 8935/-5882
QQQ 249/68 -718/-2463 -1388/-5109 91/-9515 4466/-11318 5774/-12975
IWM 163/82 2456/617 3057/2235 5188/917 6955/2870 8950/2094
AAPL 219/60 3901/-978 1837/-2601 2008/-5555 6634/-5316 10460/-6654
AMZN 169/34 -1685/-2236 -1130/-5903 -397/-6310 3148/-7550 6830/-6188
GE 138/91 969/1315 -1600/6072 -679/5257 748/5819 3337/2798
WMT 90/79 -465/-1144 174/-456 -93/-1071 -760/-1130 135/-2431
TSLA 151/62 16146/-4006 30939/-3936 42024/-3501 54105/-1597 70309/-915

Total Profit: 221201 (2212%)

Random, NoTrend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 264/245 2161/-2628 3479/-4002 4118/-3867 8184/-5686 3223/-7478
QQQ 274/256 6391/-6434 4669/-3082 6549/-7386 4095/-2891 11244/-6941
IWM 247/227 4546/-2869 7429/114 4610/1211 10551/1705 8259/-6920
AAPL 265/248 2130/-9670 2688/-4795 12171/-6907 17141/-9278 12451/-25170
AMZN 239/215 2970/-4240 3796/-6628 4579/-8738 7889/-6822 10022/-11536
GE 254/259 1016/-4440 -1860/-3617 3682/-2421 10588/552 -8794/-12820
WMT 258/260 670/-2260 2830/-459 -55/-2294 -3118/-5824 4529/-3994
TSLA 209/250 8366/-9571 -4906/-32877 17063/-33473 19937/-13389 8788/-53044

Total Profit: -115840 (-1158%)

Random With Trend:

Asset Long/Short Day 1 Day 2 Day 3 Day 4 Day 5
SPY 189/55 2273/-386 597/-600 2753/-747 1864/883 -1143/-3324
QQQ 194/53 2461/-2976 3675/-947 1360/-3750 7607/-7892 12958/-2974
IWM 182/85 3478/2507 4262/-969 3827/-2263 902/-3130 8135/-4938
AAPL 183/70 7704/-2776 9537/-3962 8168/-759 13781/-5973 13117/-2048
AMZN 174/70 1795/-4108 -362/-2203 3809/-5626 9710/-8699 16006/-8834
GE 125/109 1187/3311 -3939/-1684 264/2732 -3265/4335 425/-8270
WMT 174/91 1962/132 2008/-1731 -946/-285 1927/1556 1790/-5483
TSLA 148/92 13794/-7286 13013/-3592 15655/-6054 15511/-10460 15264/-18641

Total Profit: 85014 (850%)

We can see that the trend filter always improves the results, which indicates some momentum in the stock markets. Aside from that, patterns show no real advantage over random trading. But wait, there’s an exception: the Compression pattern looks extraordinarily successful. Have we finally found the one pattern that beats randomness?

Unfortunately, a closer look reveals the real reason. The compression pattern opens only long positions, and since most stocks are bullish in the long term, a positive result is to be expected. If you modify the random pattern function so that it only opens long positions, you’ll get the same effect. Randomly opening long positions beats hands down all symmetric candle patterns. So it seems that trading with candle patterns, no matter old or new, still requires strong faith – and some disposable money.

The pattern functions and the test script can be downloaded from the 2020 script repository.

9 thoughts on “Petra on Programming: Short-Term Candle Patterns”

  1. I knew I was onto something with my random entries ๐Ÿ˜‰
    Really thought provoking, thanks. [Good find Paul Smith :)]

  2. Correct me if I’m wrong… but when running some tests of your code (and by previous experience), if the goal is only to assess (Test/Backtest) the efficacy of a certain algo in a number of different assets, I don’t think running it in an asset loop is the correct way. It seems each asset loop doesn’t consider an asset as a “clean slate”, and instead run a certain algo as it would be managing a portfolio, where an entry in an asset could interfere with the possibility of entry in another asset if there is no margin, etc.

  3. Hmm, asset loops are simply used for running the same code with many assets. They dont interfere with entry possibilities, whatever thats supposed to mean.

  4. That is what I thought, until I saw huge inconsistencies between running backtests in asset loops and individually.. the documentation on this is scarce, and I couldn’t find explicitly why. If you can, I’d like you to run similar tests and help me verify if this is consistent with your findings as well. I assume all assets are sharing the same “Capital”/”Margin” variables, although I later realized this is probably being “ignored” as you are setting the lots by using a local variable (Investment)

    On a side note, specifically for this code, I can’t seem to replicate your results. In fact, every run keeps returning different results (3 consecutive runs without modifying the code, just hitting “Test”):
    Test 1:

    AAPL avg .276 0.89 1249/1395 34.7
    AMZN avg .025 0.92 1317/1362 24.9
    GE avg .364 1.00 1260/1393 1.2
    IWM avg .330 1.03 1363/1384 -6.6
    QQQ avg .213 0.97 1313/1360 6.5
    SPY avg .286 1.02 1338/1349 -3.0
    TSLA avg .142 0.92 1194/1275 45.3
    WMT avg .236 1.02 1331/1301 -3.1

    Day1 avg .288 0.95 2059/2185 16.2
    Day2 avg .200 0.89 1990/2175 42.3
    Day3 avg .302 0.96 2142/2167 18.6
    Day4 avg .370 1.03 2105/2148 -15.7
    Day5 avg .244 0.93 2069/2144 38.6

    Test 2:

    AAPL avg .334 0.96 1323/1383 9.3
    AMZN avg .032 1.18 1404/1271 -44.3
    GE avg .251 0.91 1258/1393 24.6
    IWM avg .220 1.02 1267/1278 -3.8
    QQQ avg .216 0.94 1275/1382 9.3
    SPY avg .169 0.98 1310/1301 2.5
    TSLA avg .087 0.79 1201/1333 109.1
    WMT avg .500 1.05 1326/1298 -6.8

    Day1 avg .307 0.94 2082/2108 15.6
    Day2 avg .344 0.95 2107/2118 15.6
    Day3 avg .137 0.93 1982/2167 27.8
    Day4 avg .311 0.99 2098/2098 5.2
    Day5 avg .259 0.93 2095/2148 35.9

    Test 3:

    AAPL avg .100 0.87 1252/1386 36.8
    AMZN avg .018 0.95 1404/1390 14.8
    GE avg .172 0.92 1319/1368 22.4
    IWM avg .176 0.93 1291/1299 13.2
    QQQ avg .258 0.95 1254/1330 8.1
    SPY avg .283 1.04 1288/1353 -4.9
    TSLA avg .071 0.98 1216/1244 7.6
    WMT avg .320 0.99 1221/1289 2.0

    Day1 avg .186 0.98 2110/2115 4.4
    Day2 avg .209 0.95 2051/2107 16.2
    Day3 avg .305 0.98 2038/2084 6.2
    Day4 avg .144 0.91 1999/2147 40.7
    Day5 avg .206 0.94 2047/2206 32.4

  5. I cannot tell why your script produces strange results because I don’t know it. Maybe you forgot to initialize some variable. If in doubt, just use my script from the repository.

  6. Hi everyone! I’m new here.

    Thanks, Petra, for sharing your work!
    It’s mind-blowing and helpful for a beginner like me.

    Hands down!

    Looking forward to learning more from your posts.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.