Building anything that touches markets means re-implementing the same machinery: indicators that update as candles stream in, option maths, "is the market even open right now?", and a way to simulate trades without risking money. StockKit is that machinery, packaged — the same suite that powers Lacspace’s own StockYatra.

Four packages

StockKit packages: indicators, market, market-clock, paper-trade
The suite behind a real trading simulator — reuse it in your own app.
  • @lacspace/indicators — RSI, MACD and friends as streaming (O(1)) calculators.
  • @lacspace/market — market maths: Black-Scholes pricing and greeks, implied volatility and portfolio analytics (Sharpe, Sortino, max drawdown). It does calculations, not data fetching.
  • @lacspace/market-clock — market sessions: is it open, when does it close, holidays.
  • @lacspace/paper-trade — a full paper-trading engine: positions, orders and P&L.

Streaming, not batch

Recomputing an indicator over the whole history on every tick is how charts get slow. StockKit’s indicators update incrementally, so a live chart or an algo loop stays fast no matter how long it runs.

import { rsi } from '@lacspace/indicators';
const r = rsi(closes, 14);   // and O(1) as each new close arrives

Get started

Browse StockKit at lacspace.com/packages, read the API at lacspace.com/docs, and see it live in StockYatra. It’s part of the wider zero-dependency ecosystem.

Install

npm i @lacspace/indicators @lacspace/market @lacspace/market-clock @lacspace/paper-trade

All four have zero dependencies and run in the browser and in Node, so the same code can drive a chart in the client and a bot on the server.

What each package actually does

The READMEs draw clear lines between the four, which helps when deciding what to import:

  • @lacspace/indicators holds the technical indicators (30+ of them, from SMA and RSI to Ichimoku, Supertrend, ADX, MFI and Chaikin Money Flow), crossover helpers, a tick-to-candle aggregator and candlestick pattern detection.
  • @lacspace/market is data and money maths: P&L, CAGR, XIRR, tick and lot rounding, circuit limits, risk-based position sizing, an Indian brokerage and statutory charges breakdown, and, since 1.1, Black-Scholes pricing with greeks, implied volatility and portfolio statistics such as Sharpe, Sortino and max drawdown. It does not fetch quotes; you bring the prices from your own feed.
  • @lacspace/market-clock answers whether an exchange is open, with presets for NSE, BSE, NYSE, NASDAQ, LSE, TSE, HKEX and SGX.
  • @lacspace/paper-trade is the simulator: a virtual wallet, orders that fill against the prices you feed it, positions and mark-to-market P&L.

Quick start

Streaming indicators on a live feed

The batch function shown above has a class twin for live data. Each class returns null during its warm-up window, then a number or a struct.

import { RSI, MACD, CandleAggregator } from '@lacspace/indicators';

const rsi = new RSI(14);
const macd = new MACD(12, 26, 9);
const agg = new CandleAggregator(60_000); // 1-minute candles from ticks

socket.on('tick', ({ time, price, volume }) => {
  const r = rsi.next(price);            // O(1) per tick
  const m = macd.next(price);
  if (r !== null && r > 70) console.log('overbought', r.toFixed(1));
  if (m) console.log('histogram', m.histogram.toFixed(2));
  const candle = agg.add({ time, price, volume });
  if (candle) candles.push(candle);     // emitted on each rollover
});

For signals, crossedAbove and crossedBelow compare two consecutive pairs of values, and detectPatterns(candles) reports patterns such as doji, hammer and bullish engulfing with their bar index.

Option greeks and returns

import { blackScholes, impliedVolatility, xirr, positionSize } from '@lacspace/market';

const g = blackScholes({ type: 'call', spot: 100, strike: 100, timeYears: 30 / 365, rate: 0.07, volatility: 0.25 });
// { price, delta, gamma, theta, vega, rho }

positionSize({ capital: 100000, riskPercent: 1, entry: 500, stop: 480 }); // 50 shares
xirr([
  { amount: -10000, date: '2024-01-01' },
  { amount: 17000, date: '2025-01-01' },
]);

Is the market open?

import { MarketClock, NSE, NYSE } from '@lacspace/market-clock';

const nse = new MarketClock(NSE);
nse.status();          // "open" | "pre-open" | "closed"
nse.nextOpen();        // Date of the next session open
nse.msToClose();       // 0 when closed

new MarketClock(NYSE).currentSegment(); // "pre-open" | "regular" | "post" | "closed"

No-DST exchanges use a fixed UTC offset, while the US and UK presets use an IANA time zone, so open and close times stay correct across daylight-saving changes.

A paper account in a few lines

import { PaperAccount, percentSlippage } from '@lacspace/paper-trade';

const acct = new PaperAccount({ cash: 100_000, slippage: percentSlippage(0.05), trackEquity: true });
acct.mark({ RELIANCE: 2900 });
acct.buy('RELIANCE', { qty: 10 });                       // market order
acct.sell('RELIANCE', { qty: 10, triggerPrice: 2850 });  // stop-loss, rests until hit
acct.mark({ RELIANCE: 2840 });                           // every mark() checks open orders
acct.summary();       // { cash, equity, unrealizedPnl, realizedPnl, ... }
acct.performance();   // returns and max drawdown from the equity curve

Supported order types are market, limit, stop-loss, stop-limit and trailing stop, with DAY, GTC, IOC and FOK time-in-force.

Wiring them together

The paper-trade README suggests two pairings. Pass a charges function built on charges() from @lacspace/market so each fill deducts brokerage, STT and GST, which gives net rather than optimistic P&L. Then check isOpen() from @lacspace/market-clock before accepting an order.

import { PaperAccount } from '@lacspace/paper-trade';
import { charges } from '@lacspace/market';
import { MarketClock, NSE } from '@lacspace/market-clock';

const clock = new MarketClock(NSE);
const acct = new PaperAccount({
  cash: 100_000,
  charges: ({ side, qty, price }) => charges({
    segment: 'intraday', qty,
    buy: side === 'BUY' ? price : 0,
    sell: side === 'SELL' ? price : 0,
  }).totalCharges,
});

function placeBuy(symbol, qty) {
  if (!clock.isOpen()) throw new Error('Market closed');
  return acct.buy(symbol, { qty });
}

Persist state with acct.toJSON() and bring it back with PaperAccount.restore(snapshot), for example between app sessions.

Caveats from the READMEs

  • Charges rates change. The calculator defaults to a Zerodha-style Indian discount broker rate card for FY2024-25. Override fields via IN_DISCOUNT_BROKER and verify against the live rate card.
  • Holiday lists need upkeep. Bundled holidays cover 2025-2026 nationally fixed observances only; extend holidays from the exchange circular for full-year accuracy. Intraday lunch breaks (TSE, HKEX) are not modelled.
  • Warm-up values are null. Guard for null before comparing indicator output, as in the examples above.
  • Short selling is off by default in PaperAccount; enable it with allowShort.
  • Analytics need tracking. equityCurve() and performance() require trackEquity: true when you create the account.

Per-package docs: indicators, market, market-clock and paper-trade. On npm: @lacspace/indicators, @lacspace/market, @lacspace/market-clock and @lacspace/paper-trade. All are released under the Lacspace Free Licence v1.0.

Frequently asked questions

What is StockKit?

StockKit is the suite of packages that powers StockYatra — @lacspace/indicators (technical indicators), @lacspace/market (Black-Scholes option greeks and portfolio maths), @lacspace/market-clock (session/open-close logic) and @lacspace/paper-trade (a full paper-trading engine). Everything a trading, charting or algo app re-implements, done once and zero-dependency.

What indicators are included?

The common technical indicators (RSI, MACD, moving averages and more) implemented for streaming use — O(1) incremental updates as new candles arrive. Black-Scholes option pricing and greeks (delta, gamma, theta, vega) live in the companion @lacspace/market package.

Can I build a paper-trading app with it?

Yes — @lacspace/paper-trade is a complete paper-trading engine (positions, orders, P&L) and it is exactly what StockYatra uses. Pair it with @lacspace/market and @lacspace/market-clock to simulate real sessions with no real risk.

Where does it run?

Anywhere JavaScript runs — Node, edge and the browser — because the packages are isomorphic and zero-dependency. Great for both a backend algo and an in-browser chart.