<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: cm45t3r</title>
    <description>The latest articles on DEV Community by cm45t3r (@cm45t3r).</description>
    <link>https://dev.to/cm45t3r</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4125717%2F209b32c1-6ca6-4ac5-9452-daf39bbc37ed.png</url>
      <title>DEV Community: cm45t3r</title>
      <link>https://dev.to/cm45t3r</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cm45t3r"/>
    <language>en</language>
    <item>
      <title>The Hammer and the Hanging Man Are the Same Candle</title>
      <dc:creator>cm45t3r</dc:creator>
      <pubDate>Tue, 15 Sep 2026 07:22:44 +0000</pubDate>
      <link>https://dev.to/cm45t3r/the-hammer-and-the-hanging-man-are-the-same-candle-543b</link>
      <guid>https://dev.to/cm45t3r/the-hammer-and-the-hanging-man-are-the-same-candle-543b</guid>
      <description>&lt;p&gt;A hammer is a small body with a long lower wick. It's a &lt;em&gt;bullish&lt;/em&gt; reversal signal.&lt;/p&gt;

&lt;p&gt;A hanging man is a small body with a long lower wick. It's a &lt;em&gt;bearish&lt;/em&gt; reversal signal.&lt;/p&gt;

&lt;p&gt;Geometrically, they are the same candle. Nothing about the shape distinguishes them. The only thing that separates a buy signal from a sell signal is what happened &lt;em&gt;before&lt;/em&gt; it: a hammer means something after a downtrend, a hanging man after an uptrend.&lt;/p&gt;

&lt;p&gt;Most pattern detection libraries will hand you both matches on the same candle and leave you to sort it out. That's the problem I spent most of my time on in &lt;a href="https://github.com/cm45t3r/candlestick?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=candlestick" rel="noopener noreferrer"&gt;&lt;code&gt;candlestick&lt;/code&gt;&lt;/a&gt;, and it's the one worth writing about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a static confidence score is worse than none
&lt;/h2&gt;

&lt;p&gt;The tempting fix is to attach a reliability number to each pattern — hammer is 0.7, doji is 0.4, and so on. I shipped exactly that, and it was a mistake.&lt;/p&gt;

&lt;p&gt;A fixed score is context-blind by construction. The same hammer geometry is a strong signal after a sustained decline and nearly worthless in the middle of a rally, but a static 0.7 reports both identically. It reads as precision while carrying no information. The &lt;code&gt;confidence&lt;/code&gt; field is still there, marked &lt;code&gt;@deprecated&lt;/code&gt;, because removing it outright would break callers — but nothing in the library encourages its use any more.&lt;/p&gt;

&lt;p&gt;What replaced it measures the actual preceding trend and scores how well each match fits the context that pattern requires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;patternChain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;allPatterns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;candlestick&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;patternChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;allPatterns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;trendContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;trendMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sma-slope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;trendPeriod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 11: hangingMan     trendContext: "uptrend"  contextFit: 1.00&lt;/span&gt;
&lt;span class="c1"&gt;// 12: hammer         trendContext: "uptrend"  contextFit: 0.41&lt;/span&gt;
&lt;span class="c1"&gt;// 12: bearishHammer  trendContext: "uptrend"  contextFit: 0.41&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same uptrend, same long-lower-wick geometry, and the library tells you which reading the context supports. &lt;code&gt;metadata.enrichWithMetadata&lt;/code&gt; folds that into an &lt;code&gt;effectiveConfidence&lt;/code&gt; of &lt;code&gt;confidence × contextFit&lt;/code&gt;: the hammer drops to 0.29, because a bullish reversal signal in the middle of an uptrend isn't worth much.&lt;/p&gt;

&lt;p&gt;Passing &lt;code&gt;resolveConflicts: true&lt;/code&gt; goes further and drops the weaker side of a same-candle, opposite-direction conflict outright. Built-in trend methods are &lt;code&gt;sma-slope&lt;/code&gt; (default), &lt;code&gt;ema-slope&lt;/code&gt; and &lt;code&gt;pct-change&lt;/code&gt;, and you can supply your own &lt;code&gt;externalTrend&lt;/code&gt; series or a &lt;code&gt;trendFn&lt;/code&gt; callback if you already have a better regime model.&lt;/p&gt;

&lt;p&gt;One implementation detail worth knowing: multi-candle patterns anchor their &lt;code&gt;index&lt;/code&gt; at the &lt;em&gt;first&lt;/em&gt; candle of the formation, while single-candle patterns anchor at the candle itself. In the output above, that's why &lt;code&gt;hangingMan&lt;/code&gt; reports at 11 and &lt;code&gt;hammer&lt;/code&gt; at 12 for what is visually the same pair. Conflict resolution operates per anchor index, so overlapping formations of different lengths are surfaced rather than silently merged.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of the design
&lt;/h2&gt;

&lt;p&gt;The trend-context work is the interesting part, but a few other decisions shaped the library enough to be worth naming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A consistent, dual-mode API.&lt;/strong&gt; Every pattern ships two functions: a boolean check for a single candle or pair (&lt;code&gt;isHammer(candle)&lt;/code&gt;), and an array scanner that returns match indices (&lt;code&gt;hammer(dataArray)&lt;/code&gt;). Learn the shape once and you know it for all 18 patterns — 29 variants once you count the bullish/bearish splits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pattern chaining instead of pattern-by-pattern loops.&lt;/strong&gt; &lt;code&gt;patternChain(data, allPatterns)&lt;/code&gt; scans for everything in a single pass and returns a normalized list of &lt;code&gt;{ index, pattern, match }&lt;/code&gt; results, rather than making you run each detector separately over the same data. Pass your own list instead of &lt;code&gt;allPatterns&lt;/code&gt; if you only care about a subset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript wasn't bolted on.&lt;/strong&gt; Full definitions ship in &lt;code&gt;types/index.d.ts&lt;/code&gt;, so &lt;code&gt;OHLC&lt;/code&gt; and &lt;code&gt;PatternMatch&lt;/code&gt; give you real IntelliSense instead of &lt;code&gt;any&lt;/code&gt;. The package dual-exports ESM and CommonJS, so &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;require&lt;/code&gt; both work without a shim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A plugin system, because hardcoded pattern lists are a dead end.&lt;/strong&gt; &lt;code&gt;plugins.registerPattern()&lt;/code&gt; lets you define your own detection function, attach metadata — type, direction, expected trend context — and drop it into &lt;code&gt;patternChain&lt;/code&gt; alongside the built-ins. Custom patterns get the same treatment as shipped ones, including trend context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gaps measured against volatility, not against zero.&lt;/strong&gt; The same context problem shows up&lt;br&gt;
in a second place. A kicker is defined by a gap between two candle bodies — but "any nonzero&lt;br&gt;
gap" treats a $0.30 move on a $210 stock as a signal, when it's indistinguishable from noise.&lt;br&gt;
Passing &lt;code&gt;minGapVol&lt;/code&gt; requires the gap to clear a volatility-relative threshold instead, using&lt;br&gt;
ATR, standard deviation of returns, or a series you supply yourself. It's opt-in: omit it and&lt;br&gt;
the original behaviour is preserved exactly.&lt;/p&gt;

&lt;p&gt;It's the same argument as the hammer. Geometry alone will happily report a pattern that the&lt;br&gt;
surrounding data says is meaningless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation that fails loudly.&lt;/strong&gt; &lt;code&gt;validateOHLC&lt;/code&gt; and &lt;code&gt;validateOHLCArray&lt;/code&gt; throw on malformed data instead of letting a missing &lt;code&gt;high&lt;/code&gt; field silently produce wrong results three functions downstream.&lt;/p&gt;
&lt;h2&gt;
  
  
  What "zero dependencies" is actually worth
&lt;/h2&gt;

&lt;p&gt;This is the claim every library makes and few quantify, so here is the whole install:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;candlestick&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime dependencies&lt;/td&gt;
&lt;td&gt;0 (0 transitive)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native build step&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install scripts&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform-specific code&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I had a size in kilobytes here and took it out, for a reason worth repeating: the&lt;br&gt;
README ships inside the package, so writing the install size into it changes the&lt;br&gt;
install size. Two attempts were both stale by the time they were committed. It's&lt;br&gt;
on the order of tens of kilobytes; &lt;code&gt;npm pack --dry-run&lt;/code&gt; gives the exact figure at&lt;br&gt;
any commit.&lt;/p&gt;

&lt;p&gt;The kilobytes were never the interesting part anyway. The rows above are what&lt;br&gt;
determines whether an install succeeds.&lt;/p&gt;

&lt;p&gt;Several established libraries in this space are bindings to TA-Lib or Tulip — C libraries with decades of history and far more indicators than I'll ever ship. The tradeoff is that installing them compiles a native addon: &lt;code&gt;node-gyp&lt;/code&gt;, a toolchain, Python, and a build that can fail differently on every platform in your matrix. If you've ever watched a CI job go red on Windows only, you know the shape of it.&lt;/p&gt;

&lt;p&gt;For reference, measured with &lt;code&gt;npm view &amp;lt;pkg&amp;gt; dist.unpackedSize&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Unpacked&lt;/th&gt;
&lt;th&gt;Runtime deps&lt;/th&gt;
&lt;th&gt;Native build&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;candlestick&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;171 kB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tulind&lt;/td&gt;
&lt;td&gt;233 kB&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;indicatorts&lt;/td&gt;
&lt;td&gt;563 kB&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;trading-signals&lt;/td&gt;
&lt;td&gt;574 kB&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;technicalindicators&lt;/td&gt;
&lt;td&gt;5.1 MB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;talib&lt;/td&gt;
&lt;td&gt;27.6 MB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Figures as of September 2026 — they'll drift as those projects release.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This isn't a claim that &lt;code&gt;candlestick&lt;/code&gt; replaces TA-Lib. It doesn't; TA-Lib does far more. It's a claim about a narrower job: if what you need is candlestick pattern detection specifically, you can have it as plain JavaScript that installs identically on Linux, Windows and macOS, with nothing to compile and no supply-chain surface you didn't ask for.&lt;/p&gt;

&lt;p&gt;When I first wrote this, there was a caveat here: the package was not usefully&lt;br&gt;
tree-shakeable. The ESM entry re-exported the CommonJS module by destructuring it&lt;br&gt;
at runtime, which is opaque to a bundler's static analysis, so importing one&lt;br&gt;
pattern pulled in the same code as importing all eighteen. &lt;code&gt;sideEffects: false&lt;/code&gt;&lt;br&gt;
in the manifest was writing a cheque the entry point could not cash.&lt;/p&gt;

&lt;p&gt;It's fixed in v3.0.0. The entry now names each export against its own module,&lt;br&gt;
which a bundler can follow. &lt;code&gt;import { hammer }&lt;/code&gt; measures &lt;strong&gt;1.5 kB&lt;/strong&gt; minified and&lt;br&gt;
gzipped, against 8.1 kB for the whole library. The default export still pulls&lt;br&gt;
everything in, because that is what it means.&lt;/p&gt;

&lt;p&gt;I'm leaving the original paragraph's substance here rather than quietly swapping&lt;br&gt;
the numbers, because the interesting part is not the fix — it's that measuring&lt;br&gt;
the claim is what turned up the defect. Writing "modular, tree-shakeable" in a&lt;br&gt;
README costs nothing. Running esbuild over a single import is what tells you&lt;br&gt;
whether it was true.&lt;/p&gt;
&lt;h2&gt;
  
  
  Streaming, and where the savings actually come from
&lt;/h2&gt;

&lt;p&gt;Once you're past a few hundred thousand candles, holding the whole series in memory while running pattern checks over it stops being free. &lt;code&gt;createStream&lt;/code&gt; takes candles in configurable chunks and fires an &lt;code&gt;onMatch&lt;/code&gt; callback per hit, so you can drive it from a file or a socket without ever materializing the full array.&lt;/p&gt;

&lt;p&gt;Measured on 200,000 candles with five patterns: &lt;strong&gt;41.9 MB live heap for &lt;code&gt;patternChain&lt;/code&gt; against 0.2 MB for the stream&lt;/strong&gt; — producing the same 61,866 matches in both cases.&lt;/p&gt;

&lt;p&gt;That's a much larger reduction than I used to claim for this API, and I want to be precise about why, because the number is easy to misread. Resident memory is bounded by &lt;code&gt;chunkSize&lt;/code&gt; instead of scaling with the dataset. The saving is not the streaming machinery — it's &lt;em&gt;never holding the whole dataset&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Two conditions are doing all the work, and both are easy to lose by accident:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consume matches in &lt;code&gt;onMatch&lt;/code&gt; rather than collecting them.&lt;/strong&gt; Pushing every match into an array puts the result set straight back into memory, and at high match counts that dominates whatever the buffering saved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed the stream incrementally.&lt;/strong&gt; Passing &lt;code&gt;process()&lt;/code&gt; slices of an array you already built in memory saves you nothing at all. The win comes from a generator, a file read, or a socket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Streaming a pre-built array is a no-op dressed up as an optimization. Worth stating plainly, because it's the mistake I'd expect most people to make first.&lt;/p&gt;
&lt;h2&gt;
  
  
  There's a CLI, too
&lt;/h2&gt;

&lt;p&gt;Not everything needs to be a &lt;code&gt;require&lt;/code&gt;. A fair amount of this work is exploratory — you have a&lt;br&gt;
CSV, you want to know what's in it, and writing a script first is friction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx candlestick &lt;span class="nt"&gt;-i&lt;/span&gt; data.csv &lt;span class="nt"&gt;--patterns&lt;/span&gt; hammer,doji &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;span class="nb"&gt;cat &lt;/span&gt;data.json | candlestick &lt;span class="nt"&gt;--output&lt;/span&gt; csv &lt;span class="nt"&gt;--confidence&lt;/span&gt; 0.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads CSV or JSON from a file or stdin, filters by pattern, minimum confidence, type&lt;br&gt;
(reversal / continuation / neutral) or direction, and prints JSON, CSV, or a formatted table.&lt;br&gt;
Table and CSV output always carry the pattern metadata — type, direction, confidence,&lt;br&gt;
strength — so the columns are never empty.&lt;/p&gt;

&lt;p&gt;It's the same detection code the library exposes, so whatever you confirm at the terminal is&lt;br&gt;
what you'll get in your program.&lt;/p&gt;
&lt;h2&gt;
  
  
  Proving it works
&lt;/h2&gt;

&lt;p&gt;Design decisions are cheap to write about and expensive to get right, so the test suite does the real talking: &lt;strong&gt;484 tests across 107 suites, 99.94% line coverage, 100% function coverage, and branch coverage a hair over 99%.&lt;/strong&gt; Property-based tests via &lt;code&gt;fast-check&lt;/code&gt; generate randomized OHLC scenarios per invariant, which is how most of the interesting edge cases surfaced — hand-written examples tend to test the cases you already thought of.&lt;/p&gt;

&lt;p&gt;That randomness is also why I won't quote a branch-coverage decimal: the generated cases reach slightly different branches on every run, so the figure moves between 99.1% and 99.3% run to run. A precise number there would be one nobody could reproduce, including me.&lt;/p&gt;

&lt;p&gt;CI runs the full suite on Node 22, 24 and 26 across Linux, Windows and macOS.&lt;/p&gt;

&lt;p&gt;Throughput for the full 29-pattern chain, measured 2026-09-11 on Node v24.21.0, Intel Core i7-9750H, 16 GB RAM, macOS 26.6:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Candles&lt;/th&gt;
&lt;th&gt;Chain time&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;2.7 ms&lt;/td&gt;
&lt;td&gt;370K candles/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;21.1 ms&lt;/td&gt;
&lt;td&gt;474K candles/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;227.1 ms&lt;/td&gt;
&lt;td&gt;440K candles/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;2,436.9 ms&lt;/td&gt;
&lt;td&gt;410K candles/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Single-run figures on one machine — treat them as an order of magnitude, not a guarantee. &lt;code&gt;npm run bench&lt;/code&gt; reproduces them on your own hardware, which is the only number that should matter to you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Nine years, and what's next
&lt;/h2&gt;

&lt;p&gt;The package has been on npm since 2016. The trend-context work is recent; a lot of the rest is the accumulated result of using it, finding it wrong, and fixing it — which is why the roadmap carries corrections to claims earlier versions made, including a memory figure that turned out to be unsubstantiated.&lt;/p&gt;

&lt;p&gt;Next up: visual examples for each pattern (they're text-only descriptions today), and a few more multi-candle formations. If there's a pattern you rely on that isn't covered, &lt;a href="https://github.com/cm45t3r/candlestick/issues?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=candlestick" rel="noopener noreferrer"&gt;issues and PRs are open&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;candlestick
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requires Node.js &amp;gt;= 22. Node 20 reached end of life on 2026-04-30 and was dropped in v3.0.0.&lt;/p&gt;




&lt;h2&gt;
  
  
  A quick aside: hardware wallets
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Disclosure: the link below is an affiliate link. I earn a commission if you buy through it, at no extra cost to you. It doesn't change what the library does or what I've written above.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A caveat before the recommendation: this is a detour from the topic. If you're here for OHLC tooling and don't hold crypto, skip it — nothing below is about the library.&lt;/p&gt;

&lt;p&gt;Some of the people running this library are analyzing crypto markets rather than equities, and if you're holding assets rather than just charting them, a hardware wallet is the baseline I'd suggest over leaving anything on an exchange. I use &lt;strong&gt;&lt;a href="https://onekey.so/r/KFTH04" rel="noopener noreferrer"&gt;OneKey&lt;/a&gt;&lt;/strong&gt; — open-source firmware, an EAL6+ certified secure element, and backing from Coinbase Ventures and Dragonfly. Worth a look if you don't already have cold storage sorted.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The full source, docs and examples are on &lt;a href="https://github.com/cm45t3r/candlestick?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=candlestick" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. A video walkthrough of the trend-context design is in progress — I'll link it here when it's up.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>opensource</category>
      <category>trading</category>
    </item>
  </channel>
</rss>
