@dooboostore/algorithm

NPM versionBuild and TestLicense: MIT

Pure-function stock/candle analysis algorithms — technical indicators (SMA/EMA/MACD/RSI/OBV), trend-regime segmentation, and a rule-based trading strategy generator + backtest simulator. No DOM, no chart, no I/O — every export is a plain function/namespace operating on plain data.


Features

  • Technical indicators(trend.ts): SMA, EMA, MACD (line/signal/histogram), RSI, OBV as standalone series functions.
  • Trend-regime segmentation(TrendRange): turns a MACD+RSI+OBV bar series into contiguous, direction-labeled "zones" (up/down/side, or your owngroupBylabels), with a prefix-stable, deterministic algorithm — the same input prefix always produces the same committed zones even as more bars are appended.
  • Rule-based trading engine(TradingSimulator):findBestConfigderives a small set of buy/sell conditions (MA cross, MACD cross, RSI overbought/oversold) from candle physics-style features (velocity/acceleration/force/gravity relative to a long MA, PCA-projected "regime" score) plus an inlined gradient-boosting direction model;simulatereplays those conditions bar-by-bar into an actual trade ledger (fees, cooldowns, batching, stop-loss/take-profit, insufficient-funds handling).
  • Zero DOM/runtime dependencies— only depends on@dooboostore/core(forHashUtils). Safe to use in Node, browser, or worker contexts.

Public API

@dooboostore/algorithm's root (src/index.ts) re-exports exactly these modules:

  • Candle— theinterface CandleOHLCV bar type (stock/Candle.ts)
  • trend.ts—computeSmaSeries,computeEmaSeries,computeMacdSeries,computeRsiSeries,computeObvSeries
  • TrendRange— namespace withscoreBars,strengthBars,trendRangesand their types
  • TradingSimulator— namespace withestimateMarketRate,forecast,directionProbability,findBestConfig,simulateand their types

boostModel.ts/boostModelMacro.ts(the inlined gradient-boosting trees used internally bydirectionProbability) arenotre-exported from the package root — they're auto-generated model weights (seeInternal Model Filesbelow), not a public API surface.

Installation

# pnpmpnpm add @dooboostore/algorithm# npmnpm install @dooboostore/algorithm# yarnyarn add @dooboostore/algorithm

Quick Start

Technical indicators

import{ computeSmaSeries, computeRsiSeries, computeMacdSeries }from'@dooboostore/algorithm';constcloses = [10,10.5,11,10.8,11.2,11.5,11.3,11.8,12,12.4];constsma5 =computeSmaSeries(closes,5);// (number | null)[] — null until the window has formedconstrsi14 =computeRsiSeries(closes,14);// number[] — same length as input, seeded to 50 until enough barsconst{ macd, signal, hist } =computeMacdSeries(closes,12,26,9);

Trend-regime zones

import{TrendRange}from'@dooboostore/algorithm';// One bar per candle — macd/rsi/obv usually come from computeMacdSeries/computeRsiSeries/computeObvSeriesconstbars:TrendRange.TrendBar[] = closes.map((c, i) =>({macd: macd[i],rsi: rsi14[i],obv:0,
}));constzones =TrendRange.trendRanges(bars);// zones: TrendRange.TendRange[] — { startIndex, endIndex, scoreRate, strengthRate, group, uuid, confirmed, forecastRate }// all zones but the last are `confirmed: true` and are prefix-stable as new bars are appended.

Strategy generation + backtest

import{TradingSimulator}from'@dooboostore/algorithm';importtype{Candle}from'@dooboostore/algorithm';constcandles:Candle[] = [
  {date:'d0',open:10,high:10,low:10,close:10,volume:1000},
  {date:'d1',open:11,high:11,low:11,close:11,volume:1000},// ... at least a few dozen bars for directionProbability to leave its 0.5 (neutral) fallback];constindicators:TradingSimulator.IndicatorParams= {macdFast:12,macdSlow:26,macdSignal:9,rsiPeriod:14,rsiOb:70,rsiOs:30,maShort:5,maMid:10,maLong:60,maExponential:false,
};// 1) Derive buy/sell conditions from the candlesconstplan =TradingSimulator.findBestConfig(candles, {strategyRate:0.5, indicators });// plan: { conditions, applyMode: 'min'|'max'|'combined', conviction, upProbability, execPolicy? }// 2) Replay the plan against the same (or a later/expanding) candle windowconstresult =TradingSimulator.simulate({
  candles,config: plan,history: [],// prior TradingSimulator.UserTrade[]; [] for a fresh runindicators,capital:1_000_000,fee:0.001,
});// result: { trades: TradingSimulator.UserTrade[], returnPct: number, batchState }

API Reference

Candle(stock/Candle.ts)

interfaceCandle{date:string;open:number;high:number;low:number;close:number;volume:number;
}

The common OHLCV input type accepted by every other function in this package.

Indicators (stock/trend.ts)

All functions are pure and take/return plain arrays — no shared state between calls.

FunctionSignatureNotes
computeSmaSeries(closes: number[], period: number) => (number | null)[]Simple moving average;nulluntil the window has fully formed.
computeEmaSeries(values: number[], period: number) => number[]Exponential moving average; seeded with the first value, defined over the whole input.
computeMacdSeries(closes: number[], fast: number, slow: number, sig: number) => { macd: number[]; signal: number[]; hist: number[] }macd = EMA(fast) - EMA(slow),signal = EMA(macd, sig),hist = macd - signal. Auto-correctsslowtofast + 1ifslow <= fast.
computeRsiSeries(closes: number[], period: number) => number[]Wilder's RSI (smoothed average gain/loss). Returns50for the warm-up region.
computeObvSeries(closes: number[], volumes: number[]) => number[]On-Balance Volume — cumulative signed volume by close-to-close direction.

TrendRange(stock/TrendRange.ts)

Turns per-bar MACD/RSI/OBV (optionally OHLC too) into contiguous, labeled trend zones. Everything is a pure function over arrays — no DOM/chart/engine dependency.

Types:

  • TrendRange.TrendBar—{ macd, rsi, obv, open?, high?, low?, close? }(allnumber | null, OHLC optional)
  • TrendRange.TendRange— one output zone:{ startIndex, endIndex, scoreRate, strengthRate, group, uuid, confirmed, forecastRate }
    • scoreRate(0~1): direction — 0 = bearish, 0.5 = neutral, 1 = bullish (average of the bar-level scores in the zone)
    • strengthRate(0~1): trend strength regardless of direction — 0 = ranging, 1 = strongly trending
    • confirmed:falseonly for the last zone (the "live edge") — it can still change shape as new bars arrive; every earlier zone is frozen and prefix-stable
    • forecastRate: linear extrapolation of the last up-to-3 confirmed zones'scoreRateslope (causal — only ever looks atpriorconfirmed zones), ornullif there's no prior zone
    • uuid: deterministic hash (HashUtils.hash53) of the zone's full bar data — identical input always yields the identical id
  • TrendRange.TrendRangeConfig— tuning knobs, all optional:obvPeriod(default 10),macdSignalMa(default 9),strengthPeriod(default 10),mergeCut(default 10 — shorter closed runs get absorbed into the next zone),decisiveCut(default 0.15 — a short run this decisive still freezes immediately instead of being absorbed),groupBy?: (rate, strength) => string(default:String(rate)— your own labeling function is also the sole merge criterion: adjacent runs only fuse when their materialized label is equal)

Functions:

  • TrendRange.scoreBars(bars, config?): (number | null)[]— per-bar direction score (0~1), averaging whichever of {MACD-vs-zero-and-signal, RSI/100, OBV-vs-N-bar-average} are present on that bar.
  • TrendRange.strengthBars(bars, scores, config?): (number | null)[]— per-bar strength (0~1), averaging distance-from-neutral, candle body ratio, and (if OHLC present) N-bar displacement-over-range efficiency.
  • TrendRange.trendRanges(bars, config?): TendRange[]— the single entry point: scores + groups bars, merges/freezes runs left-to-right in one pass, and fills inforecastRate. Seetest/trendrange.test.tsandtest/trendrange-stability.test.tsfor the exact freeze/merge/prefix-stability contract this function guarantees.

TradingSimulator(stock/TradingSimulator.ts)

A two-step "generate a plan, then replay it" trading engine.

TradingSimulator.findBestConfig(candles, options?): AutoTradeResult

Derives trade conditions from a physics-flavored feature set computed over the candle window — price velocity/acceleration (via Catmull-Rom-smoothed closes), volume as "mass", force = mass × acceleration, "gravity" pull toward the long MA, and a PCA-projected regime score over those — blended with an inlined gradient-boostingdirectionProbabilitymodel.

  • options.strategyRate?: number(0~1, default 0.5) — 0 leans toward averaging down on dips, 1 leans toward adding to winners
  • options.marketRate?: number(0~1, default 0.5) — overall market condition; shiftsapplyMode(< 0.35→'min',> 0.65→'max', else'combined') and position-size scaling
  • options.indicators?: IndicatorParams—{ macdFast, macdSlow, macdSignal, rsiPeriod, rsiOb, rsiOs, maShort, maMid, maLong, maExponential }
  • options.macro?: MarketSeries— optional{ tnx, wti, spx }external-factor series (index-aligned withcandles); when present,directionProbabilityuses a 13-feature model instead of the base 10-feature one
  • options.invertActions?: boolean— flips every generated condition'sbuy/sell
  • options.tuning?: TuningOverrides— advanced knobs (quietBarsAdd,percentCapScale,cooldownScale,gateShift,maxConsecBars,consecRestBars,batchBars,minBudgetEquityPct,minEdge,convictionBlend) — all default to the current production behavior when omitted
  • ReturnsAutoTradeResult:{ conditions: TradeCondition[], applyMode, conviction, upProbability, execPolicy? }

TradingSimulator.simulate(options): SimulateResult

ReplaysAutoTradeResult.conditionsbar-by-bar againstoptions.candles, one order at most per bar.

  • options.candles,options.config(anAutoTradeResult),options.history: UserTrade[](prior trades —initial: trueentries seed a starting position without moving cash),options.indicators,options.capital?,options.fee?(0~1, taken on both buy and sell)
  • options.risk?: { stopLossPct?, takeProfitPct? }— checked before condition evaluation each bar, against the running average cost basis
  • options.fromBar?/options.batchState?— resume points for incremental/streaming re-simulation of a growing candle window
  • Returns{ trades: UserTrade[], returnPct: number, batchState }—trades[].actionis'buy' | 'sell' | 'buy-failed' | 'sell-failed', with areasonstring literal (Korean values, seesrc/stock/TradingSimulator.ts):'잔액부족'insufficient funds,'보유없음'nothing to sell,'소액제외'order belowminBudgetEquityPctof total equity (all on*-failed), and'스탑로스'on a stop-losssell; pluscondition/candidatessnapshots of what fired

Standalone helpers:

  • TradingSimulator.estimateMarketRate(candles, indicators?): number— 0~1 market-condition estimate (long-MA slope + volatility penalty + RSI position), meant as a drop-in for themarketRateoption above.
  • TradingSimulator.forecast(candles, maSize, market?): number[]— naive near-term close forecast: reverses the lastmaSizebar-over-bar return rates and compounds them forward from the last close, with damping scaled bydirectionProbability's confidence.
  • TradingSimulator.directionProbability(candles, market?): number— 0~1 probability of being up 5 bars from now; returns0.5(neutral) until at least 30 bars of history exist.

Seetest/engine.test.tsfor further worked examples ofsimulate(partial fills,applyModevariants, cooldowns, batching) andtest/trendrange.test.ts/test/trendrange-stability.test.tsforTrendRange's exact merge/freeze/prefix-stability guarantees.

Internal model files (not part of the public API)

stock/boostModel.tsandstock/boostModelMacro.tshold auto-generated gradient-boosting tree weights (exported from a Pythonsklearn.HistGradientBoostingClassifier, per their own header comments) plus the smallpredictBoostLogOdds/predictMacroLogOddsevaluators.TradingSimulator.directionProbabilityimports and calls these directly; they arenotre-exported fromsrc/index.ts, are not meant to be consumed directly, and their bin/tree arrays are not hand-authored data you should read as documentation.

Related Packages

License

This package is licensed under theMIT License.