@tradecanvas/commons
Advanced tools
+1
-1
@@ -1,2 +0,2 @@ | ||
| Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=function(e){return e[e.Background=0]=`Background`,e[e.Main=1]=`Main`,e[e.Panel=2]=`Panel`,e[e.Overlay=3]=`Overlay`,e[e.UI=4]=`UI`,e}({}),t={color:`#2196F3`,lineWidth:1,lineStyle:`solid`,fillColor:`rgba(33, 150, 243, 0.1)`,fillOpacity:.1,fontSize:12},n={enabled:!0,orderColors:{buy:`#26A69A`,sell:`#EF5350`},positionColors:{profit:`#26A69A`,loss:`#EF5350`,entry:`#2196F3`},depthOverlay:{enabled:!1,bidColor:`rgba(38,166,154,0.15)`,askColor:`rgba(239,83,80,0.15)`,maxWidth:100},contextMenu:{enabled:!0},pricePrecision:2,dragThreshold:3},r={longColor:`#26A69A`,shortColor:`#EF5350`,neutralColor:`#9E9E9E`,arrowSize:12,showLabel:!0,showConfidence:!0},i={profitColor:`#26A69A`,lossColor:`#EF5350`,activeColor:`#2196F3`,fillOpacity:.12,borderWidth:1,showLabel:!0,showPnl:!0},a={enabled:!0,maxRetries:1/0,baseDelay:1e3,maxDelay:3e4,backoffMultiplier:2},o={historyLimit:500,autoScroll:!0,showCurrentPriceLine:!0,aggregateTicks:!1};function s(e,t,n){return Math.max(t,Math.min(n,e))}function c(e,t,n){return e+(t-e)*n}function l(e,t,n){return e===t?0:(n-e)/(t-e)}function u(e,t){return Math.round(e/t)*t}function d(e,t){let n=Math.floor(Math.log10(e)),r=e/10**n,i;return i=t?r<1.5?1:r<3?2:r<7?5:10:r<=1?1:r<=2?2:r<=5?5:10,i*10**n}function f(e,t,n){return d(d(t-e,!1)/(n-1),!0)}function p(e){return e>0xe8d4a51000?e:e*1e3}function ee(e){return{time:p(e.time??e.t??0),open:e.open??e.o??0,high:e.high??e.h??0,low:e.low??e.l??0,close:e.close??e.c??0,volume:e.volume??e.v??0}}function m(e,t,n){let r=Math.max(0,t),i=Math.min(e.length,n+1);return e.slice(r,i)}function h(e,t){let n=0,r=e.length-1;for(;n<=r;){let i=n+r>>>1;if(e[i].time<t)n=i+1;else if(e[i].time>t)r=i-1;else return i}return n}function g(e,t,n,r=.05){if(e.length===0)return{min:0,max:1};let i=Math.max(0,t),a=Math.min(e.length-1,n),o=1/0,s=-1/0;for(let t=i;t<=a;t++)e[t].low<o&&(o=e[t].low),e[t].high>s&&(s=e[t].high);if(o===1/0)return{min:0,max:1};let c=s-o||1;return{min:o-c*r,max:s+c*r}}function te(e,t){return{...e,high:Math.max(e.high,t.price),low:Math.min(e.low,t.price),close:t.price,volume:e.volume+(t.volume??0),time:t.time}}function _(e,t=1){return`rgba(${parseInt(e.slice(1,3),16)}, ${parseInt(e.slice(3,5),16)}, ${parseInt(e.slice(5,7),16)}, ${t})`}function v(e,t){if(e.startsWith(`#`))return _(e,t);let n=e.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);return n?`rgba(${n[1]}, ${n[2]}, ${n[3]}, ${t})`:e}function y(e,t,n){let r=e=>(e=e.replace(`#`,``),e.length===3&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),{r:parseInt(e.slice(0,2),16),g:parseInt(e.slice(2,4),16),b:parseInt(e.slice(4,6),16)}),i=r(e),a=r(t);return`rgb(${Math.round(i.r+(a.r-i.r)*n)},${Math.round(i.g+(a.g-i.g)*n)},${Math.round(i.b+(a.b-i.b)*n)})`}var b={"1s":1e3,"5s":5e3,"15s":15e3,"30s":3e4,"1m":6e4,"3m":18e4,"5m":3e5,"15m":9e5,"30m":18e5,"45m":27e5,"1h":36e5,"2h":72e5,"3h":108e5,"4h":144e5,"6h":216e5,"8h":288e5,"12h":432e5,"1d":864e5,"2d":1728e5,"3d":2592e5,"1w":6048e5,"2w":12096e5,"1M":2592e6,"3M":7776e6,"6M":15552e6,"12M":31536e6};function ne(e){return b[e]}function re(e,t){let n=new Date(e),r=b[t];return r>=864e5?n.toLocaleDateString(void 0,{month:`short`,day:`numeric`}):r>=36e5?n.toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`}):n.toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`})}function ie(e,t){let n=b[t];return Math.floor(e/n)*n}function x(e,t=2,n=`en-US`){return e.toLocaleString(n,{minimumFractionDigits:t,maximumFractionDigits:t})}function S(e){return e>=1e9?(e/1e9).toFixed(2)+`B`:e>=1e6?(e/1e6).toFixed(2)+`M`:e>=1e3?(e/1e3).toFixed(2)+`K`:e.toFixed(0)}function C(e){let t=0;for(let n of e){let e=n.toString(),r=e.indexOf(`.`);r>=0&&(t=Math.max(t,e.length-r-1))}return Math.min(t,8)}var w={autoScale:!0,rightMargin:5,minBarSpacing:2,maxBarSpacing:30,grid:{visible:!0,hLineStyle:`solid`,vLineStyle:`solid`},crosshair:{mode:`magnet`}},T=[`1s`,`1m`,`3m`,`5m`,`15m`,`30m`,`1h`,`2h`,`4h`,`6h`,`8h`,`12h`,`1d`,`3d`,`1w`,`1M`],E=[`1m`,`5m`,`15m`,`30m`,`1h`,`2h`,`4h`,`1d`,`1w`,`1M`,`3M`,`6M`,`12M`],D=[`1m`,`5m`,`15m`,`30m`,`1h`,`4h`,`1d`,`1w`,`1M`],O=[`1m`,`5m`,`15m`,`1h`,`4h`,`1d`,`1w`],k=8,A=2,j=70,M=30,N=60,P=120,F={family:`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`,sizeSmall:10,sizeMedium:12,sizeLarge:14},I={name:`dark`,background:`#131722`,text:`#D1D4DC`,textSecondary:`#787B86`,grid:`#1E222D`,crosshair:`#9598A1`,candleUp:`#26A69A`,candleDown:`#EF5350`,candleUpWick:`#26A69A`,candleDownWick:`#EF5350`,lineColor:`#2196F3`,areaTopColor:`rgba(33, 150, 243, 0.4)`,areaBottomColor:`rgba(33, 150, 243, 0.0)`,volumeUp:`rgba(38, 166, 154, 0.3)`,volumeDown:`rgba(239, 83, 80, 0.3)`,axisLine:`#2A2E39`,axisLabel:`#D1D4DC`,axisLabelBackground:`#2A2E39`,font:F},L={name:`light`,background:`#FFFFFF`,text:`#131722`,textSecondary:`#787B86`,grid:`#F0F3FA`,crosshair:`#9598A1`,candleUp:`#26A69A`,candleDown:`#EF5350`,candleUpWick:`#26A69A`,candleDownWick:`#EF5350`,lineColor:`#2196F3`,areaTopColor:`rgba(33, 150, 243, 0.4)`,areaBottomColor:`rgba(33, 150, 243, 0.0)`,volumeUp:`rgba(38, 166, 154, 0.3)`,volumeDown:`rgba(239, 83, 80, 0.3)`,axisLine:`#E0E3EB`,axisLabel:`#131722`,axisLabelBackground:`#F0F3FA`,font:F},R={name:`terminal`,background:`#0E0E0E`,text:`#C0C0C0`,textSecondary:`#8A8A8A`,grid:`#1A1A1A`,crosshair:`#666666`,candleUp:`#00FF87`,candleDown:`#FF3B4D`,candleUpWick:`#00FF87`,candleDownWick:`#FF3B4D`,lineColor:`#3D8BFD`,areaTopColor:`rgba(61, 139, 253, 0.3)`,areaBottomColor:`rgba(61, 139, 253, 0.0)`,volumeUp:`rgba(0, 255, 135, 0.2)`,volumeDown:`rgba(255, 59, 77, 0.2)`,axisLine:`#1A1A1A`,axisLabel:`#8A8A8A`,axisLabelBackground:`#1A1A1A`,font:{family:`'Roboto Mono', 'JetBrains Mono', 'SF Mono', Consolas, monospace`,sizeSmall:10,sizeMedium:12,sizeLarge:14}},z={candlestick:`Candlestick`,line:`Line`,area:`Area`,bar:`OHLC Bar`,price:`Price`,volume:`Volume`,time:`Time`,open:`Open`,high:`High`,low:`Low`,close:`Close`,sma:`SMA`,ema:`EMA`,bollingerBands:`Bollinger Bands`,vwap:`VWAP`,ichimoku:`Ichimoku Cloud`,parabolicSAR:`Parabolic SAR`,supertrend:`Supertrend`,keltnerChannel:`Keltner Channel`,donchianChannel:`Donchian Channel`,rsi:`RSI`,macd:`MACD`,stochastic:`Stochastic`,atr:`ATR`,adx:`ADX`,obv:`OBV`,williamsR:`Williams %R`,cci:`CCI`,mfi:`MFI`,aroon:`Aroon`,roc:`ROC`,tsi:`TSI`,cmf:`CMF`,stddev:`Std Dev`,volumeProfile:`Volume Profile`,accumulationDistribution:`A/D Line`,vroc:`VROC`,trendLine:`Trend Line`,horizontalLine:`Horizontal Line`,verticalLine:`Vertical Line`,ray:`Ray`,extendedLine:`Extended Line`,parallelChannel:`Parallel Channel`,regressionChannel:`Regression Channel`,fibRetracement:`Fibonacci Retracement`,fibExtension:`Fibonacci Extension`,rectangle:`Rectangle`,ellipse:`Ellipse`,triangle:`Triangle`,pitchfork:`Andrews' Pitchfork`,elliottWave:`Elliott Wave`,priceRange:`Price Range`,dateRange:`Date Range`,measure:`Measure`,textTool:`Text`,arrow:`Arrow`,clearAll:`Clear All`,buy:`Buy`,sell:`Sell`,buyLimit:`Buy Limit`,sellLimit:`Sell Limit`,buyStop:`Buy Stop`,sellStop:`Sell Stop`,stopLoss:`Stop Loss`,takeProfit:`Take Profit`,market:`Market`,limit:`Limit`,stop:`Stop`,cancel:`Cancel`,modify:`Modify`,quantity:`Qty`,pnl:`P&L`,activeOrders:`Active Orders`,positions:`Positions`,noOrders:`No active orders`,noPositions:`No open positions`,placeOrder:`Place Order`,rightClickToTrade:`Right-click chart to place orders`,ceiling:`Ceiling`,floor:`Floor`,reference:`Reference`,session:`Session`,preOpen:`Pre-Open`,continuous:`Continuous`,preClose:`Pre-Close`,closed:`Closed`,settings:`Settings`,theme:`Theme`,darkTheme:`Dark`,lightTheme:`Light`,tools:`Tools`,indicators:`Indicators`,overlays:`Overlays`,panels:`Panels`,orders:`Orders`,autoScale:`Auto Scale`,crosshair:`Crosshair`,grid:`Grid`,loading:`Loading...`,error:`Error`,numberDecimalSeparator:`.`,numberGroupSeparator:`,`},B={candlestick:`Nến`,line:`Đường`,area:`Vùng`,bar:`Thanh OHLC`,price:`Giá`,volume:`Khối lượng`,time:`Thời gian`,open:`Mở`,high:`Cao`,low:`Thấp`,close:`Đóng`,sma:`SMA`,ema:`EMA`,bollingerBands:`Dải Bollinger`,vwap:`VWAP`,ichimoku:`Mây Ichimoku`,parabolicSAR:`Parabolic SAR`,supertrend:`Supertrend`,keltnerChannel:`Kênh Keltner`,donchianChannel:`Kênh Donchian`,rsi:`RSI`,macd:`MACD`,stochastic:`Stochastic`,atr:`ATR`,adx:`ADX`,obv:`OBV`,williamsR:`Williams %R`,cci:`CCI`,mfi:`MFI`,aroon:`Aroon`,roc:`ROC`,tsi:`TSI`,cmf:`CMF`,stddev:`Độ lệch chuẩn`,volumeProfile:`Phân bổ KL`,accumulationDistribution:`Tích lũy/Phân phối`,vroc:`VROC`,trendLine:`Đường xu hướng`,horizontalLine:`Đường ngang`,verticalLine:`Đường dọc`,ray:`Tia`,extendedLine:`Đường kéo dài`,parallelChannel:`Kênh song song`,regressionChannel:`Kênh hồi quy`,fibRetracement:`Fibonacci thoái lui`,fibExtension:`Fibonacci mở rộng`,rectangle:`Hình chữ nhật`,ellipse:`Hình elip`,triangle:`Tam giác`,pitchfork:`Chĩa ba Andrews`,elliottWave:`Sóng Elliott`,priceRange:`Khoảng giá`,dateRange:`Khoảng thời gian`,measure:`Đo lường`,textTool:`Chữ`,arrow:`Mũi tên`,clearAll:`Xóa tất cả`,buy:`Mua`,sell:`Bán`,buyLimit:`Mua giới hạn`,sellLimit:`Bán giới hạn`,buyStop:`Mua chặn`,sellStop:`Bán chặn`,stopLoss:`Cắt lỗ`,takeProfit:`Chốt lời`,market:`Thị trường`,limit:`Giới hạn`,stop:`Dừng`,cancel:`Hủy`,modify:`Sửa`,quantity:`KL`,pnl:`Lãi/Lỗ`,activeOrders:`Lệnh chờ`,positions:`Vị thế`,noOrders:`Không có lệnh chờ`,noPositions:`Không có vị thế mở`,placeOrder:`Đặt lệnh`,rightClickToTrade:`Nhấp chuột phải để đặt lệnh`,ceiling:`Trần`,floor:`Sàn`,reference:`Tham chiếu`,session:`Phiên`,preOpen:`Trước giờ mở`,continuous:`Liên tục`,preClose:`Trước giờ đóng`,closed:`Đóng cửa`,settings:`Cài đặt`,theme:`Giao diện`,darkTheme:`Tối`,lightTheme:`Sáng`,tools:`Công cụ`,indicators:`Chỉ báo`,overlays:`Phủ lên`,panels:`Bảng`,orders:`Lệnh`,autoScale:`Tự co giãn`,crosshair:`Chữ thập`,grid:`Lưới`,loading:`Đang tải...`,error:`Lỗi`,numberDecimalSeparator:`,`,numberGroupSeparator:`.`},V=new Map([[`en`,z],[`vi`,B]]),H=`en`,U=z;function W(e){H=e,U=V.get(e)??z}function G(){return H}function K(e){return U[e]??z[e]??e}function q(e,t){V.set(e,t)}function J(e){return V.get(e??H)??z}function Y(e,t=2,n){let r=V.get(n??H)??z,i=r.numberDecimalSeparator,a=r.numberGroupSeparator,[o,s]=e.toFixed(t).split(`.`),c=o.startsWith(`-`),l=c?o.slice(1):o,u=``;for(let e=l.length-1,t=0;e>=0;e--,t++)t>0&&t%3==0&&(u=a+u),u=l[e]+u;return c&&(u=`-`+u),s?u+i+s:u}function ae(e){return Y(e,0,`vi`)}function oe(e,t){return e>=1e9?Y(e/1e9,2,t??H)+`B`:e>=1e6?Y(e/1e6,2,t??H)+`M`:e>=1e3?Y(e/1e3,2,t??H)+`K`:Y(e,0,t??H)}var X={up:`#FF0000`,down:`#0000FF`,unchanged:`#FFD700`,ceiling:`#FF00FF`,floor:`#00FFFF`,reference:`#FFD700`},Z=[{name:`ATO`,startTime:`09:00`,endTime:`09:15`,type:`preOpen`},{name:`Phiên 1`,startTime:`09:15`,endTime:`11:30`,type:`continuous`},{name:`Nghỉ trưa`,startTime:`11:30`,endTime:`13:00`,type:`closed`},{name:`Phiên 2`,startTime:`13:00`,endTime:`14:30`,type:`continuous`},{name:`ATC`,startTime:`14:30`,endTime:`14:45`,type:`preClose`}],Q=[{name:`Phiên 1`,startTime:`09:00`,endTime:`11:30`,type:`continuous`},{name:`Nghỉ trưa`,startTime:`11:30`,endTime:`13:00`,type:`closed`},{name:`Phiên 2`,startTime:`13:00`,endTime:`14:30`,type:`continuous`},{name:`ATC`,startTime:`14:30`,endTime:`14:45`,type:`preClose`}],$={type:`stock`,exchange:`HOSE`,currency:`VND`,pricePrecision:2,volumeUnit:10,priceStep:.05,priceLimits:{enabled:!0,ceilingPercent:7,floorPercent:7},sessions:Z,colorScheme:X},se={type:`stock`,exchange:`HNX`,currency:`VND`,pricePrecision:1,volumeUnit:100,priceStep:.1,priceLimits:{enabled:!0,ceilingPercent:10,floorPercent:10},sessions:Q,colorScheme:X},ce={type:`stock`,exchange:`UPCOM`,currency:`VND`,pricePrecision:1,volumeUnit:100,priceStep:.1,priceLimits:{enabled:!0,ceilingPercent:15,floorPercent:15},sessions:Q,colorScheme:X},le={type:`crypto`,currency:`USDT`,pricePrecision:2,priceLimits:{enabled:!1}},ue={type:`stock`,exchange:`NYSE`,currency:`USD`,pricePrecision:2,priceStep:.01,priceLimits:{enabled:!1},sessions:[{name:`Pre-Market`,startTime:`04:00`,endTime:`09:30`,type:`preOpen`},{name:`Regular`,startTime:`09:30`,endTime:`16:00`,type:`continuous`},{name:`After-Hours`,startTime:`16:00`,endTime:`20:00`,type:`preClose`}]};function de(e){return{...e,candleUp:X.up,candleDown:X.down,candleUpWick:X.up,candleDownWick:X.down,volumeUp:`rgba(255, 0, 0, 0.3)`,volumeDown:`rgba(0, 0, 255, 0.3)`}}function fe(e,t){if(!t.priceLimits?.enabled||!t.priceLimits.ceilingPercent)return null;let n=t.priceLimits.ceilingPercent/100,r=(t.priceLimits.floorPercent??t.priceLimits.ceilingPercent)/100;return{ceiling:e*(1+n),floor:e*(1-r),reference:e}}function pe(e){let t=new Date,n=`${String(t.getHours()).padStart(2,`0`)}:${String(t.getMinutes()).padStart(2,`0`)}`;for(let t of e)if(n>=t.startTime&&n<t.endTime)return t;return null}exports.DARK_TERMINAL=R,exports.DARK_THEME=I,exports.DEFAULT_BAR_SPACING=A,exports.DEFAULT_BAR_WIDTH=k,exports.DEFAULT_CHART_OPTIONS=w,exports.DEFAULT_DRAWING_STYLE=t,exports.DEFAULT_PANEL_HEIGHT=P,exports.DEFAULT_RECONNECT=a,exports.DEFAULT_SIGNAL_STYLE=r,exports.DEFAULT_STREAM_CONFIG=o,exports.DEFAULT_TIMEFRAME_FAVORITES=O,exports.DEFAULT_TRADE_ZONE_STYLE=i,exports.DEFAULT_TRADING_CONFIG=n,exports.HNX_SESSIONS=Q,exports.HOSE_SESSIONS=Z,exports.LIGHT_THEME=L,exports.LayerType=e,exports.MARKET_CRYPTO=le,exports.MARKET_HNX=se,exports.MARKET_HOSE=$,exports.MARKET_NYSE=ue,exports.MARKET_UPCOM=ce,exports.MIN_PANEL_HEIGHT=N,exports.PRICE_AXIS_WIDTH=j,exports.TIMEFRAMES_CRYPTO=T,exports.TIMEFRAMES_FOREX=D,exports.TIMEFRAMES_STOCK=E,exports.TIME_AXIS_HEIGHT=M,exports.VN_COLORS=X,exports.alignToTimeframe=ie,exports.clamp=s,exports.computePriceLimits=fe,exports.computePriceRange=g,exports.computeTickStep=f,exports.createVNTheme=de,exports.detectPrecision=C,exports.en=z,exports.findBarIndex=h,exports.formatNumber=Y,exports.formatPrice=x,exports.formatTimestamp=re,exports.formatVND=ae,exports.formatVolume=S,exports.formatVolumeLoc=oe,exports.getCurrentSession=pe,exports.getLocale=G,exports.getLocaleStrings=J,exports.hexToRgba=_,exports.inverseLerp=l,exports.lerp=c,exports.lerpColor=y,exports.mergeBar=te,exports.niceNumber=d,exports.normalizeBar=ee,exports.normalizeBarTime=p,exports.registerLocale=q,exports.roundToStep=u,exports.setLocale=W,exports.sliceVisibleData=m,exports.t=K,exports.timeframeToMs=ne,exports.vi=B,exports.withAlpha=v; | ||
| Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=function(e){return e[e.Background=0]=`Background`,e[e.Main=1]=`Main`,e[e.Panel=2]=`Panel`,e[e.Overlay=3]=`Overlay`,e[e.UI=4]=`UI`,e}({}),t={color:`#2196F3`,lineWidth:1,lineStyle:`solid`,fillColor:`rgba(33, 150, 243, 0.1)`,fillOpacity:.1,fontSize:12},n={enabled:!0,orderColors:{buy:`#26A69A`,sell:`#EF5350`},positionColors:{profit:`#26A69A`,loss:`#EF5350`,entry:`#2196F3`},depthOverlay:{enabled:!1,bidColor:`rgba(38,166,154,0.15)`,askColor:`rgba(239,83,80,0.15)`,maxWidth:100},contextMenu:{enabled:!0},pricePrecision:2,dragThreshold:3},r={longColor:`#26A69A`,shortColor:`#EF5350`,neutralColor:`#9E9E9E`,arrowSize:12,showLabel:!0,showConfidence:!0},i={profitColor:`#26A69A`,lossColor:`#EF5350`,activeColor:`#2196F3`,fillOpacity:.12,borderWidth:1,showLabel:!0,showPnl:!0},a={enabled:!0,maxRetries:1/0,baseDelay:1e3,maxDelay:3e4,backoffMultiplier:2},o={historyLimit:500,autoScroll:!0,showCurrentPriceLine:!0,aggregateTicks:!1};function s(e,t,n){return Math.max(t,Math.min(n,e))}function c(e,t,n){return e+(t-e)*n}function l(e,t,n){return e===t?0:(n-e)/(t-e)}function u(e,t){return Math.round(e/t)*t}function d(e,t){let n=Math.floor(Math.log10(e)),r=e/10**n,i;return i=t?r<1.5?1:r<3?2:r<7?5:10:r<=1?1:r<=2?2:r<=5?5:10,i*10**n}function f(e,t,n){return d(d(t-e,!1)/(n-1),!0)}function p(e){return e>0xe8d4a51000?e:e*1e3}function m(e){return{time:p(e.time??e.t??0),open:e.open??e.o??0,high:e.high??e.h??0,low:e.low??e.l??0,close:e.close??e.c??0,volume:e.volume??e.v??0}}function h(e,t,n){let r=Math.max(0,t),i=Math.min(e.length,n+1);return e.slice(r,i)}function g(e,t){let n=0,r=e.length-1;for(;n<=r;){let i=n+r>>>1;if(e[i].time<t)n=i+1;else if(e[i].time>t)r=i-1;else return i}return n}function _(e,t,n,r=.05){if(e.length===0)return{min:0,max:1};let i=Math.max(0,t),a=Math.min(e.length-1,n),o=1/0,s=-1/0;for(let t=i;t<=a;t++)e[t].low<o&&(o=e[t].low),e[t].high>s&&(s=e[t].high);if(o===1/0)return{min:0,max:1};let c=s-o||1;return{min:o-c*r,max:s+c*r}}function v(e,t){return{...e,high:Math.max(e.high,t.price),low:Math.min(e.low,t.price),close:t.price,volume:e.volume+(t.volume??0),time:t.time}}function y(e,t=1){return`rgba(${parseInt(e.slice(1,3),16)}, ${parseInt(e.slice(3,5),16)}, ${parseInt(e.slice(5,7),16)}, ${t})`}function b(e,t){if(e.startsWith(`#`))return y(e,t);let n=e.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);return n?`rgba(${n[1]}, ${n[2]}, ${n[3]}, ${t})`:e}function x(e,t,n){let r=e=>(e=e.replace(`#`,``),e.length===3&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),{r:parseInt(e.slice(0,2),16),g:parseInt(e.slice(2,4),16),b:parseInt(e.slice(4,6),16)}),i=r(e),a=r(t);return`rgb(${Math.round(i.r+(a.r-i.r)*n)},${Math.round(i.g+(a.g-i.g)*n)},${Math.round(i.b+(a.b-i.b)*n)})`}var S={"1s":1e3,"5s":5e3,"15s":15e3,"30s":3e4,"1m":6e4,"3m":18e4,"5m":3e5,"15m":9e5,"30m":18e5,"45m":27e5,"1h":36e5,"2h":72e5,"3h":108e5,"4h":144e5,"6h":216e5,"8h":288e5,"12h":432e5,"1d":864e5,"2d":1728e5,"3d":2592e5,"1w":6048e5,"2w":12096e5,"1M":2592e6,"3M":7776e6,"6M":15552e6,"12M":31536e6};function ee(e){return S[e]}function te(e,t){let n=new Date(e),r=S[t];return r>=864e5?n.toLocaleDateString(void 0,{month:`short`,day:`numeric`}):r>=36e5?n.toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`}):n.toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`})}function ne(e,t){let n=S[t];return Math.floor(e/n)*n}function re(e,t){if(t===null){let t=new Date(e);return{month:t.getMonth()+1,day:t.getDate(),hours:t.getHours(),minutes:t.getMinutes()}}let n=new Date(e+t*6e4);return{month:n.getUTCMonth()+1,day:n.getUTCDate(),hours:n.getUTCHours(),minutes:n.getUTCMinutes()}}function ie(e){let t=e===null?-new Date().getTimezoneOffset():e,n=t>=0?`+`:`-`,r=Math.abs(t),i=Math.floor(r/60),a=r%60;return a===0?`UTC${n}${i}`:`UTC${n}${i}:${String(a).padStart(2,`0`)}`}var C=Date.UTC(1970,0,5);function w(e){let t=/^(\d+)([smhdwM])$/.exec(e);return t?{count:parseInt(t[1],10),unit:t[2]}:{count:1,unit:`m`}}function T(e,t,n=1){let{count:r,unit:i}=w(t);if(i===`s`||i===`m`||i===`h`||i===`d`){let n=S[t];return Math.floor(e/n)*n}let a=new Date(e);if(i===`w`){let e=S[`1d`],t=(a.getUTCDay()-n+7)%7,i=Date.UTC(a.getUTCFullYear(),a.getUTCMonth(),a.getUTCDate()-t);if(r<=1)return i;let o=Math.floor((i-C)/(7*e));return C+Math.floor(o/r)*r*7*e}let o=a.getUTCFullYear()*12+a.getUTCMonth(),s=Math.floor(o/r)*r;return Date.UTC(Math.floor(s/12),s%12,1)}function E(e,t=2,n=`en-US`){return e.toLocaleString(n,{minimumFractionDigits:t,maximumFractionDigits:t})}function D(e,t,n,r=2,i=`en-US`){if((t===`percentage`||t===`indexedTo100`)&&n&&n!==0){if(t===`percentage`){let t=(e/n-1)*100;return`${t>0?`+`:``}${t.toFixed(2)}%`}return(e/n*100).toLocaleString(i,{minimumFractionDigits:2,maximumFractionDigits:2})}return E(e,r,i)}function O(e){return e>=1e9?(e/1e9).toFixed(2)+`B`:e>=1e6?(e/1e6).toFixed(2)+`M`:e>=1e3?(e/1e3).toFixed(2)+`K`:e.toFixed(0)}function k(e){let t=0;for(let n of e){let e=n.toString(),r=e.indexOf(`.`);r>=0&&(t=Math.max(t,e.length-r-1))}return Math.min(t,8)}var A={autoScale:!0,rightMargin:5,minBarSpacing:2,maxBarSpacing:30,grid:{visible:!0,hLineStyle:`solid`,vLineStyle:`solid`},crosshair:{mode:`magnet`}},j=[`1s`,`1m`,`3m`,`5m`,`15m`,`30m`,`1h`,`2h`,`4h`,`6h`,`8h`,`12h`,`1d`,`3d`,`1w`,`1M`],M=[`1m`,`5m`,`15m`,`30m`,`1h`,`2h`,`4h`,`1d`,`1w`,`1M`,`3M`,`6M`,`12M`],N=[`1m`,`5m`,`15m`,`30m`,`1h`,`4h`,`1d`,`1w`,`1M`],P=[`1m`,`5m`,`15m`,`1h`,`4h`,`1d`,`1w`],F=8,I=2,L=70,R=30,z=60,B=120,V={family:`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`,sizeSmall:10,sizeMedium:12,sizeLarge:14},H={name:`dark`,background:`#131722`,text:`#D1D4DC`,textSecondary:`#787B86`,grid:`#1E222D`,crosshair:`#9598A1`,candleUp:`#26A69A`,candleDown:`#EF5350`,candleUpWick:`#26A69A`,candleDownWick:`#EF5350`,lineColor:`#2196F3`,areaTopColor:`rgba(33, 150, 243, 0.4)`,areaBottomColor:`rgba(33, 150, 243, 0.0)`,volumeUp:`rgba(38, 166, 154, 0.3)`,volumeDown:`rgba(239, 83, 80, 0.3)`,axisLine:`#2A2E39`,axisLabel:`#D1D4DC`,axisLabelBackground:`#2A2E39`,font:V},ae={name:`light`,background:`#FFFFFF`,text:`#131722`,textSecondary:`#787B86`,grid:`#F0F3FA`,crosshair:`#9598A1`,candleUp:`#26A69A`,candleDown:`#EF5350`,candleUpWick:`#26A69A`,candleDownWick:`#EF5350`,lineColor:`#2196F3`,areaTopColor:`rgba(33, 150, 243, 0.4)`,areaBottomColor:`rgba(33, 150, 243, 0.0)`,volumeUp:`rgba(38, 166, 154, 0.3)`,volumeDown:`rgba(239, 83, 80, 0.3)`,axisLine:`#E0E3EB`,axisLabel:`#131722`,axisLabelBackground:`#F0F3FA`,font:V},U={name:`terminal`,background:`#0E0E0E`,text:`#C0C0C0`,textSecondary:`#8A8A8A`,grid:`#1A1A1A`,crosshair:`#666666`,candleUp:`#00FF87`,candleDown:`#FF3B4D`,candleUpWick:`#00FF87`,candleDownWick:`#FF3B4D`,lineColor:`#3D8BFD`,areaTopColor:`rgba(61, 139, 253, 0.3)`,areaBottomColor:`rgba(61, 139, 253, 0.0)`,volumeUp:`rgba(0, 255, 135, 0.2)`,volumeDown:`rgba(255, 59, 77, 0.2)`,axisLine:`#1A1A1A`,axisLabel:`#8A8A8A`,axisLabelBackground:`#1A1A1A`,font:{family:`'Roboto Mono', 'JetBrains Mono', 'SF Mono', Consolas, monospace`,sizeSmall:10,sizeMedium:12,sizeLarge:14}},W={candlestick:`Candlestick`,line:`Line`,area:`Area`,bar:`OHLC Bar`,price:`Price`,volume:`Volume`,time:`Time`,open:`Open`,high:`High`,low:`Low`,close:`Close`,sma:`SMA`,ema:`EMA`,bollingerBands:`Bollinger Bands`,vwap:`VWAP`,ichimoku:`Ichimoku Cloud`,parabolicSAR:`Parabolic SAR`,supertrend:`Supertrend`,keltnerChannel:`Keltner Channel`,donchianChannel:`Donchian Channel`,rsi:`RSI`,macd:`MACD`,stochastic:`Stochastic`,atr:`ATR`,adx:`ADX`,obv:`OBV`,williamsR:`Williams %R`,cci:`CCI`,mfi:`MFI`,aroon:`Aroon`,roc:`ROC`,tsi:`TSI`,cmf:`CMF`,stddev:`Std Dev`,volumeProfile:`Volume Profile`,accumulationDistribution:`A/D Line`,vroc:`VROC`,trendLine:`Trend Line`,horizontalLine:`Horizontal Line`,verticalLine:`Vertical Line`,ray:`Ray`,extendedLine:`Extended Line`,parallelChannel:`Parallel Channel`,regressionChannel:`Regression Channel`,fibRetracement:`Fibonacci Retracement`,fibExtension:`Fibonacci Extension`,rectangle:`Rectangle`,ellipse:`Ellipse`,triangle:`Triangle`,pitchfork:`Andrews' Pitchfork`,elliottWave:`Elliott Wave`,priceRange:`Price Range`,dateRange:`Date Range`,measure:`Measure`,textTool:`Text`,arrow:`Arrow`,clearAll:`Clear All`,buy:`Buy`,sell:`Sell`,buyLimit:`Buy Limit`,sellLimit:`Sell Limit`,buyStop:`Buy Stop`,sellStop:`Sell Stop`,stopLoss:`Stop Loss`,takeProfit:`Take Profit`,market:`Market`,limit:`Limit`,stop:`Stop`,cancel:`Cancel`,modify:`Modify`,quantity:`Qty`,pnl:`P&L`,activeOrders:`Active Orders`,positions:`Positions`,noOrders:`No active orders`,noPositions:`No open positions`,placeOrder:`Place Order`,rightClickToTrade:`Right-click chart to place orders`,ceiling:`Ceiling`,floor:`Floor`,reference:`Reference`,session:`Session`,preOpen:`Pre-Open`,continuous:`Continuous`,preClose:`Pre-Close`,closed:`Closed`,settings:`Settings`,theme:`Theme`,darkTheme:`Dark`,lightTheme:`Light`,tools:`Tools`,indicators:`Indicators`,overlays:`Overlays`,panels:`Panels`,orders:`Orders`,autoScale:`Auto Scale`,crosshair:`Crosshair`,grid:`Grid`,loading:`Loading...`,error:`Error`,numberDecimalSeparator:`.`,numberGroupSeparator:`,`},G={candlestick:`Nến`,line:`Đường`,area:`Vùng`,bar:`Thanh OHLC`,price:`Giá`,volume:`Khối lượng`,time:`Thời gian`,open:`Mở`,high:`Cao`,low:`Thấp`,close:`Đóng`,sma:`SMA`,ema:`EMA`,bollingerBands:`Dải Bollinger`,vwap:`VWAP`,ichimoku:`Mây Ichimoku`,parabolicSAR:`Parabolic SAR`,supertrend:`Supertrend`,keltnerChannel:`Kênh Keltner`,donchianChannel:`Kênh Donchian`,rsi:`RSI`,macd:`MACD`,stochastic:`Stochastic`,atr:`ATR`,adx:`ADX`,obv:`OBV`,williamsR:`Williams %R`,cci:`CCI`,mfi:`MFI`,aroon:`Aroon`,roc:`ROC`,tsi:`TSI`,cmf:`CMF`,stddev:`Độ lệch chuẩn`,volumeProfile:`Phân bổ KL`,accumulationDistribution:`Tích lũy/Phân phối`,vroc:`VROC`,trendLine:`Đường xu hướng`,horizontalLine:`Đường ngang`,verticalLine:`Đường dọc`,ray:`Tia`,extendedLine:`Đường kéo dài`,parallelChannel:`Kênh song song`,regressionChannel:`Kênh hồi quy`,fibRetracement:`Fibonacci thoái lui`,fibExtension:`Fibonacci mở rộng`,rectangle:`Hình chữ nhật`,ellipse:`Hình elip`,triangle:`Tam giác`,pitchfork:`Chĩa ba Andrews`,elliottWave:`Sóng Elliott`,priceRange:`Khoảng giá`,dateRange:`Khoảng thời gian`,measure:`Đo lường`,textTool:`Chữ`,arrow:`Mũi tên`,clearAll:`Xóa tất cả`,buy:`Mua`,sell:`Bán`,buyLimit:`Mua giới hạn`,sellLimit:`Bán giới hạn`,buyStop:`Mua chặn`,sellStop:`Bán chặn`,stopLoss:`Cắt lỗ`,takeProfit:`Chốt lời`,market:`Thị trường`,limit:`Giới hạn`,stop:`Dừng`,cancel:`Hủy`,modify:`Sửa`,quantity:`KL`,pnl:`Lãi/Lỗ`,activeOrders:`Lệnh chờ`,positions:`Vị thế`,noOrders:`Không có lệnh chờ`,noPositions:`Không có vị thế mở`,placeOrder:`Đặt lệnh`,rightClickToTrade:`Nhấp chuột phải để đặt lệnh`,ceiling:`Trần`,floor:`Sàn`,reference:`Tham chiếu`,session:`Phiên`,preOpen:`Trước giờ mở`,continuous:`Liên tục`,preClose:`Trước giờ đóng`,closed:`Đóng cửa`,settings:`Cài đặt`,theme:`Giao diện`,darkTheme:`Tối`,lightTheme:`Sáng`,tools:`Công cụ`,indicators:`Chỉ báo`,overlays:`Phủ lên`,panels:`Bảng`,orders:`Lệnh`,autoScale:`Tự co giãn`,crosshair:`Chữ thập`,grid:`Lưới`,loading:`Đang tải...`,error:`Lỗi`,numberDecimalSeparator:`,`,numberGroupSeparator:`.`},K=new Map([[`en`,W],[`vi`,G]]),q=`en`,J=W;function oe(e){q=e,J=K.get(e)??W}function se(){return q}function ce(e){return J[e]??W[e]??e}function le(e,t){K.set(e,t)}function ue(e){return K.get(e??q)??W}function Y(e,t=2,n){let r=K.get(n??q)??W,i=r.numberDecimalSeparator,a=r.numberGroupSeparator,[o,s]=e.toFixed(t).split(`.`),c=o.startsWith(`-`),l=c?o.slice(1):o,u=``;for(let e=l.length-1,t=0;e>=0;e--,t++)t>0&&t%3==0&&(u=a+u),u=l[e]+u;return c&&(u=`-`+u),s?u+i+s:u}function de(e){return Y(e,0,`vi`)}function fe(e,t){return e>=1e9?Y(e/1e9,2,t??q)+`B`:e>=1e6?Y(e/1e6,2,t??q)+`M`:e>=1e3?Y(e/1e3,2,t??q)+`K`:Y(e,0,t??q)}var X={up:`#FF0000`,down:`#0000FF`,unchanged:`#FFD700`,ceiling:`#FF00FF`,floor:`#00FFFF`,reference:`#FFD700`},Z=[{name:`ATO`,startTime:`09:00`,endTime:`09:15`,type:`preOpen`},{name:`Phiên 1`,startTime:`09:15`,endTime:`11:30`,type:`continuous`},{name:`Nghỉ trưa`,startTime:`11:30`,endTime:`13:00`,type:`closed`},{name:`Phiên 2`,startTime:`13:00`,endTime:`14:30`,type:`continuous`},{name:`ATC`,startTime:`14:30`,endTime:`14:45`,type:`preClose`}],Q=[{name:`Phiên 1`,startTime:`09:00`,endTime:`11:30`,type:`continuous`},{name:`Nghỉ trưa`,startTime:`11:30`,endTime:`13:00`,type:`closed`},{name:`Phiên 2`,startTime:`13:00`,endTime:`14:30`,type:`continuous`},{name:`ATC`,startTime:`14:30`,endTime:`14:45`,type:`preClose`}],pe={type:`stock`,exchange:`HOSE`,currency:`VND`,pricePrecision:2,volumeUnit:10,priceStep:.05,priceLimits:{enabled:!0,ceilingPercent:7,floorPercent:7},sessions:Z,colorScheme:X},me={type:`stock`,exchange:`HNX`,currency:`VND`,pricePrecision:1,volumeUnit:100,priceStep:.1,priceLimits:{enabled:!0,ceilingPercent:10,floorPercent:10},sessions:Q,colorScheme:X},he={type:`stock`,exchange:`UPCOM`,currency:`VND`,pricePrecision:1,volumeUnit:100,priceStep:.1,priceLimits:{enabled:!0,ceilingPercent:15,floorPercent:15},sessions:Q,colorScheme:X},ge={type:`crypto`,currency:`USDT`,pricePrecision:2,priceLimits:{enabled:!1}},$={type:`stock`,exchange:`NYSE`,currency:`USD`,pricePrecision:2,priceStep:.01,priceLimits:{enabled:!1},sessions:[{name:`Pre-Market`,startTime:`04:00`,endTime:`09:30`,type:`preOpen`},{name:`Regular`,startTime:`09:30`,endTime:`16:00`,type:`continuous`},{name:`After-Hours`,startTime:`16:00`,endTime:`20:00`,type:`preClose`}]};function _e(e){return{...e,candleUp:X.up,candleDown:X.down,candleUpWick:X.up,candleDownWick:X.down,volumeUp:`rgba(255, 0, 0, 0.3)`,volumeDown:`rgba(0, 0, 255, 0.3)`}}function ve(e,t){if(!t.priceLimits?.enabled||!t.priceLimits.ceilingPercent)return null;let n=t.priceLimits.ceilingPercent/100,r=(t.priceLimits.floorPercent??t.priceLimits.ceilingPercent)/100;return{ceiling:e*(1+n),floor:e*(1-r),reference:e}}function ye(e){let t=new Date,n=`${String(t.getHours()).padStart(2,`0`)}:${String(t.getMinutes()).padStart(2,`0`)}`;for(let t of e)if(n>=t.startTime&&n<t.endTime)return t;return null}exports.DARK_TERMINAL=U,exports.DARK_THEME=H,exports.DEFAULT_BAR_SPACING=I,exports.DEFAULT_BAR_WIDTH=F,exports.DEFAULT_CHART_OPTIONS=A,exports.DEFAULT_DRAWING_STYLE=t,exports.DEFAULT_PANEL_HEIGHT=B,exports.DEFAULT_RECONNECT=a,exports.DEFAULT_SIGNAL_STYLE=r,exports.DEFAULT_STREAM_CONFIG=o,exports.DEFAULT_TIMEFRAME_FAVORITES=P,exports.DEFAULT_TRADE_ZONE_STYLE=i,exports.DEFAULT_TRADING_CONFIG=n,exports.HNX_SESSIONS=Q,exports.HOSE_SESSIONS=Z,exports.LIGHT_THEME=ae,exports.LayerType=e,exports.MARKET_CRYPTO=ge,exports.MARKET_HNX=me,exports.MARKET_HOSE=pe,exports.MARKET_NYSE=$,exports.MARKET_UPCOM=he,exports.MIN_PANEL_HEIGHT=z,exports.PRICE_AXIS_WIDTH=L,exports.TIMEFRAMES_CRYPTO=j,exports.TIMEFRAMES_FOREX=N,exports.TIMEFRAMES_STOCK=M,exports.TIME_AXIS_HEIGHT=R,exports.VN_COLORS=X,exports.alignToTimeframe=ne,exports.clamp=s,exports.computePriceLimits=ve,exports.computePriceRange=_,exports.computeTickStep=f,exports.createVNTheme=_e,exports.detectPrecision=k,exports.en=W,exports.findBarIndex=g,exports.formatNumber=Y,exports.formatPrice=E,exports.formatPriceScaleLabel=D,exports.formatTimestamp=te,exports.formatVND=de,exports.formatVolume=O,exports.formatVolumeLoc=fe,exports.getCurrentSession=ye,exports.getLocale=se,exports.getLocaleStrings=ue,exports.hexToRgba=y,exports.inverseLerp=l,exports.lerp=c,exports.lerpColor=x,exports.mergeBar=v,exports.niceNumber=d,exports.normalizeBar=m,exports.normalizeBarTime=p,exports.registerLocale=le,exports.roundToStep=u,exports.setLocale=oe,exports.sliceVisibleData=h,exports.t=ce,exports.timeParts=re,exports.timeframeBucketStart=T,exports.timeframeToMs=ee,exports.tzLabel=ie,exports.vi=G,exports.withAlpha=b; | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,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 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 logScale?: 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 like TradingView. When unset,\n * `anchor.time` is treated as a raw bar index (legacy behavior).\n */\n data?: ReadonlyArray<{ time: number }>;\n}\n\nexport enum LayerType {\n Background = 0,\n Main = 1,\n Panel = 2,\n Overlay = 3,\n UI = 4,\n}\n","import type { Point, ViewportState } from './rendering.js';\n\nexport type DrawingToolType =\n | 'trendLine' | 'horizontalLine' | 'verticalLine' | 'ray' | 'extendedLine'\n | 'parallelChannel' | 'regressionChannel'\n | 'fibRetracement' | 'fibExtension' | 'fibTimeZones'\n | 'rectangle' | 'ellipse' | 'triangle'\n | 'pitchfork' | 'elliottWave'\n | 'priceRange' | 'dateRange' | 'measure'\n | 'text' | 'arrow'\n | 'gannFan' | 'gannBox'\n | 'anchoredVWAP'\n | 'volumeProfileRange';\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 meta?: Record<string, unknown>;\n}\n\nexport interface DrawingDescriptor {\n type: DrawingToolType;\n name: string;\n requiredAnchors: number;\n singleClick?: boolean;\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\nexport const DEFAULT_DRAWING_STYLE: DrawingStyle = {\n color: '#2196F3',\n lineWidth: 1,\n lineStyle: 'solid',\n fillColor: 'rgba(33, 150, 243, 0.1)',\n fillOpacity: 0.1,\n fontSize: 12,\n};\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 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: '#26A69A', sell: '#EF5350' },\r\n positionColors: { profit: '#26A69A', loss: '#EF5350', entry: '#2196F3' },\r\n depthOverlay: { enabled: false, bidColor: 'rgba(38,166,154,0.15)', askColor: 'rgba(239,83,80,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';\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: '#26A69A',\n shortColor: '#EF5350',\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: '#26A69A',\n lossColor: '#EF5350',\n activeColor: '#2196F3',\n fillOpacity: 0.12,\n borderWidth: 1,\n showLabel: true,\n showPnl: true,\n};\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';\n\nconst TIMEFRAME_MS: Record<TimeFrame, 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\nexport function timeframeToMs(tf: TimeFrame): number {\n return TIMEFRAME_MS[tf];\n}\n\nexport function formatTimestamp(timestamp: number, tf: TimeFrame): string {\n const d = new Date(timestamp);\n const ms = TIMEFRAME_MS[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 = TIMEFRAME_MS[tf];\n return Math.floor(timestamp / ms) * ms;\n}\n","export function formatPrice(value: number, precision = 2, locale = 'en-US'): string {\n return value.toLocaleString(locale, {\n minimumFractionDigits: precision,\n maximumFractionDigits: precision,\n });\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 { 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';\n\nconst DEFAULT_FONT = {\n family: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n sizeSmall: 10,\n sizeMedium: 12,\n sizeLarge: 14,\n};\n\nexport const DARK_THEME: Theme = {\n name: 'dark',\n background: '#131722',\n text: '#D1D4DC',\n textSecondary: '#787B86',\n grid: '#1E222D',\n crosshair: '#9598A1',\n candleUp: '#26A69A',\n candleDown: '#EF5350',\n candleUpWick: '#26A69A',\n candleDownWick: '#EF5350',\n lineColor: '#2196F3',\n areaTopColor: 'rgba(33, 150, 243, 0.4)',\n areaBottomColor: 'rgba(33, 150, 243, 0.0)',\n volumeUp: 'rgba(38, 166, 154, 0.3)',\n volumeDown: 'rgba(239, 83, 80, 0.3)',\n axisLine: '#2A2E39',\n axisLabel: '#D1D4DC',\n axisLabelBackground: '#2A2E39',\n font: DEFAULT_FONT,\n};\n\nexport const LIGHT_THEME: Theme = {\n name: 'light',\n background: '#FFFFFF',\n text: '#131722',\n textSecondary: '#787B86',\n grid: '#F0F3FA',\n crosshair: '#9598A1',\n candleUp: '#26A69A',\n candleDown: '#EF5350',\n candleUpWick: '#26A69A',\n candleDownWick: '#EF5350',\n lineColor: '#2196F3',\n areaTopColor: 'rgba(33, 150, 243, 0.4)',\n areaBottomColor: 'rgba(33, 150, 243, 0.0)',\n volumeUp: 'rgba(38, 166, 154, 0.3)',\n volumeDown: 'rgba(239, 83, 80, 0.3)',\n axisLine: '#E0E3EB',\n axisLabel: '#131722',\n axisLabelBackground: '#F0F3FA',\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","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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"} | ||
| {"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 /**\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 like TradingView. When unset,\n * `anchor.time` is treated as a raw bar index (legacy behavior).\n */\n data?: ReadonlyArray<{ time: number }>;\n}\n\nexport enum LayerType {\n Background = 0,\n Main = 1,\n Panel = 2,\n Overlay = 3,\n UI = 4,\n}\n","import type { Point, ViewportState } from './rendering.js';\n\nexport type DrawingToolType =\n | 'trendLine' | 'horizontalLine' | 'verticalLine' | 'ray' | 'extendedLine'\n | 'parallelChannel' | 'regressionChannel'\n | 'fibRetracement' | 'fibExtension' | 'fibTimeZones'\n | 'rectangle' | 'ellipse' | 'triangle'\n | 'pitchfork' | 'elliottWave'\n | 'priceRange' | 'dateRange' | 'measure'\n | 'text' | 'arrow'\n | 'gannFan' | 'gannBox'\n | 'anchoredVWAP'\n | 'volumeProfileRange';\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 meta?: Record<string, unknown>;\n}\n\nexport interface DrawingDescriptor {\n type: DrawingToolType;\n name: string;\n requiredAnchors: number;\n singleClick?: boolean;\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\nexport const DEFAULT_DRAWING_STYLE: DrawingStyle = {\n color: '#2196F3',\n lineWidth: 1,\n lineStyle: 'solid',\n fillColor: 'rgba(33, 150, 243, 0.1)',\n fillOpacity: 0.1,\n fontSize: 12,\n};\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 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: '#26A69A', sell: '#EF5350' },\r\n positionColors: { profit: '#26A69A', loss: '#EF5350', entry: '#2196F3' },\r\n depthOverlay: { enabled: false, bidColor: 'rgba(38,166,154,0.15)', askColor: 'rgba(239,83,80,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';\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: '#26A69A',\n shortColor: '#EF5350',\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: '#26A69A',\n lossColor: '#EF5350',\n activeColor: '#2196F3',\n fillOpacity: 0.12,\n borderWidth: 1,\n showLabel: true,\n showPnl: true,\n};\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';\n\nconst TIMEFRAME_MS: Record<TimeFrame, 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\nexport function timeframeToMs(tf: TimeFrame): number {\n return TIMEFRAME_MS[tf];\n}\n\nexport function formatTimestamp(timestamp: number, tf: TimeFrame): string {\n const d = new Date(timestamp);\n const ms = TIMEFRAME_MS[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 = TIMEFRAME_MS[tf];\n return Math.floor(timestamp / ms) * ms;\n}\n\nexport interface TimeParts {\n month: number; // 1-12\n day: number;\n hours: number;\n minutes: number;\n}\n\n/**\n * Calendar parts of a timestamp in either the browser-local timezone\n * (`tzOffsetMinutes === null`) or a fixed UTC offset (e.g. -300 for EST).\n */\nexport function timeParts(timeMs: number, tzOffsetMinutes: number | null): TimeParts {\n if (tzOffsetMinutes === null) {\n const d = new Date(timeMs);\n return { month: d.getMonth() + 1, day: d.getDate(), hours: d.getHours(), minutes: d.getMinutes() };\n }\n const d = new Date(timeMs + tzOffsetMinutes * 60_000);\n return { month: d.getUTCMonth() + 1, day: d.getUTCDate(), hours: d.getUTCHours(), minutes: d.getUTCMinutes() };\n}\n\n/** A short timezone label like `UTC-5` or `UTC+5:30` (browser-local when null). */\nexport function tzLabel(tzOffsetMinutes: number | null): string {\n const min = tzOffsetMinutes === null ? -new Date().getTimezoneOffset() : tzOffsetMinutes;\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\nfunction parseTimeframe(tf: TimeFrame): { count: number; unit: 's' | 'm' | 'h' | 'd' | 'w' | 'M' } {\n const match = /^(\\d+)([smhdwM])$/.exec(tf);\n if (!match) return { count: 1, unit: 'm' };\n return { count: parseInt(match[1], 10), unit: match[2] as 's' | 'm' | 'h' | 'd' | 'w' | 'M' };\n}\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);\n\n if (unit === 's' || unit === 'm' || unit === 'h' || unit === 'd') {\n const ms = TIMEFRAME_MS[tf];\n return Math.floor(timestamp / ms) * ms;\n }\n\n const d = new Date(timestamp);\n\n if (unit === 'w') {\n const dayMs = TIMEFRAME_MS['1d'];\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","export function formatPrice(value: number, precision = 2, locale = 'en-US'): string {\n return value.toLocaleString(locale, {\n minimumFractionDigits: precision,\n maximumFractionDigits: precision,\n });\n}\n\nimport type { PriceScaleMode } from '../types/rendering.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 indexed.toLocaleString(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n }\n return formatPrice(price, precision, locale);\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 { 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';\n\nconst DEFAULT_FONT = {\n family: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n sizeSmall: 10,\n sizeMedium: 12,\n sizeLarge: 14,\n};\n\nexport const DARK_THEME: Theme = {\n name: 'dark',\n background: '#131722',\n text: '#D1D4DC',\n textSecondary: '#787B86',\n grid: '#1E222D',\n crosshair: '#9598A1',\n candleUp: '#26A69A',\n candleDown: '#EF5350',\n candleUpWick: '#26A69A',\n candleDownWick: '#EF5350',\n lineColor: '#2196F3',\n areaTopColor: 'rgba(33, 150, 243, 0.4)',\n areaBottomColor: 'rgba(33, 150, 243, 0.0)',\n volumeUp: 'rgba(38, 166, 154, 0.3)',\n volumeDown: 'rgba(239, 83, 80, 0.3)',\n axisLine: '#2A2E39',\n axisLabel: '#D1D4DC',\n axisLabelBackground: '#2A2E39',\n font: DEFAULT_FONT,\n};\n\nexport const LIGHT_THEME: Theme = {\n name: 'light',\n background: '#FFFFFF',\n text: '#131722',\n textSecondary: '#787B86',\n grid: '#F0F3FA',\n crosshair: '#9598A1',\n candleUp: '#26A69A',\n candleDown: '#EF5350',\n candleUpWick: '#26A69A',\n candleDownWick: '#EF5350',\n lineColor: '#2196F3',\n areaTopColor: 'rgba(33, 150, 243, 0.4)',\n areaBottomColor: 'rgba(33, 150, 243, 0.0)',\n volumeUp: 'rgba(38, 166, 154, 0.3)',\n volumeDown: 'rgba(239, 83, 80, 0.3)',\n axisLine: '#E0E3EB',\n axisLabel: '#131722',\n axisLabelBackground: '#F0F3FA',\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","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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"} |
+118
-56
@@ -84,3 +84,3 @@ //#region src/types/rendering.ts | ||
| } | ||
| function ee(e) { | ||
| function m(e) { | ||
| return { | ||
@@ -95,7 +95,7 @@ time: p(e.time ?? e.t ?? 0), | ||
| } | ||
| function m(e, t, n) { | ||
| function h(e, t, n) { | ||
| let r = Math.max(0, t), i = Math.min(e.length, n + 1); | ||
| return e.slice(r, i); | ||
| } | ||
| function h(e, t) { | ||
| function g(e, t) { | ||
| let n = 0, r = e.length - 1; | ||
@@ -110,3 +110,3 @@ for (; n <= r;) { | ||
| } | ||
| function g(e, t, n, r = .05) { | ||
| function _(e, t, n, r = .05) { | ||
| if (e.length === 0) return { | ||
@@ -128,3 +128,3 @@ min: 0, | ||
| } | ||
| function te(e, t) { | ||
| function v(e, t) { | ||
| return { | ||
@@ -141,11 +141,11 @@ ...e, | ||
| //#region src/utils/color.ts | ||
| function _(e, t = 1) { | ||
| function y(e, t = 1) { | ||
| return `rgba(${parseInt(e.slice(1, 3), 16)}, ${parseInt(e.slice(3, 5), 16)}, ${parseInt(e.slice(5, 7), 16)}, ${t})`; | ||
| } | ||
| function v(e, t) { | ||
| if (e.startsWith("#")) return _(e, t); | ||
| function b(e, t) { | ||
| if (e.startsWith("#")) return y(e, t); | ||
| let n = e.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); | ||
| return n ? `rgba(${n[1]}, ${n[2]}, ${n[3]}, ${t})` : e; | ||
| } | ||
| function y(e, t, n) { | ||
| function x(e, t, n) { | ||
| let r = (e) => (e = e.replace("#", ""), e.length === 3 && (e = e[0] + e[0] + e[1] + e[1] + e[2] + e[2]), { | ||
@@ -160,3 +160,3 @@ r: parseInt(e.slice(0, 2), 16), | ||
| //#region src/utils/time.ts | ||
| var b = { | ||
| var S = { | ||
| "1s": 1e3, | ||
@@ -189,7 +189,7 @@ "5s": 5e3, | ||
| }; | ||
| function ne(e) { | ||
| return b[e]; | ||
| function ee(e) { | ||
| return S[e]; | ||
| } | ||
| function re(e, t) { | ||
| let n = new Date(e), r = b[t]; | ||
| function te(e, t) { | ||
| let n = new Date(e), r = S[t]; | ||
| return r >= 864e5 ? n.toLocaleDateString(void 0, { | ||
@@ -207,9 +207,58 @@ month: "short", | ||
| } | ||
| function ie(e, t) { | ||
| let n = b[t]; | ||
| function ne(e, t) { | ||
| let n = S[t]; | ||
| return Math.floor(e / n) * n; | ||
| } | ||
| function re(e, t) { | ||
| if (t === null) { | ||
| let t = new Date(e); | ||
| return { | ||
| month: t.getMonth() + 1, | ||
| day: t.getDate(), | ||
| hours: t.getHours(), | ||
| minutes: t.getMinutes() | ||
| }; | ||
| } | ||
| let n = new Date(e + t * 6e4); | ||
| return { | ||
| month: n.getUTCMonth() + 1, | ||
| day: n.getUTCDate(), | ||
| hours: n.getUTCHours(), | ||
| minutes: n.getUTCMinutes() | ||
| }; | ||
| } | ||
| function ie(e) { | ||
| let t = e === null ? -(/* @__PURE__ */ new Date()).getTimezoneOffset() : e, n = t >= 0 ? "+" : "-", r = Math.abs(t), i = Math.floor(r / 60), a = r % 60; | ||
| return a === 0 ? `UTC${n}${i}` : `UTC${n}${i}:${String(a).padStart(2, "0")}`; | ||
| } | ||
| var C = Date.UTC(1970, 0, 5); | ||
| function w(e) { | ||
| let t = /^(\d+)([smhdwM])$/.exec(e); | ||
| return t ? { | ||
| count: parseInt(t[1], 10), | ||
| unit: t[2] | ||
| } : { | ||
| count: 1, | ||
| unit: "m" | ||
| }; | ||
| } | ||
| function T(e, t, n = 1) { | ||
| let { count: r, unit: i } = w(t); | ||
| if (i === "s" || i === "m" || i === "h" || i === "d") { | ||
| let n = S[t]; | ||
| return Math.floor(e / n) * n; | ||
| } | ||
| let a = new Date(e); | ||
| if (i === "w") { | ||
| let e = S["1d"], t = (a.getUTCDay() - n + 7) % 7, i = Date.UTC(a.getUTCFullYear(), a.getUTCMonth(), a.getUTCDate() - t); | ||
| if (r <= 1) return i; | ||
| let o = Math.floor((i - C) / (7 * e)); | ||
| return C + Math.floor(o / r) * r * 7 * e; | ||
| } | ||
| let o = a.getUTCFullYear() * 12 + a.getUTCMonth(), s = Math.floor(o / r) * r; | ||
| return Date.UTC(Math.floor(s / 12), s % 12, 1); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/precision.ts | ||
| function x(e, t = 2, n = "en-US") { | ||
| function E(e, t = 2, n = "en-US") { | ||
| return e.toLocaleString(n, { | ||
@@ -220,6 +269,19 @@ minimumFractionDigits: t, | ||
| } | ||
| function S(e) { | ||
| function D(e, t, n, r = 2, i = "en-US") { | ||
| if ((t === "percentage" || t === "indexedTo100") && n && n !== 0) { | ||
| if (t === "percentage") { | ||
| let t = (e / n - 1) * 100; | ||
| return `${t > 0 ? "+" : ""}${t.toFixed(2)}%`; | ||
| } | ||
| return (e / n * 100).toLocaleString(i, { | ||
| minimumFractionDigits: 2, | ||
| maximumFractionDigits: 2 | ||
| }); | ||
| } | ||
| return E(e, r, i); | ||
| } | ||
| function O(e) { | ||
| return e >= 1e9 ? (e / 1e9).toFixed(2) + "B" : e >= 1e6 ? (e / 1e6).toFixed(2) + "M" : e >= 1e3 ? (e / 1e3).toFixed(2) + "K" : e.toFixed(0); | ||
| } | ||
| function C(e) { | ||
| function k(e) { | ||
| let t = 0; | ||
@@ -234,3 +296,3 @@ for (let n of e) { | ||
| //#region src/constants/defaults.ts | ||
| var w = { | ||
| var A = { | ||
| autoScale: !0, | ||
@@ -246,3 +308,3 @@ rightMargin: 5, | ||
| crosshair: { mode: "magnet" } | ||
| }, T = [ | ||
| }, j = [ | ||
| "1s", | ||
@@ -264,3 +326,3 @@ "1m", | ||
| "1M" | ||
| ], E = [ | ||
| ], M = [ | ||
| "1m", | ||
@@ -279,3 +341,3 @@ "5m", | ||
| "12M" | ||
| ], D = [ | ||
| ], N = [ | ||
| "1m", | ||
@@ -290,3 +352,3 @@ "5m", | ||
| "1M" | ||
| ], O = [ | ||
| ], P = [ | ||
| "1m", | ||
@@ -299,3 +361,3 @@ "5m", | ||
| "1w" | ||
| ], k = 8, A = 2, j = 70, M = 30, N = 60, P = 120, F = { | ||
| ], F = 8, I = 2, L = 70, R = 30, z = 60, B = 120, V = { | ||
| family: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif", | ||
@@ -305,3 +367,3 @@ sizeSmall: 10, | ||
| sizeLarge: 14 | ||
| }, I = { | ||
| }, H = { | ||
| name: "dark", | ||
@@ -325,4 +387,4 @@ background: "#131722", | ||
| axisLabelBackground: "#2A2E39", | ||
| font: F | ||
| }, L = { | ||
| font: V | ||
| }, ae = { | ||
| name: "light", | ||
@@ -346,4 +408,4 @@ background: "#FFFFFF", | ||
| axisLabelBackground: "#F0F3FA", | ||
| font: F | ||
| }, R = { | ||
| font: V | ||
| }, U = { | ||
| name: "terminal", | ||
@@ -373,3 +435,3 @@ background: "#0E0E0E", | ||
| } | ||
| }, z = { | ||
| }, W = { | ||
| candlestick: "Candlestick", | ||
@@ -477,3 +539,3 @@ line: "Line", | ||
| numberGroupSeparator: "," | ||
| }, B = { | ||
| }, G = { | ||
| candlestick: "Nến", | ||
@@ -581,28 +643,28 @@ line: "Đường", | ||
| numberGroupSeparator: "." | ||
| }, V = new Map([["en", z], ["vi", B]]), H = "en", U = z; | ||
| function W(e) { | ||
| H = e, U = V.get(e) ?? z; | ||
| }, K = new Map([["en", W], ["vi", G]]), q = "en", J = W; | ||
| function oe(e) { | ||
| q = e, J = K.get(e) ?? W; | ||
| } | ||
| function G() { | ||
| return H; | ||
| function se() { | ||
| return q; | ||
| } | ||
| function K(e) { | ||
| return U[e] ?? z[e] ?? e; | ||
| function ce(e) { | ||
| return J[e] ?? W[e] ?? e; | ||
| } | ||
| function q(e, t) { | ||
| V.set(e, t); | ||
| function le(e, t) { | ||
| K.set(e, t); | ||
| } | ||
| function J(e) { | ||
| return V.get(e ?? H) ?? z; | ||
| function ue(e) { | ||
| return K.get(e ?? q) ?? W; | ||
| } | ||
| function Y(e, t = 2, n) { | ||
| let r = V.get(n ?? H) ?? z, i = r.numberDecimalSeparator, a = r.numberGroupSeparator, [o, s] = e.toFixed(t).split("."), c = o.startsWith("-"), l = c ? o.slice(1) : o, u = ""; | ||
| let r = K.get(n ?? q) ?? W, i = r.numberDecimalSeparator, a = r.numberGroupSeparator, [o, s] = e.toFixed(t).split("."), c = o.startsWith("-"), l = c ? o.slice(1) : o, u = ""; | ||
| for (let e = l.length - 1, t = 0; e >= 0; e--, t++) t > 0 && t % 3 == 0 && (u = a + u), u = l[e] + u; | ||
| return c && (u = "-" + u), s ? u + i + s : u; | ||
| } | ||
| function ae(e) { | ||
| function de(e) { | ||
| return Y(e, 0, "vi"); | ||
| } | ||
| function oe(e, t) { | ||
| return e >= 1e9 ? Y(e / 1e9, 2, t ?? H) + "B" : e >= 1e6 ? Y(e / 1e6, 2, t ?? H) + "M" : e >= 1e3 ? Y(e / 1e3, 2, t ?? H) + "K" : Y(e, 0, t ?? H); | ||
| function fe(e, t) { | ||
| return e >= 1e9 ? Y(e / 1e9, 2, t ?? q) + "B" : e >= 1e6 ? Y(e / 1e6, 2, t ?? q) + "M" : e >= 1e3 ? Y(e / 1e3, 2, t ?? q) + "K" : Y(e, 0, t ?? q); | ||
| } | ||
@@ -674,3 +736,3 @@ //#endregion | ||
| } | ||
| ], $ = { | ||
| ], pe = { | ||
| type: "stock", | ||
@@ -689,3 +751,3 @@ exchange: "HOSE", | ||
| colorScheme: X | ||
| }, se = { | ||
| }, me = { | ||
| type: "stock", | ||
@@ -704,3 +766,3 @@ exchange: "HNX", | ||
| colorScheme: X | ||
| }, ce = { | ||
| }, he = { | ||
| type: "stock", | ||
@@ -719,3 +781,3 @@ exchange: "UPCOM", | ||
| colorScheme: X | ||
| }, le = { | ||
| }, ge = { | ||
| type: "crypto", | ||
@@ -725,3 +787,3 @@ currency: "USDT", | ||
| priceLimits: { enabled: !1 } | ||
| }, ue = { | ||
| }, $ = { | ||
| type: "stock", | ||
@@ -754,3 +816,3 @@ exchange: "NYSE", | ||
| }; | ||
| function de(e) { | ||
| function _e(e) { | ||
| return { | ||
@@ -766,3 +828,3 @@ ...e, | ||
| } | ||
| function fe(e, t) { | ||
| function ve(e, t) { | ||
| if (!t.priceLimits?.enabled || !t.priceLimits.ceilingPercent) return null; | ||
@@ -776,3 +838,3 @@ let n = t.priceLimits.ceilingPercent / 100, r = (t.priceLimits.floorPercent ?? t.priceLimits.ceilingPercent) / 100; | ||
| } | ||
| function pe(e) { | ||
| function ye(e) { | ||
| let t = /* @__PURE__ */ new Date(), n = `${String(t.getHours()).padStart(2, "0")}:${String(t.getMinutes()).padStart(2, "0")}`; | ||
@@ -783,4 +845,4 @@ for (let t of e) if (n >= t.startTime && n < t.endTime) return t; | ||
| //#endregion | ||
| export { R as DARK_TERMINAL, I as DARK_THEME, A as DEFAULT_BAR_SPACING, k as DEFAULT_BAR_WIDTH, w as DEFAULT_CHART_OPTIONS, t as DEFAULT_DRAWING_STYLE, P as DEFAULT_PANEL_HEIGHT, a as DEFAULT_RECONNECT, r as DEFAULT_SIGNAL_STYLE, o as DEFAULT_STREAM_CONFIG, O as DEFAULT_TIMEFRAME_FAVORITES, i as DEFAULT_TRADE_ZONE_STYLE, n as DEFAULT_TRADING_CONFIG, Q as HNX_SESSIONS, Z as HOSE_SESSIONS, L as LIGHT_THEME, e as LayerType, le as MARKET_CRYPTO, se as MARKET_HNX, $ as MARKET_HOSE, ue as MARKET_NYSE, ce as MARKET_UPCOM, N as MIN_PANEL_HEIGHT, j as PRICE_AXIS_WIDTH, T as TIMEFRAMES_CRYPTO, D as TIMEFRAMES_FOREX, E as TIMEFRAMES_STOCK, M as TIME_AXIS_HEIGHT, X as VN_COLORS, ie as alignToTimeframe, s as clamp, fe as computePriceLimits, g as computePriceRange, f as computeTickStep, de as createVNTheme, C as detectPrecision, z as en, h as findBarIndex, Y as formatNumber, x as formatPrice, re as formatTimestamp, ae as formatVND, S as formatVolume, oe as formatVolumeLoc, pe as getCurrentSession, G as getLocale, J as getLocaleStrings, _ as hexToRgba, l as inverseLerp, c as lerp, y as lerpColor, te as mergeBar, d as niceNumber, ee as normalizeBar, p as normalizeBarTime, q as registerLocale, u as roundToStep, W as setLocale, m as sliceVisibleData, K as t, ne as timeframeToMs, B as vi, v as withAlpha }; | ||
| export { U as DARK_TERMINAL, H as DARK_THEME, I as DEFAULT_BAR_SPACING, F as DEFAULT_BAR_WIDTH, A as DEFAULT_CHART_OPTIONS, t as DEFAULT_DRAWING_STYLE, B as DEFAULT_PANEL_HEIGHT, a as DEFAULT_RECONNECT, r as DEFAULT_SIGNAL_STYLE, o as DEFAULT_STREAM_CONFIG, P as DEFAULT_TIMEFRAME_FAVORITES, i as DEFAULT_TRADE_ZONE_STYLE, n as DEFAULT_TRADING_CONFIG, Q as HNX_SESSIONS, Z as HOSE_SESSIONS, ae as LIGHT_THEME, e as LayerType, ge as MARKET_CRYPTO, me as MARKET_HNX, pe as MARKET_HOSE, $ as MARKET_NYSE, he as MARKET_UPCOM, z as MIN_PANEL_HEIGHT, L as PRICE_AXIS_WIDTH, j as TIMEFRAMES_CRYPTO, N as TIMEFRAMES_FOREX, M as TIMEFRAMES_STOCK, R as TIME_AXIS_HEIGHT, X as VN_COLORS, ne as alignToTimeframe, s as clamp, ve as computePriceLimits, _ as computePriceRange, f as computeTickStep, _e as createVNTheme, k as detectPrecision, W as en, g as findBarIndex, Y as formatNumber, E as formatPrice, D as formatPriceScaleLabel, te as formatTimestamp, de as formatVND, O as formatVolume, fe as formatVolumeLoc, ye as getCurrentSession, se as getLocale, ue as getLocaleStrings, y as hexToRgba, l as inverseLerp, c as lerp, x as lerpColor, v as mergeBar, d as niceNumber, m as normalizeBar, p as normalizeBarTime, le as registerLocale, u as roundToStep, oe as setLocale, h as sliceVisibleData, ce as t, re as timeParts, T as timeframeBucketStart, ee as timeframeToMs, ie as tzLabel, G as vi, b as withAlpha }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","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 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 logScale?: 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 like TradingView. When unset,\n * `anchor.time` is treated as a raw bar index (legacy behavior).\n */\n data?: ReadonlyArray<{ time: number }>;\n}\n\nexport enum LayerType {\n Background = 0,\n Main = 1,\n Panel = 2,\n Overlay = 3,\n UI = 4,\n}\n","import type { Point, ViewportState } from './rendering.js';\n\nexport type DrawingToolType =\n | 'trendLine' | 'horizontalLine' | 'verticalLine' | 'ray' | 'extendedLine'\n | 'parallelChannel' | 'regressionChannel'\n | 'fibRetracement' | 'fibExtension' | 'fibTimeZones'\n | 'rectangle' | 'ellipse' | 'triangle'\n | 'pitchfork' | 'elliottWave'\n | 'priceRange' | 'dateRange' | 'measure'\n | 'text' | 'arrow'\n | 'gannFan' | 'gannBox'\n | 'anchoredVWAP'\n | 'volumeProfileRange';\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 meta?: Record<string, unknown>;\n}\n\nexport interface DrawingDescriptor {\n type: DrawingToolType;\n name: string;\n requiredAnchors: number;\n singleClick?: boolean;\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\nexport const DEFAULT_DRAWING_STYLE: DrawingStyle = {\n color: '#2196F3',\n lineWidth: 1,\n lineStyle: 'solid',\n fillColor: 'rgba(33, 150, 243, 0.1)',\n fillOpacity: 0.1,\n fontSize: 12,\n};\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 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: '#26A69A', sell: '#EF5350' },\r\n positionColors: { profit: '#26A69A', loss: '#EF5350', entry: '#2196F3' },\r\n depthOverlay: { enabled: false, bidColor: 'rgba(38,166,154,0.15)', askColor: 'rgba(239,83,80,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';\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: '#26A69A',\n shortColor: '#EF5350',\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: '#26A69A',\n lossColor: '#EF5350',\n activeColor: '#2196F3',\n fillOpacity: 0.12,\n borderWidth: 1,\n showLabel: true,\n showPnl: true,\n};\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';\n\nconst TIMEFRAME_MS: Record<TimeFrame, 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\nexport function timeframeToMs(tf: TimeFrame): number {\n return TIMEFRAME_MS[tf];\n}\n\nexport function formatTimestamp(timestamp: number, tf: TimeFrame): string {\n const d = new Date(timestamp);\n const ms = TIMEFRAME_MS[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 = TIMEFRAME_MS[tf];\n return Math.floor(timestamp / ms) * ms;\n}\n","export function formatPrice(value: number, precision = 2, locale = 'en-US'): string {\n return value.toLocaleString(locale, {\n minimumFractionDigits: precision,\n maximumFractionDigits: precision,\n });\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 { 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';\n\nconst DEFAULT_FONT = {\n family: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n sizeSmall: 10,\n sizeMedium: 12,\n sizeLarge: 14,\n};\n\nexport const DARK_THEME: Theme = {\n name: 'dark',\n background: '#131722',\n text: '#D1D4DC',\n textSecondary: '#787B86',\n grid: '#1E222D',\n crosshair: '#9598A1',\n candleUp: '#26A69A',\n candleDown: '#EF5350',\n candleUpWick: '#26A69A',\n candleDownWick: '#EF5350',\n lineColor: '#2196F3',\n areaTopColor: 'rgba(33, 150, 243, 0.4)',\n areaBottomColor: 'rgba(33, 150, 243, 0.0)',\n volumeUp: 'rgba(38, 166, 154, 0.3)',\n volumeDown: 'rgba(239, 83, 80, 0.3)',\n axisLine: '#2A2E39',\n axisLabel: '#D1D4DC',\n axisLabelBackground: '#2A2E39',\n font: DEFAULT_FONT,\n};\n\nexport const LIGHT_THEME: Theme = {\n name: 'light',\n background: '#FFFFFF',\n text: '#131722',\n textSecondary: '#787B86',\n grid: '#F0F3FA',\n crosshair: '#9598A1',\n candleUp: '#26A69A',\n candleDown: '#EF5350',\n candleUpWick: '#26A69A',\n candleDownWick: '#EF5350',\n lineColor: '#2196F3',\n areaTopColor: 'rgba(33, 150, 243, 0.4)',\n areaBottomColor: 'rgba(33, 150, 243, 0.0)',\n volumeUp: 'rgba(38, 166, 154, 0.3)',\n volumeDown: 'rgba(239, 83, 80, 0.3)',\n axisLine: '#E0E3EB',\n axisLabel: '#131722',\n axisLabelBackground: '#F0F3FA',\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","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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"} | ||
| {"version":3,"file":"index.js","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 /**\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 like TradingView. When unset,\n * `anchor.time` is treated as a raw bar index (legacy behavior).\n */\n data?: ReadonlyArray<{ time: number }>;\n}\n\nexport enum LayerType {\n Background = 0,\n Main = 1,\n Panel = 2,\n Overlay = 3,\n UI = 4,\n}\n","import type { Point, ViewportState } from './rendering.js';\n\nexport type DrawingToolType =\n | 'trendLine' | 'horizontalLine' | 'verticalLine' | 'ray' | 'extendedLine'\n | 'parallelChannel' | 'regressionChannel'\n | 'fibRetracement' | 'fibExtension' | 'fibTimeZones'\n | 'rectangle' | 'ellipse' | 'triangle'\n | 'pitchfork' | 'elliottWave'\n | 'priceRange' | 'dateRange' | 'measure'\n | 'text' | 'arrow'\n | 'gannFan' | 'gannBox'\n | 'anchoredVWAP'\n | 'volumeProfileRange';\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 meta?: Record<string, unknown>;\n}\n\nexport interface DrawingDescriptor {\n type: DrawingToolType;\n name: string;\n requiredAnchors: number;\n singleClick?: boolean;\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\nexport const DEFAULT_DRAWING_STYLE: DrawingStyle = {\n color: '#2196F3',\n lineWidth: 1,\n lineStyle: 'solid',\n fillColor: 'rgba(33, 150, 243, 0.1)',\n fillOpacity: 0.1,\n fontSize: 12,\n};\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 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: '#26A69A', sell: '#EF5350' },\r\n positionColors: { profit: '#26A69A', loss: '#EF5350', entry: '#2196F3' },\r\n depthOverlay: { enabled: false, bidColor: 'rgba(38,166,154,0.15)', askColor: 'rgba(239,83,80,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';\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: '#26A69A',\n shortColor: '#EF5350',\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: '#26A69A',\n lossColor: '#EF5350',\n activeColor: '#2196F3',\n fillOpacity: 0.12,\n borderWidth: 1,\n showLabel: true,\n showPnl: true,\n};\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';\n\nconst TIMEFRAME_MS: Record<TimeFrame, 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\nexport function timeframeToMs(tf: TimeFrame): number {\n return TIMEFRAME_MS[tf];\n}\n\nexport function formatTimestamp(timestamp: number, tf: TimeFrame): string {\n const d = new Date(timestamp);\n const ms = TIMEFRAME_MS[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 = TIMEFRAME_MS[tf];\n return Math.floor(timestamp / ms) * ms;\n}\n\nexport interface TimeParts {\n month: number; // 1-12\n day: number;\n hours: number;\n minutes: number;\n}\n\n/**\n * Calendar parts of a timestamp in either the browser-local timezone\n * (`tzOffsetMinutes === null`) or a fixed UTC offset (e.g. -300 for EST).\n */\nexport function timeParts(timeMs: number, tzOffsetMinutes: number | null): TimeParts {\n if (tzOffsetMinutes === null) {\n const d = new Date(timeMs);\n return { month: d.getMonth() + 1, day: d.getDate(), hours: d.getHours(), minutes: d.getMinutes() };\n }\n const d = new Date(timeMs + tzOffsetMinutes * 60_000);\n return { month: d.getUTCMonth() + 1, day: d.getUTCDate(), hours: d.getUTCHours(), minutes: d.getUTCMinutes() };\n}\n\n/** A short timezone label like `UTC-5` or `UTC+5:30` (browser-local when null). */\nexport function tzLabel(tzOffsetMinutes: number | null): string {\n const min = tzOffsetMinutes === null ? -new Date().getTimezoneOffset() : tzOffsetMinutes;\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\nfunction parseTimeframe(tf: TimeFrame): { count: number; unit: 's' | 'm' | 'h' | 'd' | 'w' | 'M' } {\n const match = /^(\\d+)([smhdwM])$/.exec(tf);\n if (!match) return { count: 1, unit: 'm' };\n return { count: parseInt(match[1], 10), unit: match[2] as 's' | 'm' | 'h' | 'd' | 'w' | 'M' };\n}\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);\n\n if (unit === 's' || unit === 'm' || unit === 'h' || unit === 'd') {\n const ms = TIMEFRAME_MS[tf];\n return Math.floor(timestamp / ms) * ms;\n }\n\n const d = new Date(timestamp);\n\n if (unit === 'w') {\n const dayMs = TIMEFRAME_MS['1d'];\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","export function formatPrice(value: number, precision = 2, locale = 'en-US'): string {\n return value.toLocaleString(locale, {\n minimumFractionDigits: precision,\n maximumFractionDigits: precision,\n });\n}\n\nimport type { PriceScaleMode } from '../types/rendering.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 indexed.toLocaleString(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n }\n return formatPrice(price, precision, locale);\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 { 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';\n\nconst DEFAULT_FONT = {\n family: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n sizeSmall: 10,\n sizeMedium: 12,\n sizeLarge: 14,\n};\n\nexport const DARK_THEME: Theme = {\n name: 'dark',\n background: '#131722',\n text: '#D1D4DC',\n textSecondary: '#787B86',\n grid: '#1E222D',\n crosshair: '#9598A1',\n candleUp: '#26A69A',\n candleDown: '#EF5350',\n candleUpWick: '#26A69A',\n candleDownWick: '#EF5350',\n lineColor: '#2196F3',\n areaTopColor: 'rgba(33, 150, 243, 0.4)',\n areaBottomColor: 'rgba(33, 150, 243, 0.0)',\n volumeUp: 'rgba(38, 166, 154, 0.3)',\n volumeDown: 'rgba(239, 83, 80, 0.3)',\n axisLine: '#2A2E39',\n axisLabel: '#D1D4DC',\n axisLabelBackground: '#2A2E39',\n font: DEFAULT_FONT,\n};\n\nexport const LIGHT_THEME: Theme = {\n name: 'light',\n background: '#FFFFFF',\n text: '#131722',\n textSecondary: '#787B86',\n grid: '#F0F3FA',\n crosshair: '#9598A1',\n candleUp: '#26A69A',\n candleDown: '#EF5350',\n candleUpWick: '#26A69A',\n candleDownWick: '#EF5350',\n lineColor: '#2196F3',\n areaTopColor: 'rgba(33, 150, 243, 0.4)',\n areaBottomColor: 'rgba(33, 150, 243, 0.0)',\n volumeUp: 'rgba(38, 166, 154, 0.3)',\n volumeDown: 'rgba(239, 83, 80, 0.3)',\n axisLine: '#E0E3EB',\n axisLabel: '#131722',\n axisLabelBackground: '#F0F3FA',\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","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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"} |
| import { OHLCBar, DataSeries } from './ohlc.js'; | ||
| import { Point } from './rendering.js'; | ||
| import { IndicatorValue } from './indicator.js'; | ||
| export type ChartEventType = 'crosshairMove' | 'click' | 'barClick' | 'visibleRangeChange' | 'priceRangeChange' | 'zoomChange' | 'dataUpdate' | 'indicatorAdd' | 'indicatorRemove' | 'themeChange' | 'resize' | 'orderPlace' | 'orderModify' | 'orderCancel' | 'positionClose' | 'positionModify' | 'drawingCreate' | 'drawingRemove' | 'signalMarkerAdd' | 'signalMarkerRemove' | 'tradeZoneAdd' | 'tradeZoneRemove'; | ||
| export type ChartEventType = 'crosshairMove' | 'click' | 'barClick' | 'visibleRangeChange' | 'priceRangeChange' | 'zoomChange' | 'dataUpdate' | 'indicatorAdd' | 'indicatorRemove' | 'themeChange' | 'resize' | 'orderPlace' | 'orderModify' | 'orderCancel' | 'positionClose' | 'positionModify' | 'bracketPlace' | 'drawingCreate' | 'drawingRemove' | 'signalMarkerAdd' | 'signalMarkerRemove' | 'tradeZoneAdd' | 'tradeZoneRemove' | 'alertAdd' | 'alertRemove' | 'alertTriggered' | 'alertUpdate'; | ||
| export interface ChartEvent<T = unknown> { | ||
@@ -47,2 +47,11 @@ type: ChartEventType; | ||
| } | ||
| export interface BracketPlacePayload { | ||
| side: 'buy' | 'sell'; | ||
| entry: number; | ||
| stopLoss: number; | ||
| takeProfit: number; | ||
| quantity?: number; | ||
| /** Reward-to-risk ratio = |TP − entry| / |entry − SL|. */ | ||
| riskReward: number; | ||
| } | ||
| export interface IndicatorChangePayload { | ||
@@ -88,2 +97,9 @@ instanceId: string; | ||
| } | ||
| export interface AlertPayload { | ||
| id: string; | ||
| price: number; | ||
| condition: string; | ||
| message?: string; | ||
| triggered: boolean; | ||
| } | ||
| export interface ChartEventMap { | ||
@@ -105,2 +121,3 @@ crosshairMove: CrosshairMovePayload; | ||
| orderPlace: OrderPlacePayload; | ||
| bracketPlace: BracketPlacePayload; | ||
| orderModify: OrderModifyPayload; | ||
@@ -116,3 +133,10 @@ orderCancel: OrderCancelPayload; | ||
| tradeZoneRemove: TradeZoneRemovePayload; | ||
| alertAdd: AlertPayload; | ||
| alertRemove: AlertRemovePayload; | ||
| alertTriggered: AlertPayload; | ||
| alertUpdate: AlertPayload; | ||
| } | ||
| export interface AlertRemovePayload { | ||
| id: string; | ||
| } | ||
| export interface TauriBridgeOptions { | ||
@@ -119,0 +143,0 @@ enabled: boolean; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":"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"} |
@@ -15,2 +15,3 @@ export interface Point { | ||
| } | ||
| export type PriceScaleMode = 'regular' | 'logarithmic' | 'percentage' | 'indexedTo100'; | ||
| export interface ViewportState { | ||
@@ -29,4 +30,20 @@ visibleRange: { | ||
| chartRect: Rect; | ||
| /** | ||
| * Logarithmic price geometry. Kept for back-compat; mirrors | ||
| * `scaleMode === 'logarithmic'`. Prefer reading `scaleMode`. | ||
| */ | ||
| logScale?: boolean; | ||
| /** | ||
| * Price-scale presentation. `regular`, `percentage`, and `indexedTo100` share | ||
| * the same linear geometry — only the axis labels differ (rebased to | ||
| * `scaleBaseline`). `logarithmic` changes the geometry. Defaults to | ||
| * `regular` when unset. | ||
| */ | ||
| scaleMode?: PriceScaleMode; | ||
| /** | ||
| * Reference price for `percentage` / `indexedTo100` axis labels — usually the | ||
| * close of the first visible bar. Set by the chart each frame. | ||
| */ | ||
| scaleBaseline?: number; | ||
| /** | ||
| * Optional reference to the current bar series. When set, drawings/indicators | ||
@@ -33,0 +50,0 @@ * treat `anchor.time` as a real timestamp and convert to bar index via |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"rendering.d.ts","sourceRoot":"","sources":["../../src/types/rendering.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,UAAU,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxC;AAED,oBAAY,SAAS;IACnB,UAAU,IAAI;IACd,IAAI,IAAI;IACR,KAAK,IAAI;IACT,OAAO,IAAI;IACX,EAAE,IAAI;CACP"} | ||
| {"version":3,"file":"rendering.d.ts","sourceRoot":"","sources":["../../src/types/rendering.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,aAAa,GAAG,YAAY,GAAG,cAAc,CAAC;AAEvF,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,UAAU,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxC;AAED,oBAAY,SAAS;IACnB,UAAU,IAAI;IACd,IAAI,IAAI;IACR,KAAK,IAAI;IACT,OAAO,IAAI;IACX,EAAE,IAAI;CACP"} |
@@ -0,4 +1,15 @@ | ||
| import { PriceScaleMode } from '../types/rendering.js'; | ||
| export declare function formatPrice(value: number, precision?: number, locale?: string): string; | ||
| /** | ||
| * Format a price-axis label for a given scale mode. | ||
| * | ||
| * - `regular` / `logarithmic`: the raw price. | ||
| * - `percentage`: change from `baseline`, e.g. `+12.34%`. | ||
| * - `indexedTo100`: the price rebased so `baseline` reads as 100. | ||
| * | ||
| * Falls back to a plain price when a baseline is required but missing/zero. | ||
| */ | ||
| export declare function formatPriceScaleLabel(price: number, mode: PriceScaleMode, baseline: number | undefined, precision?: number, locale?: string): string; | ||
| export declare function formatVolume(value: number): string; | ||
| export declare function detectPrecision(values: number[]): number; | ||
| //# sourceMappingURL=precision.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"precision.d.ts","sourceRoot":"","sources":["../../src/utils/precision.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAI,EAAE,MAAM,SAAU,GAAG,MAAM,CAKlF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAUxD"} | ||
| {"version":3,"file":"precision.d.ts","sourceRoot":"","sources":["../../src/utils/precision.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAI,EAAE,MAAM,SAAU,GAAG,MAAM,CAKlF;AAED,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,SAAI,EACb,MAAM,SAAU,GACf,MAAM,CAWR;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAUxD"} |
+22
-0
@@ -5,2 +5,24 @@ import { TimeFrame } from '../types/ohlc.js'; | ||
| export declare function alignToTimeframe(timestamp: number, tf: TimeFrame): number; | ||
| export interface TimeParts { | ||
| month: number; | ||
| day: number; | ||
| hours: number; | ||
| minutes: number; | ||
| } | ||
| /** | ||
| * Calendar parts of a timestamp in either the browser-local timezone | ||
| * (`tzOffsetMinutes === null`) or a fixed UTC offset (e.g. -300 for EST). | ||
| */ | ||
| export declare function timeParts(timeMs: number, tzOffsetMinutes: number | null): TimeParts; | ||
| /** A short timezone label like `UTC-5` or `UTC+5:30` (browser-local when null). */ | ||
| export declare function tzLabel(tzOffsetMinutes: number | null): string; | ||
| /** | ||
| * Start of the bucket a timestamp falls into for a given timeframe. | ||
| * | ||
| * Intraday and daily frames are anchored to the Unix epoch (UTC) via fixed-ms | ||
| * flooring. Weekly frames are anchored to Monday (or `weekStartsOn`), monthly | ||
| * and yearly frames use calendar boundaries (1st of month, Jan for years) so | ||
| * 3M aligns to quarters and 12M to calendar years. | ||
| */ | ||
| export declare function timeframeBucketStart(timestamp: number, tf: TimeFrame, weekStartsOn?: 0 | 1): number; | ||
| //# sourceMappingURL=time.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AA+BlD,wBAAgB,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,CAEnD;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GAAG,MAAM,CAUxE;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GAAG,MAAM,CAGzE"} | ||
| {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AA+BlD,wBAAgB,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,CAEnD;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GAAG,MAAM,CAUxE;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GAAG,MAAM,CAGzE;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAOnF;AAED,mFAAmF;AACnF,wBAAgB,OAAO,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAO9D;AAWD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,SAAS,EACb,YAAY,GAAE,CAAC,GAAG,CAAK,GACtB,MAAM,CAyBR"} |
+1
-1
| { | ||
| "name": "@tradecanvas/commons", | ||
| "version": "0.9.0", | ||
| "version": "0.14.0", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "description": "Shared types and utilities for @tradecanvas/chart", |
219787
10.35%2050
7.39%