candlestick_rs/
candle_stick.rs

1/// The `CandleStick` trait provides analytical capabilities to detect key single-candle
2/// formations that signal potential market reversals, continuations, or indecision.
3///
4/// These patterns form the foundation of candlestick chart analysis, a technique
5/// developed in 18th century Japan and widely used in modern technical trading.
6///
7/// Each pattern detection method (`is_*`) returns whether the specific formation criteria
8/// are met, along with detailed documentation on trading significance and application.
9///
10/// Implementers need to provide only the four basic OHLC methods (open, high, low, close),
11/// and all pattern recognition capabilities become automatically available.
12///
13/// For multi-candle pattern identification across a series of candles, use: [`crate::CandleStream`]
14pub trait CandleStick {
15    /// Hammer body to range ratio for both hammer and inverse hammer pattern.
16    /// Can be overridden for custom ratio.
17    ///
18    /// Default: __30%__
19    fn hammer_body_ratio(&self) -> f64 {
20        0.3
21    }
22
23    /// Hammer upper shadow or wick to range ratio. Also, inversely used as tail to range ratio for inverse hammer pattern.
24    /// Can be overridden for custom ratio
25    ///
26    /// Default: __20%__
27    fn hammer_wick_ratio(&self) -> f64 {
28        0.2
29    }
30
31    /// Hammer lower shadow or tail to range ratio. Also, inversely used as wick to range ratio for inverse hammer pattern.
32    /// Can be overridden for custom ratio
33    ///
34    /// Default: __60%__
35    fn hammer_tail_ratio(&self) -> f64 {
36        0.6
37    }
38
39    /// Spinning top body ratio. Can be overridden for custom ratio
40    ///
41    /// Default: __20%__
42    fn spinning_top_body_ratio(&self) -> f64 {
43        0.2
44    }
45
46    /// Spinning top shadow ratio. Can be overridden for custom ratio
47    ///
48    /// Default: __30%__
49    fn spinning_top_shadow_ratio(&self) -> f64 {
50        0.3
51    }
52
53    /// Doji body to range ratio. Can be overridden for custom ratio.
54    ///
55    /// Default: __10%__
56    fn doji_body_ratio(&self) -> f64 {
57        0.1
58    }
59
60    /// Doji long leg to range ratio. Can be overridden for custom ratio.
61    ///
62    /// Default: __30%__
63    fn doji_long_leg_ratio(&self) -> f64 {
64        0.3
65    }
66
67    /// Doji tail to range ratio. Can be overridden for custom ratio.
68    ///
69    /// Default: __30%__
70    fn doji_tail_ratio(&self) -> f64 {
71        0.3
72    }
73
74    /// Doji wick to range ratio. Can be overridden for custom ratio.
75    ///
76    /// Default: __30%__
77    fn doji_wick_ratio(&self) -> f64 {
78        0.3
79    }
80
81    /// Doji minimum ratio. Can be overridden for custom ratio.
82    ///
83    /// Default: __5%__
84    fn doji_min_ratio(&self) -> f64 {
85        0.05
86    }
87
88    /// Marubozu minimum ratio. Can be overridden for custom ratio.
89    ///
90    /// Default: __20%__
91    fn marubozu_ratio(&self) -> f64 {
92        0.2
93    }
94
95    /// Returns the open price
96    fn open(&self) -> f64;
97
98    /// Returns the high price
99    fn high(&self) -> f64;
100
101    /// Returns the low price
102    fn low(&self) -> f64;
103
104    /// Returns the close price
105    fn close(&self) -> f64;
106
107    /// Returns the volume
108    fn volume(&self) -> f64;
109
110    /// Helper function to return the OHLC tuple
111    #[doc(hidden)]
112    fn ohlc(&self) -> (f64, f64, f64, f64) {
113        (self.open(), self.high(), self.low(), self.close())
114    }
115
116    /// Helper function to return the candle length with small epsilon
117    #[doc(hidden)]
118    fn range(&self) -> f64 {
119        (self.high() - self.low()).max(0.001)
120    }
121
122    /// Helper function to return the candle wick length of the candle
123    #[doc(hidden)]
124    fn wick(&self) -> f64 {
125        self.high() - self.open().max(self.close())
126    }
127
128    /// Helper function to return the candle body as the absolute difference between the open and close prices with small epsilon
129    #[doc(hidden)]
130    fn body(&self) -> f64 {
131        ((self.open() - self.close()).abs()).max(0.0001)
132    }
133
134    /// Helper function to return the candle tail length
135    #[doc(hidden)]
136    fn tail(&self) -> f64 {
137        self.open().min(self.close()) - self.low()
138    }
139
140    /// Helper function to return the candle's wick to range ratio
141    #[doc(hidden)]
142    fn wick_range_ratio(&self) -> f64 {
143        self.wick() / self.range()
144    }
145
146    /// Helper function to return the candle's wick to body ratio
147    #[doc(hidden)]
148    fn wick_body_ratio(&self) -> f64 {
149        self.wick() / self.body()
150    }
151
152    /// Helper function to return the candle's body to range ratio
153    #[doc(hidden)]
154    fn body_range_ratio(&self) -> f64 {
155        self.body() / self.range()
156    }
157
158    /// Helper function to return the candle's tail to range ratio
159    #[doc(hidden)]
160    fn tail_range_ratio(&self) -> f64 {
161        self.tail() / self.range()
162    }
163
164    /// Helper function to return the candle's tail to body ratio
165    #[doc(hidden)]
166    fn tail_body_ratio(&self) -> f64 {
167        self.tail() / self.body()
168    }
169
170    /// Identifies a Bullish Candlestick, a foundational pattern in price action analysis.
171    ///
172    /// This basic pattern forms when the closing price is higher than the opening price,
173    /// creating a filled (often green/white) candle body. The length of the body indicates
174    /// the strength of buying pressure during the period.
175    ///
176    /// **Trading Significance**:
177    /// - Signals buying pressure and bullish sentiment in the market
178    /// - Longer bullish bodies indicate stronger buying conviction
179    /// - Series of bullish candles confirm uptrends, especially with higher highs and lows
180    /// - Often used as confirmation for other technical signals in trend-following strategies
181    ///
182    /// # Example
183    /// ```
184    /// use candlestick_rs::CandleStick;
185    /// let candle = (100.0, 110.0, 99.0, 109.0, 0.0);
186    /// assert!(candle.is_bullish());
187    /// ```
188    fn is_bullish(&self) -> bool {
189        self.open() < self.close()
190    }
191
192    /// Identifies a Bearish Candlestick, a foundational pattern in price action analysis.
193    ///
194    /// This basic pattern forms when the closing price is lower than the opening price,
195    /// creating a filled (often red/black) candle body. The length of the body indicates
196    /// the strength of selling pressure during the period.
197    ///
198    /// **Trading Significance**:
199    /// - Signals selling pressure and bearish sentiment in the market
200    /// - Longer bearish bodies indicate stronger selling conviction
201    /// - Series of bearish candles confirm downtrends, especially with lower highs and lows
202    /// - Often used as confirmation for other technical signals in trend-following strategies
203    ///
204    /// # Example
205    /// ```
206    /// use candlestick_rs::CandleStick;
207    /// let candle = (110.0, 111.0, 99.0, 100.0, 0.0);
208    /// assert!(candle.is_bearish());
209    /// ```
210    fn is_bearish(&self) -> bool {
211        self.open() > self.close()
212    }
213
214    /// Identifies a Marubozu pattern, one of the strongest single-candle signals.
215    ///
216    /// This pattern forms when a candle has virtually no upper or lower shadows (wicks),
217    /// with the body extending across nearly the entire range. The term "marubozu" means
218    /// "bald head" or "shaved head" in Japanese, referring to the absence of shadows.
219    ///
220    /// **Trading Significance**:
221    /// - Represents complete dominance of either buyers (bullish marubozu) or sellers (bearish marubozu)
222    /// - Signals exceptional conviction in the market direction
223    /// - Often precedes continuation in the same direction, especially early in trends
224    /// - When appearing against the prevailing trend, can signal potential exhaustion and reversal
225    ///
226    /// # Example
227    /// ```
228    /// use candlestick_rs::CandleStick;
229    /// let candle = (100.0, 110.0, 99.0, 109.0, 0.0);
230    /// assert!(candle.is_marubozu());
231    /// ```
232    fn is_marubozu(&self) -> bool {
233        self.wick_body_ratio() < self.marubozu_ratio()
234            && self.tail_body_ratio() < self.marubozu_ratio()
235    }
236
237    /// Identifies a Bullish Marubozu, a powerful signal of buyer dominance.
238    ///
239    /// This pattern forms when a bullish candle (close > open) has virtually no shadows,
240    /// with the open at or near the low and the close at or near the high. It shows
241    /// buyers controlled the price action throughout the entire period with no significant
242    /// selling pressure.
243    ///
244    /// **Trading Significance**:
245    /// - Indicates exceptional buying pressure and momentum
246    /// - Often signals the beginning or acceleration of an uptrend
247    /// - Traders frequently use it as a strong entry signal for long positions
248    /// - When appearing after a consolidation or pullback, suggests the resumption of a bullish trend
249    ///
250    /// # Example
251    /// ```
252    /// use candlestick_rs::CandleStick;
253    /// let candle = (100.0, 110.0, 99.0, 109.0, 0.0);
254    /// assert!(candle.is_bullish_marubozu());
255    /// ```
256    fn is_bullish_marubozu(&self) -> bool {
257        self.is_bullish() && self.is_marubozu()
258    }
259
260    /// Identifies a Bearish Marubozu, a powerful signal of seller dominance.
261    ///
262    /// This pattern forms when a bearish candle (close < open) has virtually no shadows,
263    /// with the open at or near the high and the close at or near the low. It shows
264    /// sellers controlled the price action throughout the entire period with no significant
265    /// buying pressure.
266    ///
267    /// **Trading Significance**:
268    /// - Indicates exceptional selling pressure and downward momentum
269    /// - Often signals the beginning or acceleration of a downtrend
270    /// - Traders frequently use it as a strong exit signal for long positions or entry for shorts
271    /// - When appearing after an uptrend, can signal a potential trend reversal
272    ///
273    /// # Example
274    /// ```
275    /// use candlestick_rs::CandleStick;
276    /// let candle = (110.0, 111.0, 99.0, 100.0, 0.0);
277    /// assert!(candle.is_bearish_marubozu());
278    /// ```
279    fn is_bearish_marubozu(&self) -> bool {
280        self.is_bearish() && self.is_marubozu()
281    }
282
283    /// Identifies a Hammer pattern, a significant bullish reversal signal.
284    ///
285    /// This single-candle pattern is characterized by a small body at the upper portion of the
286    /// trading range and a long lower shadow (tail) at least twice the length of the body.
287    /// It resembles a hammer with a handle at the top.
288    ///
289    /// **Trading Significance**:
290    /// - Signals potential bullish reversal when appearing at the bottom of a downtrend
291    /// - Indicates rejection of lower prices as buyers stepped in after initial selling
292    /// - More reliable when followed by confirmation (a bullish candle or increased volume)
293    /// - Body color is less important than the overall shape, though bullish hammers (close > open) are slightly more significant
294    ///
295    /// # Example
296    /// ```
297    /// use candlestick_rs::CandleStick;
298    /// let candle = (100.0, 101.0, 95.0, 100.8, 0.0);
299    /// assert!(candle.is_hammer());
300    /// ```
301    fn is_hammer(&self) -> bool {
302        self.body_range_ratio() < self.hammer_body_ratio()
303            && self.wick_range_ratio() < self.hammer_wick_ratio()
304            && self.tail_range_ratio() > self.hammer_tail_ratio()
305    }
306
307    /// Identifies an Inverted Hammer pattern, a potential bullish reversal signal.
308    ///
309    /// This single-candle pattern features a small body at the lower portion of the
310    /// trading range and a long upper shadow (wick) at least twice the length of the body.
311    /// It resembles an upside-down hammer.
312    ///
313    /// **Trading Significance**:
314    /// - Signals potential bullish reversal when appearing at the bottom of a downtrend
315    /// - Indicates attempted upside movement, though sellers pushed prices back down
316    /// - Generally requires stronger confirmation than a standard hammer
317    /// - Often precedes the end of a downtrend, especially when followed by bullish price action
318    ///
319    /// # Example
320    /// ```
321    /// use candlestick_rs::CandleStick;
322    /// let candle = (100.0, 104.0, 99.8, 100.5, 0.0);
323    /// assert!(candle.is_inverted_hammer());
324    /// ```
325    fn is_inverted_hammer(&self) -> bool {
326        self.body_range_ratio() < self.hammer_body_ratio()
327            && self.wick_range_ratio() > self.hammer_tail_ratio()
328            && self.tail_range_ratio() < self.hammer_wick_ratio()
329    }
330
331    /// Identifies a Hanging Man pattern, an important bearish reversal signal.
332    ///
333    /// This pattern has the same shape as a hammer (small body at the top with a long lower shadow),
334    /// but appears during an uptrend. The long lower shadow indicates selling pressure that emerged
335    /// but was overcome by buyers—a warning sign after an advance.
336    ///
337    /// **Trading Significance**:
338    /// - Signals potential bearish reversal when appearing after an uptrend
339    /// - Suggests market vulnerability as sellers tested lower prices
340    /// - Most effective at resistance levels or after extended price advances
341    /// - Traders typically wait for next-day confirmation (a bearish candle or gap down)
342    ///
343    /// # Example
344    /// ```
345    /// use candlestick_rs::CandleStick;
346    /// let candle = (592.0, 593.75, 587.0, 593.0, 0.0);
347    /// assert!(candle.is_hanging_man());
348    /// ```
349    fn is_hanging_man(&self) -> bool {
350        self.is_hammer()
351    }
352
353    /// Identifies a Shooting Star pattern, a significant bearish reversal signal.
354    ///
355    /// This pattern has the same shape as an inverted hammer (small body at the bottom with a long upper shadow),
356    /// but appears during an uptrend. The long upper shadow indicates rejection of higher prices,
357    /// as buyers attempted to push prices up but ultimately failed.
358    ///
359    /// **Trading Significance**:
360    /// - Signals potential bearish reversal when appearing after an uptrend
361    /// - Indicates strong rejection of higher prices and exhaustion of buying pressure
362    /// - More significant when the upper shadow is at least twice the length of the body
363    /// - Often used by traders to exit long positions or initiate shorts, especially when confirmed
364    ///
365    /// # Example
366    /// ```
367    /// use candlestick_rs::CandleStick;
368    /// let candle = (100.0, 106.0, 99.7, 100.8, 0.0);
369    /// assert!(candle.is_shooting_star());
370    /// ```
371    fn is_shooting_star(&self) -> bool {
372        self.is_inverted_hammer()
373    }
374
375    /// Identifies a Spinning Top pattern, a signal of market indecision and equilibrium.
376    ///
377    /// This single-candle pattern features a small body centered within the trading range
378    /// with relatively long upper and lower shadows of similar length. The small body shows
379    /// little net movement from open to close despite the larger trading range.
380    ///
381    /// **Trading Significance**:
382    /// - Indicates indecision and balance between buyers and sellers
383    /// - Often appears during consolidation phases or at potential reversal points
384    /// - Suggests weakening of the current trend when appearing after a strong directional move
385    /// - By itself provides limited directional bias; traders use it as an alert for potential change
386    ///
387    /// # Example
388    /// ```
389    /// use candlestick_rs::CandleStick;
390    /// let candle = (100.0, 105.0, 95.0, 100.5, 0.0);
391    /// assert!(candle.is_spinning_top());
392    /// ```
393    fn is_spinning_top(&self) -> bool {
394        self.body_range_ratio() < self.spinning_top_body_ratio()
395            && self.wick_range_ratio() > self.spinning_top_shadow_ratio()
396            && self.tail_range_ratio() > self.spinning_top_shadow_ratio()
397    }
398
399    /// Identifies a Doji pattern, a powerful signal of market equilibrium and indecision.
400    ///
401    /// This pattern forms when the opening and closing prices are virtually the same,
402    /// creating an extremely small or non-existent body with shadows extending above and below.
403    /// The Japanese word "doji" means "mistake" or "blunder," referring to the rare equality of open and close.
404    ///
405    /// **Trading Significance**:
406    /// - Represents perfect equilibrium between buyers and sellers
407    /// - Signals potential trend reversal, especially after extended price moves
408    /// - Indicates exhaustion of the prevailing trend and possible consolidation
409    /// - Traders use it as an alert to reduce position size or tighten stops
410    ///
411    /// # Example
412    /// ```
413    /// use candlestick_rs::CandleStick;
414    /// let candle = (100.0, 105.0, 95.0, 100.0, 0.0);
415    /// assert!(candle.is_doji());
416    /// ```
417    fn is_doji(&self) -> bool {
418        self.body_range_ratio() < self.doji_body_ratio()
419    }
420
421    /// Identifies a Long-Legged Doji, a volatility-based signal of strong market indecision.
422    ///
423    /// This distinctive doji variant has unusually long upper and lower shadows compared to its minimal body.
424    /// It shows significant price movement in both directions during the period, but ultimately
425    /// closing near the opening price.
426    ///
427    /// **Trading Significance**:
428    /// - Indicates extreme volatility and fierce battle between buyers and sellers
429    /// - Frequently signals an impending trend change when appearing in extended trends
430    /// - Represents exhaustion of the prevailing trend as neither side maintains control
431    /// - Particularly significant when occurring at support/resistance levels or after strong directional moves
432    ///
433    /// # Example
434    /// ```
435    /// use candlestick_rs::CandleStick;
436    /// let candle = (100.0, 110.0, 90.0, 100.2, 0.0);
437    /// assert!(candle.is_long_legged_doji());
438    /// ```
439    fn is_long_legged_doji(&self) -> bool {
440        self.is_doji()
441            && self.tail_range_ratio() > self.doji_long_leg_ratio()
442            && self.wick_range_ratio() > self.doji_long_leg_ratio()
443    }
444
445    /// Identifies a Dragonfly Doji, a specialized pattern suggesting potential bullish reversal.
446    ///
447    /// This doji variant has its open and close at or near the high of the period,
448    /// with virtually no upper shadow but a long lower shadow, creating a "T" shape.
449    /// It shows sellers pushing prices down during the period, but buyers regained control by the close.
450    ///
451    /// **Trading Significance**:
452    /// - Strong bullish reversal signal when appearing at the bottom of downtrends
453    /// - Indicates rejection of lower prices and return to the opening level
454    /// - More reliable when formed at key support levels or round numbers
455    /// - When appearing at market tops, can act as a warning signal of waning momentum
456    ///
457    /// # Example
458    /// ```
459    /// use candlestick_rs::CandleStick;
460    /// let candle = (100.0, 100.5, 90.0, 100.1, 0.0);
461    /// assert!(candle.is_dragonfly_doji());
462    /// ```
463    fn is_dragonfly_doji(&self) -> bool {
464        self.is_doji()
465            && self.tail_range_ratio() > self.doji_tail_ratio()
466            && self.wick_range_ratio() < self.doji_min_ratio()
467    }
468
469    /// Identifies a Gravestone Doji, a specialized pattern suggesting potential bearish reversal.
470    ///
471    /// This doji variant has its open and close at or near the low of the period,
472    /// with virtually no lower shadow but a long upper shadow, creating an inverted "T" shape.
473    /// It shows buyers pushing prices up during the period, but sellers regained control by the close.
474    ///
475    /// **Trading Significance**:
476    /// - Strong bearish reversal signal when appearing at the top of uptrends
477    /// - Indicates rejection of higher prices and return to the opening level
478    /// - Particularly ominous when formed at key resistance levels or after extended rallies
479    /// - Named for its resemblance to a gravestone, suggesting the "death" of the current uptrend
480    ///
481    /// # Example
482    /// ```
483    /// use candlestick_rs::CandleStick;
484    /// let candle = (100.0, 110.0, 99.5, 100.1, 0.0);
485    /// assert!(candle.is_gravestone_doji());
486    /// ```
487    fn is_gravestone_doji(&self) -> bool {
488        self.is_doji()
489            && self.wick_range_ratio() > self.doji_wick_ratio()
490            && self.tail_range_ratio() < self.doji_min_ratio()
491    }
492
493    /// Summarizes the price action for the candle
494    fn typical_price(&self) -> f64 {
495        (self.high() + self.low() + self.close()) / 3.0
496    }
497
498    /// Flow of money into or out
499    fn raw_money_flow(&self) -> f64 {
500        self.typical_price() * self.volume()
501    }
502}
503
504impl CandleStick for (f64, f64, f64, f64, f64) {
505    fn open(&self) -> f64 {
506        self.0
507    }
508
509    /// Returns the high price
510    fn high(&self) -> f64 {
511        self.1
512    }
513
514    /// Returns the low price
515    fn low(&self) -> f64 {
516        self.2
517    }
518
519    /// Returns the close price
520    fn close(&self) -> f64 {
521        self.3
522    }
523
524    /// Returns the volume
525    fn volume(&self) -> f64 {
526        self.4
527    }
528}
529
530impl CandleStick for &(f64, f64, f64, f64, f64) {
531    fn open(&self) -> f64 {
532        self.0
533    }
534
535    /// Returns the high price
536    fn high(&self) -> f64 {
537        self.1
538    }
539
540    /// Returns the low price
541    fn low(&self) -> f64 {
542        self.2
543    }
544
545    /// Returns the close price
546    fn close(&self) -> f64 {
547        self.3
548    }
549
550    /// Returns the volume
551    fn volume(&self) -> f64 {
552        self.4
553    }
554}