Indicators¶
The tradetropy.ta package ships the classic technical studies plus market
structure and order-flow tools, all through a single declarative contract. You
add any of them with add_indicator() and read their developing values in
on_data().
Adding an indicator¶
add_indicator(source, indicator) attaches an indicator to a source and returns
a handle. Single-output indicators are read with [-1]; multi-output ones
expose named bands:
from tradetropy.ta import SMA, BollingerBands
self.sma = self.add_indicator(self.btc.close, SMA(20))
self.bb = self.add_indicator(self.btc.close, BollingerBands(20, 2.0))
# in on_data():
fast = self.sma[-1]
upper, mid, lower = self.bb.upper[-1], self.bb.mid[-1], self.bb.lower[-1]
Multi-source indicators declare the columns they expect through a .refs()
helper or by passing the *_ref accessors in order:
from tradetropy.ta import Alligator, PivotPoints
self.alli = self.add_indicator(
[self.btc.high_ref, self.btc.low_ref], Alligator(),
name=['Jaw', 'Teeth', 'Lips'],
)
self.piv = self.add_indicator(PivotPoints.refs(self.btc), PivotPoints('classic', '1d'))
You can override any visual field at add_indicator() time (name, color,
line_width, ...); it merges over the indicator's own plot configuration.
Session-based indicators¶
MarketSessions, SessionLevels and KillZones share the same UTC time-window
machinery but answer different questions:
MarketSessions- only exposes a binary in/out-of-session series per named session, plus background zones. Use it to filter trades by time of day.SessionLevels- the running open/high/low of the CURRENT session occurrence plus the open/high/low/close of the last CLOSED occurrence, projected forward as reference levels (Session High/Low, Asian Range, Previous Session H/L).KillZones- the same idea scoped to narrow ICT kill zones (London Open, NY Open, London Close, Asian) instead of whole sessions; each zone's high/low freezes when the window closes and stays projected as a breakout reference until the SAME zone opens again.
from tradetropy.ta import SessionLevels, KillZones
self.sl = self.add_indicator(
SessionLevels.refs(self.btc), SessionLevels(sessions=['london', 'new_york']),
plot=True,
)
self.kz = self.add_indicator(
KillZones.refs(self.btc), KillZones(windows=['london_open', 'ny_open']),
plot=True,
)
# in on_data():
prev_london_high = self.sl.london_prev_high[-1] # yesterday's London high
if self.kz.london_open_active[-1] == 0.0: # kill zone already closed
if self.btc.close[-1] > self.kz.london_open_high[-1]:
pass # breakout above the London Open kill zone range
Both accept the same predefined-string-or-custom-dict window format as
MarketSessions ({"name": "silver_bullet", "start": 10, "end": 11}).
Candlestick pattern detection¶
CandlePatterns is a statistical detector: it classifies a candle relative to
the recent distribution (e.g. a hammer needs a lower wick beyond an adaptive
percentile of the last window bars and a small body) rather than matching a
fixed shape, and can gate reversal patterns by price context (z-score of close
vs its rolling mean). Detection is pure and causal, so it is identical in
backtest, live and replay. It draws the pattern name on each candle and exposes
a query API - including each pattern's own causal hit-rate - through the handle
add_indicator() returns:
from tradetropy.ta import CandlePatterns
self.candles = self.add_indicator(self.btc, CandlePatterns())
# in on_data():
if self.candles.last_pattern() == 'Bullish Engulfing':
eff = self.candles.efficacy('Bullish Engulfing')
if eff['sample_size'] >= 20 and eff['hit_rate'] > 0.55:
self.sesh.buy('BTCUSDT', volume=1)
last_pattern() / pattern_at(i) / patterns(n) read the pattern at a bar
offset; is_bullish() / is_bearish() read the current bar's bias;
efficacy(pattern) / efficacy_all() return the causal hit-rate (only signals
whose horizon has elapsed are scored, so it never uses future bars).
Manual marks¶
ManualMarks lets a strategy draw its own segments/levels from on_data() -
useful for annotating signals, support/resistance a strategy computes at
runtime, or debugging a detector visually. A mark is a line from
(ts0, price0) to (ts1, price1); leaving the end open (None) keeps it
"live" until closed, extending to the latest bar/tick meanwhile:
from tradetropy.ta import ManualMarks
self.marks = self.add_indicator(ManualMarks.refs(self.btc), ManualMarks())
# in on_data():
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])
update_mark(mark_id, **fields) edits any field of an open mark;
remove_mark(mark_id) / clear_marks() delete marks; .marks returns a
read-only snapshot of every current mark.
Built-in catalog¶
- Trend / moving averages:
SMA,EMA,WMA,DEMA,TEMA,HMA,KAMA,FRAMA,VIDYA,MACD,Ichimoku,ParabolicSAR,Supertrend. - Oscillators / momentum:
RSI,Stochastic,StochasticRSI,CCI,WilliamsR,Momentum,ROC,CMO,TSI,TRIX,UltimateOscillator,DeMarker,RVI,OsMA,BullsPower,BearsPower,Aroon,Vortex,SchaffTrendCycle,PO,PPO,BOP,DPO,MassIndex,ADX. - Volatility:
BollingerBands,ATR,StdDev,Envelopes,KeltnerChannels,DonchianChannels. - Volume / order flow figures:
OBV,VWAP,VWMA,MFI,ChaikinAD,ChaikinOsc,ForceIndex,EMV,MarketFacilitationIndex. - Bill Williams:
Alligator,GatorOscillator,AwesomeOscillator,AcceleratorOscillator,Fractals. - Levels / structure:
PivotPoints,PivotHighLow,ConfirmedPivot,ZigZag,SwingHL,EqualHL,HHLL,NBS,FairValueGap,OrderBlock,MarketSessions,SessionLevels,KillZones. - Annotation:
CandlePatterns(statistical candlestick pattern detector with causal efficacy tracking),ManualMarks(strategy-driven marks/levels). - Volume profile:
VolumeProfile,TickVolumeProfile,RollingVolumeProfile. - Order flow:
LargeTrades,DeepTrades,DeltaBars,CVD,VolumeInfo,COT,DeepWall,DeepReload,StopRun- see Order flow and L2.
Writing your own indicator¶
Indicators are external: subclass Indicator, implement calculate(source),
set name / category / output_names, and assign a plot_config. A single
generic renderer turns any indicator's output into glyphs for both the static
and live charts - a normal indicator needs no special plotting code.
import numpy as np
from tradetropy.ta import Indicator, IndicatorPlotConfig
class SMA(Indicator):
name = 'sma'
category = 'trend' # trend|momentum|volatility|volume|structure|annotation|other
def __init__(self, length: int):
self.length = length
self.plot_config = IndicatorPlotConfig() # defaults are enough
@property
def min_periods(self) -> int:
return self.length
def calculate(self, source: np.ndarray) -> np.ndarray:
n = len(source)
out = np.full(n, np.nan, dtype=np.float64)
if n < self.length:
return out
cs = np.cumsum(source)
L = self.length
out[L - 1:] = (cs[L - 1:] - np.concatenate(([0.0], cs[:-L]))) / L
return out
calculate returns [N] for a single band or [K x N] for a multi-band
indicator (one row per name in output_names), with NaN during warmup.
category decides default placement: trend/volatility/structure/
annotation overlay on price; momentum/volume/other get their own panel.
Override with overlay=True|False and set panel_title/reference_lines for
own-panel indicators. Per-band styling (color, line_dash, line_width, ...)
accepts a scalar or a list (one entry per band).
For geometry that is not "one value per bar" (histograms, zones, session bands),
implement an optional draw() method returning declarative primitives (HBars,
HLines, Segments, Points, Rects, Labels from tradetropy.ta.draw). The
same generic renderer draws them in both charts.
Recursive indicators and engine parity
If your indicator carries running state (EMA, Wilder smoothing), set
warmup_factor on the class so the auto-warmup reserves enough bars to
converge before the first on_data(), keeping backtest and live/replay in
parity. RSI and MACD use warmup_factor = 5.
See the full API in the Indicators reference.