Indicators¶
The tradetropy.ta package: built-in technical studies, order-flow indicators and
the base classes for writing your own.
ta ¶
Indicator ¶
Base class for indicators. Only requires calculate.
calculate(source) source : 1D array [N] (single-source) or 2D [N x K] (multi-source) returns : array [N] (single-band) or [K x N] (multi-band, n_outputs > 1) NaN where the window is not complete
CLASS ATTRIBUTES (override in subclasses)¶
name : str Short lowercase identifier. E.g.: "sma", "rsi", "bb".
IndicatorCategory
Functional classification. Determines the default overlay when plot_config.overlay is None (see _CATEGORY_OVERLAY_DEFAULTS).
list[str]
Names of PRICE output series accessible by the user. Can be class attribute (static) or instance attribute (in init for indicators where names depend on parameters). Do not include timestamp series (those go in ts_band_indices). Examples: ["upper","mid","lower"] for BB, ["pivot_high","pivot_low"] for PivotHighLow.
list[int]
Indices of internal timestamp auxiliary bands. Plotting excludes them from rendering and uses them to reposition markers at the real bar.
list[str]
Names for ts bands, in the same order as ts_band_indices. Enables attribute access from on_data(): self.cpivot.ts_high[-1] -> real timestamp of last PH self.cpivot.ts_low[-1] -> real timestamp of last PL Opt-in: leave [] if explicit names are not needed. Index access (self.cpivot[2][-1]) always works.
IndicatorPlotConfig
Visualization configuration for the indicator. Indicators should assign self.plot_config in init to declare their defaults. add_indicator() can override any field.
METHODS¶
col_name(symbol, col_source) -> str Internal DataStore key. Do not show to user.
display_name() -> str Human-readable name for legends. Used when plot_config.name is None.
plot_config
property
writable
¶
Visualization config. Subclasses should override this by assigning self.plot_config = IndicatorPlotConfig(...) in init. Default here is an empty config - overlay is resolved by category.
calculate ¶
Calculate the indicator over the full array. Must be vectorized.
draw ¶
Optional: emit declarative draw primitives for geometry that is not a per-bar series (e.g. a volume-by-price histogram, price-time zones).
Return a list of primitives from :mod:tradetropy.ta.draw (HBars,
HLines, Segments, Points, Rects, Labels); a single
generic renderer turns them into glyphs for both the static and live
charts, so no plotting-package code is needed. The default returns no
primitives: ordinary series indicators draw purely from their
IndicatorPlotConfig.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cfg
|
IndicatorPlotConfig | None
|
The effective plot config for this use (after add_indicator() overrides). May be None. |
None
|
interval_ms
|
int | None
|
OHLC interval, available for geometry that needs candle-width anchoring (e.g. VPVR). |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
list
|
Draw primitives, or [] when the indicator draws as series only. |
Source code in src/tradetropy/ta/base.py
refs
classmethod
¶
Build the ColumnRef list this indicator consumes, in the expected order.
Generic convenience so a multi-source indicator does not force the
caller to spell out every column by hand. Reads cls.source_cols and
resolves each name against the proxy:
self.stoch = self.add_indicator(
Stochastic.refs(self.btc), Stochastic(),
)
Equivalent to passing the proxy directly to add_indicator (which
calls :meth:default_refs). Passing an explicit ColumnRef list stays
supported. Indicators with tick-specific columns (order flow) override
this.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proxy
|
OhlcProxy | TickProxy
|
An already-subscribed source proxy. |
required |
Returns:
| Type | Description |
|---|---|
list
|
list[ColumnRef]: One ColumnRef per name in |
Source code in src/tradetropy/ta/base.py
default_refs ¶
Resolve the source columns when the proxy is passed directly to
add_indicator (add_indicator(self.btc, Stochastic())).
Instance-level counterpart of :meth:refs; reads self.source_cols
so subclasses that make source_cols an instance attribute (dynamic
columns) resolve correctly. Order-flow indicators override this to build
tick-specific refs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proxy
|
OhlcProxy | TickProxy
|
An already-subscribed source proxy. |
required |
Returns:
| Type | Description |
|---|---|
list
|
list[ColumnRef]: One ColumnRef per name in |
Source code in src/tradetropy/ta/base.py
col_name ¶
Internal DataStore key. Do not use as display label.
Source code in src/tradetropy/ta/base.py
display_name ¶
Human-readable name for legends and panel titles.
Examples:
RSI(14) -> "RSI(14)" SMA(20) -> "SMA(20)" BollingerBands() -> "BB(20)" PivotHighLow(3) -> "Pivot(3)"
Source code in src/tradetropy/ta/base.py
IndicatorPlotConfig
dataclass
¶
IndicatorPlotConfig(plot: bool = True, overlay: bool | None = None, panel_height: int = 100, panel_title: str | None = None, name: str | list[str] | None = None, zorder: int = 0, color: str | list[str] | None = None, line_width: float | list = 1.8, line_dash: str | list = 'solid', line_alpha: float | list = 0.9, renderer: str | list = 'line', front_dim: int | None = None, marker: str | list = 'circle', marker_size: int | list = 8, marker_alpha: float | list = 0.85, marker_fill: bool | list = True, marker_line_width: float | list = 1.0, bar_width_factor: float | list = 0.8, bar_align: str | list = 'center', bar_alpha: float | list = 0.75, bar_color_positive: str | list | None = None, bar_color_negative: str | list | None = None, show_legend: bool | None = None, reference_lines: list[dict] = list(), autoscale: bool = False, exclude_from_autoscale: bool = False, rect_fill_alpha: float = 0.15, rect_line_alpha: float = 0.6, rect_line_width: float = 1.0, label_font_size: str = '9pt', label_x_offset: int = 5, label_y_offset: int = 5, label_text_align: str = 'left', label_text_baseline: str = 'bottom', arrow_size: int = 10, arrow_length: int = 0)
Complete visualization configuration for an indicator.
Visibility fields¶
plot : False -> do not render in any panel. overlay : True -> on top of OHLC / False -> own panel. None -> resolved from indicator category (see _CATEGORY_OVERLAY_DEFAULTS). panel_height : height in px of own panel (only if overlay=False). panel_title : Y-axis title when indicator is in its own panel. None -> uses name (if str) or display_name() of indicator. name : legend label. - str -> single legend entry controlling all dimensions with one click. - list[str] -> one entry per dimension, independent clicks. - None -> uses display_name() of indicator. zorder : render order within panel (higher = on top).
Line fields (when scatter=False) - scalar or list per dimension¶
color : str | list[str] | None - color per dimension or single for all. None -> automatic color cycle. line_width : float | list[float] - line width per dimension. line_dash : LineDash | list[LineDash] - line style per dimension. line_alpha : float | list[float] - opacity per dimension.
Scatter fields (when scatter=True) - scalar or list per dimension¶
scatter : True -> render as points instead of line. marker : MarkerType | list[MarkerType] - marker shape per dimension. marker_size : int | list[int] - marker size per dimension. marker_alpha : float | list[float] - opacity per dimension. marker_fill : bool | list[bool] - fill per dimension. marker_line_width : float | list[float] - border width per dimension.
Reference lines¶
reference_lines : list of dicts with horizontal lines in the panel. Format: {"value": float, "color": str, "dash": str, "label": str, "width": float}
Per-dimension style usage¶
Bollinger Bands with differentiated lines:
self.bb = self.add_indicator(
self.btc.close_ref, BollingerBands(20, 2.0),
name="BB(20)", # one legend -> one click
line_dash=["solid", "dashed", "solid"],
line_width=[1.5, 1.0, 1.5],
line_alpha=[0.9, 0.6, 0.9],
color=["#FF6B35", "#888888", "#3B82F6"],
)
Legend per dimension (independent clicks):
self.bb = self.add_indicator(
self.btc.close_ref, BollingerBands(20),
name=["BB Upper", "BB Mid", "BB Lower"],
)
Explicit panel title:
self.rsi = self.add_indicator(
self.btc.close_ref, RSI(14),
panel_title="RSI",
)
resolve_overlay ¶
Returns the effective overlay value, resolving None from the category.
Source code in src/tradetropy/ta/base.py
dim ¶
Returns the field value for dimension idx.
Delegates to _resolve_dim() - supports scalar and list.
Example
pc.dim("line_dash", 0) -> "solid" pc.dim("line_dash", 1) -> "dashed" (if line_dash is a list)
Source code in src/tradetropy/ta/base.py
SMA ¶
Bases: Indicator
Simple Moving Average. source: close [N]
Usage on ticks
self.sma_price = self.add_indicator(self.btc.price_ref, SMA(length=20))
Usage on OHLC candles
self.btc_1m = self.subscribe_ohlc("BTCUSDT", interval_min=1) self.sma_close = self.add_indicator(self.btc_1m.close_ref, SMA(length=10))
Source code in src/tradetropy/ta/trend.py
EMA ¶
Bases: Indicator
Exponential Moving Average. alpha = 2 / (length + 1).
Seed: SMA of the first length bars.
source: close [N]
Usage
self.ema_close = self.add_indicator(self.btc_1m.close_ref, EMA(length=20))
Source code in src/tradetropy/ta/trend.py
MACD ¶
Bases: Indicator
Moving Average Convergence Divergence. Produces 3 series: macd, signal, histogram. source: close [N]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fast
|
fast EMA period (default 12) |
required | |
slow
|
slow EMA period (default 26) |
required | |
signal
|
signal EMA period over MACD line (default 9) |
required |
Usage
self.btc = self.subscribe_ohlc("BTCUSDT", timeframe='1m') self.macd = self.add_indicator( self.btc.close_ref, MACD(12, 26, 9), plot=True, overlay=False, )
Access in on_data(): self.macd.macd[-1] -> MACD value of current (partial) candle self.macd.signal[-1] -> signal line self.macd.histogram[-1] -> histogram (MACD - Signal)
# Bullish crossover:
if self.macd.macd[-2] < self.macd.signal[-2] and \
self.macd.macd[-1] > self.macd.signal[-1]:
... # golden cross
Note
- The seed of each EMA is the SMA of the first
lengthbars, same as the EMA in this module. - min_periods = slow + signal - 1 (minimum to have a valid histogram)
- The histogram is rendered as colored bars (green/red).
Source code in src/tradetropy/ta/trend.py
RSI ¶
Bases: Indicator
Relative Strength Index. source: close [N]
Source code in src/tradetropy/ta/momentum.py
calculate ¶
Wilder RSI. O(N) with Python loop.
Source code in src/tradetropy/ta/momentum.py
ATR ¶
Bases: Indicator
Average True Range. source: HLC [N×3]
Usage
self.bb = self.add_indicator( self.btc.close_ref, BollingerBands(20, 2.0), name=["BB Upper", "BB Mid", "BB Lower"], plot=True, overlay=True, color=["#FF6B35", "#888888", "#3B82F6"], )
Access
self.bb.upper[-1] -> upper band of partial candle self.bb.mid[-1] -> mid (SMA) self.bb.lower[-1] -> lower band self.bb[0][-1] -> equivalent to upper
Source code in src/tradetropy/ta/volatility.py
BollingerBands ¶
Bases: Indicator
Bollinger Bands. Produces 3 series: upper, mid, lower. source: close [N]
Usage
self.bb = self.add_indicator( self.btc.close_ref, BollingerBands(20, 2.0), name=["BB Upper", "BB Mid", "BB Lower"], plot=True, overlay=True, color=["#FF6B35", "#888888", "#3B82F6"], )
Access
self.bb.upper[-1] -> upper band of partial candle self.bb.mid[-1] -> mid (SMA) self.bb.lower[-1] -> lower band self.bb[0][-1] -> equivalent to upper
Source code in src/tradetropy/ta/volatility.py
VolumeProfile ¶
VolumeProfile(period='1d', tick_size: float | None = None, bins: int = 100, value_area_pct: float = 0.7, va_recompute_every: int = 1, view: str = 'session', nodes: str | None = None, node_prominence: float = 0.1, max_nodes: int | None = None, anchor=None, buy_color: str = DEFAULT_VP_BUY, sell_color: str = DEFAULT_VP_SELL, poc_color: str = DEFAULT_VP_POC, hvn_color: str = DEFAULT_VP_HVN, lvn_color: str = DEFAULT_VP_LVN)
Bases: _VolumeProfileBase
Kline-based volume profile (uniform high-low distribution).
Source columns (use VolumeProfile.refs(ohlc_proxy)):
[ts, open, high, low, close, volume]
Each candle's volume is spread uniformly across the price levels between its low and high. Aggressor side is approximated from candle direction: a bullish candle (close >= open) counts as ask (buy) volume, a bearish one as bid (sell) volume.
Source code in src/tradetropy/ta/volume.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
refs
staticmethod
¶
Build the ColumnRef list for this indicator in the expected order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ohlc_proxy
|
OhlcProxy
|
Proxy returned by subscribe_ohlc(). |
required |
Returns:
| Type | Description |
|---|---|
|
list[ColumnRef]: [ts, open, high, low, close, volume] refs. |
Source code in src/tradetropy/ta/volume.py
RollingVolumeProfile ¶
RollingVolumeProfile(length: int = 200, tick_size: float | None = None, bins: int = 100, value_area_pct: float = 0.7, va_recompute_every: int = 1, nodes: str | None = None, node_prominence: float = 0.1, max_nodes: int | None = None, buy_color: str = DEFAULT_VP_BUY, sell_color: str = DEFAULT_VP_SELL, poc_color: str = DEFAULT_VP_POC, hvn_color: str = DEFAULT_VP_HVN, lvn_color: str = DEFAULT_VP_LVN)
Bases: VolumeProfile
Kline-based volume profile over a sliding window of the last length bars.
source: ts, open, high, low, close, volume [N×6]
Unlike VolumeProfile (which resets every period and gives a saw-tooth
developing series), this profile never resets: at each bar it aggregates the
trailing length candles, producing continuous POC / VAH / VAL lines
suitable for plotting and for reading in on_data()::
self.rvp = self.add_indicator(self.btc, RollingVolumeProfile(length=200))
# in on_data():
if self.btc.close[-1] > self.rvp.vah[-1]:
...
The single histogram exposed for plotting is the profile of the current (most recent) window, drawn at the right edge like TradingView's VPVR.
Initialize a rolling volume profile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
int
|
Number of trailing candles aggregated into the profile. |
200
|
tick_size
|
float or None
|
Price level size; inferred when None. |
None
|
bins
|
int
|
Target number of levels, used only when tick_size is None. |
100
|
value_area_pct
|
float
|
Fraction of volume defining the value area. |
0.7
|
va_recompute_every
|
int
|
Recompute the value area every N bars. |
1
|
nodes
|
str or None
|
HVN/LVN detection (see _VolumeProfileBase). The
nodes cover the trailing |
None
|
node_prominence
|
float
|
Minimum node strength (0..1) to keep. |
0.1
|
max_nodes
|
int or None
|
Optional cap per node kind. |
None
|
buy_color
|
str
|
Color of the buy-aggressor (ask) volume segment. |
DEFAULT_VP_BUY
|
sell_color
|
str
|
Color of the sell-aggressor (bid) volume segment. |
DEFAULT_VP_SELL
|
poc_color
|
str
|
Color of the point-of-control line. |
DEFAULT_VP_POC
|
hvn_color
|
str
|
Color of the high-volume node markers. |
DEFAULT_VP_HVN
|
lvn_color
|
str
|
Color of the low-volume node markers. |
DEFAULT_VP_LVN
|
Source code in src/tradetropy/ta/volume.py
period_profiles ¶
Single render-ready profile of the most recent window (VPVR-style).
Returns a one-element list (or empty) shaped like the per-period dicts consumed by the plotting layer, so build_volume_profile_source() can draw it at the right edge via view="visible".
Source code in src/tradetropy/ta/volume.py
LargeTrades ¶
LargeTrades(threshold='p99', by: str = 'volume', window: int = 2000, aggregate_ms: int = 0, min_gap_ms: int = 0, scale: str = 'sqrt', min_size: float = 10.0, max_size: float = 40.0, buy_color: str = DEFAULT_BUY_COLOR, sell_color: str = DEFAULT_SELL_COLOR, fill: bool = False, fill_alpha: float | None = None, label_color: str | None = None, line_width: float = 1.6, label: 'str | None' = 'volume', label_font_size: str = '7pt')
Bases: Indicator
Large-trade detector and bubble overlay (order flow).
Source columns (use LargeTrades.refs(tick_proxy) or pass the tick proxy
directly): [ts, price, volume, flags, bid, ask].
Outputs - [4 x N] (1 price band + 3 auxiliary bands):
Price band (accessible by the user, drawn as bubbles): row 0 : price - price of a detected large trade at this tick (NaN otherwise).
Auxiliary bands (excluded from rendering, exposed for on_data() access
via ts_output_names):
row 1 : volume - traded volume of the large trade (NaN otherwise).
row 2 : notional - price * volume of the large trade (NaN otherwise).
row 3 : side - aggressor side (+1 buy / -1 sell), NaN otherwise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
threshold
|
float | str
|
What makes a trade "large".
- float: absolute magnitude threshold.
- 'pXX': trailing quantile (e.g. 'p99' = top 1% within |
'p99'
|
by
|
str
|
Magnitude metric: 'volume', 'notional' (price * volume) or
'delta' (net aggression). 'delta' is only meaningful with
|
'volume'
|
window
|
int
|
Trailing window (in events) for relative thresholds. |
2000
|
aggregate_ms
|
int
|
Execution-burst window in ms. When > 0, consecutive trades are merged before thresholding (same-side for volume/notional, net signed for delta); 0 disables aggregation (per-trade detection). |
0
|
min_gap_ms
|
int
|
Minimum spacing in ms between detections (anti-cluster; 0 disables it). |
0
|
scale
|
str
|
Bubble sizing: 'sqrt' (area proportional, default) or 'linear'. |
'sqrt'
|
min_size
|
float
|
Smallest bubble diameter in px. |
10.0
|
max_size
|
float
|
Largest bubble diameter in px. |
40.0
|
buy_color
|
str
|
Color for buy-aggressor bubbles (default blue). |
DEFAULT_BUY_COLOR
|
sell_color
|
str
|
Color for sell-aggressor bubbles (default pink). |
DEFAULT_SELL_COLOR
|
fill
|
bool
|
True fills the circle, False border only (hollow). |
False
|
fill_alpha
|
float
|
Fill transparency (0.0-1.0). Only applies when fill=True. None = uses default alpha (0.35). |
None
|
label_color
|
str | None
|
Color of the internal labels (magnitude labels). None = uses the same color as the circle border. |
None
|
line_width
|
float
|
Border width of the hollow circles. |
1.6
|
label
|
str | None
|
Magnitude number drawn next to each bubble: 'volume', 'notional', 'delta', or None to hide labels. The label always shows the trade/burst's real magnitude (not a sequential index). |
'volume'
|
label_font_size
|
str
|
Font size for the magnitude labels. |
'7pt'
|
Source code in src/tradetropy/ta/order_flow/large_trades.py
refs
staticmethod
¶
Build the ColumnRef list for this indicator in the expected order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tick_proxy
|
TickProxy
|
Proxy returned by subscribe_ticks(). |
required |
Returns:
| Type | Description |
|---|---|
|
list[ColumnRef]: [ts, price, volume, flags, bid, ask] refs. |
Source code in src/tradetropy/ta/order_flow/large_trades.py
default_refs ¶
detect ¶
Run causal detection over a [N x 6] source matrix.
Shared by calculate (backtest) and the live streaming path so both flag the exact same trades.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
ndarray
|
[N x 6] columns [ts, price, volume, flags, bid, ask]. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
|
dict
|
'side' and 'events'. |
Source code in src/tradetropy/ta/order_flow/large_trades.py
calculate ¶
Detect large trades and emit the [4 x N] band matrix.
Bands are anchored at each detected event's representative tick (the
last trade of an aggregated burst) and carry the burst's aggregated
magnitudes, so self.volume / self.notional reflect the merged
execution, not a single child print.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
ndarray
|
[N x 6] columns [ts, price, volume, flags, bid, ask]. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
np.ndarray: [4 x N] - row 0 price (NaN unless large), rows 1-3 |
ndarray
|
volume / notional / side (NaN unless large). |
Source code in src/tradetropy/ta/order_flow/large_trades.py
event_labels ¶
Build the magnitude label strings for the detected events.
Returns:
| Type | Description |
|---|---|
list
|
list[str]: One compact magnitude string per detected trade, or an |
list
|
empty list when |
Source code in src/tradetropy/ta/order_flow/large_trades.py
draw ¶
Emit the large-trade bubbles as a Points primitive plus magnitude Labels.
Bubbles are hollow circles (border only) sized per trade and colored by aggressor side; one label per bubble shows the real magnitude. Colors come from the indicator object (buy_color / sell_color), theme-independent.
Source code in src/tradetropy/ta/order_flow/large_trades.py
live_refresh ¶
Re-run causal detection over the source tick window (live mode).
Tick-mounted indicators are not recomputed by the standard live indicator path (which reads OHLC ring bands), so the generic live primitive updater calls this hook before draw(): it rebuilds the source matrix from the tick proxy's current window and reuses calculate for exact parity with the backtest. No-op when the proxy is empty.
Source code in src/tradetropy/ta/order_flow/large_trades.py
CandlePatterns ¶
CandlePatterns(*, window: int = 100, doji_body='p10', small_body='p50', long_body='p60', wick='p80', marubozu_wick='p20', doji_frac: float = 0.1, zscore_len: int = 50, zscore_thresh: float = 0.5, context_filter: bool = True, horizon: int = 10, show_stats: bool = False, bull_color: str = _CANDLE_BULL_COLOR, bear_color: str = _CANDLE_BEAR_COLOR, neutral_color: str = _CANDLE_NEUTRAL_COLOR, label_offset: float = 0.01)
Bases: Indicator
Statistical candlestick pattern detector (annotation) with adaptive percentile thresholds, a z-score context filter and causal efficacy tracking.
Unlike a "detect anything" pattern list, the classification is relative to
the recent distribution (a hammer needs a lower wick beyond the wick
percentile of the last window bars and a small body) and, optionally,
to the price context (a hammer only counts when price is stretched down,
z-score of close vs its rolling mean <= -zscore_thresh). All detection
is pure and causal (see tradetropy.ta._candlestick), so it is identical
in backtest, live and replay.
It draws the pattern name on each candle (below bullish, above bearish) and
exposes a public query API through the handle add_indicator returns, so
a strategy reads the current pattern - and each pattern's empirical hit-rate
- causally from on_data().
Usage
def init(self): self.btc = self.subscribe_ohlc("BTCUSDT", "1h", window_size=300) # Proxy-direct (source_cols resolves ts+OHLC), or CandlePatterns.refs(self.btc). self.candles = self.add_indicator(self.btc, CandlePatterns())
def on_data(self): if self.candles.last_pattern() == "Bullish Engulfing": # Only act if it has historically worked on THIS symbol/timeframe. eff = self.candles.efficacy("Bullish Engulfing") if eff["sample_size"] >= 20 and eff["hit_rate"] > 0.55: self.sesh.buy("BTCUSDT", volume=1)
Output bands (data, read in on_data via the handle): code[-1] -> numeric pattern code of the current bar (0 = none) direction[-1] -> +1 bullish / -1 bearish / 0 neutral
Query API (through the add_indicator() handle): last_pattern() -> name of the current bar's pattern ('none') pattern_at(i) -> name at offset i (e.g. -1 last, -2 previous) patterns(n=None) -> list of names (last n, or all in the window) is_bullish() / is_bearish() -> bias of the current bar name_of(code) -> label for a numeric code efficacy(pattern) -> {'hit_rate', 'sample_size', 'wins', 'name'} for one pattern (name or code), causal efficacy_all() -> {name: {...}} for every pattern seen so far
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
Trailing window for the adaptive size percentiles. |
100
|
doji_body/small_body/long_body/wick/marubozu_wick
|
str | float
|
Threshold
specs (absolute, 'pXX' quantile or 'Nx' median multiple) - see
|
required |
doji_frac
|
float
|
Max body/range for a doji. |
0.1
|
zscore_len
|
int
|
Rolling window for the context z-score. |
50
|
zscore_thresh
|
float
|
|z| beyond which price counts as stretched. |
0.5
|
context_filter
|
bool
|
Gate hammer/hanging-man/inverted-hammer/shooting -star by context (default True). |
True
|
horizon
|
int
|
Bars ahead used by the efficacy hit-rate. |
10
|
show_stats
|
bool
|
Append each pattern's causal hit-rate to its drawn
label (default False; the full dashboard is |
False
|
bull_color/bear_color/neutral_color
|
str
|
Label colors by direction. |
required |
label_offset
|
float
|
Label distance from the wick, in percent of price. |
0.01
|
Source code in src/tradetropy/ta/annotations.py
calculate ¶
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
ndarray
|
[N x 5] - ts(0), open(1), high(2), low(3), close(4) |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
[2 x N] - row 0 = pattern code (0 = none), row 1 = direction bias. |
Source code in src/tradetropy/ta/annotations.py
set_query_source ¶
last_pattern ¶
Name of the current (last) bar's pattern ('none' if there is none).
Source code in src/tradetropy/ta/annotations.py
pattern_at ¶
Name of the pattern at offset i within the causal window.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
i
|
int
|
Index (e.g. -1 current bar, -2 previous). Out of range returns 'none'. |
required |
Source code in src/tradetropy/ta/annotations.py
patterns ¶
List of pattern names over the causal window.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int | None
|
Return only the last |
None
|
Source code in src/tradetropy/ta/annotations.py
is_bullish ¶
True if the current bar's pattern has a bullish bias.
is_bearish ¶
True if the current bar's pattern has a bearish bias.
name_of ¶
efficacy ¶
Causal empirical hit-rate for one pattern (name or code).
Only signals resolved by the current bar (s + horizon bars elapsed)
are counted, so the statistic never uses future information.
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
{'hit_rate', 'sample_size', 'wins', 'name'}. An unseen pattern yields sample_size 0 and hit_rate NaN. |
Source code in src/tradetropy/ta/annotations.py
efficacy_all ¶
Causal empirical hit-rate for every pattern seen so far, keyed by name.
Returns:
| Type | Description |
|---|---|
dict
|
dict[str, dict]: name -> {'hit_rate', 'sample_size', 'wins', 'code'}. |
Source code in src/tradetropy/ta/annotations.py
draw ¶
Emit the pattern name over each detected candle: below bullish candles,
above bearish/neutral candles. With show_stats the pattern's causal
hit-rate (as of the last bar) is appended to the label.
Source code in src/tradetropy/ta/annotations.py
ManualMarks ¶
Bases: Indicator
Strategy-driven manual marks: segments with a start and an end that the strategy adds/updates/removes from on_data().
A mark is a line in price x time space defined by two endpoints (ts0, price0) -> (ts1, price1). Use the same price for both endpoints for a horizontal level; use different prices for a sloped line. ts1 (and price1) can be left open (None) while the mark is "live" - draw() extends it to the current bar/tick until the strategy closes it.
This indicator draws no series (its clock band is a NaN placeholder,
required only so add_indicator() returns a query-capable proxy - see
n_outputs note above the class attributes). Attach it to any
already-subscribed source purely as a causal clock
(ManualMarks.refs(proxy)), then reach it through the handle
add_indicator() returns.
Usage
def init(self): self.btc = self.subscribe_ohlc('BTCUSDT', '5m') self.marks = self.add_indicator( ManualMarks.refs(self.btc), ManualMarks(), )
def on_data(self): if some_signal: self.mark_id = self.marks.add_mark( price0=self.btc.close[-1], ts0=self.ts, color='#F6465D', label='Signal', ) if close_condition and self.mark_id is not None: self.marks.close_mark(self.mark_id, ts1=self.ts, price1=self.btc.close[-1])
Query API (through the add_indicator() handle): add_mark(price0, ts0=None, price1=None, ts1=None, color=None, dash='solid', width=1.6, label=None, marker=None) -> int Adds a mark and returns its id. ts0 defaults to the current clock (self.ts equivalent); price1/ts1 left None keep the mark open (draw() extends it to the latest known ts). marker (optional) also drops a point at (ts0, price0) - e.g. to flag the pivot that triggered the mark. update_mark(mark_id, **fields) -> None Updates any field of an existing mark (e.g. drag the open end). close_mark(mark_id, ts1=None, price1=None) -> None Fixes the end of an open mark. ts1 defaults to the current clock; price1 defaults to the mark's own price0 (horizontal level). remove_mark(mark_id) -> None Deletes a mark (no-op if the id does not exist). clear_marks() -> None Deletes every mark. marks -> list[dict] Read-only snapshot of the current marks (copies).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_marks
|
int
|
Ring cap on stored marks (oldest dropped first, 0 = unlimited). Keeps a long-running live session bounded. |
500
|
Source code in src/tradetropy/ta/manual_marks.py
refs
staticmethod
¶
Build the ColumnRef list for this indicator: only a causal clock.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proxy
|
OhlcProxy | TickProxy
|
Any already-subscribed source. |
required |
Returns:
| Type | Description |
|---|---|
|
list[ColumnRef]: [ts] ref (close is not needed, kept minimal). |
Source code in src/tradetropy/ta/manual_marks.py
add_mark ¶
add_mark(price0: float, ts0: 'int | None' = None, price1: 'float | None' = None, ts1: 'int | None' = None, *, color: 'str | None' = None, dash: str = 'solid', width: float = 1.6, alpha: float = 0.9, label: 'str | None' = None, marker: 'str | None' = None) -> int
Add a manual mark and return its id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
price0
|
float
|
Start price. |
required |
ts0
|
int | None
|
Start timestamp (epoch ms). None -> current causal clock (the ts of the last processed bar/tick). |
None
|
price1
|
float | None
|
End price. None -> mark stays open (horizontal at price0 until closed or drawn to "now"). |
None
|
ts1
|
int | None
|
End timestamp. None -> mark stays open. |
None
|
color
|
str | None
|
Line/point color. None -> module default. |
None
|
dash
|
str
|
Line style ('solid'|'dashed'|'dotted'|'dashdot'|'dotdash'). |
'solid'
|
width
|
float
|
Line width. |
1.6
|
alpha
|
float
|
Line opacity. |
0.9
|
label
|
str | None
|
Optional floating text at the start point. |
None
|
marker
|
str | None
|
Optional marker shape dropped at (ts0, price0). |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The new mark's id (pass it to update_mark/close_mark/remove_mark). |
Source code in src/tradetropy/ta/manual_marks.py
update_mark ¶
Update fields of an existing mark (e.g. drag the open end as price moves). Unknown mark_id is a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mark_id
|
int
|
Id returned by add_mark(). |
required |
**fields
|
Any of price0/ts0/price1/ts1/color/dash/width/alpha/ label/marker. |
{}
|
Source code in src/tradetropy/ta/manual_marks.py
close_mark ¶
Fix the end of an open mark.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mark_id
|
int
|
Id returned by add_mark(). |
required |
ts1
|
int | None
|
End timestamp. None -> current causal clock. |
None
|
price1
|
float | None
|
End price. None -> the mark's own price0 (horizontal level). |
None
|
Source code in src/tradetropy/ta/manual_marks.py
remove_mark ¶
clear_marks ¶
draw ¶
Emit the current marks as Segments (sloped/horizontal lines), plus optional Points (markers) and Labels for marks that requested them.
Open marks (price1/ts1 is None) are extended to the latest causal clock so a live/backtest mark is visible as soon as it is added, without waiting for it to be closed.