Markets / OHLC Feature Enhancements
This document covers the architectural additions introduced in the feat/markets-enhancements branch: three new harmonic pattern types, a composite signal score, multi-timeframe confirmation, backtest improvements, technical indicator overlays, live pattern alerts, and two new crypto symbols.
Pattern Detection Architecture Recap
The detection pipeline runs whenever new candles arrive and on admin-triggered redetection:
OhlcCandle[] (DB)
│
▼
findSwingPoints() ← pivot-lookback = 5 candles
│
▼
detectPatterns(swings) ← check each 5-swing window against all pattern checkers
│
▼
ohlcPatternService ← enriches with prevSwingPrice + signalScore, persists to DB
│
▼
broadcast(ohlc:pattern:detected) ← WebSocket push to all connected clients
The core detection code lives in backend/services/ohlcPatternDetector.ts; enrichment and persistence live in backend/services/ohlcPatternService.ts.
New Pattern Types
Three pattern types were added to the harmonic_pattern_type Postgres enum and to the HarmonicPatternType TypeScript union:
| Pattern | Key Fibonacci Ratios | Notes |
|---|---|---|
cypher | AB: 0.382–0.618 of XA; BC ext: 1.272–1.414; XD: 0.786 of XC | Uses XC range (not XA) for the D-point retracement — distinguishing feature vs Gartley/Bat |
three_drives | Each retracement ≈ 0.618 of preceding drive; drive legs ≈ equal (±10%) | 5-point window maps to 3 drives + 2 retracements; retracement tolerance = ±0.08 |
abcd | BC ≈ 0.618 retracement of AB (±0.08); CD ≈ AB in length (±10%) | X point is used only for direction validation; AB/CD equality is the primary signal |
Global tolerance for all “near” ratio checks is 5% (FIB_TOLERANCE = 0.05). Confidence is 1 − mean(ratio errors), clamped to [0, 1].
DB Schema
// backend/db/timescale/schemas/ohlcPatterns.ts
export const harmonicPatternTypeEnum = pgEnum('harmonic_pattern_type', [
'gartley',
'bat',
'butterfly',
'crab',
'shark',
'head_and_shoulders',
'cypher', // ← new
'three_drives', // ← new
'abcd', // ← new
]);Migration: backend/db/timescale/migrations/0005_common_katie_power.sql
SL/TP Levels (ohlcTradeSimulator.ts)
| Pattern | SL | TP1 | TP2 | TP3 |
|---|---|---|---|---|
cypher | Beyond X | 0.382 of XC | 0.618 of XC | 0.786 of XC |
three_drives | Beyond X | 1st drive high | 2nd drive high | D-point |
abcd | Beyond C extended | B (start of CD) | A (start of AB) | — (mirrors TP1) |
Composite Signal Score
Every detected pattern is scored [0, 1] and stored as signal_score on ohlc_patterns.
Formula
signalScore = harmonicConfidence × 0.4
+ rsiDivergence × 0.3
+ srConfluence × 0.3
| Component | Weight | How it is computed |
|---|---|---|
| Harmonic confidence | 40% | Raw confidence from the pattern checker (ratio accuracy) |
| RSI divergence | 30% | RSI-14 at the D-point candle: bullish if RSI < 30, bearish if > 70. Score = (30 − rsi) / 30 (bullish) or (rsi − 70) / 30 (bearish). Zero if RSI is neutral. |
| S/R confluence | 30% | 1 − dist / 0.002 when D-price is within 0.2% of prevSwingPrice; otherwise 0 |
prevSwingPrice is the prior S/R wick level found by scanning backward from X up to 120 candles (PREV_SR_LOOKBACK) for the nearest contiguous wick cluster that breaks X’s price level.
Schema Column
signalScore: numeric('signal_score', { precision: 5, scale: 4 }),The value is stored as a 4-decimal numeric string (e.g. "0.7340"). The service writes it with .toFixed(4).
Multi-Timeframe Confirmation
Endpoint
GET /api/ohlc/patterns/multi-tf?symbol=EUR%2FUSD
No auth required (same as all other read endpoints).
Logic (ohlcPatternService.getMultiTimeframeConfirmed)
- Fetches the 50 most recent patterns for each of the 5 tracked timeframes (
5m,15m,1h,4h,1d) in parallel. - Builds a
Map<"patternType:direction", Set<Timeframe>>. - Returns only entries where the set size is ≥ 2.
export interface MultiTfConfirmedPattern {
patternType: string;
direction: string;
timeframes: Timeframe[];
}Frontend Component
MultiTfConfirmationBadge is rendered in MarketsChart above the chart. It calls the RTK Query useGetMultiTfPatternsQuery(symbol) hook and displays a chip per confirmed combination. The badge is always visible for the current symbol regardless of the selected timeframe.
Backtest Enhancements
Date Range Filtering
The POST /api/ohlc/backtest request body accepts optional ISO 8601 date strings:
// backend/features/validation/schemas/ohlc.ts
from: z.string().datetime({ offset: true }).optional(),
to: z.string().datetime({ offset: true }).optional(),When omitted the service defaults to the last 7 days. The controller converts the strings via dayjs(from).toDate() before passing them to the service.
Partial-Close Simulation
The backtest now runs two parallel simulations per pattern:
| Simulation | Behaviour | Stored as |
|---|---|---|
| Standard | Single exit at SL, TP1, TP2, or TP3 (first hit wins) | pnlPips |
| Partial-close | 1/3 at TP1 (then SL moves to breakeven), 1/3 at TP2, 1/3 at TP3 | partialClosePnlPips |
The partial-close simulator (simulatePartialClose) tracks a currentSl that is reassigned to dPrice (breakeven) as soon as TP1 is reached. If SL fires after TP1, only the remaining open third is closed at the updated stop.
Max Drawdown
Computed server-side from the PnL series of all completed trades:
// Peak-to-trough drawdown on cumulative PnL curve
maxDrawdownPips: number; // absolute pips
maxDrawdownPct: number; // fraction of peak equity (4 decimal places)A client-side fallback with identical logic lives in frontend/features/ohlc/logic/calculateMaxDrawdown.ts for scenarios where the server result is not yet available.
Equity Curve Chart
EquityCurveChart.tsx receives the backtest trades array and renders a lightweight-charts LineSeries of cumulative PnL over time. It is rendered inside BacktestPanel below the summary stats table.
Technical Indicators
All indicators are computed client-side from the same candle array already loaded for the chart.
| Indicator | Location | Chart pane |
|---|---|---|
| EMA 20 | calculateEma(candles, 20) | Pane 0 (main) |
| EMA 50 | calculateEma(candles, 50) | Pane 0 (main) |
| EMA 200 | calculateEma(candles, 200) | Pane 0 (main) |
| Bollinger Bands | calculateBollingerBands(candles) | Pane 0 (main) — 3 dashed LineSeries |
| Volume histogram | HistogramSeries of candle.volume | Pane 3 (new) |
Pane layout:
Pane 0 main candles + EMA + Bollinger Bands + harmonic overlays
Pane 1 RSI (14)
Pane 2 MACD
Pane 3 Volume histogram (height = 80px, collapsible)
IndicatorControls
IndicatorControls.tsx renders MUI Chip toggles for each indicator group. Toggle state is stored in Redux (ohlcSlice.indicators) and the MarketsChart component reads it to show/hide series and resize pane heights via chart.panes()[n]?.setHeight(h).
EMA Implementation
Standard exponential moving average seeded with the SMA of the first period candles:
const multiplier = 2 / (period + 1);
// seed
let ema = sum(candles[(0).period - 1].close) / period;
// iterate
ema = (close - ema) * multiplier + ema;Bollinger Bands Implementation
20-period SMA ± 2 standard deviations (population σ, not sample):
const sma = window.reduce((s, c) => s + c.close, 0) / period;
const variance = window.reduce((s, c) => s + (c.close - sma) ** 2, 0) / period;
const stdDev = Math.sqrt(variance);
upper = sma + 2 * stdDev;
middle = sma;
lower = sma - 2 * stdDev;Live Pattern Alerts
Hook: useOhlcAlerts
// frontend/features/ohlc/hooks/useOhlcAlerts.ts
export const useOhlcAlerts = (symbol: string, timeframe: string) => {
const dispatch = useDispatch<AppDispatch>();
useWebSocket('ohlc:pattern:detected', (payload: unknown) => {
const event = payload as OhlcPatternDetectionEvent;
if (event.symbol === symbol && event.timeframe === timeframe) {
dispatch(addLiveAlert(event));
}
});
};The hook is called unconditionally in MarketsChart. It filters events by the currently selected symbol and timeframe so alerts from other instruments do not surface.
Redux Integration
Alerts accumulate in ohlcSlice.liveAlerts (addLiveAlert reducer). MarketsChart reads selectOhlcLiveAlerts and renders up to 3 simultaneous MUI Snackbar dismissible notifications.
WebSocket Event
type: 'ohlc:pattern:detected'
payload: OhlcPatternDetectionEvent (same shape returned by GET /api/ohlc/detection-events)
The event is broadcast by ohlcPatternService immediately after a new pattern is persisted and a detection event is created.
New Symbols
Two crypto symbols were added to TRACKED_SYMBOLS and YAHOO_FINANCE_SYMBOLS:
| Symbol | Data source | Pip multiplier | Chart price format |
|---|---|---|---|
BTC/USD | Yahoo Finance (BTC-USD) | 1 | 0 decimal places, minMove = 1 |
ETH/USD | Yahoo Finance (ETH-USD) | 10 | 2 decimal places, minMove = 0.01 |
For reference, existing symbols:
| Symbol | Data source | Pip multiplier | Chart price format |
|---|---|---|---|
EUR/USD | Twelve Data | 10 000 | 5 decimal places |
XAU/USD | Yahoo Finance | 100 | 2 decimal places |
WTI/USD | Yahoo Finance | 100 | 2 decimal places |
Pip multiplier (getPipMultiplier in ohlcTradeSimulator.ts) converts raw price differences to pip-equivalent units for PnL reporting. BTC uses a multiplier of 1 because a $1 price move represents one “unit” of significance at that price scale.
Price format (getPriceFormat in MarketsChart.tsx) is passed to lightweight-charts’ series priceFormat option to control axis label and tooltip precision.
Both new symbols follow the Yahoo Finance fetcher path (yahooFinanceFetcher.ts) using ticker strings BTC-USD and ETH-USD.
Related Docs
- Database Schema —
ohlc_patternstable structure and migration workflow - Architecture Overview — full request flow and directory structure