@tradecanvas/commons 1.4.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/themes.d.ts +19 -0
- package/dist/constants/themes.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +659 -126
- package/dist/index.js.map +1 -1
- package/dist/types/a11y.d.ts +18 -0
- package/dist/types/a11y.d.ts.map +1 -0
- package/dist/types/chart.d.ts +113 -2
- package/dist/types/chart.d.ts.map +1 -1
- package/dist/types/drawing.d.ts +85 -1
- package/dist/types/drawing.d.ts.map +1 -1
- package/dist/types/events.d.ts +150 -1
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/execution.d.ts +20 -1
- package/dist/types/execution.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/indicator.d.ts +81 -0
- package/dist/types/indicator.d.ts.map +1 -1
- package/dist/types/layout.d.ts +8 -0
- package/dist/types/layout.d.ts.map +1 -1
- package/dist/types/news.d.ts +18 -0
- package/dist/types/news.d.ts.map +1 -0
- package/dist/types/ohlc.d.ts +11 -1
- package/dist/types/ohlc.d.ts.map +1 -1
- package/dist/types/quote.d.ts +40 -0
- package/dist/types/quote.d.ts.map +1 -0
- package/dist/types/realtime.d.ts +50 -0
- package/dist/types/realtime.d.ts.map +1 -1
- package/dist/types/rendering.d.ts +11 -0
- package/dist/types/rendering.d.ts.map +1 -1
- package/dist/types/symbol.d.ts +42 -0
- package/dist/types/symbol.d.ts.map +1 -0
- package/dist/types/theme.d.ts +6 -0
- package/dist/types/theme.d.ts.map +1 -1
- package/dist/types/trading.d.ts +33 -2
- package/dist/types/trading.d.ts.map +1 -1
- package/dist/utils/chartTypeOptions.d.ts +9 -0
- package/dist/utils/chartTypeOptions.d.ts.map +1 -0
- package/dist/utils/color.d.ts +4 -0
- package/dist/utils/color.d.ts.map +1 -1
- package/dist/utils/drawingOptions.d.ts +8 -0
- package/dist/utils/drawingOptions.d.ts.map +1 -0
- package/dist/utils/drawingStyle.d.ts +9 -0
- package/dist/utils/drawingStyle.d.ts.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/marketStatus.d.ts +16 -0
- package/dist/utils/marketStatus.d.ts.map +1 -0
- package/dist/utils/precision.d.ts +18 -0
- package/dist/utils/precision.d.ts.map +1 -1
- package/dist/utils/priceSource.d.ts +15 -0
- package/dist/utils/priceSource.d.ts.map +1 -0
- package/dist/utils/resample.d.ts +13 -0
- package/dist/utils/resample.d.ts.map +1 -0
- package/dist/utils/symbols.d.ts +13 -0
- package/dist/utils/symbols.d.ts.map +1 -0
- package/dist/utils/time.d.ts +29 -8
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/timezone.d.ts +28 -0
- package/dist/utils/timezone.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/types/rendering.ts","../src/types/drawing.ts","../src/types/trading.ts","../src/types/signal.ts","../src/types/realtime.ts","../src/utils/math.ts","../src/utils/data.ts","../src/utils/color.ts","../src/utils/time.ts","../src/utils/precision.ts","../src/constants/defaults.ts","../src/constants/themes.ts","../src/i18n/en.ts","../src/i18n/vi.ts","../src/i18n/index.ts","../src/market/presets.ts"],"sourcesContent":["export interface Point {\n x: number;\n y: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport type PriceScaleMode = 'regular' | 'logarithmic' | 'percentage' | 'indexedTo100';\n\nexport interface ViewportState {\n visibleRange: { from: number; to: number };\n priceRange: { min: number; max: number };\n barWidth: number;\n barSpacing: number;\n offset: number;\n chartRect: Rect;\n /**\n * Logarithmic price geometry. Kept for back-compat; mirrors\n * `scaleMode === 'logarithmic'`. Prefer reading `scaleMode`.\n */\n logScale?: boolean;\n /**\n * Price-scale presentation. `regular`, `percentage`, and `indexedTo100` share\n * the same linear geometry — only the axis labels differ (rebased to\n * `scaleBaseline`). `logarithmic` changes the geometry. Defaults to\n * `regular` when unset.\n */\n scaleMode?: PriceScaleMode;\n /**\n * Reference price for `percentage` / `indexedTo100` axis labels — usually the\n * close of the first visible bar. Set by the chart each frame.\n */\n scaleBaseline?: number;\n /** Upside down: higher prices lower on screen. */\n invertScale?: boolean;\n /**\n * Optional reference to the current bar series. When set, drawings/indicators\n * treat `anchor.time` as a real timestamp and convert to bar index via\n * `timestampToBarIndex(time, data)` at render/hit-test time. This lets\n * anchors survive timeframe / symbol switches. When unset,\n * `anchor.time` is treated as a raw bar index (legacy behavior).\n */\n data?: ReadonlyArray<{ time: number }>;\n /**\n * Width of the price axis strip right of `chartRect`, in CSS pixels. The\n * chart widens it to fit long labels (sub-cent prices); renderers drawing\n * badges on the axis size them to it. Defaults to `PRICE_AXIS_WIDTH`.\n */\n priceAxisWidth?: number;\n}\n\n/**\n * What changed, for `requestRender()`. The chart paints two canvases: a scene\n * (everything that follows data, viewport and chart objects) and a thin top\n * canvas for pointer-tied visuals. Any type except `Hover` redraws both;\n * `Hover` redraws only the top canvas (crosshair, its axis pills, legend,\n * bar countdown, measure ruler, selection box).\n */\nexport enum LayerType {\n Background = 0,\n Main = 1,\n Panel = 2,\n Overlay = 3,\n UI = 4,\n Hover = 5,\n}\n","import type { Point, ViewportState } from './rendering.js';\r\n\r\nexport type DrawingToolType =\r\n | 'trendLine' | 'horizontalLine' | 'horizontalRay' | 'verticalLine' | 'ray' | 'extendedLine'\r\n | 'crossLine' | 'trendAngle' | 'infoLine'\r\n | 'parallelChannel' | 'regressionChannel'\r\n | 'fibRetracement' | 'fibExtension' | 'fibTimeZones' | 'fibChannel' | 'fibSpeedResistanceFan'\r\n | 'rectangle' | 'ellipse' | 'triangle' | 'circle'\r\n | 'pitchfork' | 'schiffPitchfork' | 'modifiedSchiffPitchfork' | 'elliottWave'\r\n | 'xabcdPattern' | 'abcdPattern' | 'headAndShoulders'\r\n | 'priceRange' | 'dateRange' | 'dateAndPriceRange' | 'measure'\r\n | 'text' | 'arrow' | 'priceLabel'\r\n | 'gannFan' | 'gannBox' | 'cyclicLines'\r\n | 'anchoredVWAP'\r\n | 'volumeProfileRange'\r\n | 'riskReward';\r\n\r\nexport interface AnchorPoint {\r\n time: number;\r\n price: number;\r\n}\r\n\r\nexport interface DrawingStyle {\r\n color: string;\r\n lineWidth: number;\r\n lineStyle: 'solid' | 'dashed' | 'dotted';\r\n fillColor?: string;\r\n fillOpacity?: number;\r\n fontSize?: number;\r\n text?: string;\r\n}\r\n\r\nexport interface DrawingState {\r\n id: string;\r\n type: DrawingToolType;\r\n anchors: AnchorPoint[];\r\n style: DrawingStyle;\r\n visible: boolean;\r\n locked: boolean;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\nexport interface DrawingDescriptor {\r\n type: DrawingToolType;\r\n name: string;\r\n requiredAnchors: number;\r\n singleClick?: boolean;\r\n}\r\n\r\nexport interface DrawingPlugin {\r\n descriptor: DrawingDescriptor;\r\n render(\r\n ctx: CanvasRenderingContext2D,\r\n state: DrawingState,\r\n viewport: ViewportState,\r\n selected: boolean,\r\n ): void;\r\n hitTest(\r\n point: Point,\r\n state: DrawingState,\r\n viewport: ViewportState,\r\n tolerance: number,\r\n ): boolean;\r\n hitTestAnchor(\r\n point: Point,\r\n state: DrawingState,\r\n viewport: ViewportState,\r\n tolerance: number,\r\n ): number;\r\n}\r\n\r\nexport const DEFAULT_DRAWING_STYLE: DrawingStyle = {\r\n color: '#4c8dff',\r\n lineWidth: 1,\r\n lineStyle: 'solid',\r\n fillColor: 'rgba(76, 141, 255, 0.1)',\r\n fillOpacity: 0.1,\r\n fontSize: 12,\r\n};\r\n","export type OrderSide = 'buy' | 'sell';\r\nexport type OrderType = 'market' | 'limit' | 'stop' | 'stopLimit';\r\nexport type OrderStatus = 'pending' | 'filled' | 'cancelled' | 'rejected';\r\nexport type OrderLabel = 'LIMIT' | 'STOP' | 'SL' | 'TP' | 'STOP LIMIT';\r\n\r\nexport interface TradingOrder {\r\n id: string;\r\n side: OrderSide;\r\n type: OrderType;\r\n price: number;\r\n stopPrice?: number;\r\n quantity: number;\r\n label?: OrderLabel;\r\n draggable?: boolean;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\nexport interface TradingPosition {\r\n id: string;\r\n side: OrderSide;\r\n entryPrice: number;\r\n quantity: number;\r\n /** Quantity already closed (for partial-close visualization). 0 ≤ closedQuantity ≤ quantity. */\r\n closedQuantity?: number;\r\n stopLoss?: number;\r\n takeProfit?: number;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\n/** Threshold-based P&L color stop. Sorted ascending by `pnl` is recommended. */\r\nexport interface PnLThreshold {\r\n /** Inclusive lower bound. Use -Infinity for the bottom-most stop. */\r\n pnl: number;\r\n color: string;\r\n}\r\n\r\n/** Tokens passed to position label templates. */\r\nexport interface PositionLabelContext {\r\n side: OrderSide;\r\n quantity: number;\r\n closedQuantity: number;\r\n openQuantity: number;\r\n entryPrice: number;\r\n currentPrice: number;\r\n pnl: number;\r\n pnlPct: number;\r\n precision: number;\r\n}\r\n\r\nexport interface DepthLevel {\r\n price: number;\r\n volume: number;\r\n}\r\n\r\nexport interface DepthData {\r\n bids: DepthLevel[];\r\n asks: DepthLevel[];\r\n}\r\n\r\nexport interface TradingConfig {\r\n enabled: boolean;\r\n orderColors?: { buy?: string; sell?: string };\r\n positionColors?: { profit?: string; loss?: string; entry?: string };\r\n /**\r\n * Optional gradient of colors keyed to P&L value. When provided, the rendered\r\n * position zone uses the color of the highest threshold whose `pnl` ≤ live P&L.\r\n * Falls back to `positionColors.profit`/`.loss` when unset.\r\n */\r\n pnlThresholds?: PnLThreshold[];\r\n /**\r\n * Position P&L label template. Supports tokens: {side} {qty} {closedQty}\r\n * {openQty} {entry} {price} {pnl} {pnlPct} {pnlSign}. Pass a function for\r\n * full control. Default: `{side} {qty} | P&L: {pnlSign}{pnl}`.\r\n */\r\n positionLabel?: string | ((ctx: PositionLabelContext) => string);\r\n depthOverlay?: {\r\n enabled?: boolean;\r\n bidColor?: string;\r\n askColor?: string;\r\n maxWidth?: number;\r\n };\r\n contextMenu?: { enabled?: boolean };\r\n pricePrecision?: number;\r\n dragThreshold?: number;\r\n}\r\n\r\nexport interface OrderPlaceIntent {\r\n side: OrderSide;\r\n type: OrderType;\r\n price: number;\r\n stopPrice?: number;\r\n quantity?: number;\r\n}\r\n\r\nexport interface OrderModifyIntent {\r\n orderId: string;\r\n newPrice: number;\r\n /** The price before the change, when known (e.g. from a drag). Optional so\r\n * the chart's `orderModify` event payload routes straight to an adapter. */\r\n previousPrice?: number;\r\n}\r\n\r\nexport interface OrderCancelIntent {\r\n orderId: string;\r\n}\r\n\r\nexport interface PositionModifyIntent {\r\n positionId: string;\r\n stopLoss?: number;\r\n takeProfit?: number;\r\n}\r\n\r\nexport interface PositionCloseIntent {\r\n positionId: string;\r\n}\r\n\r\nexport const DEFAULT_TRADING_CONFIG: TradingConfig = {\r\n enabled: true,\r\n orderColors: { buy: '#1fa874', sell: '#e8505b' },\r\n positionColors: { profit: '#1fa874', loss: '#e8505b', entry: '#4c8dff' },\r\n depthOverlay: { enabled: false, bidColor: 'rgba(31, 168, 116,0.15)', askColor: 'rgba(232, 80, 91,0.15)', maxWidth: 100 },\r\n contextMenu: { enabled: true },\r\n pricePrecision: 2,\r\n dragThreshold: 3,\r\n};\r\n","export type SignalDirection = 'long' | 'short' | 'neutral';\r\n\r\nexport interface SignalMarker {\r\n id: string;\r\n time: number;\r\n price: number;\r\n direction: SignalDirection;\r\n confidence: number;\r\n source: string;\r\n label?: string;\r\n color?: string;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\nexport interface SignalMarkerStyle {\r\n longColor?: string;\r\n shortColor?: string;\r\n neutralColor?: string;\r\n arrowSize?: number;\r\n showLabel?: boolean;\r\n showConfidence?: boolean;\r\n sourceColors?: Record<string, string>;\r\n}\r\n\r\nexport const DEFAULT_SIGNAL_STYLE: SignalMarkerStyle = {\r\n longColor: '#1fa874',\r\n shortColor: '#e8505b',\r\n neutralColor: '#9E9E9E',\r\n arrowSize: 12,\r\n showLabel: true,\r\n showConfidence: true,\r\n};\r\n\r\nexport type TradeZoneDirection = 'long' | 'short';\r\n\r\nexport interface TradeZone {\r\n id: string;\r\n entryTime: number;\r\n entryPrice: number;\r\n exitTime?: number;\r\n exitPrice?: number;\r\n direction: TradeZoneDirection;\r\n pnl?: number;\r\n pnlPercent?: number;\r\n label?: string;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\nexport interface TradeZoneStyle {\r\n profitColor?: string;\r\n lossColor?: string;\r\n activeColor?: string;\r\n fillOpacity?: number;\r\n borderWidth?: number;\r\n showLabel?: boolean;\r\n showPnl?: boolean;\r\n}\r\n\r\nexport const DEFAULT_TRADE_ZONE_STYLE: TradeZoneStyle = {\r\n profitColor: '#1fa874',\r\n lossColor: '#e8505b',\r\n activeColor: '#4c8dff',\r\n fillOpacity: 0.12,\r\n borderWidth: 1,\r\n showLabel: true,\r\n showPnl: true,\r\n};\r\n","import type { OHLCBar, TimeFrame } from './ohlc.js';\n\n// --- Connection ---\n\nexport type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';\n\nexport interface ConnectionInfo {\n state: ConnectionState;\n latency?: number;\n reconnectAttempt?: number;\n lastMessageTime?: number;\n error?: string;\n}\n\n// --- Ticks & Trades ---\n\nexport interface RawTick {\n time: number;\n price: number;\n volume: number;\n side?: 'buy' | 'sell';\n}\n\nexport interface AggregatedBar extends OHLCBar {\n closed: boolean; // true when bar is finalized\n tickCount: number; // number of ticks in this bar\n}\n\n// --- Data Adapter (Strategy Pattern) ---\n\nexport interface DataAdapterConfig {\n symbol: string;\n timeframe: TimeFrame;\n reconnect?: boolean; // default: true\n reconnectMaxRetries?: number; // default: Infinity\n reconnectBaseDelay?: number; // ms, default: 1000\n reconnectMaxDelay?: number; // ms, default: 30000\n heartbeatInterval?: number; // ms, default: 30000\n bufferSize?: number; // max ticks to buffer, default: 1000\n}\n\nexport type DataAdapterEventType =\n | 'tick'\n | 'bar'\n | 'barClose'\n | 'snapshot' // initial historical data loaded\n | 'connectionChange'\n | 'error';\n\nexport interface DataAdapterEvent<T = unknown> {\n type: DataAdapterEventType;\n data: T;\n timestamp: number;\n}\n\nexport type DataAdapterListener<T = unknown> = (event: DataAdapterEvent<T>) => void;\n\n/**\n * Data adapter interface. Implements the observer pattern:\n * - connect() to start receiving data\n * - on('bar'|'tick'|'connectionChange', handler) to receive events\n * - disconnect() to stop, then connect() again to switch symbols/timeframes\n * - No separate subscribe/unsubscribe — reconnect is the intended pattern\n *\n * Strategy pattern for pluggable data sources.\n * Implementations handle the specifics of each data source (WebSocket, REST,\n * SSE, etc.) while the StreamManager orchestrates lifecycle and aggregation.\n *\n * Built-in: BinanceAdapter\n * Implement this for: custom exchange APIs, broker feeds, mock data\n */\nexport interface DataAdapter {\n readonly name: string;\n\n connect(config: DataAdapterConfig): void;\n disconnect(): void;\n getConnectionState(): ConnectionState;\n\n /**\n * Load historical bars. Called once on connect, before streaming starts.\n * Returns bars sorted by time ascending.\n */\n fetchHistory(symbol: string, timeframe: TimeFrame, limit?: number): Promise<OHLCBar[]>;\n\n on<T = unknown>(event: DataAdapterEventType, listener: DataAdapterListener<T>): void;\n off<T = unknown>(event: DataAdapterEventType, listener: DataAdapterListener<T>): void;\n\n dispose(): void;\n}\n\n// --- Stream Manager Config ---\n\nexport interface StreamConfig {\n adapter: DataAdapter;\n symbol: string;\n timeframe: TimeFrame;\n historyLimit?: number; // bars to load initially, default: 500\n autoScroll?: boolean; // scroll to end on new bar, default: true\n showCurrentPriceLine?: boolean; // default: true\n aggregateTicks?: boolean; // build bars from ticks, default: false\n reconnect?: ReconnectConfig;\n}\n\nexport interface ReconnectConfig {\n enabled: boolean; // default: true\n maxRetries: number; // default: Infinity\n baseDelay: number; // ms, default: 1000\n maxDelay: number; // ms, default: 30000\n backoffMultiplier: number; // default: 2\n}\n\nexport const DEFAULT_RECONNECT: ReconnectConfig = {\n enabled: true,\n maxRetries: Infinity,\n baseDelay: 1000,\n maxDelay: 30000,\n backoffMultiplier: 2,\n};\n\nexport const DEFAULT_STREAM_CONFIG: Partial<StreamConfig> = {\n historyLimit: 500,\n autoScroll: true,\n showCurrentPriceLine: true,\n aggregateTicks: false,\n};\n","export function clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n\nexport function lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\nexport function inverseLerp(a: number, b: number, value: number): number {\n if (a === b) return 0;\n return (value - a) / (b - a);\n}\n\nexport function roundToStep(value: number, step: number): number {\n return Math.round(value / step) * step;\n}\n\nexport function niceNumber(value: number, round: boolean): number {\n const exp = Math.floor(Math.log10(value));\n const frac = value / Math.pow(10, exp);\n let nice: number;\n if (round) {\n if (frac < 1.5) nice = 1;\n else if (frac < 3) nice = 2;\n else if (frac < 7) nice = 5;\n else nice = 10;\n } else {\n if (frac <= 1) nice = 1;\n else if (frac <= 2) nice = 2;\n else if (frac <= 5) nice = 5;\n else nice = 10;\n }\n return nice * Math.pow(10, exp);\n}\n\nexport function computeTickStep(min: number, max: number, maxTicks: number): number {\n const range = niceNumber(max - min, false);\n return niceNumber(range / (maxTicks - 1), true);\n}\n","import type { OHLCBar, DataSeries } from '../types/ohlc.js';\n\n/**\n * Normalize bar timestamp to milliseconds.\n * Auto-detects: time > 1e12 is already ms, otherwise treats as seconds.\n */\nexport function normalizeBarTime(time: number): number {\n return time > 1e12 ? time : time * 1000;\n}\n\n/**\n * Normalize a bar's timestamp field to milliseconds.\n * Accepts either { time } (ms or s) or { t, o, h, l, c, v } wire format.\n */\nexport function normalizeBar(raw: Record<string, number>): OHLCBar {\n const time = normalizeBarTime(raw.time ?? raw.t ?? 0);\n return {\n time,\n open: raw.open ?? raw.o ?? 0,\n high: raw.high ?? raw.h ?? 0,\n low: raw.low ?? raw.l ?? 0,\n close: raw.close ?? raw.c ?? 0,\n volume: raw.volume ?? raw.v ?? 0,\n };\n}\n\nexport function sliceVisibleData(\n data: DataSeries,\n from: number,\n to: number,\n): DataSeries {\n const startIdx = Math.max(0, from);\n const endIdx = Math.min(data.length, to + 1);\n return data.slice(startIdx, endIdx);\n}\n\nexport function findBarIndex(data: DataSeries, timestamp: number): number {\n let lo = 0;\n let hi = data.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n if (data[mid].time < timestamp) lo = mid + 1;\n else if (data[mid].time > timestamp) hi = mid - 1;\n else return mid;\n }\n return lo;\n}\n\nexport function computePriceRange(\n data: DataSeries,\n from: number,\n to: number,\n padding = 0.05,\n): { min: number; max: number } {\n if (data.length === 0) return { min: 0, max: 1 };\n const startIdx = Math.max(0, from);\n const endIdx = Math.min(data.length - 1, to);\n let min = Infinity;\n let max = -Infinity;\n for (let i = startIdx; i <= endIdx; i++) {\n if (data[i].low < min) min = data[i].low;\n if (data[i].high > max) max = data[i].high;\n }\n if (min === Infinity) return { min: 0, max: 1 };\n const range = max - min || 1;\n return {\n min: min - range * padding,\n max: max + range * padding,\n };\n}\n\nexport function mergeBar(existing: OHLCBar, tick: { price: number; volume?: number; time: number }): OHLCBar {\n return {\n ...existing,\n high: Math.max(existing.high, tick.price),\n low: Math.min(existing.low, tick.price),\n close: tick.price,\n volume: existing.volume + (tick.volume ?? 0),\n time: tick.time,\n };\n}\n","export function hexToRgba(hex: string, alpha = 1): string {\n const r = parseInt(hex.slice(1, 3), 16);\n const g = parseInt(hex.slice(3, 5), 16);\n const b = parseInt(hex.slice(5, 7), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nexport function withAlpha(color: string, alpha: number): string {\n if (color.startsWith('#')) {\n return hexToRgba(color, alpha);\n }\n const rgbaMatch = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);\n if (rgbaMatch) {\n return `rgba(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]}, ${alpha})`;\n }\n return color;\n}\n\nexport function lerpColor(colorA: string, colorB: string, t: number): string {\n const parseHex = (hex: string) => {\n hex = hex.replace('#', '');\n if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];\n return {\n r: parseInt(hex.slice(0, 2), 16),\n g: parseInt(hex.slice(2, 4), 16),\n b: parseInt(hex.slice(4, 6), 16),\n };\n };\n const a = parseHex(colorA);\n const b = parseHex(colorB);\n const r = Math.round(a.r + (b.r - a.r) * t);\n const g = Math.round(a.g + (b.g - a.g) * t);\n const bl = Math.round(a.b + (b.b - a.b) * t);\n return `rgb(${r},${g},${bl})`;\n}\n","import type { TimeFrame } from '../types/ohlc.js';\r\n\r\nconst TIMEFRAME_MS: Record<TimeFrame, number> = {\r\n '1s': 1_000,\r\n '5s': 5_000,\r\n '15s': 15_000,\r\n '30s': 30_000,\r\n '1m': 60_000,\r\n '3m': 180_000,\r\n '5m': 300_000,\r\n '15m': 900_000,\r\n '30m': 1_800_000,\r\n '45m': 2_700_000,\r\n '1h': 3_600_000,\r\n '2h': 7_200_000,\r\n '3h': 10_800_000,\r\n '4h': 14_400_000,\r\n '6h': 21_600_000,\r\n '8h': 28_800_000,\r\n '12h': 43_200_000,\r\n '1d': 86_400_000,\r\n '2d': 172_800_000,\r\n '3d': 259_200_000,\r\n '1w': 604_800_000,\r\n '2w': 1_209_600_000,\r\n '1M': 2_592_000_000,\r\n '3M': 7_776_000_000,\r\n '6M': 15_552_000_000,\r\n '12M': 31_536_000_000,\r\n};\r\n\r\nexport function timeframeToMs(tf: TimeFrame): number {\r\n return TIMEFRAME_MS[tf];\r\n}\r\n\r\nexport function formatTimestamp(timestamp: number, tf: TimeFrame): string {\r\n const d = new Date(timestamp);\r\n const ms = TIMEFRAME_MS[tf];\r\n if (ms >= 86_400_000) {\r\n return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\r\n }\r\n if (ms >= 3_600_000) {\r\n return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });\r\n }\r\n return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\r\n}\r\n\r\nexport function alignToTimeframe(timestamp: number, tf: TimeFrame): number {\r\n const ms = TIMEFRAME_MS[tf];\r\n return Math.floor(timestamp / ms) * ms;\r\n}\r\n\r\nexport interface TimeParts {\r\n year: number;\r\n month: number; // 1-12\r\n day: number;\r\n hours: number;\r\n minutes: number;\r\n}\r\n\r\n/**\r\n * Calendar parts of a timestamp in either the browser-local timezone\r\n * (`tzOffsetMinutes === null`) or a fixed UTC offset (e.g. -300 for EST).\r\n */\r\nexport function timeParts(timeMs: number, tzOffsetMinutes: number | null): TimeParts {\r\n if (tzOffsetMinutes === null) {\r\n const d = new Date(timeMs);\r\n return {\r\n year: d.getFullYear(),\r\n month: d.getMonth() + 1,\r\n day: d.getDate(),\r\n hours: d.getHours(),\r\n minutes: d.getMinutes(),\r\n };\r\n }\r\n const d = new Date(timeMs + tzOffsetMinutes * 60_000);\r\n return {\r\n year: d.getUTCFullYear(),\r\n month: d.getUTCMonth() + 1,\r\n day: d.getUTCDate(),\r\n hours: d.getUTCHours(),\r\n minutes: d.getUTCMinutes(),\r\n };\r\n}\r\n\r\n/**\r\n * A bar with no time-of-day component (a daily, weekly, monthly or yearly candle — always anchored\r\n * to a calendar-day boundary, so hours/minutes read 00:00) needs the YEAR in its label instead: a\r\n * bare `month/day` is ambiguous across years, and `00:00` tells a trader nothing. Detected from the\r\n * parts themselves rather than a separate timeframe parameter, so every caller that formats a bar's\r\n * time gets the same rule for free. The one false positive this accepts — a genuine intraday bar\r\n * landing exactly on midnight — is acceptable: real trade-driven timestamps essentially never do.\r\n */\r\nexport function isDateOnly(parts: Pick<TimeParts, 'hours' | 'minutes'>): boolean {\r\n return parts.hours === 0 && parts.minutes === 0;\r\n}\r\n\r\n/** A short timezone label like `UTC-5` or `UTC+5:30` (browser-local when null). */\r\nexport function tzLabel(tzOffsetMinutes: number | null): string {\r\n const min = tzOffsetMinutes === null ? -new Date().getTimezoneOffset() : tzOffsetMinutes;\r\n const sign = min >= 0 ? '+' : '-';\r\n const abs = Math.abs(min);\r\n const h = Math.floor(abs / 60);\r\n const m = abs % 60;\r\n return m === 0 ? `UTC${sign}${h}` : `UTC${sign}${h}:${String(m).padStart(2, '0')}`;\r\n}\r\n\r\n/** Day-of-week for a Monday-anchored reference week (1970-01-05 was a Monday, UTC). */\r\nconst WEEK_ANCHOR_MS = Date.UTC(1970, 0, 5);\r\n\r\nfunction parseTimeframe(tf: TimeFrame): { count: number; unit: 's' | 'm' | 'h' | 'd' | 'w' | 'M' } {\r\n const match = /^(\\d+)([smhdwM])$/.exec(tf);\r\n if (!match) return { count: 1, unit: 'm' };\r\n return { count: parseInt(match[1], 10), unit: match[2] as 's' | 'm' | 'h' | 'd' | 'w' | 'M' };\r\n}\r\n\r\n/**\r\n * Start of the bucket a timestamp falls into for a given timeframe.\r\n *\r\n * Intraday and daily frames are anchored to the Unix epoch (UTC) via fixed-ms\r\n * flooring. Weekly frames are anchored to Monday (or `weekStartsOn`), monthly\r\n * and yearly frames use calendar boundaries (1st of month, Jan for years) so\r\n * 3M aligns to quarters and 12M to calendar years.\r\n */\r\nexport function timeframeBucketStart(\r\n timestamp: number,\r\n tf: TimeFrame,\r\n weekStartsOn: 0 | 1 = 1,\r\n): number {\r\n const { count, unit } = parseTimeframe(tf);\r\n\r\n if (unit === 's' || unit === 'm' || unit === 'h' || unit === 'd') {\r\n const ms = TIMEFRAME_MS[tf];\r\n return Math.floor(timestamp / ms) * ms;\r\n }\r\n\r\n const d = new Date(timestamp);\r\n\r\n if (unit === 'w') {\r\n const dayMs = TIMEFRAME_MS['1d'];\r\n const dow = d.getUTCDay();\r\n const shift = (dow - weekStartsOn + 7) % 7;\r\n const weekStart = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - shift);\r\n if (count <= 1) return weekStart;\r\n const weekIndex = Math.floor((weekStart - WEEK_ANCHOR_MS) / (7 * dayMs));\r\n const grouped = Math.floor(weekIndex / count) * count;\r\n return WEEK_ANCHOR_MS + grouped * 7 * dayMs;\r\n }\r\n\r\n // months / quarters / years\r\n const totalMonths = d.getUTCFullYear() * 12 + d.getUTCMonth();\r\n const grouped = Math.floor(totalMonths / count) * count;\r\n return Date.UTC(Math.floor(grouped / 12), grouped % 12, 1);\r\n}\r\n","const NUMBER_FORMAT_CACHE_LIMIT = 64;\r\nconst numberFormatCache = new Map<string, Intl.NumberFormat>();\r\n\r\n/**\r\n * `Number#toLocaleString(locale, options)` constructs a fresh\r\n * `Intl.NumberFormat` on every call — ~20µs in V8 versus ~0.4µs to reuse one.\r\n * Axis labels, the legend, crosshair pills and panel axes format dozens of\r\n * numbers per frame, so formatters are cached per (locale, precision). The\r\n * output is identical: per spec, `toLocaleString` is defined as exactly this.\r\n */\r\nfunction numberFormat(locale: string, precision: number): Intl.NumberFormat {\r\n const key = `${locale}|${precision}`;\r\n let nf = numberFormatCache.get(key);\r\n if (!nf) {\r\n if (numberFormatCache.size >= NUMBER_FORMAT_CACHE_LIMIT) numberFormatCache.clear();\r\n nf = new Intl.NumberFormat(locale, {\r\n minimumFractionDigits: precision,\r\n maximumFractionDigits: precision,\r\n });\r\n numberFormatCache.set(key, nf);\r\n }\r\n return nf;\r\n}\r\n\r\nexport function formatPrice(value: number, precision = 2, locale = 'en-US'): string {\r\n return numberFormat(locale, precision).format(value);\r\n}\r\n\r\n/**\r\n * Decimals the price axis uses for a visible price range: one more than its\r\n * tick step needs, 2 at minimum. Everything that prints a price next to the\r\n * axis (legend, crosshair pill, last-price tag) shares it, so a sub-cent\r\n * asset reads \"0.000004349\" everywhere instead of \"0.00\" off the axis.\r\n */\r\nexport function autoPricePrecision(min: number, max: number): number {\r\n if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return 2;\r\n const step = computeTickStep(min, max, 8);\r\n return step > 0 && step < 1 ? Math.ceil(-Math.log10(step)) + 1 : 2;\r\n}\r\n\r\nimport type { PriceScaleMode } from '../types/rendering.js';\r\nimport { computeTickStep } from './math.js';\r\n\r\n/**\r\n * Format a price-axis label for a given scale mode.\r\n *\r\n * - `regular` / `logarithmic`: the raw price.\r\n * - `percentage`: change from `baseline`, e.g. `+12.34%`.\r\n * - `indexedTo100`: the price rebased so `baseline` reads as 100.\r\n *\r\n * Falls back to a plain price when a baseline is required but missing/zero.\r\n */\r\nexport function formatPriceScaleLabel(\r\n price: number,\r\n mode: PriceScaleMode,\r\n baseline: number | undefined,\r\n precision = 2,\r\n locale = 'en-US',\r\n): string {\r\n if ((mode === 'percentage' || mode === 'indexedTo100') && baseline && baseline !== 0) {\r\n if (mode === 'percentage') {\r\n const pct = (price / baseline - 1) * 100;\r\n const sign = pct > 0 ? '+' : '';\r\n return `${sign}${pct.toFixed(2)}%`;\r\n }\r\n const indexed = (price / baseline) * 100;\r\n return formatPrice(indexed, 2, locale);\r\n }\r\n return formatPrice(price, precision, locale);\r\n}\r\n\r\nexport function formatVolume(value: number): string {\r\n if (value >= 1_000_000_000) return (value / 1_000_000_000).toFixed(2) + 'B';\r\n if (value >= 1_000_000) return (value / 1_000_000).toFixed(2) + 'M';\r\n if (value >= 1_000) return (value / 1_000).toFixed(2) + 'K';\r\n return value.toFixed(0);\r\n}\r\n\r\nexport function detectPrecision(values: number[]): number {\r\n let maxDecimals = 0;\r\n for (const v of values) {\r\n const str = v.toString();\r\n const dot = str.indexOf('.');\r\n if (dot >= 0) {\r\n maxDecimals = Math.max(maxDecimals, str.length - dot - 1);\r\n }\r\n }\r\n return Math.min(maxDecimals, 8);\r\n}\r\n","import type { ChartOptions } from '../types/chart.js';\n\nexport const DEFAULT_CHART_OPTIONS: Required<Pick<ChartOptions, 'autoScale' | 'rightMargin' | 'minBarSpacing' | 'maxBarSpacing'>> & Pick<ChartOptions, 'grid' | 'crosshair'> = {\n autoScale: true,\n rightMargin: 5,\n minBarSpacing: 2,\n maxBarSpacing: 30,\n grid: {\n visible: true,\n hLineStyle: 'solid',\n vLineStyle: 'solid',\n },\n crosshair: {\n mode: 'magnet',\n },\n};\n\n// Standard timeframe presets for different market types\nimport type { TimeFrame } from '../types/ohlc.js';\n\n/** Crypto: all timeframes including seconds */\nexport const TIMEFRAMES_CRYPTO: TimeFrame[] = [\n '1s', '1m', '3m', '5m', '15m', '30m',\n '1h', '2h', '4h', '6h', '8h', '12h',\n '1d', '3d', '1w', '1M',\n];\n\n/** Stocks: minute-level and above (no seconds) */\nexport const TIMEFRAMES_STOCK: TimeFrame[] = [\n '1m', '5m', '15m', '30m',\n '1h', '2h', '4h',\n '1d', '1w', '1M', '3M', '6M', '12M',\n];\n\n/** Forex: common forex timeframes */\nexport const TIMEFRAMES_FOREX: TimeFrame[] = [\n '1m', '5m', '15m', '30m',\n '1h', '4h',\n '1d', '1w', '1M',\n];\n\n/** Default favorites shown in quick-access bar */\nexport const DEFAULT_TIMEFRAME_FAVORITES: TimeFrame[] = [\n '1m', '5m', '15m', '1h', '4h', '1d', '1w',\n];\n\nexport const DEFAULT_BAR_WIDTH = 8;\nexport const DEFAULT_BAR_SPACING = 2;\nexport const PRICE_AXIS_WIDTH = 70;\nexport const TIME_AXIS_HEIGHT = 30;\nexport const MIN_PANEL_HEIGHT = 60;\nexport const DEFAULT_PANEL_HEIGHT = 120;\n","import type { Theme } from '../types/theme.js';\r\n\r\nconst DEFAULT_FONT = {\r\n family: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n sizeSmall: 10,\r\n sizeMedium: 12,\r\n sizeLarge: 14,\r\n};\r\n\r\n/**\r\n * TradeCanvas palette. Themes pick their own candle colours for their\r\n * background; everything drawn on top of either theme (indicators, orders,\r\n * signals, drawings) uses the theme-neutral values below, which keep at\r\n * least 3:1 contrast on both the dark ink and a white background. `amber` is\r\n * the brand accent for dark surfaces (2:1 on white); light surfaces use\r\n * `LIGHT_THEME.lineColor` / `ochre` instead.\r\n */\r\nexport const TC_PALETTE = {\r\n /** Brand amber — the default series line and the accent. */\r\n amber: '#f2a93b',\r\n /** Rising / bullish, legible on dark and light. */\r\n up: '#1fa874',\r\n /** Falling / bearish, legible on dark and light. */\r\n down: '#e8505b',\r\n /** Amber deep enough for white backgrounds; auto-coloured series use it. */\r\n ochre: '#c98316',\r\n /** Secondary series, signal lines. */\r\n blue: '#4c8dff',\r\n violet: '#a57cff',\r\n cyan: '#1398a8',\r\n rose: '#e25592',\r\n /** Zero lines, neutral guides. */\r\n neutral: '#7d8696',\r\n /** Ink ground of the dark theme. */\r\n ink: '#0c1016',\r\n} as const;\r\n\r\n/** Order in which overlay indicators are coloured when no colour is given. */\r\nexport const TC_SERIES_COLORS: readonly string[] = [\r\n TC_PALETTE.blue,\r\n TC_PALETTE.ochre,\r\n TC_PALETTE.violet,\r\n TC_PALETTE.cyan,\r\n TC_PALETTE.rose,\r\n];\r\n\r\nexport const DARK_THEME: Theme = {\r\n name: 'dark',\r\n background: TC_PALETTE.ink,\r\n text: '#d9dde4',\r\n textSecondary: '#7d8696',\r\n grid: '#161b23',\r\n crosshair: '#8a93a3',\r\n candleUp: '#3ccf91',\r\n candleDown: '#f0616d',\r\n candleUpWick: '#3ccf91',\r\n candleDownWick: '#f0616d',\r\n lineColor: TC_PALETTE.amber,\r\n areaTopColor: 'rgba(242, 169, 59, 0.32)',\r\n areaBottomColor: 'rgba(242, 169, 59, 0.0)',\r\n volumeUp: 'rgba(60, 207, 145, 0.28)',\r\n volumeDown: 'rgba(240, 97, 109, 0.28)',\r\n axisLine: '#1f2630',\r\n axisLabel: '#c3c9d3',\r\n axisLabelBackground: '#1f2630',\r\n font: DEFAULT_FONT,\r\n};\r\n\r\nexport const LIGHT_THEME: Theme = {\r\n name: 'light',\r\n background: '#ffffff',\r\n text: '#0f131a',\r\n textSecondary: '#6b7380',\r\n grid: '#eef0f3',\r\n crosshair: '#8a93a3',\r\n candleUp: '#16a36a',\r\n candleDown: '#d9414f',\r\n candleUpWick: '#16a36a',\r\n candleDownWick: '#d9414f',\r\n lineColor: '#c77a0a',\r\n areaTopColor: 'rgba(199, 122, 10, 0.24)',\r\n areaBottomColor: 'rgba(199, 122, 10, 0.0)',\r\n volumeUp: 'rgba(22, 163, 106, 0.25)',\r\n volumeDown: 'rgba(217, 65, 79, 0.25)',\r\n axisLine: '#dde1e7',\r\n axisLabel: '#3f4754',\r\n axisLabelBackground: '#eef0f3',\r\n font: DEFAULT_FONT,\r\n};\r\n\r\nexport const DARK_TERMINAL: Theme = {\r\n name: 'terminal',\r\n background: '#0E0E0E',\r\n text: '#C0C0C0',\r\n textSecondary: '#8A8A8A',\r\n grid: '#1A1A1A',\r\n crosshair: '#666666',\r\n candleUp: '#00FF87',\r\n candleDown: '#FF3B4D',\r\n candleUpWick: '#00FF87',\r\n candleDownWick: '#FF3B4D',\r\n lineColor: '#3D8BFD',\r\n areaTopColor: 'rgba(61, 139, 253, 0.3)',\r\n areaBottomColor: 'rgba(61, 139, 253, 0.0)',\r\n volumeUp: 'rgba(0, 255, 135, 0.2)',\r\n volumeDown: 'rgba(255, 59, 77, 0.2)',\r\n axisLine: '#1A1A1A',\r\n axisLabel: '#8A8A8A',\r\n axisLabelBackground: '#1A1A1A',\r\n font: {\r\n family: \"'Roboto Mono', 'JetBrains Mono', 'SF Mono', Consolas, monospace\",\r\n sizeSmall: 10,\r\n sizeMedium: 12,\r\n sizeLarge: 14,\r\n },\r\n};\r\n","import type { LocaleStrings } from './types.js';\n\nexport const en: LocaleStrings = {\n // Chart types\n candlestick: 'Candlestick',\n line: 'Line',\n area: 'Area',\n bar: 'OHLC Bar',\n\n // Axes\n price: 'Price',\n volume: 'Volume',\n time: 'Time',\n open: 'Open',\n high: 'High',\n low: 'Low',\n close: 'Close',\n\n // Indicators - overlays\n sma: 'SMA',\n ema: 'EMA',\n bollingerBands: 'Bollinger Bands',\n vwap: 'VWAP',\n ichimoku: 'Ichimoku Cloud',\n parabolicSAR: 'Parabolic SAR',\n supertrend: 'Supertrend',\n keltnerChannel: 'Keltner Channel',\n donchianChannel: 'Donchian Channel',\n\n // Indicators - panels\n rsi: 'RSI',\n macd: 'MACD',\n stochastic: 'Stochastic',\n atr: 'ATR',\n adx: 'ADX',\n obv: 'OBV',\n williamsR: 'Williams %R',\n cci: 'CCI',\n mfi: 'MFI',\n aroon: 'Aroon',\n roc: 'ROC',\n tsi: 'TSI',\n cmf: 'CMF',\n stddev: 'Std Dev',\n volumeProfile: 'Volume Profile',\n accumulationDistribution: 'A/D Line',\n vroc: 'VROC',\n\n // Drawing tools\n trendLine: 'Trend Line',\n horizontalLine: 'Horizontal Line',\n verticalLine: 'Vertical Line',\n ray: 'Ray',\n extendedLine: 'Extended Line',\n parallelChannel: 'Parallel Channel',\n regressionChannel: 'Regression Channel',\n fibRetracement: 'Fibonacci Retracement',\n fibExtension: 'Fibonacci Extension',\n rectangle: 'Rectangle',\n ellipse: 'Ellipse',\n triangle: 'Triangle',\n pitchfork: \"Andrews' Pitchfork\",\n elliottWave: 'Elliott Wave',\n priceRange: 'Price Range',\n dateRange: 'Date Range',\n measure: 'Measure',\n textTool: 'Text',\n arrow: 'Arrow',\n clearAll: 'Clear All',\n\n // Trading\n buy: 'Buy',\n sell: 'Sell',\n buyLimit: 'Buy Limit',\n sellLimit: 'Sell Limit',\n buyStop: 'Buy Stop',\n sellStop: 'Sell Stop',\n stopLoss: 'Stop Loss',\n takeProfit: 'Take Profit',\n market: 'Market',\n limit: 'Limit',\n stop: 'Stop',\n cancel: 'Cancel',\n modify: 'Modify',\n quantity: 'Qty',\n pnl: 'P&L',\n activeOrders: 'Active Orders',\n positions: 'Positions',\n noOrders: 'No active orders',\n noPositions: 'No open positions',\n placeOrder: 'Place Order',\n rightClickToTrade: 'Right-click chart to place orders',\n\n // Market\n ceiling: 'Ceiling',\n floor: 'Floor',\n reference: 'Reference',\n session: 'Session',\n preOpen: 'Pre-Open',\n continuous: 'Continuous',\n preClose: 'Pre-Close',\n closed: 'Closed',\n\n // UI\n settings: 'Settings',\n theme: 'Theme',\n darkTheme: 'Dark',\n lightTheme: 'Light',\n tools: 'Tools',\n indicators: 'Indicators',\n overlays: 'Overlays',\n panels: 'Panels',\n orders: 'Orders',\n autoScale: 'Auto Scale',\n crosshair: 'Crosshair',\n grid: 'Grid',\n loading: 'Loading...',\n error: 'Error',\n\n numberDecimalSeparator: '.',\n numberGroupSeparator: ',',\n};\n","import type { LocaleStrings } from './types.js';\n\nexport const vi: LocaleStrings = {\n // Chart types\n candlestick: 'Nến',\n line: 'Đường',\n area: 'Vùng',\n bar: 'Thanh OHLC',\n\n // Axes\n price: 'Giá',\n volume: 'Khối lượng',\n time: 'Thời gian',\n open: 'Mở',\n high: 'Cao',\n low: 'Thấp',\n close: 'Đóng',\n\n // Indicators - overlays\n sma: 'SMA',\n ema: 'EMA',\n bollingerBands: 'Dải Bollinger',\n vwap: 'VWAP',\n ichimoku: 'Mây Ichimoku',\n parabolicSAR: 'Parabolic SAR',\n supertrend: 'Supertrend',\n keltnerChannel: 'Kênh Keltner',\n donchianChannel: 'Kênh Donchian',\n\n // Indicators - panels\n rsi: 'RSI',\n macd: 'MACD',\n stochastic: 'Stochastic',\n atr: 'ATR',\n adx: 'ADX',\n obv: 'OBV',\n williamsR: 'Williams %R',\n cci: 'CCI',\n mfi: 'MFI',\n aroon: 'Aroon',\n roc: 'ROC',\n tsi: 'TSI',\n cmf: 'CMF',\n stddev: 'Độ lệch chuẩn',\n volumeProfile: 'Phân bổ KL',\n accumulationDistribution: 'Tích lũy/Phân phối',\n vroc: 'VROC',\n\n // Drawing tools\n trendLine: 'Đường xu hướng',\n horizontalLine: 'Đường ngang',\n verticalLine: 'Đường dọc',\n ray: 'Tia',\n extendedLine: 'Đường kéo dài',\n parallelChannel: 'Kênh song song',\n regressionChannel: 'Kênh hồi quy',\n fibRetracement: 'Fibonacci thoái lui',\n fibExtension: 'Fibonacci mở rộng',\n rectangle: 'Hình chữ nhật',\n ellipse: 'Hình elip',\n triangle: 'Tam giác',\n pitchfork: 'Chĩa ba Andrews',\n elliottWave: 'Sóng Elliott',\n priceRange: 'Khoảng giá',\n dateRange: 'Khoảng thời gian',\n measure: 'Đo lường',\n textTool: 'Chữ',\n arrow: 'Mũi tên',\n clearAll: 'Xóa tất cả',\n\n // Trading\n buy: 'Mua',\n sell: 'Bán',\n buyLimit: 'Mua giới hạn',\n sellLimit: 'Bán giới hạn',\n buyStop: 'Mua chặn',\n sellStop: 'Bán chặn',\n stopLoss: 'Cắt lỗ',\n takeProfit: 'Chốt lời',\n market: 'Thị trường',\n limit: 'Giới hạn',\n stop: 'Dừng',\n cancel: 'Hủy',\n modify: 'Sửa',\n quantity: 'KL',\n pnl: 'Lãi/Lỗ',\n activeOrders: 'Lệnh chờ',\n positions: 'Vị thế',\n noOrders: 'Không có lệnh chờ',\n noPositions: 'Không có vị thế mở',\n placeOrder: 'Đặt lệnh',\n rightClickToTrade: 'Nhấp chuột phải để đặt lệnh',\n\n // Market\n ceiling: 'Trần',\n floor: 'Sàn',\n reference: 'Tham chiếu',\n session: 'Phiên',\n preOpen: 'Trước giờ mở',\n continuous: 'Liên tục',\n preClose: 'Trước giờ đóng',\n closed: 'Đóng cửa',\n\n // UI\n settings: 'Cài đặt',\n theme: 'Giao diện',\n darkTheme: 'Tối',\n lightTheme: 'Sáng',\n tools: 'Công cụ',\n indicators: 'Chỉ báo',\n overlays: 'Phủ lên',\n panels: 'Bảng',\n orders: 'Lệnh',\n autoScale: 'Tự co giãn',\n crosshair: 'Chữ thập',\n grid: 'Lưới',\n loading: 'Đang tải...',\n error: 'Lỗi',\n\n numberDecimalSeparator: ',',\n numberGroupSeparator: '.',\n};\n","export type { Locale, LocaleStrings, NumberFormatConfig, DateFormatConfig } from './types.js';\nexport { en } from './en.js';\nexport { vi } from './vi.js';\n\nimport type { Locale, LocaleStrings } from './types.js';\nimport { en } from './en.js';\nimport { vi } from './vi.js';\n\nconst locales = new Map<string, LocaleStrings>([\n ['en', en],\n ['vi', vi],\n]);\n\nlet currentLocale: Locale = 'en';\nlet currentStrings: LocaleStrings = en;\n\nexport function setLocale(locale: Locale): void {\n currentLocale = locale;\n currentStrings = locales.get(locale) ?? en;\n}\n\nexport function getLocale(): Locale {\n return currentLocale;\n}\n\nexport function t(key: keyof LocaleStrings): string {\n return currentStrings[key] ?? (en as any)[key] ?? key;\n}\n\nexport function registerLocale(locale: string, strings: LocaleStrings): void {\n locales.set(locale, strings);\n}\n\nexport function getLocaleStrings(locale?: string): LocaleStrings {\n return locales.get(locale ?? currentLocale) ?? en;\n}\n\n// Number formatting\nexport function formatNumber(value: number, precision = 2, locale?: string): string {\n const strings = locales.get(locale ?? currentLocale) ?? en;\n const dec = strings.numberDecimalSeparator;\n const grp = strings.numberGroupSeparator;\n\n const fixed = value.toFixed(precision);\n const [intPart, decPart] = fixed.split('.');\n\n // Group integer part\n const negative = intPart.startsWith('-');\n const digits = negative ? intPart.slice(1) : intPart;\n let grouped = '';\n for (let i = digits.length - 1, count = 0; i >= 0; i--, count++) {\n if (count > 0 && count % 3 === 0) grouped = grp + grouped;\n grouped = digits[i] + grouped;\n }\n if (negative) grouped = '-' + grouped;\n\n return decPart ? grouped + dec + decPart : grouped;\n}\n\nexport function formatVND(value: number): string {\n return formatNumber(value, 0, 'vi');\n}\n\nexport function formatVolumeLoc(value: number, locale?: string): string {\n if (value >= 1e9) return formatNumber(value / 1e9, 2, locale ?? currentLocale) + 'B';\n if (value >= 1e6) return formatNumber(value / 1e6, 2, locale ?? currentLocale) + 'M';\n if (value >= 1e3) return formatNumber(value / 1e3, 2, locale ?? currentLocale) + 'K';\n return formatNumber(value, 0, locale ?? currentLocale);\n}\n","import type { MarketConfig, MarketColorScheme, TradingSession } from './types.js';\nimport type { Theme } from '../types/theme.js';\n\n// Vietnam stock color convention:\n// Purple/Red = ceiling (trần) - max up\n// Green/Cyan = floor (sàn) - max down\n// Yellow = reference (tham chiếu)\n// Red = up, Blue = down (common VN convention)\nexport const VN_COLORS: MarketColorScheme = {\n up: '#FF0000', // Đỏ - tăng\n down: '#0000FF', // Xanh dương - giảm\n unchanged: '#FFD700', // Vàng - tham chiếu\n ceiling: '#FF00FF', // Tím - trần\n floor: '#00FFFF', // Xanh lam - sàn\n reference: '#FFD700', // Vàng - tham chiếu\n};\n\nexport const HOSE_SESSIONS: TradingSession[] = [\n { name: 'ATO', startTime: '09:00', endTime: '09:15', type: 'preOpen' },\n { name: 'Phiên 1', startTime: '09:15', endTime: '11:30', type: 'continuous' },\n { name: 'Nghỉ trưa', startTime: '11:30', endTime: '13:00', type: 'closed' },\n { name: 'Phiên 2', startTime: '13:00', endTime: '14:30', type: 'continuous' },\n { name: 'ATC', startTime: '14:30', endTime: '14:45', type: 'preClose' },\n];\n\nexport const HNX_SESSIONS: TradingSession[] = [\n { name: 'Phiên 1', startTime: '09:00', endTime: '11:30', type: 'continuous' },\n { name: 'Nghỉ trưa', startTime: '11:30', endTime: '13:00', type: 'closed' },\n { name: 'Phiên 2', startTime: '13:00', endTime: '14:30', type: 'continuous' },\n { name: 'ATC', startTime: '14:30', endTime: '14:45', type: 'preClose' },\n];\n\n// Market presets\nexport const MARKET_HOSE: MarketConfig = {\n type: 'stock',\n exchange: 'HOSE',\n currency: 'VND',\n pricePrecision: 2,\n volumeUnit: 10,\n priceStep: 0.05,\n priceLimits: { enabled: true, ceilingPercent: 7, floorPercent: 7 },\n sessions: HOSE_SESSIONS,\n colorScheme: VN_COLORS,\n};\n\nexport const MARKET_HNX: MarketConfig = {\n type: 'stock',\n exchange: 'HNX',\n currency: 'VND',\n pricePrecision: 1,\n volumeUnit: 100,\n priceStep: 0.1,\n priceLimits: { enabled: true, ceilingPercent: 10, floorPercent: 10 },\n sessions: HNX_SESSIONS,\n colorScheme: VN_COLORS,\n};\n\nexport const MARKET_UPCOM: MarketConfig = {\n type: 'stock',\n exchange: 'UPCOM',\n currency: 'VND',\n pricePrecision: 1,\n volumeUnit: 100,\n priceStep: 0.1,\n priceLimits: { enabled: true, ceilingPercent: 15, floorPercent: 15 },\n sessions: HNX_SESSIONS,\n colorScheme: VN_COLORS,\n};\n\nexport const MARKET_CRYPTO: MarketConfig = {\n type: 'crypto',\n currency: 'USDT',\n pricePrecision: 2,\n priceLimits: { enabled: false },\n};\n\nexport const MARKET_NYSE: MarketConfig = {\n type: 'stock',\n exchange: 'NYSE',\n currency: 'USD',\n pricePrecision: 2,\n priceStep: 0.01,\n priceLimits: { enabled: false },\n sessions: [\n { name: 'Pre-Market', startTime: '04:00', endTime: '09:30', type: 'preOpen' },\n { name: 'Regular', startTime: '09:30', endTime: '16:00', type: 'continuous' },\n { name: 'After-Hours', startTime: '16:00', endTime: '20:00', type: 'preClose' },\n ],\n};\n\n// Build a theme variant for VN stock market\nexport function createVNTheme(base: Theme): Theme {\n return {\n ...base,\n candleUp: VN_COLORS.up,\n candleDown: VN_COLORS.down,\n candleUpWick: VN_COLORS.up,\n candleDownWick: VN_COLORS.down,\n volumeUp: 'rgba(255, 0, 0, 0.3)',\n volumeDown: 'rgba(0, 0, 255, 0.3)',\n };\n}\n\nexport function computePriceLimits(referencePrice: number, config: MarketConfig): { ceiling: number; floor: number; reference: number } | null {\n if (!config.priceLimits?.enabled || !config.priceLimits.ceilingPercent) return null;\n const ceilPct = config.priceLimits.ceilingPercent / 100;\n const floorPct = (config.priceLimits.floorPercent ?? config.priceLimits.ceilingPercent) / 100;\n return {\n ceiling: referencePrice * (1 + ceilPct),\n floor: referencePrice * (1 - floorPct),\n reference: referencePrice,\n };\n}\n\nexport function getCurrentSession(sessions: TradingSession[]): TradingSession | null {\n const now = new Date();\n const hhmm = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;\n for (const session of sessions) {\n if (hhmm >= session.startTime && hhmm < session.endTime) return session;\n }\n return null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/types/rendering.ts","../src/types/drawing.ts","../src/types/trading.ts","../src/types/signal.ts","../src/types/realtime.ts","../src/types/quote.ts","../src/types/news.ts","../src/utils/math.ts","../src/utils/data.ts","../src/utils/color.ts","../src/utils/timezone.ts","../src/utils/time.ts","../src/utils/precision.ts","../src/utils/priceSource.ts","../src/utils/resample.ts","../src/utils/symbols.ts","../src/utils/marketStatus.ts","../src/utils/drawingOptions.ts","../src/utils/drawingStyle.ts","../src/utils/chartTypeOptions.ts","../src/constants/defaults.ts","../src/constants/themes.ts","../src/i18n/en.ts","../src/i18n/vi.ts","../src/i18n/index.ts","../src/market/presets.ts"],"sourcesContent":["export interface Point {\n x: number;\n y: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport type PriceScaleMode = 'regular' | 'logarithmic' | 'percentage' | 'indexedTo100';\n\nexport interface ViewportState {\n visibleRange: { from: number; to: number };\n priceRange: { min: number; max: number };\n barWidth: number;\n barSpacing: number;\n offset: number;\n chartRect: Rect;\n /**\n * Logarithmic price geometry. Kept for back-compat; mirrors\n * `scaleMode === 'logarithmic'`. Prefer reading `scaleMode`.\n */\n logScale?: boolean;\n /**\n * Price-scale presentation. `regular`, `percentage`, and `indexedTo100` share\n * the same linear geometry — only the axis labels differ (rebased to\n * `scaleBaseline`). `logarithmic` changes the geometry. Defaults to\n * `regular` when unset.\n */\n scaleMode?: PriceScaleMode;\n /**\n * Reference price for `percentage` / `indexedTo100` axis labels — usually the\n * close of the first visible bar. Set by the chart each frame.\n */\n scaleBaseline?: number;\n /** Upside down: higher prices lower on screen. */\n invertScale?: boolean;\n /**\n * How prices on this scale read, when the chart has a price format of its\n * own (`ChartOptions.priceFormat`). Unset: decimals. Indicator panes leave\n * it unset.\n */\n formatPrice?: (price: number) => string;\n /**\n * The step prices move in under that format (a 32nd): the axis puts its\n * ticks on whole numbers of it.\n */\n priceUnit?: number;\n /**\n * Optional reference to the current bar series. When set, drawings/indicators\n * treat `anchor.time` as a real timestamp and convert to bar index via\n * `timestampToBarIndex(time, data)` at render/hit-test time. This lets\n * anchors survive timeframe / symbol switches. When unset,\n * `anchor.time` is treated as a raw bar index (legacy behavior).\n */\n data?: ReadonlyArray<{ time: number }>;\n /**\n * Width of the price axis strip right of `chartRect`, in CSS pixels. The\n * chart widens it to fit long labels (sub-cent prices); renderers drawing\n * badges on the axis size them to it. Defaults to `PRICE_AXIS_WIDTH`.\n */\n priceAxisWidth?: number;\n}\n\n/**\n * What changed, for `requestRender()`. The chart paints two canvases: a scene\n * (everything that follows data, viewport and chart objects) and a thin top\n * canvas for pointer-tied visuals. Any type except `Hover` redraws both;\n * `Hover` redraws only the top canvas (crosshair, its axis pills, legend,\n * bar countdown, measure ruler, selection box).\n */\nexport enum LayerType {\n Background = 0,\n Main = 1,\n Panel = 2,\n Overlay = 3,\n UI = 4,\n Hover = 5,\n}\n","import type { Point, ViewportState } from './rendering.js';\nimport type { OHLCBar } from './ohlc.js';\n\nexport type DrawingToolType =\n | 'trendLine' | 'horizontalLine' | 'horizontalRay' | 'verticalLine' | 'ray' | 'extendedLine'\n | 'crossLine' | 'trendAngle' | 'infoLine'\n | 'parallelChannel' | 'regressionChannel'\n | 'fibRetracement' | 'fibExtension' | 'fibTimeZones' | 'fibChannel' | 'fibSpeedResistanceFan'\n | 'rectangle' | 'ellipse' | 'triangle' | 'circle'\n | 'pitchfork' | 'schiffPitchfork' | 'modifiedSchiffPitchfork' | 'elliottWave'\n | 'xabcdPattern' | 'abcdPattern' | 'headAndShoulders'\n | 'priceRange' | 'dateRange' | 'dateAndPriceRange' | 'measure'\n | 'text' | 'arrow' | 'priceLabel'\n | 'gannFan' | 'gannBox' | 'cyclicLines'\n | 'anchoredVWAP'\n | 'volumeProfileRange'\n | 'riskReward'\n | 'note' | 'callout' | 'flag' | 'arrowMark' | 'icon'\n | 'brush' | 'highlighter' | 'path' | 'polyline' | 'curve' | 'arc'\n | 'fibCircles' | 'fibSpiral' | 'fibArcs' | 'fibWedge' | 'pitchfan' | 'gannSquare'\n | 'elliottImpulse' | 'elliottCorrection' | 'elliottTriangle' | 'elliottDoubleCombo' | 'elliottTripleCombo'\n | 'threeDrives' | 'cypherPattern'\n | 'timeCycles' | 'sineLine'\n | 'forecast' | 'projection' | 'barsPattern';\n\nexport interface AnchorPoint {\n time: number;\n price: number;\n}\n\nexport interface DrawingStyle {\n color: string;\n lineWidth: number;\n lineStyle: 'solid' | 'dashed' | 'dotted';\n fillColor?: string;\n fillOpacity?: number;\n fontSize?: number;\n text?: string;\n}\n\nexport interface DrawingState {\n id: string;\n type: DrawingToolType;\n anchors: AnchorPoint[];\n style: DrawingStyle;\n visible: boolean;\n locked: boolean;\n /** The tool's own settings (levels, extend left/right…); see `DrawingDescriptor.options`. */\n options?: DrawingOptions;\n /** The group it belongs to: drawings selected, hidden and locked together. */\n group?: { id: string; name: string };\n meta?: Record<string, unknown>;\n}\n\n/** One level of a Fibonacci or Gann tool: a ratio, shown or not, in its own colour. */\nexport interface DrawingLevel {\n value: number;\n visible: boolean;\n /** CSS colour; the drawing's colour when left out. */\n color?: string;\n}\n\nexport type DrawingOptionValue = boolean | number | string | DrawingLevel[];\n\n/** A drawing's settings beyond the shared style, by key. Missing keys take the tool's defaults. */\nexport type DrawingOptions = Record<string, DrawingOptionValue>;\n\n/** A setting a drawing tool offers beyond the shared style, with its default. */\nexport type DrawingOptionDef =\n | { kind: 'boolean'; label: string; default: boolean }\n | { kind: 'number'; label: string; default: number; min?: number; max?: number; step?: number }\n | { kind: 'choice'; label: string; default: string; choices: readonly { value: string; label: string }[] }\n | { kind: 'levels'; label: string; default: readonly DrawingLevel[] }\n | { kind: 'text'; label: string; default: string };\n\nexport type DrawingOptionDefs = Readonly<Record<string, DrawingOptionDef>>;\n\nexport interface DrawingDescriptor {\n type: DrawingToolType;\n name: string;\n requiredAnchors: number;\n singleClick?: boolean;\n /** Settings the tool offers beyond the shared style; a settings dialog is built from them. */\n options?: DrawingOptionDefs;\n /** It fills an area with `style.fillColor` (a settings dialog offers the fill). */\n fill?: boolean;\n /** It draws `style.text` (a settings dialog offers the text and its size). */\n text?: boolean;\n /**\n * How it is drawn with the pointer. 'clicks' (the default): a click per\n * anchor up to `requiredAnchors`. 'freehand': press and drag, a point every\n * few pixels, release to finish. 'path': a click per point, at least\n * `requiredAnchors` and at most `maxAnchors`; a double-click, Enter or a\n * click on the last point finishes it.\n */\n creation?: 'clicks' | 'freehand' | 'path';\n /** Most points a 'path' takes (100 when left out). */\n maxAnchors?: number;\n}\n\nexport interface DrawingPlugin {\n descriptor: DrawingDescriptor;\n render(\n ctx: CanvasRenderingContext2D,\n state: DrawingState,\n viewport: ViewportState,\n selected: boolean,\n ): void;\n hitTest(\n point: Point,\n state: DrawingState,\n viewport: ViewportState,\n tolerance: number,\n ): boolean;\n hitTestAnchor(\n point: Point,\n state: DrawingState,\n viewport: ViewportState,\n tolerance: number,\n ): number;\n /**\n * The drawing after handle `index` (from `hitTestAnchor`) is dragged to\n * `anchor`. Without it, the handle is anchor `index` and moves there. A\n * tool with handles that are not anchors (a position's target, say)\n * changes its options instead.\n */\n moveHandle?(state: DrawingState, index: number, anchor: AnchorPoint): DrawingState;\n /**\n * The prices of the drawing's lines at `time` (what an alert on it\n * crosses), or null where it doesn't reach. Tools without it take no alerts.\n * With `viewport` the lines follow the chart as drawn: straight across bars\n * (not clock time, so a weekend gap doesn't bend them) and on a log scale\n * straight in log price.\n */\n priceAt?(state: DrawingState, time: number, viewport?: ViewportState): number[] | null;\n /**\n * For a tool drawn from the chart's bars (an anchored VWAP, a volume\n * profile): the chart hands it a getter for them when it is registered.\n */\n setDataGetter?(getter: () => readonly OHLCBar[]): void;\n}\n\nexport const DEFAULT_DRAWING_STYLE: DrawingStyle = {\n color: '#4c8dff',\n lineWidth: 1,\n lineStyle: 'solid',\n fillColor: 'rgba(76, 141, 255, 0.1)',\n fillOpacity: 0.1,\n fontSize: 12,\n};\n","export type OrderSide = 'buy' | 'sell';\nexport type OrderType = 'market' | 'limit' | 'stop' | 'stopLimit';\nexport type OrderStatus = 'pending' | 'filled' | 'cancelled' | 'rejected';\nexport type OrderLabel = 'LIMIT' | 'STOP' | 'SL' | 'TP' | 'STOP LIMIT';\n\n/** How long an order works: good till cancelled, or for the day. */\nexport type TimeInForce = 'gtc' | 'day';\n\nexport interface TradingOrder {\n id: string;\n side: OrderSide;\n type: OrderType;\n price: number;\n stopPrice?: number;\n quantity: number;\n label?: OrderLabel;\n draggable?: boolean;\n /** Stop-loss and take-profit the position gets when the order fills. */\n stopLoss?: number;\n takeProfit?: number;\n timeInForce?: TimeInForce;\n meta?: Record<string, unknown>;\n}\n\nexport interface TradingPosition {\n id: string;\n side: OrderSide;\n entryPrice: number;\n quantity: number;\n /** Quantity already closed (for partial-close visualization). 0 ≤ closedQuantity ≤ quantity. */\n closedQuantity?: number;\n stopLoss?: number;\n takeProfit?: number;\n meta?: Record<string, unknown>;\n}\n\n/** Threshold-based P&L color stop. Sorted ascending by `pnl` is recommended. */\nexport interface PnLThreshold {\n /** Inclusive lower bound. Use -Infinity for the bottom-most stop. */\n pnl: number;\n color: string;\n}\n\n/** Tokens passed to position label templates. */\nexport interface PositionLabelContext {\n side: OrderSide;\n quantity: number;\n closedQuantity: number;\n openQuantity: number;\n entryPrice: number;\n currentPrice: number;\n pnl: number;\n pnlPct: number;\n precision: number;\n /** The chart's price format, for `{entry}` and `{price}` (decimals at `precision` without one). */\n formatPrice?: (price: number) => string;\n}\n\nexport interface DepthLevel {\n price: number;\n volume: number;\n}\n\nexport interface DepthData {\n bids: DepthLevel[];\n asks: DepthLevel[];\n}\n\nexport interface TradingConfig {\n enabled: boolean;\n orderColors?: { buy?: string; sell?: string };\n positionColors?: { profit?: string; loss?: string; entry?: string };\n /**\n * Optional gradient of colors keyed to P&L value. When provided, the rendered\n * position zone uses the color of the highest threshold whose `pnl` ≤ live P&L.\n * Falls back to `positionColors.profit`/`.loss` when unset.\n */\n pnlThresholds?: PnLThreshold[];\n /**\n * Position P&L label template. Supports tokens: {side} {qty} {closedQty}\n * {openQty} {entry} {price} {pnl} {pnlPct} {pnlSign}. Pass a function for\n * full control. Default: `{side} {qty} | P&L: {pnlSign}{pnl}`.\n */\n positionLabel?: string | ((ctx: PositionLabelContext) => string);\n depthOverlay?: {\n enabled?: boolean;\n bidColor?: string;\n askColor?: string;\n maxWidth?: number;\n };\n contextMenu?: { enabled?: boolean };\n /**\n * Buttons on the lines: × on an order cancels it, × on a position closes it\n * and ⇅ reverses it, × on a stop-loss or take-profit removes it. All on by\n * default.\n */\n lineButtons?: { cancel?: boolean; close?: boolean; reverse?: boolean; removeStops?: boolean };\n /** Marks where orders filled (from the execution adapter's fills). On by default. */\n fillMarks?: boolean;\n pricePrecision?: number;\n dragThreshold?: number;\n}\n\nexport interface OrderPlaceIntent {\n side: OrderSide;\n type: OrderType;\n price: number;\n stopPrice?: number;\n quantity?: number;\n /** Stop-loss and take-profit for the position the order opens. */\n stopLoss?: number;\n takeProfit?: number;\n timeInForce?: TimeInForce;\n}\n\nexport interface OrderModifyIntent {\n orderId: string;\n newPrice: number;\n /** The price before the change, when known (e.g. from a drag). Optional so\n * the chart's `orderModify` event payload routes straight to an adapter. */\n previousPrice?: number;\n}\n\nexport interface OrderCancelIntent {\n orderId: string;\n}\n\nexport interface PositionModifyIntent {\n positionId: string;\n /** A new stop-loss; null removes it, left out keeps it. */\n stopLoss?: number | null;\n /** A new take-profit; null removes it, left out keeps it. */\n takeProfit?: number | null;\n}\n\nexport interface PositionCloseIntent {\n positionId: string;\n}\n\n/** Close a position and open the same size the other way. */\nexport interface PositionReverseIntent {\n positionId: string;\n}\n\nexport const DEFAULT_TRADING_CONFIG: TradingConfig = {\n enabled: true,\n orderColors: { buy: '#1fa874', sell: '#e8505b' },\n positionColors: { profit: '#1fa874', loss: '#e8505b', entry: '#4c8dff' },\n depthOverlay: { enabled: false, bidColor: 'rgba(31, 168, 116,0.15)', askColor: 'rgba(232, 80, 91,0.15)', maxWidth: 100 },\n contextMenu: { enabled: true },\n lineButtons: { cancel: true, close: true, reverse: true, removeStops: true },\n fillMarks: true,\n pricePrecision: 2,\n dragThreshold: 3,\n};\n","export type SignalDirection = 'long' | 'short' | 'neutral';\n\nexport interface SignalMarker {\n id: string;\n time: number;\n price: number;\n direction: SignalDirection;\n confidence: number;\n source: string;\n label?: string;\n color?: string;\n meta?: Record<string, unknown>;\n}\n\nexport interface SignalMarkerStyle {\n longColor?: string;\n shortColor?: string;\n neutralColor?: string;\n arrowSize?: number;\n showLabel?: boolean;\n showConfidence?: boolean;\n sourceColors?: Record<string, string>;\n}\n\nexport const DEFAULT_SIGNAL_STYLE: SignalMarkerStyle = {\n longColor: '#1fa874',\n shortColor: '#e8505b',\n neutralColor: '#9E9E9E',\n arrowSize: 12,\n showLabel: true,\n showConfidence: true,\n};\n\nexport type TradeZoneDirection = 'long' | 'short';\n\nexport interface TradeZone {\n id: string;\n entryTime: number;\n entryPrice: number;\n exitTime?: number;\n exitPrice?: number;\n direction: TradeZoneDirection;\n pnl?: number;\n pnlPercent?: number;\n label?: string;\n meta?: Record<string, unknown>;\n}\n\nexport interface TradeZoneStyle {\n profitColor?: string;\n lossColor?: string;\n activeColor?: string;\n fillOpacity?: number;\n borderWidth?: number;\n showLabel?: boolean;\n showPnl?: boolean;\n}\n\nexport const DEFAULT_TRADE_ZONE_STYLE: TradeZoneStyle = {\n profitColor: '#1fa874',\n lossColor: '#e8505b',\n activeColor: '#4c8dff',\n fillOpacity: 0.12,\n borderWidth: 1,\n showLabel: true,\n showPnl: true,\n};\n","import type { OHLCBar, TimeFrame } from './ohlc.js';\nimport type { SymbolInfo, SymbolSearchOptions } from './symbol.js';\nimport type { Quote } from './quote.js';\nimport type { NewsItem } from './news.js';\n\n/** One trade: when (ms), at what price, how much. */\nexport interface Trade {\n time: number;\n price: number;\n volume: number;\n}\n\n// --- Connection ---\n\nexport type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';\n\nexport interface ConnectionInfo {\n state: ConnectionState;\n latency?: number;\n reconnectAttempt?: number;\n lastMessageTime?: number;\n error?: string;\n}\n\n// --- Ticks & Trades ---\n\nexport interface RawTick {\n time: number;\n price: number;\n volume: number;\n side?: 'buy' | 'sell';\n /** The best bid and ask with it, when the feed has them (the chart marks them). */\n bid?: number;\n ask?: number;\n}\n\nexport interface AggregatedBar extends OHLCBar {\n closed: boolean; // true when bar is finalized\n tickCount: number; // number of ticks in this bar\n}\n\n// --- Data Adapter (Strategy Pattern) ---\n\nexport interface DataAdapterConfig {\n symbol: string;\n timeframe: TimeFrame;\n reconnect?: boolean; // default: true\n reconnectMaxRetries?: number; // default: Infinity\n reconnectBaseDelay?: number; // ms, default: 1000\n reconnectMaxDelay?: number; // ms, default: 30000\n heartbeatInterval?: number; // ms, default: 30000\n bufferSize?: number; // max ticks to buffer, default: 1000\n}\n\nexport type DataAdapterEventType =\n | 'tick'\n | 'bar'\n | 'barClose'\n | 'snapshot' // initial historical data loaded\n | 'connectionChange'\n | 'error';\n\nexport interface DataAdapterEvent<T = unknown> {\n type: DataAdapterEventType;\n data: T;\n timestamp: number;\n}\n\nexport type DataAdapterListener<T = unknown> = (event: DataAdapterEvent<T>) => void;\n\n/**\n * Data adapter interface. Implements the observer pattern:\n * - connect() to start receiving data\n * - on('bar'|'tick'|'connectionChange', handler) to receive events\n * - disconnect() to stop, then connect() again to switch symbols/timeframes\n * - No separate subscribe/unsubscribe — reconnect is the intended pattern\n *\n * Strategy pattern for pluggable data sources.\n * Implementations handle the specifics of each data source (WebSocket, REST,\n * SSE, etc.) while the StreamManager orchestrates lifecycle and aggregation.\n *\n * Built-in: BinanceAdapter\n * Implement this for: custom exchange APIs, broker feeds, mock data\n */\nexport interface DataAdapter {\n readonly name: string;\n\n /**\n * Optional: the timeframes the feed serves. Charts build any other\n * timeframe (7m, 90m, 2d…) from the coarsest of these that divides it.\n * Leave it out when the feed serves every timeframe.\n */\n readonly supportedTimeframes?: readonly TimeFrame[];\n\n connect(config: DataAdapterConfig): void;\n disconnect(): void;\n getConnectionState(): ConnectionState;\n\n /**\n * Load historical bars. Called once on connect, before streaming starts.\n * Returns bars sorted by time ascending.\n */\n fetchHistory(symbol: string, timeframe: TimeFrame, limit?: number): Promise<OHLCBar[]>;\n\n /**\n * Optional: load up to `limit` bars older than `before` (the oldest loaded\n * bar's time, in the bars' own unit), sorted by time ascending. Return an\n * empty array when no older data exists. A chart connected to an adapter\n * with this method loads older bars as the user scrolls back in time.\n */\n fetchHistoryBefore?(symbol: string, timeframe: TimeFrame, before: number, limit: number): Promise<OHLCBar[]>;\n\n /** Optional: symbols matching `query`, best first — for a symbol search box. */\n searchSymbols?(query: string, options?: SymbolSearchOptions): Promise<SymbolInfo[]>;\n\n /**\n * Optional: what the feed knows about `symbol` (name, price step, exchange\n * timezone and hours), or null when it doesn't know it. A connected chart\n * asks for it and applies it.\n */\n resolveSymbol?(symbol: string): Promise<SymbolInfo | null>;\n\n /**\n * Optional: live quotes (last price, the day's change, high, low, volume)\n * for many symbols at once, until the function it returns is called. A\n * widget's watchlist fills its rows from it.\n */\n subscribeQuotes?(symbols: readonly string[], onQuotes: (quotes: Quote[]) => void): () => void;\n\n /** Optional: recent headlines about `symbol`, newest first. */\n fetchNews?(symbol: string, limit?: number): Promise<NewsItem[]>;\n\n /**\n * Optional, with `subscribeTrades`: up to `limit` recent trades, oldest\n * first. A chart builds tick timeframes (`'100T'`) from a feed's trades.\n */\n fetchTrades?(symbol: string, limit?: number): Promise<Trade[]>;\n\n /** Optional, with `fetchTrades`: live trades of `symbol` until the function it returns is called. */\n subscribeTrades?(symbol: string, onTrades: (trades: Trade[]) => void): () => void;\n\n on<T = unknown>(event: DataAdapterEventType, listener: DataAdapterListener<T>): void;\n off<T = unknown>(event: DataAdapterEventType, listener: DataAdapterListener<T>): void;\n\n dispose(): void;\n}\n\n// --- Stream Manager Config ---\n\nexport interface StreamConfig {\n adapter: DataAdapter;\n symbol: string;\n timeframe: TimeFrame;\n historyLimit?: number; // bars to load initially, default: 500\n /** Bars per request when scrolling back for older bars (needs `adapter.fetchHistoryBefore`), default: 500. */\n historyPageSize?: number;\n autoScroll?: boolean; // scroll to end on new bar, default: true\n showCurrentPriceLine?: boolean; // default: true\n aggregateTicks?: boolean; // build bars from ticks, default: false\n reconnect?: ReconnectConfig;\n}\n\nexport interface ReconnectConfig {\n enabled: boolean; // default: true\n maxRetries: number; // default: Infinity\n baseDelay: number; // ms, default: 1000\n maxDelay: number; // ms, default: 30000\n backoffMultiplier: number; // default: 2\n}\n\nexport const DEFAULT_RECONNECT: ReconnectConfig = {\n enabled: true,\n maxRetries: Infinity,\n baseDelay: 1000,\n maxDelay: 30000,\n backoffMultiplier: 2,\n};\n\nexport const DEFAULT_STREAM_CONFIG: Partial<StreamConfig> = {\n historyLimit: 500,\n autoScroll: true,\n showCurrentPriceLine: true,\n aggregateTicks: false,\n};\n","/**\n * A symbol's latest quote: its price and the day's move, for a watchlist row\n * or a symbol's details. Prices are in the symbol's quote currency.\n */\nexport interface Quote {\n symbol: string;\n /** The last traded price. */\n last: number;\n /** The move from the reference price (the prior close; 24 h ago for crypto). */\n change?: number;\n /** The same move, in percent. */\n changePercent?: number;\n open?: number;\n high?: number;\n low?: number;\n /** The prior session's close. */\n prevClose?: number;\n /** Volume over the same period, in the base unit. */\n volume?: number;\n bid?: number;\n ask?: number;\n /** When the quote was made, in ms since the epoch. */\n time?: number;\n}\n\n/** Live quotes for many symbols at once. */\nexport interface QuoteSource {\n /**\n * Send quotes for `symbols` to `onQuotes` as they change (any number per\n * call) until the function it returns is called.\n */\n subscribeQuotes(symbols: readonly string[], onQuotes: (quotes: Quote[]) => void): () => void;\n}\n\nconst QUOTE_NUMBERS = ['change', 'changePercent', 'open', 'high', 'low', 'prevClose', 'volume', 'bid', 'ask', 'time'] as const;\n\n/**\n * A quote from untrusted input (a host's push, a feed's frame): its symbol\n * and last price must be usable; any other field that isn't a finite number\n * is left out. The change is worked out from the prior close or the open when\n * the input has none. Null when it isn't a quote.\n */\nexport function readQuote(raw: unknown): Quote | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, unknown>;\n const symbol = typeof r.symbol === 'string' ? r.symbol.trim() : '';\n if (!symbol || symbol.length > 64 || !isFiniteNumber(r.last)) return null;\n const quote: Quote = { symbol, last: r.last };\n for (const key of QUOTE_NUMBERS) {\n const v = r[key];\n if (isFiniteNumber(v)) quote[key] = v;\n }\n const ref = quote.prevClose ?? quote.open;\n if (quote.change === undefined && ref !== undefined) quote.change = quote.last - ref;\n if (quote.changePercent === undefined && quote.change !== undefined) {\n const base = quote.last - quote.change;\n if (base !== 0) quote.changePercent = (quote.change / base) * 100;\n }\n return quote;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v);\n}\n","/** A news headline about a symbol. */\nexport interface NewsItem {\n title: string;\n /** When it was published, in ms since the epoch. */\n time: number;\n /** The article; only http(s) links are kept. */\n url?: string;\n source?: string;\n summary?: string;\n id?: string;\n}\n\nconst MAX_TITLE = 300;\nconst MAX_TEXT = 1000;\n\nconst text = (v: unknown, max: number): string | undefined => {\n if (typeof v !== 'string') return undefined;\n const t = v.trim();\n return t ? t.slice(0, max) : undefined;\n};\n\nconst webUrl = (v: unknown): string | undefined => {\n if (typeof v !== 'string') return undefined;\n try {\n const url = new URL(v);\n return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Headlines from untrusted input (a feed, a host): each needs a title and a\n * time; links that aren't web pages are dropped. Newest first, at most\n * `limit`.\n */\nexport function readNews(raw: unknown, limit = 20): NewsItem[] {\n if (!Array.isArray(raw)) return [];\n const items: NewsItem[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue;\n const r = entry as Record<string, unknown>;\n const title = text(r.title, MAX_TITLE);\n if (!title || typeof r.time !== 'number' || !Number.isFinite(r.time)) continue;\n const item: NewsItem = { title, time: r.time };\n const url = webUrl(r.url);\n if (url) item.url = url;\n const source = text(r.source, MAX_TITLE);\n if (source) item.source = source;\n const summary = text(r.summary, MAX_TEXT);\n if (summary) item.summary = summary;\n const id = text(r.id, MAX_TITLE);\n if (id) item.id = id;\n items.push(item);\n }\n return items.sort((a, b) => b.time - a.time).slice(0, Math.max(0, limit));\n}\n","export function clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n\nexport function lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\nexport function inverseLerp(a: number, b: number, value: number): number {\n if (a === b) return 0;\n return (value - a) / (b - a);\n}\n\nexport function roundToStep(value: number, step: number): number {\n return Math.round(value / step) * step;\n}\n\nexport function niceNumber(value: number, round: boolean): number {\n const exp = Math.floor(Math.log10(value));\n const frac = value / Math.pow(10, exp);\n let nice: number;\n if (round) {\n if (frac < 1.5) nice = 1;\n else if (frac < 3) nice = 2;\n else if (frac < 7) nice = 5;\n else nice = 10;\n } else {\n if (frac <= 1) nice = 1;\n else if (frac <= 2) nice = 2;\n else if (frac <= 5) nice = 5;\n else nice = 10;\n }\n return nice * Math.pow(10, exp);\n}\n\nexport function computeTickStep(min: number, max: number, maxTicks: number): number {\n const range = niceNumber(max - min, false);\n return niceNumber(range / (maxTicks - 1), true);\n}\n","import type { OHLCBar, DataSeries } from '../types/ohlc.js';\n\n/**\n * Normalize bar timestamp to milliseconds.\n * Auto-detects: time > 1e12 is already ms, otherwise treats as seconds.\n */\nexport function normalizeBarTime(time: number): number {\n return time > 1e12 ? time : time * 1000;\n}\n\n/**\n * Normalize a bar's timestamp field to milliseconds.\n * Accepts either { time } (ms or s) or { t, o, h, l, c, v } wire format.\n */\nexport function normalizeBar(raw: Record<string, number>): OHLCBar {\n const time = normalizeBarTime(raw.time ?? raw.t ?? 0);\n return {\n time,\n open: raw.open ?? raw.o ?? 0,\n high: raw.high ?? raw.h ?? 0,\n low: raw.low ?? raw.l ?? 0,\n close: raw.close ?? raw.c ?? 0,\n volume: raw.volume ?? raw.v ?? 0,\n };\n}\n\nexport function sliceVisibleData(\n data: DataSeries,\n from: number,\n to: number,\n): DataSeries {\n const startIdx = Math.max(0, from);\n const endIdx = Math.min(data.length, to + 1);\n return data.slice(startIdx, endIdx);\n}\n\nexport function findBarIndex(data: DataSeries, timestamp: number): number {\n let lo = 0;\n let hi = data.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n if (data[mid].time < timestamp) lo = mid + 1;\n else if (data[mid].time > timestamp) hi = mid - 1;\n else return mid;\n }\n return lo;\n}\n\nexport function computePriceRange(\n data: DataSeries,\n from: number,\n to: number,\n padding = 0.05,\n): { min: number; max: number } {\n if (data.length === 0) return { min: 0, max: 1 };\n const startIdx = Math.max(0, from);\n const endIdx = Math.min(data.length - 1, to);\n let min = Infinity;\n let max = -Infinity;\n for (let i = startIdx; i <= endIdx; i++) {\n if (data[i].low < min) min = data[i].low;\n if (data[i].high > max) max = data[i].high;\n }\n if (min === Infinity) return { min: 0, max: 1 };\n const range = max - min || 1;\n return {\n min: min - range * padding,\n max: max + range * padding,\n };\n}\n\nexport function mergeBar(existing: OHLCBar, tick: { price: number; volume?: number; time: number }): OHLCBar {\n return {\n ...existing,\n high: Math.max(existing.high, tick.price),\n low: Math.min(existing.low, tick.price),\n close: tick.price,\n volume: existing.volume + (tick.volume ?? 0),\n time: tick.time,\n };\n}\n","export function hexToRgba(hex: string, alpha = 1): string {\n // #rgb and #rgba are short for #rrggbb and #rrggbbaa; a hex alpha gives way to `alpha`.\n let h = hex.slice(1);\n if (h.length === 3 || h.length === 4) h = [...h.slice(0, 3)].map((c) => c + c).join('');\n const r = parseInt(h.slice(0, 2), 16);\n const g = parseInt(h.slice(2, 4), 16);\n const b = parseInt(h.slice(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n/**\n * `color` at `alpha`: hex (#rgb, #rgba, #rrggbb, #rrggbbaa) and rgb()/rgba()\n * with commas or spaces. Any other colour (a name, hsl()) comes back as it is.\n */\nexport function withAlpha(color: string, alpha: number): string {\n const c = color.trim();\n if (/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(c)) {\n return hexToRgba(c, alpha);\n }\n const rgb = c.match(/^rgba?\\(\\s*(\\d+(?:\\.\\d+)?)[\\s,]+(\\d+(?:\\.\\d+)?)[\\s,]+(\\d+(?:\\.\\d+)?)/i);\n if (rgb) {\n return `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${alpha})`;\n }\n return color;\n}\n\nexport function lerpColor(colorA: string, colorB: string, t: number): string {\n const parseHex = (hex: string) => {\n hex = hex.replace('#', '');\n if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];\n return {\n r: parseInt(hex.slice(0, 2), 16),\n g: parseInt(hex.slice(2, 4), 16),\n b: parseInt(hex.slice(4, 6), 16),\n };\n };\n const a = parseHex(colorA);\n const b = parseHex(colorB);\n const r = Math.round(a.r + (b.r - a.r) * t);\n const g = Math.round(a.g + (b.g - a.g) * t);\n const bl = Math.round(a.b + (b.b - a.b) * t);\n return `rgb(${r},${g},${bl})`;\n}\n","/**\n * The display timezone: an IANA name (`'America/New_York'`, `'UTC'`), a fixed\n * offset in minutes east of UTC (`-300`), or null for the browser's own zone.\n */\nexport type TimeZoneSetting = string | number | null;\n\nconst MINUTE_MS = 60_000;\nconst DAY_MS = 24 * 60 * MINUTE_MS;\n/** Zones change their offset on quarter hours, so it holds for each 15 minutes. */\nconst OFFSET_BUCKET_MS = 15 * MINUTE_MS;\nconst OFFSET_CACHE_MAX = 8192;\n/**\n * Clocks change months apart, so a week whose offset is the same at both\n * ends holds it throughout: one lookup serves a week of bars.\n */\nconst WEEK_MS = 7 * DAY_MS;\nconst WEEK_CACHE_MAX = 4096;\n/** Names of UTC itself: no lookup needed. */\nconst UTC_ZONES = new Set(['UTC', 'Etc/UTC', 'GMT', 'Etc/GMT', 'UCT', 'Etc/UCT', 'Universal', 'Etc/Universal', 'Zulu', 'Etc/Zulu']);\n\nconst zoneFormats = new Map<string, Intl.DateTimeFormat>();\nconst offsetCache = new Map<string, number>();\n/** A week's offsets: `before` until the clocks change at `change`, then `after`. */\ninterface WeekOffsets {\n before: number;\n change: number;\n after: number;\n}\n/** Per zone, the offsets of each week looked at. */\nconst weekOffsets = new Map<string, Map<number, WeekOffsets>>();\n\nfunction zoneFormat(zone: string): Intl.DateTimeFormat {\n let format = zoneFormats.get(zone);\n if (!format) {\n format = new Intl.DateTimeFormat('en-US', {\n timeZone: zone,\n hourCycle: 'h23',\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric',\n });\n zoneFormats.set(zone, format);\n }\n return format;\n}\n\n/** Whether `zone` is a time zone this browser knows (an IANA name such as `'Europe/London'`). */\nexport function isValidTimeZone(zone: string): boolean {\n if (!zone) return false;\n try {\n zoneFormat(zone);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * The zone's offset from UTC, in minutes east, at `timeMs` — daylight saving\n * time included. Throws a RangeError for a zone the browser doesn't know.\n */\nexport function zoneOffsetMinutes(zone: string, timeMs: number): number {\n if (!Number.isFinite(timeMs) || UTC_ZONES.has(zone)) return 0;\n let weeks = weekOffsets.get(zone);\n if (!weeks) {\n weeks = new Map();\n weekOffsets.set(zone, weeks);\n }\n const week = Math.floor(timeMs / WEEK_MS);\n let offsets = weeks.get(week);\n if (!offsets) {\n offsets = readWeek(zone, week * WEEK_MS);\n if (weeks.size >= WEEK_CACHE_MAX) weeks.clear();\n weeks.set(week, offsets);\n }\n return timeMs < offsets.change ? offsets.before : offsets.after;\n}\n\n/** The offsets of the week from `start`, and the quarter hour the clocks change in it, if they do. */\nfunction readWeek(zone: string, start: number): WeekOffsets {\n const before = bucketOffset(zone, start);\n let last = start + WEEK_MS - OFFSET_BUCKET_MS;\n const after = bucketOffset(zone, last);\n if (after === before) return { before, change: Infinity, after };\n // Halve the week down to the first quarter hour on the new offset.\n let first = start;\n while (last - first > OFFSET_BUCKET_MS) {\n const mid = first + Math.floor((last - first) / OFFSET_BUCKET_MS / 2) * OFFSET_BUCKET_MS;\n if (bucketOffset(zone, mid) === before) first = mid;\n else last = mid;\n }\n return { before, change: last, after };\n}\n\n/** The zone's offset over the quarter hour holding `timeMs`. */\nfunction bucketOffset(zone: string, timeMs: number): number {\n const bucket = Math.floor(timeMs / OFFSET_BUCKET_MS);\n const key = `${zone}|${bucket}`;\n const cached = offsetCache.get(key);\n if (cached !== undefined) return cached;\n\n const at = bucket * OFFSET_BUCKET_MS;\n let year = 1970, month = 1, day = 1, hour = 0, minute = 0, second = 0;\n for (const part of zoneFormat(zone).formatToParts(new Date(at))) {\n const value = Number(part.value);\n switch (part.type) {\n case 'year': year = value; break;\n case 'month': month = value; break;\n case 'day': day = value; break;\n case 'hour': hour = value % 24; break;\n case 'minute': minute = value; break;\n case 'second': second = value; break;\n }\n }\n const offset = Math.round((Date.UTC(year, month - 1, day, hour, minute, second) - at) / MINUTE_MS);\n if (offsetCache.size >= OFFSET_CACHE_MAX) offsetCache.clear();\n offsetCache.set(key, offset);\n return offset;\n}\n\n/** Minutes east of UTC at `timeMs` for any display timezone. */\nexport function offsetAt(tz: TimeZoneSetting, timeMs: number): number {\n if (tz === null) return -new Date(timeMs).getTimezoneOffset();\n if (typeof tz === 'number') return tz;\n return zoneOffsetMinutes(tz, timeMs);\n}\n\n/**\n * The instant whose wall clock in `tz` reads `wallMs` (a wall-clock time\n * written as if it were UTC, e.g. `Date.UTC(2026, 0, 15, 9, 30)`). A time\n * that occurs twice (clocks going back) is its first occurrence; a time a\n * spring-forward skips moves forward by the jump (02:30 → 03:30).\n */\nexport function wallToUtc(wallMs: number, tz: TimeZoneSetting): number {\n // The instant is within 14 hours of `wallMs`, so the offsets a day either\n // side bracket any clock change near it.\n const early = wallMs - offsetAt(tz, wallMs - DAY_MS) * MINUTE_MS;\n if (early + offsetAt(tz, early) * MINUTE_MS === wallMs) return early;\n const late = wallMs - offsetAt(tz, wallMs + DAY_MS) * MINUTE_MS;\n if (late + offsetAt(tz, late) * MINUTE_MS === wallMs) return late;\n return early; // skipped: read with the offset from before the jump\n}\n\nconst dateFormatters = new Map<string, (ms: number) => string>();\n\n/**\n * `Intl.DateTimeFormat#format` for dates in a display timezone, cached per\n * locale, options and zone (building a format costs ~40µs). A fixed offset\n * is applied by shifting the instant and formatting it as UTC.\n */\nexport function zonedDateFormatter(\n locale: string,\n options: Intl.DateTimeFormatOptions,\n tz: TimeZoneSetting,\n): (ms: number) => string {\n const key = `${locale}|${JSON.stringify(options)}|${tz === null ? 'local' : typeof tz === 'number' ? `fixed:${tz}` : tz}`;\n let formatter = dateFormatters.get(key);\n if (formatter) return formatter;\n\n const timeZone = tz === null ? undefined : typeof tz === 'number' ? 'UTC' : tz;\n let format: Intl.DateTimeFormat;\n try {\n format = new Intl.DateTimeFormat(locale, { ...options, timeZone });\n } catch {\n format = new Intl.DateTimeFormat('en-US', { ...options, timeZone }); // unknown locale tag\n }\n const shift = typeof tz === 'number' ? tz * MINUTE_MS : 0;\n formatter = (ms) => format.format(new Date(ms + shift));\n dateFormatters.set(key, formatter);\n return formatter;\n}\n","import type { KnownTimeFrame, TimeFrame, TimeFrameUnit } from '../types/ohlc.js';\nimport { offsetAt, type TimeZoneSetting } from './timezone.js';\n\nconst TIMEFRAME_MS: Record<KnownTimeFrame, number> = {\n '1s': 1_000,\n '5s': 5_000,\n '15s': 15_000,\n '30s': 30_000,\n '1m': 60_000,\n '3m': 180_000,\n '5m': 300_000,\n '15m': 900_000,\n '30m': 1_800_000,\n '45m': 2_700_000,\n '1h': 3_600_000,\n '2h': 7_200_000,\n '3h': 10_800_000,\n '4h': 14_400_000,\n '6h': 21_600_000,\n '8h': 28_800_000,\n '12h': 43_200_000,\n '1d': 86_400_000,\n '2d': 172_800_000,\n '3d': 259_200_000,\n '1w': 604_800_000,\n '2w': 1_209_600_000,\n '1M': 2_592_000_000,\n '3M': 7_776_000_000,\n '6M': 15_552_000_000,\n '12M': 31_536_000_000,\n};\n\n/** Milliseconds per unit; a month counts 30 days (a 12-month frame, 365). */\nconst UNIT_MS: Record<TimeFrameUnit, number> = {\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n M: 2_592_000_000,\n};\n\nconst TIMEFRAME_PATTERN = /^([1-9]\\d*)([smhdwM])$/;\n\n/** A timeframe's count and unit, or null when `value` isn't one (`'7m'` → 7 minutes). */\nexport function parseTimeframe(value: string): { count: number; unit: TimeFrameUnit } | null {\n const match = TIMEFRAME_PATTERN.exec(value);\n if (!match) return null;\n const count = Number(match[1]);\n return Number.isSafeInteger(count) ? { count, unit: match[2] as TimeFrameUnit } : null;\n}\n\n/** Most trades a tick bar may hold. */\nconst MAX_TICKS_PER_BAR = 1_000_000;\n\n/** The trades per bar of a tick timeframe (`'100T'` → 100), or null for any other. */\nexport function tickBarCount(timeframe: string): number | null {\n const match = /^([1-9]\\d*)T$/.exec(timeframe);\n if (!match) return null;\n const count = Number(match[1]);\n return Number.isSafeInteger(count) && count <= MAX_TICKS_PER_BAR ? count : null;\n}\n\nexport function isTimeFrame(value: string): value is TimeFrame {\n return parseTimeframe(value) !== null;\n}\n\n/** Length of a timeframe in milliseconds (approximate for months); NaN for a non-timeframe. */\nexport function timeframeToMs(tf: TimeFrame): number {\n const known = TIMEFRAME_MS[tf as KnownTimeFrame];\n if (known !== undefined) return known;\n const parsed = parseTimeframe(tf);\n return parsed ? parsed.count * UNIT_MS[parsed.unit] : Number.NaN;\n}\n\nexport function formatTimestamp(timestamp: number, tf: TimeFrame): string {\n const d = new Date(timestamp);\n const ms = timeframeToMs(tf);\n if (ms >= 86_400_000) {\n return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n }\n if (ms >= 3_600_000) {\n return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });\n }\n return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n}\n\nexport function alignToTimeframe(timestamp: number, tf: TimeFrame): number {\n const ms = timeframeToMs(tf);\n return Math.floor(timestamp / ms) * ms;\n}\n\nexport interface TimeParts {\n year: number;\n month: number; // 1-12\n day: number;\n hours: number;\n minutes: number;\n}\n\n/**\n * Calendar parts of a timestamp in a display timezone: an IANA zone\n * (`'America/New_York'`, daylight saving time included), a fixed UTC offset in\n * minutes (e.g. -300 for EST) or the browser's zone (`null`).\n */\nexport function timeParts(timeMs: number, tz: TimeZoneSetting): TimeParts {\n if (tz === null) {\n const d = new Date(timeMs);\n return {\n year: d.getFullYear(),\n month: d.getMonth() + 1,\n day: d.getDate(),\n hours: d.getHours(),\n minutes: d.getMinutes(),\n };\n }\n const d = new Date(timeMs + offsetAt(tz, timeMs) * 60_000);\n return {\n year: d.getUTCFullYear(),\n month: d.getUTCMonth() + 1,\n day: d.getUTCDate(),\n hours: d.getUTCHours(),\n minutes: d.getUTCMinutes(),\n };\n}\n\n/**\n * A bar with no time-of-day component (a daily, weekly, monthly or yearly candle — always anchored\n * to a calendar-day boundary, so hours/minutes read 00:00) needs the YEAR in its label instead: a\n * bare `month/day` is ambiguous across years, and `00:00` tells a trader nothing. Detected from the\n * parts themselves rather than a separate timeframe parameter, so every caller that formats a bar's\n * time gets the same rule for free. It can't tell an hourly bar at midnight from a daily one:\n * callers that have the series ask `barsAreDaily` first and fall back to this.\n */\nexport function isDateOnly(parts: Pick<TimeParts, 'hours' | 'minutes'>): boolean {\n return parts.hours === 0 && parts.minutes === 0;\n}\n\n/** Gaps this long or longer (a day with a daylight-saving change is 23 h) mean daily-or-larger bars. */\nconst DAILY_GAP_MS = 20 * 3_600_000;\n\n/**\n * Whether bars are a day apart or more (daily, weekly, monthly): their labels\n * then carry a date and no time of day, while an hourly bar at midnight keeps\n * its time. Read from the smallest gap among the first bars, so a weekend or\n * a missing session doesn't make an intraday series look daily. Undefined for\n * fewer than two bars.\n */\nexport function barsAreDaily(data: ArrayLike<{ time: number }>): boolean | undefined {\n const n = Math.min(data.length, 64);\n if (n < 2) return undefined;\n const ms = (t: number) => (t > 1e12 ? t : t * 1000);\n let gap = Infinity;\n for (let i = 1; i < n; i++) {\n const d = Math.abs(ms(data[i].time) - ms(data[i - 1].time));\n if (d > 0 && d < gap) gap = d;\n }\n return gap === Infinity ? undefined : gap >= DAILY_GAP_MS;\n}\n\n/** A short label like `UTC-5` or `UTC+5:30` for the offset in force at `atMs` (default: now). */\nexport function tzLabel(tz: TimeZoneSetting, atMs: number = Date.now()): string {\n const min = offsetAt(tz, atMs);\n const sign = min >= 0 ? '+' : '-';\n const abs = Math.abs(min);\n const h = Math.floor(abs / 60);\n const m = abs % 60;\n return m === 0 ? `UTC${sign}${h}` : `UTC${sign}${h}:${String(m).padStart(2, '0')}`;\n}\n\n/** Day-of-week for a Monday-anchored reference week (1970-01-05 was a Monday, UTC). */\nconst WEEK_ANCHOR_MS = Date.UTC(1970, 0, 5);\n\n/**\n * Start of the bucket a timestamp falls into for a given timeframe.\n *\n * Intraday and daily frames are anchored to the Unix epoch (UTC) via fixed-ms\n * flooring. Weekly frames are anchored to Monday (or `weekStartsOn`), monthly\n * and yearly frames use calendar boundaries (1st of month, Jan for years) so\n * 3M aligns to quarters and 12M to calendar years.\n */\nexport function timeframeBucketStart(\n timestamp: number,\n tf: TimeFrame,\n weekStartsOn: 0 | 1 = 1,\n): number {\n const { count, unit } = parseTimeframe(tf) ?? { count: 1, unit: 'm' as const };\n\n if (unit === 's' || unit === 'm' || unit === 'h' || unit === 'd') {\n const ms = count * UNIT_MS[unit];\n return Math.floor(timestamp / ms) * ms;\n }\n\n const d = new Date(timestamp);\n\n if (unit === 'w') {\n const dayMs = UNIT_MS.d;\n const dow = d.getUTCDay();\n const shift = (dow - weekStartsOn + 7) % 7;\n const weekStart = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - shift);\n if (count <= 1) return weekStart;\n const weekIndex = Math.floor((weekStart - WEEK_ANCHOR_MS) / (7 * dayMs));\n const grouped = Math.floor(weekIndex / count) * count;\n return WEEK_ANCHOR_MS + grouped * 7 * dayMs;\n }\n\n // months / quarters / years\n const totalMonths = d.getUTCFullYear() * 12 + d.getUTCMonth();\n const grouped = Math.floor(totalMonths / count) * count;\n return Date.UTC(Math.floor(grouped / 12), grouped % 12, 1);\n}\n","const NUMBER_FORMAT_CACHE_LIMIT = 64;\nconst numberFormatCache = new Map<string, Intl.NumberFormat>();\n\n/**\n * `Number#toLocaleString(locale, options)` constructs a fresh\n * `Intl.NumberFormat` on every call — ~20µs in V8 versus ~0.4µs to reuse one.\n * Axis labels, the legend, crosshair pills and panel axes format dozens of\n * numbers per frame, so formatters are cached per (locale, precision). The\n * output is identical: per spec, `toLocaleString` is defined as exactly this.\n */\nfunction numberFormat(locale: string, precision: number): Intl.NumberFormat {\n const key = `${locale}|${precision}`;\n let nf = numberFormatCache.get(key);\n if (!nf) {\n if (numberFormatCache.size >= NUMBER_FORMAT_CACHE_LIMIT) numberFormatCache.clear();\n nf = new Intl.NumberFormat(locale, {\n minimumFractionDigits: precision,\n maximumFractionDigits: precision,\n });\n numberFormatCache.set(key, nf);\n }\n return nf;\n}\n\nexport function formatPrice(value: number, precision = 2, locale = 'en-US'): string {\n return numberFormat(locale, precision).format(value);\n}\n\n/**\n * Decimals the price axis uses for a visible price range: one more than its\n * tick step needs, 2 at minimum. Everything that prints a price next to the\n * axis (legend, crosshair pill, last-price tag) shares it, so a sub-cent\n * asset reads \"0.000004349\" everywhere instead of \"0.00\" off the axis.\n */\nexport function autoPricePrecision(min: number, max: number): number {\n if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return 2;\n const step = computeTickStep(min, max, 8);\n return step > 0 && step < 1 ? Math.ceil(-Math.log10(step)) + 1 : 2;\n}\n\nimport type { PriceScaleMode } from '../types/rendering.js';\nimport type { PriceFormatter, PriceFraction } from '../types/chart.js';\nimport { computeTickStep } from './math.js';\n\n/**\n * Format a price-axis label for a given scale mode.\n *\n * - `regular` / `logarithmic`: the raw price.\n * - `percentage`: change from `baseline`, e.g. `+12.34%`.\n * - `indexedTo100`: the price rebased so `baseline` reads as 100.\n *\n * Falls back to a plain price when a baseline is required but missing/zero.\n */\nexport function formatPriceScaleLabel(\n price: number,\n mode: PriceScaleMode,\n baseline: number | undefined,\n precision = 2,\n locale = 'en-US',\n): string {\n if ((mode === 'percentage' || mode === 'indexedTo100') && baseline && baseline !== 0) {\n if (mode === 'percentage') {\n const pct = (price / baseline - 1) * 100;\n const sign = pct > 0 ? '+' : '';\n return `${sign}${pct.toFixed(2)}%`;\n }\n const indexed = (price / baseline) * 100;\n return formatPrice(indexed, 2, locale);\n }\n return formatPrice(price, precision, locale);\n}\n\n/**\n * `value` in fractions of a point: whole points, an apostrophe, then the\n * fraction's numerator padded to the denominator's digits (`101'16` for\n * 101.5 in 32nds). A `subDenominator` of 2 or 4 adds the half or quarter of\n * that fraction as one more digit, as futures quote it (`101'165`, `110'162`,\n * `110'167`). Rounds to the nearest step. A denominator that isn't a whole\n * number above 1 gives plain digits.\n */\nexport function formatFraction(value: number, denominator: number, subDenominator = 1): string {\n const sub = Number.isInteger(subDenominator) && subDenominator > 1 ? subDenominator : 1;\n if (!Number.isFinite(value) || !Number.isInteger(denominator) || denominator < 2) return String(value);\n const steps = denominator * sub;\n const total = Math.round(Math.abs(value) * steps);\n const whole = Math.floor(total / steps);\n const rest = total - whole * steps;\n const numerator = Math.floor(rest / sub);\n const width = String(denominator - 1).length;\n const tail = sub > 1 ? String(Math.floor(((rest - numerator * sub) / sub) * 10)) : '';\n const sign = value < 0 && total > 0 ? '-' : '';\n return `${sign}${whole}'${String(numerator).padStart(width, '0')}${tail}`;\n}\n\n/**\n * The smallest step a fraction format prints (a 32nd; a quarter of one with\n * `subDenominator` 4), or null for a denominator it can't print (it falls\n * back to plain digits then).\n */\nexport function fractionTick(fraction: PriceFraction): number | null {\n if (!Number.isInteger(fraction.denominator) || fraction.denominator < 2) return null;\n const sub = Number.isInteger(fraction.subDenominator) && (fraction.subDenominator ?? 0) > 1 ? fraction.subDenominator as number : 1;\n return 1 / (fraction.denominator * sub);\n}\n\n/** The formatter a `priceFormat` option stands for: its function, its fraction's, or null for decimals. */\nexport function priceFormatterFor(format: PriceFormatter | PriceFraction | null | undefined): PriceFormatter | null {\n if (!format) return null;\n if (typeof format === 'function') return format;\n // A denominator it can't print leaves the chart's decimals.\n if (fractionTick(format) === null) return null;\n return (price) => formatFraction(price, format.denominator, format.subDenominator);\n}\n\nexport function formatVolume(value: number): string {\n if (value >= 1_000_000_000) return (value / 1_000_000_000).toFixed(2) + 'B';\n if (value >= 1_000_000) return (value / 1_000_000).toFixed(2) + 'M';\n if (value >= 1_000) return (value / 1_000).toFixed(2) + 'K';\n return value.toFixed(0);\n}\n\nexport function detectPrecision(values: number[]): number {\n let maxDecimals = 0;\n for (const v of values) {\n const str = v.toString();\n const dot = str.indexOf('.');\n if (dot >= 0) {\n maxDecimals = Math.max(maxDecimals, str.length - dot - 1);\n }\n }\n return Math.min(maxDecimals, 8);\n}\n","import type { OHLCBar } from '../types/ohlc.js';\n\n/** The price an indicator can be computed from. */\nexport const PRICE_SOURCES = ['close', 'open', 'high', 'low', 'hl2', 'hlc3', 'ohlc4', 'hlcc4'] as const;\n\nexport type PriceSource = (typeof PRICE_SOURCES)[number];\n\nexport function isPriceSource(value: unknown): value is PriceSource {\n return typeof value === 'string' && (PRICE_SOURCES as readonly string[]).includes(value);\n}\n\n/** A bar's price by source: `hl2` = (high + low) / 2, `hlc3`, `ohlc4`, `hlcc4` = (h + l + 2c) / 4. */\nexport function sourcePrice(bar: OHLCBar, source: PriceSource): number {\n switch (source) {\n case 'open': return bar.open;\n case 'high': return bar.high;\n case 'low': return bar.low;\n case 'hl2': return (bar.high + bar.low) / 2;\n case 'hlc3': return (bar.high + bar.low + bar.close) / 3;\n case 'ohlc4': return (bar.open + bar.high + bar.low + bar.close) / 4;\n case 'hlcc4': return (bar.high + bar.low + 2 * bar.close) / 4;\n default: return bar.close;\n }\n}\n\nconst INDICATOR_SOURCE = /^ind:([^:]+):(.+)$/;\n\n/** A source that is another indicator's line: `ind:<instanceId>:<key>`. */\nexport function indicatorSource(instanceId: string, key: string): string {\n return `ind:${instanceId}:${key}`;\n}\n\n/** The indicator line a source refers to, or null for a price source or anything else. */\nexport function parseIndicatorSource(value: unknown): { instanceId: string; key: string } | null {\n if (typeof value !== 'string') return null;\n const m = INDICATOR_SOURCE.exec(value);\n return m ? { instanceId: m[1], key: m[2] } : null;\n}\n","import type { OHLCBar, TimeFrame, TimeFrameUnit } from '../types/ohlc.js';\nimport { parseTimeframe, timeframeBucketStart, timeframeToMs } from './time.js';\n\nconst DAY_MS = 86_400_000;\n\n/**\n * Aggregate bars (ascending, times in ms) into a coarser timeframe: per\n * bucket (see `timeframeBucketStart`) the first open, the extremes, the last\n * close and the summed volume. The input bars are not changed.\n */\nexport function resampleBars(bars: readonly OHLCBar[], target: TimeFrame, weekStartsOn: 0 | 1 = 1): OHLCBar[] {\n const out: OHLCBar[] = [];\n let current: OHLCBar | null = null;\n for (const bar of bars) {\n const key = timeframeBucketStart(bar.time, target, weekStartsOn);\n if (current === null || key !== current.time) {\n current = { time: key, open: bar.open, high: bar.high, low: bar.low, close: bar.close, volume: bar.volume };\n out.push(current);\n } else {\n if (bar.high > current.high) current.high = bar.high;\n if (bar.low < current.low) current.low = bar.low;\n current.close = bar.close;\n current.volume += bar.volume;\n }\n }\n return out;\n}\n\nconst isFixedLength = (unit: TimeFrameUnit): boolean => unit === 's' || unit === 'm' || unit === 'h' || unit === 'd';\n\n/** Whether whole bars of `base` fill every bucket of `target` exactly. */\nfunction buildsInto(base: TimeFrame, target: TimeFrame): boolean {\n const b = parseTimeframe(base);\n const t = parseTimeframe(target);\n if (!b || !t) return false;\n const baseMs = timeframeToMs(base);\n // Feeds disagree on where a 3-day bar starts: only build from whole days.\n if (b.unit === 'd' && b.count > 1) return false;\n if (isFixedLength(t.unit)) {\n return isFixedLength(b.unit) && timeframeToMs(target) % baseMs === 0;\n }\n if (b.unit === t.unit) return t.count % b.count === 0;\n // Weeks and months start at midnight: build them from bars that never\n // cross one.\n return isFixedLength(b.unit) && DAY_MS % baseMs === 0;\n}\n\n/**\n * The coarsest of `supported` that builds `target` from whole bars (e.g. 7m\n * from 1m, 90m from 30m, a quarter from months), or null when none does.\n */\nexport function pickBaseTimeframe(target: TimeFrame, supported: readonly TimeFrame[]): TimeFrame | null {\n let best: TimeFrame | null = null;\n let bestMs = 0;\n for (const tf of supported) {\n if (!buildsInto(tf, target)) continue;\n const ms = timeframeToMs(tf);\n if (ms > bestMs) {\n best = tf;\n bestMs = ms;\n }\n }\n return best;\n}\n","import type { SymbolInfo } from '../types/symbol.js';\n\n/** How well `query` (upper case) matches `text` (upper case): 0 when it doesn't. */\nfunction score(text: string, query: string): number {\n if (!text) return 0;\n if (text === query) return 1000;\n if (text.startsWith(query)) return 600 - Math.min(100, text.length - query.length);\n const at = text.indexOf(query);\n return at >= 0 ? 300 - Math.min(100, at) : 0;\n}\n\n/**\n * Symbols matching `query`, best first: an exact ticker, then tickers that\n * start with it, then names that contain it. `boost` adds to a match's score\n * (say, for the quote currencies most traded). An empty query keeps the order\n * given. At most `limit` results.\n */\nexport function rankSymbols(\n symbols: readonly SymbolInfo[],\n query: string,\n limit = 50,\n boost?: (info: SymbolInfo) => number,\n): SymbolInfo[] {\n const q = query.trim().toUpperCase();\n if (!q) return symbols.slice(0, limit);\n const scored: { info: SymbolInfo; score: number }[] = [];\n for (const info of symbols) {\n const s = Math.max(\n score(info.symbol.toUpperCase(), q),\n score((info.description ?? '').toUpperCase(), q) * 0.8,\n );\n if (s > 0) scored.push({ info, score: s + (boost?.(info) ?? 0) });\n }\n scored.sort((a, b) => b.score - a.score || a.info.symbol.localeCompare(b.info.symbol));\n return scored.slice(0, limit).map((s) => s.info);\n}\n\n/** Decimals in a price step: `'0.01000000'` → 2, `'1'` → 0, `1e-8` → 8. */\nexport function stepDecimals(step: number | string): number {\n const value = typeof step === 'number' ? step : Number(step);\n if (!Number.isFinite(value) || value <= 0) return 0;\n for (let decimals = 0; decimals <= 12; decimals++) {\n const scaled = value * 10 ** decimals;\n if (Math.abs(scaled - Math.round(scaled)) < 1e-9 * Math.max(1, scaled)) return decimals;\n }\n return 12;\n}\n\n/** `'09:30'` → 570 (`'24:00'`, a day's end, → 1440), or null for anything that isn't a time of day. */\nexport function sessionMinute(time: string): number | null {\n const match = /^(\\d{1,2}):(\\d{2})$/.exec(time.trim());\n if (!match) return null;\n const hours = Number(match[1]);\n const minutes = Number(match[2]);\n if (hours === 24 && minutes === 0) return 1440;\n return hours < 24 && minutes < 60 ? hours * 60 + minutes : null;\n}\n","import type { SymbolInfo } from '../types/symbol.js';\nimport { sessionMinute } from './symbols.js';\nimport { isValidTimeZone, zoneOffsetMinutes } from './timezone.js';\n\n/** Whether a symbol's market is trading now, and when that changes. */\nexport interface MarketStatus {\n /** `'always'`: no hours known (24/7, such as crypto). */\n state: 'open' | 'closed' | 'always';\n /** When it next closes (open) or opens (closed), in ms; null when unknown. */\n next: number | null;\n}\n\nconst DAY_MS = 86_400_000;\nconst MINUTE_MS = 60_000;\n/** Days looked ahead for the next open (a long weekend, a week of holidays). */\nconst LOOK_AHEAD_DAYS = 8;\n\n/**\n * The market status of `info` at `now`: in a session of its hours (in its\n * exchange's time zone, clocks changes included) or not, and when that\n * changes. Sessions that meet count as one; a session past midnight belongs\n * to the day it starts. Without hours or a zone it knows: `'always'`.\n */\nexport function marketStatus(info: SymbolInfo | null | undefined, now: number): MarketStatus {\n const zone = info?.timezone;\n const sessions = (info?.sessions ?? []).flatMap((s) => {\n const start = sessionMinute(s.start);\n const end = sessionMinute(s.end);\n if (start === null || end === null || start === end) return [];\n return [{ start, end, days: Array.isArray(s.days) ? s.days : null }];\n });\n if (!zone || !isValidTimeZone(zone) || sessions.length === 0 || !Number.isFinite(now)) {\n return { state: 'always', next: null };\n }\n\n const offset = zoneOffsetMinutes(zone, now) * MINUTE_MS;\n const today = Math.floor((now + offset) / DAY_MS) * DAY_MS;\n // A wall-clock time in the zone, back to UTC with the offset that holds then\n // (asked twice, so a time beside a clock change settles on its side).\n const toUtc = (local: number) => {\n const guess = local - zoneOffsetMinutes(zone, local - offset) * MINUTE_MS;\n return local - zoneOffsetMinutes(zone, guess) * MINUTE_MS;\n };\n\n const spans: [number, number][] = [];\n for (let d = -1; d <= LOOK_AHEAD_DAYS; d++) {\n const dayStart = today + d * DAY_MS;\n const weekday = (Math.floor(dayStart / DAY_MS) + 4) % 7; // 1 Jan 1970 was a Thursday\n for (const s of sessions) {\n if (s.days && !s.days.includes(weekday)) continue;\n const start = dayStart + s.start * MINUTE_MS;\n const end = (s.end > s.start ? dayStart : dayStart + DAY_MS) + s.end * MINUTE_MS;\n spans.push([toUtc(start), toUtc(end)]);\n }\n }\n spans.sort((a, b) => a[0] - b[0]);\n const merged: [number, number][] = [];\n for (const [start, end] of spans) {\n const last = merged[merged.length - 1];\n if (last && start <= last[1]) merged[merged.length - 1] = [last[0], Math.max(last[1], end)];\n else merged.push([start, end]);\n }\n\n const current = merged.find(([start, end]) => start <= now && now < end);\n if (current) return { state: 'open', next: current[1] };\n const upcoming = merged.find(([start]) => start > now);\n return { state: 'closed', next: upcoming ? upcoming[0] : null };\n}\n","import type { DrawingLevel, DrawingOptionDef, DrawingOptionDefs, DrawingOptionValue, DrawingOptions } from '../types/drawing.js';\n\n/** Most levels a drawing keeps; more is a pasted or hand-edited state, not a chart. */\nconst MAX_LEVELS = 48;\nconst MAX_TEXT = 2000;\n/** Largest level ratio kept (a Fibonacci level of 4.236, a time zone of 89 bars). */\nconst MAX_LEVEL_VALUE = 10_000;\n\nfunction sanitizeLevels(raw: unknown): DrawingLevel[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const levels: DrawingLevel[] = [];\n for (const item of raw) {\n if (levels.length >= MAX_LEVELS) break;\n if (typeof item !== 'object' || item === null) continue;\n const { value, visible, color } = item as Record<string, unknown>;\n if (typeof value !== 'number' || !Number.isFinite(value) || Math.abs(value) > MAX_LEVEL_VALUE) continue;\n if (visible !== undefined && typeof visible !== 'boolean') continue;\n const level: DrawingLevel = { value, visible: visible ?? true };\n if (typeof color === 'string' && color) level.color = color;\n levels.push(level);\n }\n return levels;\n}\n\n/** `raw` as a value for `def`, or undefined when it isn't one. */\nfunction sanitizeValue(def: DrawingOptionDef, raw: unknown): DrawingOptionValue | undefined {\n switch (def.kind) {\n case 'boolean':\n return typeof raw === 'boolean' ? raw : undefined;\n case 'number': {\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return undefined;\n return Math.min(def.max ?? Infinity, Math.max(def.min ?? -Infinity, raw));\n }\n case 'choice':\n return typeof raw === 'string' && def.choices.some((c) => c.value === raw) ? raw : undefined;\n case 'levels':\n return sanitizeLevels(raw);\n case 'text':\n return typeof raw === 'string' ? raw.slice(0, MAX_TEXT) : undefined;\n }\n}\n\n/** The valid entries of `raw` for a tool's options: unknown keys and wrong values are dropped, numbers clamped. */\nexport function sanitizeDrawingOptions(defs: DrawingOptionDefs | undefined, raw: unknown): DrawingOptions {\n if (!defs || typeof raw !== 'object' || raw === null) return {};\n const out: DrawingOptions = {};\n for (const [key, value] of Object.entries(raw)) {\n const def = defs[key];\n if (!def) continue;\n const clean = sanitizeValue(def, value);\n if (clean !== undefined) out[key] = clean;\n }\n return out;\n}\n\nfunction copyValue(value: DrawingOptionValue | readonly DrawingLevel[]): DrawingOptionValue {\n return Array.isArray(value) ? value.map((level) => ({ ...level })) : (value as DrawingOptionValue);\n}\n\n/** One option of a drawing: its own value when valid, else the tool's default. */\nexport function drawingOption<T extends DrawingOptionValue = DrawingOptionValue>(\n defs: DrawingOptionDefs | undefined,\n options: DrawingOptions | undefined,\n key: string,\n): T {\n const def = defs?.[key];\n if (!def) return options?.[key] as T;\n const own = options && key in options ? sanitizeValue(def, options[key]) : undefined;\n return copyValue(own ?? def.default) as T;\n}\n\n/** Every option of a tool for a drawing, defaults filled in. */\nexport function resolveDrawingOptions(defs: DrawingOptionDefs | undefined, options: DrawingOptions | undefined): DrawingOptions {\n const out: DrawingOptions = {};\n for (const key of Object.keys(defs ?? {})) out[key] = drawingOption(defs, options, key);\n return out;\n}\n","import type { DrawingStyle } from '../types/drawing.js';\n\nconst LINE_STYLES: readonly DrawingStyle['lineStyle'][] = ['solid', 'dashed', 'dotted'];\nconst MAX_COLOR = 64;\nconst MAX_TEXT = 2000;\nconst MAX_LINE_WIDTH = 20;\nconst MAX_FONT_SIZE = 200;\n\nfunction finiteIn(value: unknown, min: number, max: number): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : undefined;\n}\n\nfunction colorValue(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 && value.length <= MAX_COLOR ? value : undefined;\n}\n\n/**\n * The valid fields of a drawing style from untrusted data (a saved layout, a\n * template): colours as short strings, a known line style, widths, opacity\n * and font size as finite numbers in range, text cut to length. Anything\n * else is dropped.\n */\nexport function sanitizeDrawingStyle(raw: unknown): Partial<DrawingStyle> {\n if (typeof raw !== 'object' || raw === null) return {};\n const obj = raw as Record<string, unknown>;\n const out: Partial<DrawingStyle> = {};\n const color = colorValue(obj.color);\n if (color !== undefined) out.color = color;\n const lineWidth = finiteIn(obj.lineWidth, 0.5, MAX_LINE_WIDTH);\n if (lineWidth !== undefined) out.lineWidth = lineWidth;\n if (LINE_STYLES.includes(obj.lineStyle as DrawingStyle['lineStyle'])) out.lineStyle = obj.lineStyle as DrawingStyle['lineStyle'];\n const fillColor = colorValue(obj.fillColor);\n if (fillColor !== undefined) out.fillColor = fillColor;\n const fillOpacity = finiteIn(obj.fillOpacity, 0, 1);\n if (fillOpacity !== undefined) out.fillOpacity = fillOpacity;\n const fontSize = finiteIn(obj.fontSize, 6, MAX_FONT_SIZE);\n if (fontSize !== undefined) out.fontSize = fontSize;\n if (typeof obj.text === 'string') out.text = obj.text.slice(0, MAX_TEXT);\n return out;\n}\n","import type { ChartTypeOptions } from '../types/chart.js';\n\nconst positive = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v) && v > 0;\nconst wholeIn = (v: unknown, lo: number, hi: number): v is number =>\n typeof v === 'number' && Number.isInteger(v) && v >= lo && v <= hi;\nconst isObject = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);\n\n/**\n * The chart type settings in `raw` the chart can use: boxes, ranges and\n * reversals above 0, whole line and box counts in range. A type given at\n * all is in the result (empty when none of its settings were usable), so\n * a type's settings can be put back to their defaults.\n */\nexport function readChartTypeOptions(raw: unknown): ChartTypeOptions {\n if (!isObject(raw)) return {};\n const out: ChartTypeOptions = {};\n if (isObject(raw.renko)) {\n const { boxSize, atrPeriod } = raw.renko;\n out.renko = {\n ...(positive(boxSize) || boxSize === 'atr' ? { boxSize } : {}),\n ...(wholeIn(atrPeriod, 1, 500) ? { atrPeriod } : {}),\n };\n }\n if (isObject(raw.lineBreak)) {\n const { lines } = raw.lineBreak;\n out.lineBreak = wholeIn(lines, 1, 10) ? { lines } : {};\n }\n if (isObject(raw.kagi)) {\n const { reversal, reversalType } = raw.kagi;\n out.kagi = {\n ...(positive(reversal) ? { reversal } : {}),\n ...(reversalType === 'percent' || reversalType === 'price' ? { reversalType } : {}),\n };\n }\n if (isObject(raw.pointAndFigure)) {\n const { boxSize, reversal } = raw.pointAndFigure;\n out.pointAndFigure = {\n ...(positive(boxSize) || boxSize === 'auto' ? { boxSize } : {}),\n ...(wholeIn(reversal, 1, 10) ? { reversal } : {}),\n };\n }\n if (isObject(raw.rangeBars)) {\n const { range } = raw.rangeBars;\n out.rangeBars = positive(range) || range === 'auto' ? { range } : {};\n }\n return out;\n}\n","import type { ChartOptions } from '../types/chart.js';\n\nexport const DEFAULT_CHART_OPTIONS: Required<Pick<ChartOptions, 'autoScale' | 'rightMargin' | 'minBarSpacing' | 'maxBarSpacing'>> & Pick<ChartOptions, 'grid' | 'crosshair'> = {\n autoScale: true,\n rightMargin: 5,\n minBarSpacing: 2,\n maxBarSpacing: 30,\n grid: {\n visible: true,\n hLineStyle: 'solid',\n vLineStyle: 'solid',\n },\n crosshair: {\n mode: 'magnet',\n },\n};\n\n// Standard timeframe presets for different market types\nimport type { TimeFrame } from '../types/ohlc.js';\n\n/** Crypto: all timeframes including seconds */\nexport const TIMEFRAMES_CRYPTO: TimeFrame[] = [\n '1s', '1m', '3m', '5m', '15m', '30m',\n '1h', '2h', '4h', '6h', '8h', '12h',\n '1d', '3d', '1w', '1M',\n];\n\n/** Stocks: minute-level and above (no seconds) */\nexport const TIMEFRAMES_STOCK: TimeFrame[] = [\n '1m', '5m', '15m', '30m',\n '1h', '2h', '4h',\n '1d', '1w', '1M', '3M', '6M', '12M',\n];\n\n/** Forex: common forex timeframes */\nexport const TIMEFRAMES_FOREX: TimeFrame[] = [\n '1m', '5m', '15m', '30m',\n '1h', '4h',\n '1d', '1w', '1M',\n];\n\n/** Default favorites shown in quick-access bar */\nexport const DEFAULT_TIMEFRAME_FAVORITES: TimeFrame[] = [\n '1m', '5m', '15m', '1h', '4h', '1d', '1w',\n];\n\nexport const DEFAULT_BAR_WIDTH = 8;\nexport const DEFAULT_BAR_SPACING = 2;\nexport const PRICE_AXIS_WIDTH = 70;\nexport const TIME_AXIS_HEIGHT = 30;\nexport const MIN_PANEL_HEIGHT = 60;\nexport const DEFAULT_PANEL_HEIGHT = 120;\n","import type { Theme } from '../types/theme.js';\nimport { withAlpha } from '../utils/color.js';\n\n/** The chart's own type: the system UI face, so text matches the page around it. */\nexport const DEFAULT_FONT_FAMILY = '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif';\n\nconst DEFAULT_FONT = {\n family: DEFAULT_FONT_FAMILY,\n sizeSmall: 10,\n sizeMedium: 12,\n sizeLarge: 14,\n};\n\n/**\n * TradeCanvas palette. Themes pick their own candle colours for their\n * background; everything drawn on top of either theme (indicators, orders,\n * signals, drawings) uses the theme-neutral values below, which keep at\n * least 3:1 contrast on both the dark ink and a white background. `amber` is\n * the brand accent for dark surfaces (2:1 on white); light surfaces use\n * `LIGHT_THEME.lineColor` / `ochre` instead.\n */\nexport const TC_PALETTE = {\n /** Brand amber — the default series line and the accent. */\n amber: '#f2a93b',\n /** Rising / bullish, legible on dark and light. */\n up: '#1fa874',\n /** Falling / bearish, legible on dark and light. */\n down: '#e8505b',\n /** Amber deep enough for white backgrounds; auto-coloured series use it. */\n ochre: '#c98316',\n /** Secondary series, signal lines. */\n blue: '#4c8dff',\n violet: '#a57cff',\n cyan: '#1398a8',\n rose: '#e25592',\n /** Zero lines, neutral guides. */\n neutral: '#7d8696',\n /** Ink ground of the dark theme. */\n ink: '#0c1016',\n} as const;\n\n/**\n * How strongly volume bars show: the candle colour at this opacity, so they\n * read as a backdrop under the candles rather than a second chart.\n */\nexport const VOLUME_ALPHA = 0.22;\n\n/** The volume bar colour for a candle colour. */\nexport function volumeColor(candleColor: string): string {\n return withAlpha(candleColor, VOLUME_ALPHA);\n}\n\nconst DARK_UP = '#2eb88a';\nconst DARK_DOWN = '#e85a67';\n\n/** Order in which overlay indicators are coloured when no colour is given. */\nexport const TC_SERIES_COLORS: readonly string[] = [\n TC_PALETTE.blue,\n TC_PALETTE.ochre,\n TC_PALETTE.violet,\n TC_PALETTE.cyan,\n TC_PALETTE.rose,\n];\n\nexport const DARK_THEME: Theme = {\n name: 'dark',\n background: TC_PALETTE.ink,\n text: '#d9dde4',\n textSecondary: '#7d8696',\n grid: '#161b23',\n crosshair: '#8a93a3',\n candleUp: DARK_UP,\n candleDown: DARK_DOWN,\n candleUpWick: DARK_UP,\n candleDownWick: DARK_DOWN,\n lineColor: TC_PALETTE.amber,\n areaTopColor: 'rgba(242, 169, 59, 0.32)',\n areaBottomColor: 'rgba(242, 169, 59, 0.0)',\n volumeUp: volumeColor(DARK_UP),\n volumeDown: volumeColor(DARK_DOWN),\n axisLine: '#1f2630',\n axisLabel: '#9aa3b2',\n axisLabelBackground: '#1f2630',\n font: DEFAULT_FONT,\n};\n\nexport const LIGHT_THEME: Theme = {\n name: 'light',\n background: '#ffffff',\n text: '#0f131a',\n textSecondary: '#6b7380',\n grid: '#eef0f3',\n crosshair: '#8a93a3',\n candleUp: '#16a36a',\n candleDown: '#d9414f',\n candleUpWick: '#16a36a',\n candleDownWick: '#d9414f',\n lineColor: '#c77a0a',\n areaTopColor: 'rgba(199, 122, 10, 0.24)',\n areaBottomColor: 'rgba(199, 122, 10, 0.0)',\n volumeUp: volumeColor('#16a36a'),\n volumeDown: volumeColor('#d9414f'),\n axisLine: '#dde1e7',\n axisLabel: '#5b6472',\n axisLabelBackground: '#eef0f3',\n font: DEFAULT_FONT,\n};\n\nexport const DARK_TERMINAL: Theme = {\n name: 'terminal',\n background: '#0E0E0E',\n text: '#C0C0C0',\n textSecondary: '#8A8A8A',\n grid: '#1A1A1A',\n crosshair: '#666666',\n candleUp: '#00FF87',\n candleDown: '#FF3B4D',\n candleUpWick: '#00FF87',\n candleDownWick: '#FF3B4D',\n lineColor: '#3D8BFD',\n areaTopColor: 'rgba(61, 139, 253, 0.3)',\n areaBottomColor: 'rgba(61, 139, 253, 0.0)',\n volumeUp: 'rgba(0, 255, 135, 0.2)',\n volumeDown: 'rgba(255, 59, 77, 0.2)',\n axisLine: '#1A1A1A',\n axisLabel: '#8A8A8A',\n axisLabelBackground: '#1A1A1A',\n font: {\n family: \"'Roboto Mono', 'JetBrains Mono', 'SF Mono', Consolas, monospace\",\n sizeSmall: 10,\n sizeMedium: 12,\n sizeLarge: 14,\n },\n};\n\n/** The built-in themes' volume colours, to tell a preset's from a theme's own. */\nconst PRESET_VOLUME = new Set([DARK_THEME.volumeUp, DARK_THEME.volumeDown, LIGHT_THEME.volumeUp, LIGHT_THEME.volumeDown, DARK_TERMINAL.volumeUp, DARK_TERMINAL.volumeDown]);\n\n/**\n * The volume bars' colours for `theme`: its own `volumeUp` / `volumeDown`,\n * except that a theme spread from a preset with new candle colours (and the\n * preset's volume left in place) gets volume in its candles' colours, as the\n * bars have always followed the candles.\n */\nexport function resolveVolumeColors(theme: Pick<Theme, 'candleUp' | 'candleDown' | 'volumeUp' | 'volumeDown'>): { up: string; down: string } {\n const pick = (volume: string, candle: string): string => {\n if (!PRESET_VOLUME.has(volume)) return volume;\n const derived = volumeColor(candle);\n return derived === candle ? volume : derived;\n };\n return { up: pick(theme.volumeUp, theme.candleUp), down: pick(theme.volumeDown, theme.candleDown) };\n}\n","import type { LocaleStrings } from './types.js';\n\nexport const en: LocaleStrings = {\n // Chart types\n candlestick: 'Candlestick',\n line: 'Line',\n area: 'Area',\n bar: 'OHLC Bar',\n\n // Axes\n price: 'Price',\n volume: 'Volume',\n time: 'Time',\n open: 'Open',\n high: 'High',\n low: 'Low',\n close: 'Close',\n\n // Indicators - overlays\n sma: 'SMA',\n ema: 'EMA',\n bollingerBands: 'Bollinger Bands',\n vwap: 'VWAP',\n ichimoku: 'Ichimoku Cloud',\n parabolicSAR: 'Parabolic SAR',\n supertrend: 'Supertrend',\n keltnerChannel: 'Keltner Channel',\n donchianChannel: 'Donchian Channel',\n\n // Indicators - panels\n rsi: 'RSI',\n macd: 'MACD',\n stochastic: 'Stochastic',\n atr: 'ATR',\n adx: 'ADX',\n obv: 'OBV',\n williamsR: 'Williams %R',\n cci: 'CCI',\n mfi: 'MFI',\n aroon: 'Aroon',\n roc: 'ROC',\n tsi: 'TSI',\n cmf: 'CMF',\n stddev: 'Std Dev',\n volumeProfile: 'Volume Profile',\n accumulationDistribution: 'A/D Line',\n vroc: 'VROC',\n\n // Drawing tools\n trendLine: 'Trend Line',\n horizontalLine: 'Horizontal Line',\n verticalLine: 'Vertical Line',\n ray: 'Ray',\n extendedLine: 'Extended Line',\n parallelChannel: 'Parallel Channel',\n regressionChannel: 'Regression Channel',\n fibRetracement: 'Fibonacci Retracement',\n fibExtension: 'Fibonacci Extension',\n rectangle: 'Rectangle',\n ellipse: 'Ellipse',\n triangle: 'Triangle',\n pitchfork: \"Andrews' Pitchfork\",\n elliottWave: 'Elliott Wave',\n priceRange: 'Price Range',\n dateRange: 'Date Range',\n measure: 'Measure',\n textTool: 'Text',\n arrow: 'Arrow',\n clearAll: 'Clear All',\n\n // Trading\n buy: 'Buy',\n sell: 'Sell',\n buyLimit: 'Buy Limit',\n sellLimit: 'Sell Limit',\n buyStop: 'Buy Stop',\n sellStop: 'Sell Stop',\n stopLoss: 'Stop Loss',\n takeProfit: 'Take Profit',\n market: 'Market',\n limit: 'Limit',\n stop: 'Stop',\n cancel: 'Cancel',\n modify: 'Modify',\n quantity: 'Qty',\n pnl: 'P&L',\n activeOrders: 'Active Orders',\n positions: 'Positions',\n noOrders: 'No active orders',\n noPositions: 'No open positions',\n placeOrder: 'Place Order',\n rightClickToTrade: 'Right-click chart to place orders',\n\n // Market\n ceiling: 'Ceiling',\n floor: 'Floor',\n reference: 'Reference',\n session: 'Session',\n preOpen: 'Pre-Open',\n continuous: 'Continuous',\n preClose: 'Pre-Close',\n closed: 'Closed',\n\n // UI\n settings: 'Settings',\n theme: 'Theme',\n darkTheme: 'Dark',\n lightTheme: 'Light',\n tools: 'Tools',\n indicators: 'Indicators',\n overlays: 'Overlays',\n panels: 'Panels',\n orders: 'Orders',\n autoScale: 'Auto Scale',\n crosshair: 'Crosshair',\n grid: 'Grid',\n loading: 'Loading...',\n error: 'Error',\n\n numberDecimalSeparator: '.',\n numberGroupSeparator: ',',\n};\n","import type { LocaleStrings } from './types.js';\n\nexport const vi: LocaleStrings = {\n // Chart types\n candlestick: 'Nến',\n line: 'Đường',\n area: 'Vùng',\n bar: 'Thanh OHLC',\n\n // Axes\n price: 'Giá',\n volume: 'Khối lượng',\n time: 'Thời gian',\n open: 'Mở',\n high: 'Cao',\n low: 'Thấp',\n close: 'Đóng',\n\n // Indicators - overlays\n sma: 'SMA',\n ema: 'EMA',\n bollingerBands: 'Dải Bollinger',\n vwap: 'VWAP',\n ichimoku: 'Mây Ichimoku',\n parabolicSAR: 'Parabolic SAR',\n supertrend: 'Supertrend',\n keltnerChannel: 'Kênh Keltner',\n donchianChannel: 'Kênh Donchian',\n\n // Indicators - panels\n rsi: 'RSI',\n macd: 'MACD',\n stochastic: 'Stochastic',\n atr: 'ATR',\n adx: 'ADX',\n obv: 'OBV',\n williamsR: 'Williams %R',\n cci: 'CCI',\n mfi: 'MFI',\n aroon: 'Aroon',\n roc: 'ROC',\n tsi: 'TSI',\n cmf: 'CMF',\n stddev: 'Độ lệch chuẩn',\n volumeProfile: 'Phân bổ KL',\n accumulationDistribution: 'Tích lũy/Phân phối',\n vroc: 'VROC',\n\n // Drawing tools\n trendLine: 'Đường xu hướng',\n horizontalLine: 'Đường ngang',\n verticalLine: 'Đường dọc',\n ray: 'Tia',\n extendedLine: 'Đường kéo dài',\n parallelChannel: 'Kênh song song',\n regressionChannel: 'Kênh hồi quy',\n fibRetracement: 'Fibonacci thoái lui',\n fibExtension: 'Fibonacci mở rộng',\n rectangle: 'Hình chữ nhật',\n ellipse: 'Hình elip',\n triangle: 'Tam giác',\n pitchfork: 'Chĩa ba Andrews',\n elliottWave: 'Sóng Elliott',\n priceRange: 'Khoảng giá',\n dateRange: 'Khoảng thời gian',\n measure: 'Đo lường',\n textTool: 'Chữ',\n arrow: 'Mũi tên',\n clearAll: 'Xóa tất cả',\n\n // Trading\n buy: 'Mua',\n sell: 'Bán',\n buyLimit: 'Mua giới hạn',\n sellLimit: 'Bán giới hạn',\n buyStop: 'Mua chặn',\n sellStop: 'Bán chặn',\n stopLoss: 'Cắt lỗ',\n takeProfit: 'Chốt lời',\n market: 'Thị trường',\n limit: 'Giới hạn',\n stop: 'Dừng',\n cancel: 'Hủy',\n modify: 'Sửa',\n quantity: 'KL',\n pnl: 'Lãi/Lỗ',\n activeOrders: 'Lệnh chờ',\n positions: 'Vị thế',\n noOrders: 'Không có lệnh chờ',\n noPositions: 'Không có vị thế mở',\n placeOrder: 'Đặt lệnh',\n rightClickToTrade: 'Nhấp chuột phải để đặt lệnh',\n\n // Market\n ceiling: 'Trần',\n floor: 'Sàn',\n reference: 'Tham chiếu',\n session: 'Phiên',\n preOpen: 'Trước giờ mở',\n continuous: 'Liên tục',\n preClose: 'Trước giờ đóng',\n closed: 'Đóng cửa',\n\n // UI\n settings: 'Cài đặt',\n theme: 'Giao diện',\n darkTheme: 'Tối',\n lightTheme: 'Sáng',\n tools: 'Công cụ',\n indicators: 'Chỉ báo',\n overlays: 'Phủ lên',\n panels: 'Bảng',\n orders: 'Lệnh',\n autoScale: 'Tự co giãn',\n crosshair: 'Chữ thập',\n grid: 'Lưới',\n loading: 'Đang tải...',\n error: 'Lỗi',\n\n numberDecimalSeparator: ',',\n numberGroupSeparator: '.',\n};\n","export type { Locale, LocaleStrings, NumberFormatConfig, DateFormatConfig } from './types.js';\nexport { en } from './en.js';\nexport { vi } from './vi.js';\n\nimport type { Locale, LocaleStrings } from './types.js';\nimport { en } from './en.js';\nimport { vi } from './vi.js';\n\nconst locales = new Map<string, LocaleStrings>([\n ['en', en],\n ['vi', vi],\n]);\n\nlet currentLocale: Locale = 'en';\nlet currentStrings: LocaleStrings = en;\n\nexport function setLocale(locale: Locale): void {\n currentLocale = locale;\n currentStrings = locales.get(locale) ?? en;\n}\n\nexport function getLocale(): Locale {\n return currentLocale;\n}\n\nexport function t(key: keyof LocaleStrings): string {\n return currentStrings[key] ?? (en as any)[key] ?? key;\n}\n\nexport function registerLocale(locale: string, strings: LocaleStrings): void {\n locales.set(locale, strings);\n}\n\nexport function getLocaleStrings(locale?: string): LocaleStrings {\n return locales.get(locale ?? currentLocale) ?? en;\n}\n\n// Number formatting\nexport function formatNumber(value: number, precision = 2, locale?: string): string {\n const strings = locales.get(locale ?? currentLocale) ?? en;\n const dec = strings.numberDecimalSeparator;\n const grp = strings.numberGroupSeparator;\n\n const fixed = value.toFixed(precision);\n const [intPart, decPart] = fixed.split('.');\n\n // Group integer part\n const negative = intPart.startsWith('-');\n const digits = negative ? intPart.slice(1) : intPart;\n let grouped = '';\n for (let i = digits.length - 1, count = 0; i >= 0; i--, count++) {\n if (count > 0 && count % 3 === 0) grouped = grp + grouped;\n grouped = digits[i] + grouped;\n }\n if (negative) grouped = '-' + grouped;\n\n return decPart ? grouped + dec + decPart : grouped;\n}\n\nexport function formatVND(value: number): string {\n return formatNumber(value, 0, 'vi');\n}\n\nexport function formatVolumeLoc(value: number, locale?: string): string {\n if (value >= 1e9) return formatNumber(value / 1e9, 2, locale ?? currentLocale) + 'B';\n if (value >= 1e6) return formatNumber(value / 1e6, 2, locale ?? currentLocale) + 'M';\n if (value >= 1e3) return formatNumber(value / 1e3, 2, locale ?? currentLocale) + 'K';\n return formatNumber(value, 0, locale ?? currentLocale);\n}\n","import type { MarketConfig, MarketColorScheme, TradingSession } from './types.js';\nimport type { Theme } from '../types/theme.js';\n\n// Vietnam stock color convention:\n// Purple/Red = ceiling (trần) - max up\n// Green/Cyan = floor (sàn) - max down\n// Yellow = reference (tham chiếu)\n// Red = up, Blue = down (common VN convention)\nexport const VN_COLORS: MarketColorScheme = {\n up: '#FF0000', // Đỏ - tăng\n down: '#0000FF', // Xanh dương - giảm\n unchanged: '#FFD700', // Vàng - tham chiếu\n ceiling: '#FF00FF', // Tím - trần\n floor: '#00FFFF', // Xanh lam - sàn\n reference: '#FFD700', // Vàng - tham chiếu\n};\n\nexport const HOSE_SESSIONS: TradingSession[] = [\n { name: 'ATO', startTime: '09:00', endTime: '09:15', type: 'preOpen' },\n { name: 'Phiên 1', startTime: '09:15', endTime: '11:30', type: 'continuous' },\n { name: 'Nghỉ trưa', startTime: '11:30', endTime: '13:00', type: 'closed' },\n { name: 'Phiên 2', startTime: '13:00', endTime: '14:30', type: 'continuous' },\n { name: 'ATC', startTime: '14:30', endTime: '14:45', type: 'preClose' },\n];\n\nexport const HNX_SESSIONS: TradingSession[] = [\n { name: 'Phiên 1', startTime: '09:00', endTime: '11:30', type: 'continuous' },\n { name: 'Nghỉ trưa', startTime: '11:30', endTime: '13:00', type: 'closed' },\n { name: 'Phiên 2', startTime: '13:00', endTime: '14:30', type: 'continuous' },\n { name: 'ATC', startTime: '14:30', endTime: '14:45', type: 'preClose' },\n];\n\n// Market presets\nexport const MARKET_HOSE: MarketConfig = {\n type: 'stock',\n exchange: 'HOSE',\n currency: 'VND',\n pricePrecision: 2,\n volumeUnit: 10,\n priceStep: 0.05,\n priceLimits: { enabled: true, ceilingPercent: 7, floorPercent: 7 },\n sessions: HOSE_SESSIONS,\n colorScheme: VN_COLORS,\n};\n\nexport const MARKET_HNX: MarketConfig = {\n type: 'stock',\n exchange: 'HNX',\n currency: 'VND',\n pricePrecision: 1,\n volumeUnit: 100,\n priceStep: 0.1,\n priceLimits: { enabled: true, ceilingPercent: 10, floorPercent: 10 },\n sessions: HNX_SESSIONS,\n colorScheme: VN_COLORS,\n};\n\nexport const MARKET_UPCOM: MarketConfig = {\n type: 'stock',\n exchange: 'UPCOM',\n currency: 'VND',\n pricePrecision: 1,\n volumeUnit: 100,\n priceStep: 0.1,\n priceLimits: { enabled: true, ceilingPercent: 15, floorPercent: 15 },\n sessions: HNX_SESSIONS,\n colorScheme: VN_COLORS,\n};\n\nexport const MARKET_CRYPTO: MarketConfig = {\n type: 'crypto',\n currency: 'USDT',\n pricePrecision: 2,\n priceLimits: { enabled: false },\n};\n\nexport const MARKET_NYSE: MarketConfig = {\n type: 'stock',\n exchange: 'NYSE',\n currency: 'USD',\n pricePrecision: 2,\n priceStep: 0.01,\n priceLimits: { enabled: false },\n sessions: [\n { name: 'Pre-Market', startTime: '04:00', endTime: '09:30', type: 'preOpen' },\n { name: 'Regular', startTime: '09:30', endTime: '16:00', type: 'continuous' },\n { name: 'After-Hours', startTime: '16:00', endTime: '20:00', type: 'preClose' },\n ],\n};\n\n// Build a theme variant for VN stock market\nexport function createVNTheme(base: Theme): Theme {\n return {\n ...base,\n candleUp: VN_COLORS.up,\n candleDown: VN_COLORS.down,\n candleUpWick: VN_COLORS.up,\n candleDownWick: VN_COLORS.down,\n volumeUp: 'rgba(255, 0, 0, 0.3)',\n volumeDown: 'rgba(0, 0, 255, 0.3)',\n };\n}\n\nexport function computePriceLimits(referencePrice: number, config: MarketConfig): { ceiling: number; floor: number; reference: number } | null {\n if (!config.priceLimits?.enabled || !config.priceLimits.ceilingPercent) return null;\n const ceilPct = config.priceLimits.ceilingPercent / 100;\n const floorPct = (config.priceLimits.floorPercent ?? config.priceLimits.ceilingPercent) / 100;\n return {\n ceiling: referencePrice * (1 + ceilPct),\n floor: referencePrice * (1 - floorPct),\n reference: referencePrice,\n };\n}\n\nexport function getCurrentSession(sessions: TradingSession[]): TradingSession | null {\n const now = new Date();\n const hhmm = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;\n for (const session of sessions) {\n if (hhmm >= session.startTime && hhmm < session.endTime) return session;\n }\n return null;\n}\n"],"mappings":"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"}
|