@tradecanvas/commons 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +67 -67
- package/dist/constants/themes.d.ts +29 -0
- package/dist/constants/themes.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +138 -114
- package/dist/index.js.map +1 -1
- package/dist/types/chart.d.ts +19 -1
- package/dist/types/chart.d.ts.map +1 -1
- package/dist/types/drawing.d.ts +1 -1
- package/dist/types/drawing.d.ts.map +1 -1
- package/dist/types/finance.d.ts +11 -0
- package/dist/types/finance.d.ts.map +1 -1
- package/dist/types/indicator.d.ts +11 -0
- package/dist/types/indicator.d.ts.map +1 -1
- package/dist/types/rendering.d.ts +16 -2
- package/dist/types/rendering.d.ts.map +1 -1
- package/dist/utils/precision.d.ts +7 -0
- package/dist/utils/precision.d.ts.map +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 TradeCanvas Contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TradeCanvas Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
# @tradecanvas/commons
|
|
2
|
-
|
|
3
|
-
Shared types, utilities, constants, themes, i18n, and market presets for the [@tradecanvas/chart](https://www.npmjs.com/package/@tradecanvas/chart) library.
|
|
4
|
-
|
|
5
|
-
**[Live Demo](https://bonguynvan.github.io/tradecanvas/)** | **[GitHub](https://github.com/bonguynvan/tradecanvas)** | **[Documentation](https://bonguynvan.github.io/tradecanvas/docs/getting-started/)**
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
You don't need to install this package directly. It's included as a dependency of `@tradecanvas/chart`.
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install @tradecanvas/chart
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## What's Inside
|
|
16
|
-
|
|
17
|
-
### Types
|
|
18
|
-
|
|
19
|
-
All TypeScript interfaces and types used across the library:
|
|
20
|
-
|
|
21
|
-
- `OHLCBar`, `DataSeries`, `TimeFrame` -- data format
|
|
22
|
-
- `ChartOptions`, `ChartType`, `FeaturesConfig` -- chart configuration
|
|
23
|
-
- `Theme`, `ThemeName` -- theming
|
|
24
|
-
- `IndicatorPlugin`, `IndicatorDescriptor`, `IndicatorConfig` -- indicators
|
|
25
|
-
- `DrawingToolType`, `DrawingState`, `DrawingStyle` -- drawing tools
|
|
26
|
-
- `TradingPosition`, `TradingOrder`, `TradingConfig` -- trading overlay
|
|
27
|
-
- `DataAdapter`, `StreamConfig`, `ConnectionState` -- real-time streaming
|
|
28
|
-
- `ChartEventType`, `ChartEvent` -- events
|
|
29
|
-
- `MarketConfig`, `TradingSession` -- market configuration
|
|
30
|
-
|
|
31
|
-
### Themes
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
import { DARK_THEME, LIGHT_THEME } from '@tradecanvas/commons'
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
19 customizable properties: `background`, `text`, `candleUp`, `candleDown`, `grid`, `crosshair`, `volumeUp`, `volumeDown`, and more.
|
|
38
|
-
|
|
39
|
-
### Constants
|
|
40
|
-
|
|
41
|
-
- Default chart options and feature flags
|
|
42
|
-
- Market presets: `MARKET_HOSE`, `MARKET_HNX`, `MARKET_UPCOM`, `MARKET_CRYPTO`, `MARKET_NYSE`
|
|
43
|
-
- Vietnamese stock market color schemes
|
|
44
|
-
|
|
45
|
-
### Utilities
|
|
46
|
-
|
|
47
|
-
- `computePriceRange()` -- compute visible price range from OHLC data
|
|
48
|
-
- `timeframeToMs()` -- convert timeframe string to milliseconds
|
|
49
|
-
- `formatNumber()`, `formatVND()`, `formatVolumeLoc()` -- number formatting
|
|
50
|
-
- Color, math, precision, and data manipulation helpers
|
|
51
|
-
|
|
52
|
-
### i18n
|
|
53
|
-
|
|
54
|
-
Built-in locales: English (`en`), Vietnamese (`vi`). Extensible via `registerLocale()`.
|
|
55
|
-
|
|
56
|
-
## Direct Usage
|
|
57
|
-
|
|
58
|
-
If you need just the types or utilities without the full chart:
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import type { OHLCBar, TimeFrame, ChartType } from '@tradecanvas/commons'
|
|
62
|
-
import { DARK_THEME, computePriceRange, timeframeToMs } from '@tradecanvas/commons'
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## License
|
|
66
|
-
|
|
67
|
-
[MIT](https://github.com/bonguynvan/tradecanvas/blob/main/LICENSE)
|
|
1
|
+
# @tradecanvas/commons
|
|
2
|
+
|
|
3
|
+
Shared types, utilities, constants, themes, i18n, and market presets for the [@tradecanvas/chart](https://www.npmjs.com/package/@tradecanvas/chart) library.
|
|
4
|
+
|
|
5
|
+
**[Live Demo](https://bonguynvan.github.io/tradecanvas/)** | **[GitHub](https://github.com/bonguynvan/tradecanvas)** | **[Documentation](https://bonguynvan.github.io/tradecanvas/docs/getting-started/)**
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
You don't need to install this package directly. It's included as a dependency of `@tradecanvas/chart`.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @tradecanvas/chart
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What's Inside
|
|
16
|
+
|
|
17
|
+
### Types
|
|
18
|
+
|
|
19
|
+
All TypeScript interfaces and types used across the library:
|
|
20
|
+
|
|
21
|
+
- `OHLCBar`, `DataSeries`, `TimeFrame` -- data format
|
|
22
|
+
- `ChartOptions`, `ChartType`, `FeaturesConfig` -- chart configuration
|
|
23
|
+
- `Theme`, `ThemeName` -- theming
|
|
24
|
+
- `IndicatorPlugin`, `IndicatorDescriptor`, `IndicatorConfig` -- indicators
|
|
25
|
+
- `DrawingToolType`, `DrawingState`, `DrawingStyle` -- drawing tools
|
|
26
|
+
- `TradingPosition`, `TradingOrder`, `TradingConfig` -- trading overlay
|
|
27
|
+
- `DataAdapter`, `StreamConfig`, `ConnectionState` -- real-time streaming
|
|
28
|
+
- `ChartEventType`, `ChartEvent` -- events
|
|
29
|
+
- `MarketConfig`, `TradingSession` -- market configuration
|
|
30
|
+
|
|
31
|
+
### Themes
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { DARK_THEME, LIGHT_THEME } from '@tradecanvas/commons'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
19 customizable properties: `background`, `text`, `candleUp`, `candleDown`, `grid`, `crosshair`, `volumeUp`, `volumeDown`, and more.
|
|
38
|
+
|
|
39
|
+
### Constants
|
|
40
|
+
|
|
41
|
+
- Default chart options and feature flags
|
|
42
|
+
- Market presets: `MARKET_HOSE`, `MARKET_HNX`, `MARKET_UPCOM`, `MARKET_CRYPTO`, `MARKET_NYSE`
|
|
43
|
+
- Vietnamese stock market color schemes
|
|
44
|
+
|
|
45
|
+
### Utilities
|
|
46
|
+
|
|
47
|
+
- `computePriceRange()` -- compute visible price range from OHLC data
|
|
48
|
+
- `timeframeToMs()` -- convert timeframe string to milliseconds
|
|
49
|
+
- `formatNumber()`, `formatVND()`, `formatVolumeLoc()` -- number formatting
|
|
50
|
+
- Color, math, precision, and data manipulation helpers
|
|
51
|
+
|
|
52
|
+
### i18n
|
|
53
|
+
|
|
54
|
+
Built-in locales: English (`en`), Vietnamese (`vi`). Extensible via `registerLocale()`.
|
|
55
|
+
|
|
56
|
+
## Direct Usage
|
|
57
|
+
|
|
58
|
+
If you need just the types or utilities without the full chart:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import type { OHLCBar, TimeFrame, ChartType } from '@tradecanvas/commons'
|
|
62
|
+
import { DARK_THEME, computePriceRange, timeframeToMs } from '@tradecanvas/commons'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
[MIT](https://github.com/bonguynvan/tradecanvas/blob/main/LICENSE)
|
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import { Theme } from '../types/theme.js';
|
|
2
|
+
/**
|
|
3
|
+
* TradeCanvas palette. Themes pick their own candle colours for their
|
|
4
|
+
* background; everything drawn on top of either theme (indicators, orders,
|
|
5
|
+
* signals, drawings) uses the theme-neutral values below, which keep at
|
|
6
|
+
* least 3:1 contrast on both the dark ink and a white background. `amber` is
|
|
7
|
+
* the brand accent for dark surfaces (2:1 on white); light surfaces use
|
|
8
|
+
* `LIGHT_THEME.lineColor` / `ochre` instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare const TC_PALETTE: {
|
|
11
|
+
/** Brand amber — the default series line and the accent. */
|
|
12
|
+
readonly amber: "#f2a93b";
|
|
13
|
+
/** Rising / bullish, legible on dark and light. */
|
|
14
|
+
readonly up: "#1fa874";
|
|
15
|
+
/** Falling / bearish, legible on dark and light. */
|
|
16
|
+
readonly down: "#e8505b";
|
|
17
|
+
/** Amber deep enough for white backgrounds; auto-coloured series use it. */
|
|
18
|
+
readonly ochre: "#c98316";
|
|
19
|
+
/** Secondary series, signal lines. */
|
|
20
|
+
readonly blue: "#4c8dff";
|
|
21
|
+
readonly violet: "#a57cff";
|
|
22
|
+
readonly cyan: "#1398a8";
|
|
23
|
+
readonly rose: "#e25592";
|
|
24
|
+
/** Zero lines, neutral guides. */
|
|
25
|
+
readonly neutral: "#7d8696";
|
|
26
|
+
/** Ink ground of the dark theme. */
|
|
27
|
+
readonly ink: "#0c1016";
|
|
28
|
+
};
|
|
29
|
+
/** Order in which overlay indicators are coloured when no colour is given. */
|
|
30
|
+
export declare const TC_SERIES_COLORS: readonly string[];
|
|
2
31
|
export declare const DARK_THEME: Theme;
|
|
3
32
|
export declare const LIGHT_THEME: Theme;
|
|
4
33
|
export declare const DARK_TERMINAL: Theme;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"themes.d.ts","sourceRoot":"","sources":["../../src/constants/themes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAS/C,eAAO,MAAM,UAAU,EAAE,KAoBxB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAoBzB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAyB3B,CAAC"}
|
|
1
|
+
{"version":3,"file":"themes.d.ts","sourceRoot":"","sources":["../../src/constants/themes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAS/C;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU;IACrB,4DAA4D;;IAE5D,mDAAmD;;IAEnD,oDAAoD;;IAEpD,4EAA4E;;IAE5E,sCAAsC;;;;;IAKtC,kCAAkC;;IAElC,oCAAoC;;CAE5B,CAAC;AAEX,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAM7C,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAoBxB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAoBzB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAyB3B,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
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 _(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 v(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 y(e,t){if(e.startsWith(`#`))return v(e,t);let n=e.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);return n?`rgba(${n[1]}, ${n[2]}, ${n[3]}, ${t})`:e}function b(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 x={"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 te(e){return x[e]}function ne(e,t){let n=new Date(e),r=x[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 re(e,t){let n=x[t];return Math.floor(e/n)*n}function ie(e,t){if(t===null){let t=new Date(e);return{year:t.getFullYear(),month:t.getMonth()+1,day:t.getDate(),hours:t.getHours(),minutes:t.getMinutes()}}let n=new Date(e+t*6e4);return{year:n.getUTCFullYear(),month:n.getUTCMonth()+1,day:n.getUTCDate(),hours:n.getUTCHours(),minutes:n.getUTCMinutes()}}function ae(e){return e.hours===0&&e.minutes===0}function S(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=x[t];return Math.floor(e/n)*n}let a=new Date(e);if(i===`w`){let e=x[`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},oe={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},H={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 se(e){q=e,J=K.get(e)??W}function ce(){return q}function le(e){return J[e]??W[e]??e}function ue(e,t){K.set(e,t)}function de(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 fe(e){return Y(e,0,`vi`)}function pe(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`}],$={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}},_e={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 ve(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 ye(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 be(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=oe,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=H,exports.LayerType=e,exports.MARKET_CRYPTO=ge,exports.MARKET_HNX=me,exports.MARKET_HOSE=$,exports.MARKET_NYSE=_e,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=re,exports.clamp=s,exports.computePriceLimits=ye,exports.computePriceRange=g,exports.computeTickStep=f,exports.createVNTheme=ve,exports.detectPrecision=k,exports.en=W,exports.findBarIndex=h,exports.formatNumber=Y,exports.formatPrice=E,exports.formatPriceScaleLabel=D,exports.formatTimestamp=ne,exports.formatVND=fe,exports.formatVolume=O,exports.formatVolumeLoc=pe,exports.getCurrentSession=be,exports.getLocale=ce,exports.getLocaleStrings=de,exports.hexToRgba=v,exports.inverseLerp=l,exports.isDateOnly=ae,exports.lerp=c,exports.lerpColor=b,exports.mergeBar=_,exports.niceNumber=d,exports.normalizeBar=ee,exports.normalizeBarTime=p,exports.registerLocale=ue,exports.roundToStep=u,exports.setLocale=se,exports.sliceVisibleData=m,exports.t=le,exports.timeParts=ie,exports.timeframeBucketStart=T,exports.timeframeToMs=te,exports.tzLabel=S,exports.vi=G,exports.withAlpha=y;
|
|
1
|
+
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[e.Hover=5]=`Hover`,e}({}),t={color:`#4c8dff`,lineWidth:1,lineStyle:`solid`,fillColor:`rgba(76, 141, 255, 0.1)`,fillOpacity:.1,fontSize:12},n={enabled:!0,orderColors:{buy:`#1fa874`,sell:`#e8505b`},positionColors:{profit:`#1fa874`,loss:`#e8505b`,entry:`#4c8dff`},depthOverlay:{enabled:!1,bidColor:`rgba(31, 168, 116,0.15)`,askColor:`rgba(232, 80, 91,0.15)`,maxWidth:100},contextMenu:{enabled:!0},pricePrecision:2,dragThreshold:3},r={longColor:`#1fa874`,shortColor:`#e8505b`,neutralColor:`#9E9E9E`,arrowSize:12,showLabel:!0,showConfidence:!0},i={profitColor:`#1fa874`,lossColor:`#e8505b`,activeColor:`#4c8dff`,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 ee(e,t,n){let r=Math.max(0,t),i=Math.min(e.length,n+1);return e.slice(r,i)}function te(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 h(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 g(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 ne(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 y={"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 re(e){return y[e]}function ie(e,t){let n=new Date(e),r=y[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 ae(e,t){let n=y[t];return Math.floor(e/n)*n}function oe(e,t){if(t===null){let t=new Date(e);return{year:t.getFullYear(),month:t.getMonth()+1,day:t.getDate(),hours:t.getHours(),minutes:t.getMinutes()}}let n=new Date(e+t*6e4);return{year:n.getUTCFullYear(),month:n.getUTCMonth()+1,day:n.getUTCDate(),hours:n.getUTCHours(),minutes:n.getUTCMinutes()}}function se(e){return e.hours===0&&e.minutes===0}function ce(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 b=Date.UTC(1970,0,5);function le(e){let t=/^(\d+)([smhdwM])$/.exec(e);return t?{count:parseInt(t[1],10),unit:t[2]}:{count:1,unit:`m`}}function ue(e,t,n=1){let{count:r,unit:i}=le(t);if(i===`s`||i===`m`||i===`h`||i===`d`){let n=y[t];return Math.floor(e/n)*n}let a=new Date(e);if(i===`w`){let e=y[`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-b)/(7*e));return b+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)}var x=64,S=new Map;function C(e,t){let n=`${e}|${t}`,r=S.get(n);return r||(S.size>=x&&S.clear(),r=new Intl.NumberFormat(e,{minimumFractionDigits:t,maximumFractionDigits:t}),S.set(n,r)),r}function w(e,t=2,n=`en-US`){return C(n,t).format(e)}function T(e,t){if(!(t>e)||!Number.isFinite(e)||!Number.isFinite(t))return 2;let n=f(e,t,8);return n>0&&n<1?Math.ceil(-Math.log10(n))+1:2}function E(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 w(e/n*100,2,i)}return w(e,r,i)}function D(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 O(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 k={autoScale:!0,rightMargin:5,minBarSpacing:2,maxBarSpacing:30,grid:{visible:!0,hLineStyle:`solid`,vLineStyle:`solid`},crosshair:{mode:`magnet`}},A=[`1s`,`1m`,`3m`,`5m`,`15m`,`30m`,`1h`,`2h`,`4h`,`6h`,`8h`,`12h`,`1d`,`3d`,`1w`,`1M`],j=[`1m`,`5m`,`15m`,`30m`,`1h`,`2h`,`4h`,`1d`,`1w`,`1M`,`3M`,`6M`,`12M`],M=[`1m`,`5m`,`15m`,`30m`,`1h`,`4h`,`1d`,`1w`,`1M`],N=[`1m`,`5m`,`15m`,`1h`,`4h`,`1d`,`1w`],P=8,F=2,I=70,L=30,R=60,z=120,B={family:`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`,sizeSmall:10,sizeMedium:12,sizeLarge:14},V={amber:`#f2a93b`,up:`#1fa874`,down:`#e8505b`,ochre:`#c98316`,blue:`#4c8dff`,violet:`#a57cff`,cyan:`#1398a8`,rose:`#e25592`,neutral:`#7d8696`,ink:`#0c1016`},H=[V.blue,V.ochre,V.violet,V.cyan,V.rose],U={name:`dark`,background:V.ink,text:`#d9dde4`,textSecondary:`#7d8696`,grid:`#161b23`,crosshair:`#8a93a3`,candleUp:`#3ccf91`,candleDown:`#f0616d`,candleUpWick:`#3ccf91`,candleDownWick:`#f0616d`,lineColor:V.amber,areaTopColor:`rgba(242, 169, 59, 0.32)`,areaBottomColor:`rgba(242, 169, 59, 0.0)`,volumeUp:`rgba(60, 207, 145, 0.28)`,volumeDown:`rgba(240, 97, 109, 0.28)`,axisLine:`#1f2630`,axisLabel:`#c3c9d3`,axisLabelBackground:`#1f2630`,font:B},de={name:`light`,background:`#ffffff`,text:`#0f131a`,textSecondary:`#6b7380`,grid:`#eef0f3`,crosshair:`#8a93a3`,candleUp:`#16a36a`,candleDown:`#d9414f`,candleUpWick:`#16a36a`,candleDownWick:`#d9414f`,lineColor:`#c77a0a`,areaTopColor:`rgba(199, 122, 10, 0.24)`,areaBottomColor:`rgba(199, 122, 10, 0.0)`,volumeUp:`rgba(22, 163, 106, 0.25)`,volumeDown:`rgba(217, 65, 79, 0.25)`,axisLine:`#dde1e7`,axisLabel:`#3f4754`,axisLabelBackground:`#eef0f3`,font:B},fe={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 pe(e){q=e,J=K.get(e)??W}function me(){return q}function he(e){return J[e]??W[e]??e}function ge(e,t){K.set(e,t)}function _e(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 ve(e){return Y(e,0,`vi`)}function ye(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`}],be={type:`stock`,exchange:`HOSE`,currency:`VND`,pricePrecision:2,volumeUnit:10,priceStep:.05,priceLimits:{enabled:!0,ceilingPercent:7,floorPercent:7},sessions:Z,colorScheme:X},xe={type:`stock`,exchange:`HNX`,currency:`VND`,pricePrecision:1,volumeUnit:100,priceStep:.1,priceLimits:{enabled:!0,ceilingPercent:10,floorPercent:10},sessions:Q,colorScheme:X},Se={type:`stock`,exchange:`UPCOM`,currency:`VND`,pricePrecision:1,volumeUnit:100,priceStep:.1,priceLimits:{enabled:!0,ceilingPercent:15,floorPercent:15},sessions:Q,colorScheme:X},Ce={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 we(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 Te(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 Ee(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=fe,exports.DARK_THEME=U,exports.DEFAULT_BAR_SPACING=F,exports.DEFAULT_BAR_WIDTH=P,exports.DEFAULT_CHART_OPTIONS=k,exports.DEFAULT_DRAWING_STYLE=t,exports.DEFAULT_PANEL_HEIGHT=z,exports.DEFAULT_RECONNECT=a,exports.DEFAULT_SIGNAL_STYLE=r,exports.DEFAULT_STREAM_CONFIG=o,exports.DEFAULT_TIMEFRAME_FAVORITES=N,exports.DEFAULT_TRADE_ZONE_STYLE=i,exports.DEFAULT_TRADING_CONFIG=n,exports.HNX_SESSIONS=Q,exports.HOSE_SESSIONS=Z,exports.LIGHT_THEME=de,exports.LayerType=e,exports.MARKET_CRYPTO=Ce,exports.MARKET_HNX=xe,exports.MARKET_HOSE=be,exports.MARKET_NYSE=$,exports.MARKET_UPCOM=Se,exports.MIN_PANEL_HEIGHT=R,exports.PRICE_AXIS_WIDTH=I,exports.TC_PALETTE=V,exports.TC_SERIES_COLORS=H,exports.TIMEFRAMES_CRYPTO=A,exports.TIMEFRAMES_FOREX=M,exports.TIMEFRAMES_STOCK=j,exports.TIME_AXIS_HEIGHT=L,exports.VN_COLORS=X,exports.alignToTimeframe=ae,exports.autoPricePrecision=T,exports.clamp=s,exports.computePriceLimits=Te,exports.computePriceRange=h,exports.computeTickStep=f,exports.createVNTheme=we,exports.detectPrecision=O,exports.en=W,exports.findBarIndex=te,exports.formatNumber=Y,exports.formatPrice=w,exports.formatPriceScaleLabel=E,exports.formatTimestamp=ie,exports.formatVND=ve,exports.formatVolume=D,exports.formatVolumeLoc=ye,exports.getCurrentSession=Ee,exports.getLocale=me,exports.getLocaleStrings=_e,exports.hexToRgba=_,exports.inverseLerp=l,exports.isDateOnly=se,exports.lerp=c,exports.lerpColor=ne,exports.mergeBar=g,exports.niceNumber=d,exports.normalizeBar=m,exports.normalizeBarTime=p,exports.registerLocale=ge,exports.roundToStep=u,exports.setLocale=pe,exports.sliceVisibleData=ee,exports.t=he,exports.timeParts=oe,exports.timeframeBucketStart=ue,exports.timeframeToMs=re,exports.tzLabel=ce,exports.vi=G,exports.withAlpha=v;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/types/rendering.ts","../src/types/drawing.ts","../src/types/trading.ts","../src/types/signal.ts","../src/types/realtime.ts","../src/utils/math.ts","../src/utils/data.ts","../src/utils/color.ts","../src/utils/time.ts","../src/utils/precision.ts","../src/constants/defaults.ts","../src/constants/themes.ts","../src/i18n/en.ts","../src/i18n/vi.ts","../src/i18n/index.ts","../src/market/presets.ts"],"sourcesContent":["export interface Point {\n x: number;\n y: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport type PriceScaleMode = 'regular' | 'logarithmic' | 'percentage' | 'indexedTo100';\n\nexport interface ViewportState {\n visibleRange: { from: number; to: number };\n priceRange: { min: number; max: number };\n barWidth: number;\n barSpacing: number;\n offset: number;\n chartRect: Rect;\n /**\n * Logarithmic price geometry. Kept for back-compat; mirrors\n * `scaleMode === 'logarithmic'`. Prefer reading `scaleMode`.\n */\n logScale?: boolean;\n /**\n * Price-scale presentation. `regular`, `percentage`, and `indexedTo100` share\n * the same linear geometry — only the axis labels differ (rebased to\n * `scaleBaseline`). `logarithmic` changes the geometry. Defaults to\n * `regular` when unset.\n */\n scaleMode?: PriceScaleMode;\n /**\n * Reference price for `percentage` / `indexedTo100` axis labels — usually the\n * close of the first visible bar. Set by the chart each frame.\n */\n scaleBaseline?: number;\n /**\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 /** The price before the change, when known (e.g. from a drag). Optional so\r\n * the chart's `orderModify` event payload routes straight to an adapter. */\r\n previousPrice?: number;\r\n}\r\n\r\nexport interface OrderCancelIntent {\r\n orderId: string;\r\n}\r\n\r\nexport interface PositionModifyIntent {\r\n positionId: string;\r\n stopLoss?: number;\r\n takeProfit?: number;\r\n}\r\n\r\nexport interface PositionCloseIntent {\r\n positionId: string;\r\n}\r\n\r\nexport const DEFAULT_TRADING_CONFIG: TradingConfig = {\r\n enabled: true,\r\n orderColors: { buy: '#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';\r\n\r\nconst TIMEFRAME_MS: Record<TimeFrame, number> = {\r\n '1s': 1_000,\r\n '5s': 5_000,\r\n '15s': 15_000,\r\n '30s': 30_000,\r\n '1m': 60_000,\r\n '3m': 180_000,\r\n '5m': 300_000,\r\n '15m': 900_000,\r\n '30m': 1_800_000,\r\n '45m': 2_700_000,\r\n '1h': 3_600_000,\r\n '2h': 7_200_000,\r\n '3h': 10_800_000,\r\n '4h': 14_400_000,\r\n '6h': 21_600_000,\r\n '8h': 28_800_000,\r\n '12h': 43_200_000,\r\n '1d': 86_400_000,\r\n '2d': 172_800_000,\r\n '3d': 259_200_000,\r\n '1w': 604_800_000,\r\n '2w': 1_209_600_000,\r\n '1M': 2_592_000_000,\r\n '3M': 7_776_000_000,\r\n '6M': 15_552_000_000,\r\n '12M': 31_536_000_000,\r\n};\r\n\r\nexport function timeframeToMs(tf: TimeFrame): number {\r\n return TIMEFRAME_MS[tf];\r\n}\r\n\r\nexport function formatTimestamp(timestamp: number, tf: TimeFrame): string {\r\n const d = new Date(timestamp);\r\n const ms = TIMEFRAME_MS[tf];\r\n if (ms >= 86_400_000) {\r\n return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\r\n }\r\n if (ms >= 3_600_000) {\r\n return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });\r\n }\r\n return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\r\n}\r\n\r\nexport function alignToTimeframe(timestamp: number, tf: TimeFrame): number {\r\n const ms = TIMEFRAME_MS[tf];\r\n return Math.floor(timestamp / ms) * ms;\r\n}\r\n\r\nexport interface TimeParts {\r\n year: number;\r\n month: number; // 1-12\r\n day: number;\r\n hours: number;\r\n minutes: number;\r\n}\r\n\r\n/**\r\n * Calendar parts of a timestamp in either the browser-local timezone\r\n * (`tzOffsetMinutes === null`) or a fixed UTC offset (e.g. -300 for EST).\r\n */\r\nexport function timeParts(timeMs: number, tzOffsetMinutes: number | null): TimeParts {\r\n if (tzOffsetMinutes === null) {\r\n const d = new Date(timeMs);\r\n return {\r\n year: d.getFullYear(),\r\n month: d.getMonth() + 1,\r\n day: d.getDate(),\r\n hours: d.getHours(),\r\n minutes: d.getMinutes(),\r\n };\r\n }\r\n const d = new Date(timeMs + tzOffsetMinutes * 60_000);\r\n return {\r\n year: d.getUTCFullYear(),\r\n month: d.getUTCMonth() + 1,\r\n day: d.getUTCDate(),\r\n hours: d.getUTCHours(),\r\n minutes: d.getUTCMinutes(),\r\n };\r\n}\r\n\r\n/**\r\n * A bar with no time-of-day component (a daily, weekly, monthly or yearly candle — always anchored\r\n * to a calendar-day boundary, so hours/minutes read 00:00) needs the YEAR in its label instead: a\r\n * bare `month/day` is ambiguous across years, and `00:00` tells a trader nothing. Detected from the\r\n * parts themselves rather than a separate timeframe parameter, so every caller that formats a bar's\r\n * time gets the same rule for free. The one false positive this accepts — a genuine intraday bar\r\n * landing exactly on midnight — is acceptable: real trade-driven timestamps essentially never do.\r\n */\r\nexport function isDateOnly(parts: Pick<TimeParts, 'hours' | 'minutes'>): boolean {\r\n return parts.hours === 0 && parts.minutes === 0;\r\n}\r\n\r\n/** A short timezone label like `UTC-5` or `UTC+5:30` (browser-local when null). */\r\nexport function tzLabel(tzOffsetMinutes: number | null): string {\r\n const min = tzOffsetMinutes === null ? -new Date().getTimezoneOffset() : tzOffsetMinutes;\r\n const sign = min >= 0 ? '+' : '-';\r\n const abs = Math.abs(min);\r\n const h = Math.floor(abs / 60);\r\n const m = abs % 60;\r\n return m === 0 ? `UTC${sign}${h}` : `UTC${sign}${h}:${String(m).padStart(2, '0')}`;\r\n}\r\n\r\n/** Day-of-week for a Monday-anchored reference week (1970-01-05 was a Monday, UTC). */\r\nconst WEEK_ANCHOR_MS = Date.UTC(1970, 0, 5);\r\n\r\nfunction parseTimeframe(tf: TimeFrame): { count: number; unit: 's' | 'm' | 'h' | 'd' | 'w' | 'M' } {\r\n const match = /^(\\d+)([smhdwM])$/.exec(tf);\r\n if (!match) return { count: 1, unit: 'm' };\r\n return { count: parseInt(match[1], 10), unit: match[2] as 's' | 'm' | 'h' | 'd' | 'w' | 'M' };\r\n}\r\n\r\n/**\r\n * Start of the bucket a timestamp falls into for a given timeframe.\r\n *\r\n * Intraday and daily frames are anchored to the Unix epoch (UTC) via fixed-ms\r\n * flooring. Weekly frames are anchored to Monday (or `weekStartsOn`), monthly\r\n * and yearly frames use calendar boundaries (1st of month, Jan for years) so\r\n * 3M aligns to quarters and 12M to calendar years.\r\n */\r\nexport function timeframeBucketStart(\r\n timestamp: number,\r\n tf: TimeFrame,\r\n weekStartsOn: 0 | 1 = 1,\r\n): number {\r\n const { count, unit } = parseTimeframe(tf);\r\n\r\n if (unit === 's' || unit === 'm' || unit === 'h' || unit === 'd') {\r\n const ms = TIMEFRAME_MS[tf];\r\n return Math.floor(timestamp / ms) * ms;\r\n }\r\n\r\n const d = new Date(timestamp);\r\n\r\n if (unit === 'w') {\r\n const dayMs = TIMEFRAME_MS['1d'];\r\n const dow = d.getUTCDay();\r\n const shift = (dow - weekStartsOn + 7) % 7;\r\n const weekStart = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - shift);\r\n if (count <= 1) return weekStart;\r\n const weekIndex = Math.floor((weekStart - WEEK_ANCHOR_MS) / (7 * dayMs));\r\n const grouped = Math.floor(weekIndex / count) * count;\r\n return WEEK_ANCHOR_MS + grouped * 7 * dayMs;\r\n }\r\n\r\n // months / quarters / years\r\n const totalMonths = d.getUTCFullYear() * 12 + d.getUTCMonth();\r\n const grouped = Math.floor(totalMonths / count) * count;\r\n return Date.UTC(Math.floor(grouped / 12), grouped % 12, 1);\r\n}\r\n","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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"}
|
|
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 {\r\n x: number;\r\n y: number;\r\n}\r\n\r\nexport interface Size {\r\n width: number;\r\n height: number;\r\n}\r\n\r\nexport interface Rect {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n}\r\n\r\nexport type PriceScaleMode = 'regular' | 'logarithmic' | 'percentage' | 'indexedTo100';\r\n\r\nexport interface ViewportState {\r\n visibleRange: { from: number; to: number };\r\n priceRange: { min: number; max: number };\r\n barWidth: number;\r\n barSpacing: number;\r\n offset: number;\r\n chartRect: Rect;\r\n /**\r\n * Logarithmic price geometry. Kept for back-compat; mirrors\r\n * `scaleMode === 'logarithmic'`. Prefer reading `scaleMode`.\r\n */\r\n logScale?: boolean;\r\n /**\r\n * Price-scale presentation. `regular`, `percentage`, and `indexedTo100` share\r\n * the same linear geometry — only the axis labels differ (rebased to\r\n * `scaleBaseline`). `logarithmic` changes the geometry. Defaults to\r\n * `regular` when unset.\r\n */\r\n scaleMode?: PriceScaleMode;\r\n /**\r\n * Reference price for `percentage` / `indexedTo100` axis labels — usually the\r\n * close of the first visible bar. Set by the chart each frame.\r\n */\r\n scaleBaseline?: number;\r\n /**\r\n * Optional reference to the current bar series. When set, drawings/indicators\r\n * treat `anchor.time` as a real timestamp and convert to bar index via\r\n * `timestampToBarIndex(time, data)` at render/hit-test time. This lets\r\n * anchors survive timeframe / symbol switches. When unset,\r\n * `anchor.time` is treated as a raw bar index (legacy behavior).\r\n */\r\n data?: ReadonlyArray<{ time: number }>;\r\n /**\r\n * Width of the price axis strip right of `chartRect`, in CSS pixels. The\r\n * chart widens it to fit long labels (sub-cent prices); renderers drawing\r\n * badges on the axis size them to it. Defaults to `PRICE_AXIS_WIDTH`.\r\n */\r\n priceAxisWidth?: number;\r\n}\r\n\r\n/**\r\n * What changed, for `requestRender()`. The chart paints two canvases: a scene\r\n * (everything that follows data, viewport and chart objects) and a thin top\r\n * canvas for pointer-tied visuals. Any type except `Hover` redraws both;\r\n * `Hover` redraws only the top canvas (crosshair, its axis pills, legend,\r\n * bar countdown, measure ruler, selection box).\r\n */\r\nexport enum LayerType {\r\n Background = 0,\r\n Main = 1,\r\n Panel = 2,\r\n Overlay = 3,\r\n UI = 4,\r\n Hover = 5,\r\n}\r\n","import type { Point, ViewportState } from './rendering.js';\r\n\r\nexport type DrawingToolType =\r\n | 'trendLine' | 'horizontalLine' | 'horizontalRay' | 'verticalLine' | 'ray' | 'extendedLine'\r\n | 'crossLine' | 'trendAngle' | 'infoLine'\r\n | 'parallelChannel' | 'regressionChannel'\r\n | 'fibRetracement' | 'fibExtension' | 'fibTimeZones' | 'fibChannel' | 'fibSpeedResistanceFan'\r\n | 'rectangle' | 'ellipse' | 'triangle' | 'circle'\r\n | 'pitchfork' | 'schiffPitchfork' | 'modifiedSchiffPitchfork' | 'elliottWave'\r\n | 'xabcdPattern' | 'abcdPattern' | 'headAndShoulders'\r\n | 'priceRange' | 'dateRange' | 'dateAndPriceRange' | 'measure'\r\n | 'text' | 'arrow' | 'priceLabel'\r\n | 'gannFan' | 'gannBox' | 'cyclicLines'\r\n | 'anchoredVWAP'\r\n | 'volumeProfileRange'\r\n | 'riskReward';\r\n\r\nexport interface AnchorPoint {\r\n time: number;\r\n price: number;\r\n}\r\n\r\nexport interface DrawingStyle {\r\n color: string;\r\n lineWidth: number;\r\n lineStyle: 'solid' | 'dashed' | 'dotted';\r\n fillColor?: string;\r\n fillOpacity?: number;\r\n fontSize?: number;\r\n text?: string;\r\n}\r\n\r\nexport interface DrawingState {\r\n id: string;\r\n type: DrawingToolType;\r\n anchors: AnchorPoint[];\r\n style: DrawingStyle;\r\n visible: boolean;\r\n locked: boolean;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\nexport interface DrawingDescriptor {\r\n type: DrawingToolType;\r\n name: string;\r\n requiredAnchors: number;\r\n singleClick?: boolean;\r\n}\r\n\r\nexport interface DrawingPlugin {\r\n descriptor: DrawingDescriptor;\r\n render(\r\n ctx: CanvasRenderingContext2D,\r\n state: DrawingState,\r\n viewport: ViewportState,\r\n selected: boolean,\r\n ): void;\r\n hitTest(\r\n point: Point,\r\n state: DrawingState,\r\n viewport: ViewportState,\r\n tolerance: number,\r\n ): boolean;\r\n hitTestAnchor(\r\n point: Point,\r\n state: DrawingState,\r\n viewport: ViewportState,\r\n tolerance: number,\r\n ): number;\r\n}\r\n\r\nexport const DEFAULT_DRAWING_STYLE: DrawingStyle = {\r\n color: '#4c8dff',\r\n lineWidth: 1,\r\n lineStyle: 'solid',\r\n fillColor: 'rgba(76, 141, 255, 0.1)',\r\n fillOpacity: 0.1,\r\n fontSize: 12,\r\n};\r\n","export type OrderSide = 'buy' | 'sell';\r\nexport type OrderType = 'market' | 'limit' | 'stop' | 'stopLimit';\r\nexport type OrderStatus = 'pending' | 'filled' | 'cancelled' | 'rejected';\r\nexport type OrderLabel = 'LIMIT' | 'STOP' | 'SL' | 'TP' | 'STOP LIMIT';\r\n\r\nexport interface TradingOrder {\r\n id: string;\r\n side: OrderSide;\r\n type: OrderType;\r\n price: number;\r\n stopPrice?: number;\r\n quantity: number;\r\n label?: OrderLabel;\r\n draggable?: boolean;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\nexport interface TradingPosition {\r\n id: string;\r\n side: OrderSide;\r\n entryPrice: number;\r\n quantity: number;\r\n /** Quantity already closed (for partial-close visualization). 0 ≤ closedQuantity ≤ quantity. */\r\n closedQuantity?: number;\r\n stopLoss?: number;\r\n takeProfit?: number;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\n/** Threshold-based P&L color stop. Sorted ascending by `pnl` is recommended. */\r\nexport interface PnLThreshold {\r\n /** Inclusive lower bound. Use -Infinity for the bottom-most stop. */\r\n pnl: number;\r\n color: string;\r\n}\r\n\r\n/** Tokens passed to position label templates. */\r\nexport interface PositionLabelContext {\r\n side: OrderSide;\r\n quantity: number;\r\n closedQuantity: number;\r\n openQuantity: number;\r\n entryPrice: number;\r\n currentPrice: number;\r\n pnl: number;\r\n pnlPct: number;\r\n precision: number;\r\n}\r\n\r\nexport interface DepthLevel {\r\n price: number;\r\n volume: number;\r\n}\r\n\r\nexport interface DepthData {\r\n bids: DepthLevel[];\r\n asks: DepthLevel[];\r\n}\r\n\r\nexport interface TradingConfig {\r\n enabled: boolean;\r\n orderColors?: { buy?: string; sell?: string };\r\n positionColors?: { profit?: string; loss?: string; entry?: string };\r\n /**\r\n * Optional gradient of colors keyed to P&L value. When provided, the rendered\r\n * position zone uses the color of the highest threshold whose `pnl` ≤ live P&L.\r\n * Falls back to `positionColors.profit`/`.loss` when unset.\r\n */\r\n pnlThresholds?: PnLThreshold[];\r\n /**\r\n * Position P&L label template. Supports tokens: {side} {qty} {closedQty}\r\n * {openQty} {entry} {price} {pnl} {pnlPct} {pnlSign}. Pass a function for\r\n * full control. Default: `{side} {qty} | P&L: {pnlSign}{pnl}`.\r\n */\r\n positionLabel?: string | ((ctx: PositionLabelContext) => string);\r\n depthOverlay?: {\r\n enabled?: boolean;\r\n bidColor?: string;\r\n askColor?: string;\r\n maxWidth?: number;\r\n };\r\n contextMenu?: { enabled?: boolean };\r\n pricePrecision?: number;\r\n dragThreshold?: number;\r\n}\r\n\r\nexport interface OrderPlaceIntent {\r\n side: OrderSide;\r\n type: OrderType;\r\n price: number;\r\n stopPrice?: number;\r\n quantity?: number;\r\n}\r\n\r\nexport interface OrderModifyIntent {\r\n orderId: string;\r\n newPrice: number;\r\n /** The price before the change, when known (e.g. from a drag). Optional so\r\n * the chart's `orderModify` event payload routes straight to an adapter. */\r\n previousPrice?: number;\r\n}\r\n\r\nexport interface OrderCancelIntent {\r\n orderId: string;\r\n}\r\n\r\nexport interface PositionModifyIntent {\r\n positionId: string;\r\n stopLoss?: number;\r\n takeProfit?: number;\r\n}\r\n\r\nexport interface PositionCloseIntent {\r\n positionId: string;\r\n}\r\n\r\nexport const DEFAULT_TRADING_CONFIG: TradingConfig = {\r\n enabled: true,\r\n orderColors: { buy: '#1fa874', sell: '#e8505b' },\r\n positionColors: { profit: '#1fa874', loss: '#e8505b', entry: '#4c8dff' },\r\n depthOverlay: { enabled: false, bidColor: 'rgba(31, 168, 116,0.15)', askColor: 'rgba(232, 80, 91,0.15)', maxWidth: 100 },\r\n contextMenu: { enabled: true },\r\n pricePrecision: 2,\r\n dragThreshold: 3,\r\n};\r\n","export type SignalDirection = 'long' | 'short' | 'neutral';\r\n\r\nexport interface SignalMarker {\r\n id: string;\r\n time: number;\r\n price: number;\r\n direction: SignalDirection;\r\n confidence: number;\r\n source: string;\r\n label?: string;\r\n color?: string;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\nexport interface SignalMarkerStyle {\r\n longColor?: string;\r\n shortColor?: string;\r\n neutralColor?: string;\r\n arrowSize?: number;\r\n showLabel?: boolean;\r\n showConfidence?: boolean;\r\n sourceColors?: Record<string, string>;\r\n}\r\n\r\nexport const DEFAULT_SIGNAL_STYLE: SignalMarkerStyle = {\r\n longColor: '#1fa874',\r\n shortColor: '#e8505b',\r\n neutralColor: '#9E9E9E',\r\n arrowSize: 12,\r\n showLabel: true,\r\n showConfidence: true,\r\n};\r\n\r\nexport type TradeZoneDirection = 'long' | 'short';\r\n\r\nexport interface TradeZone {\r\n id: string;\r\n entryTime: number;\r\n entryPrice: number;\r\n exitTime?: number;\r\n exitPrice?: number;\r\n direction: TradeZoneDirection;\r\n pnl?: number;\r\n pnlPercent?: number;\r\n label?: string;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\nexport interface TradeZoneStyle {\r\n profitColor?: string;\r\n lossColor?: string;\r\n activeColor?: string;\r\n fillOpacity?: number;\r\n borderWidth?: number;\r\n showLabel?: boolean;\r\n showPnl?: boolean;\r\n}\r\n\r\nexport const DEFAULT_TRADE_ZONE_STYLE: TradeZoneStyle = {\r\n profitColor: '#1fa874',\r\n lossColor: '#e8505b',\r\n activeColor: '#4c8dff',\r\n fillOpacity: 0.12,\r\n borderWidth: 1,\r\n showLabel: true,\r\n showPnl: true,\r\n};\r\n","import type { OHLCBar, TimeFrame } from './ohlc.js';\r\n\r\n// --- Connection ---\r\n\r\nexport type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';\r\n\r\nexport interface ConnectionInfo {\r\n state: ConnectionState;\r\n latency?: number;\r\n reconnectAttempt?: number;\r\n lastMessageTime?: number;\r\n error?: string;\r\n}\r\n\r\n// --- Ticks & Trades ---\r\n\r\nexport interface RawTick {\r\n time: number;\r\n price: number;\r\n volume: number;\r\n side?: 'buy' | 'sell';\r\n}\r\n\r\nexport interface AggregatedBar extends OHLCBar {\r\n closed: boolean; // true when bar is finalized\r\n tickCount: number; // number of ticks in this bar\r\n}\r\n\r\n// --- Data Adapter (Strategy Pattern) ---\r\n\r\nexport interface DataAdapterConfig {\r\n symbol: string;\r\n timeframe: TimeFrame;\r\n reconnect?: boolean; // default: true\r\n reconnectMaxRetries?: number; // default: Infinity\r\n reconnectBaseDelay?: number; // ms, default: 1000\r\n reconnectMaxDelay?: number; // ms, default: 30000\r\n heartbeatInterval?: number; // ms, default: 30000\r\n bufferSize?: number; // max ticks to buffer, default: 1000\r\n}\r\n\r\nexport type DataAdapterEventType =\r\n | 'tick'\r\n | 'bar'\r\n | 'barClose'\r\n | 'snapshot' // initial historical data loaded\r\n | 'connectionChange'\r\n | 'error';\r\n\r\nexport interface DataAdapterEvent<T = unknown> {\r\n type: DataAdapterEventType;\r\n data: T;\r\n timestamp: number;\r\n}\r\n\r\nexport type DataAdapterListener<T = unknown> = (event: DataAdapterEvent<T>) => void;\r\n\r\n/**\r\n * Data adapter interface. Implements the observer pattern:\r\n * - connect() to start receiving data\r\n * - on('bar'|'tick'|'connectionChange', handler) to receive events\r\n * - disconnect() to stop, then connect() again to switch symbols/timeframes\r\n * - No separate subscribe/unsubscribe — reconnect is the intended pattern\r\n *\r\n * Strategy pattern for pluggable data sources.\r\n * Implementations handle the specifics of each data source (WebSocket, REST,\r\n * SSE, etc.) while the StreamManager orchestrates lifecycle and aggregation.\r\n *\r\n * Built-in: BinanceAdapter\r\n * Implement this for: custom exchange APIs, broker feeds, mock data\r\n */\r\nexport interface DataAdapter {\r\n readonly name: string;\r\n\r\n connect(config: DataAdapterConfig): void;\r\n disconnect(): void;\r\n getConnectionState(): ConnectionState;\r\n\r\n /**\r\n * Load historical bars. Called once on connect, before streaming starts.\r\n * Returns bars sorted by time ascending.\r\n */\r\n fetchHistory(symbol: string, timeframe: TimeFrame, limit?: number): Promise<OHLCBar[]>;\r\n\r\n on<T = unknown>(event: DataAdapterEventType, listener: DataAdapterListener<T>): void;\r\n off<T = unknown>(event: DataAdapterEventType, listener: DataAdapterListener<T>): void;\r\n\r\n dispose(): void;\r\n}\r\n\r\n// --- Stream Manager Config ---\r\n\r\nexport interface StreamConfig {\r\n adapter: DataAdapter;\r\n symbol: string;\r\n timeframe: TimeFrame;\r\n historyLimit?: number; // bars to load initially, default: 500\r\n autoScroll?: boolean; // scroll to end on new bar, default: true\r\n showCurrentPriceLine?: boolean; // default: true\r\n aggregateTicks?: boolean; // build bars from ticks, default: false\r\n reconnect?: ReconnectConfig;\r\n}\r\n\r\nexport interface ReconnectConfig {\r\n enabled: boolean; // default: true\r\n maxRetries: number; // default: Infinity\r\n baseDelay: number; // ms, default: 1000\r\n maxDelay: number; // ms, default: 30000\r\n backoffMultiplier: number; // default: 2\r\n}\r\n\r\nexport const DEFAULT_RECONNECT: ReconnectConfig = {\r\n enabled: true,\r\n maxRetries: Infinity,\r\n baseDelay: 1000,\r\n maxDelay: 30000,\r\n backoffMultiplier: 2,\r\n};\r\n\r\nexport const DEFAULT_STREAM_CONFIG: Partial<StreamConfig> = {\r\n historyLimit: 500,\r\n autoScroll: true,\r\n showCurrentPriceLine: true,\r\n aggregateTicks: false,\r\n};\r\n","export function clamp(value: number, min: number, max: number): number {\r\n return Math.max(min, Math.min(max, value));\r\n}\r\n\r\nexport function lerp(a: number, b: number, t: number): number {\r\n return a + (b - a) * t;\r\n}\r\n\r\nexport function inverseLerp(a: number, b: number, value: number): number {\r\n if (a === b) return 0;\r\n return (value - a) / (b - a);\r\n}\r\n\r\nexport function roundToStep(value: number, step: number): number {\r\n return Math.round(value / step) * step;\r\n}\r\n\r\nexport function niceNumber(value: number, round: boolean): number {\r\n const exp = Math.floor(Math.log10(value));\r\n const frac = value / Math.pow(10, exp);\r\n let nice: number;\r\n if (round) {\r\n if (frac < 1.5) nice = 1;\r\n else if (frac < 3) nice = 2;\r\n else if (frac < 7) nice = 5;\r\n else nice = 10;\r\n } else {\r\n if (frac <= 1) nice = 1;\r\n else if (frac <= 2) nice = 2;\r\n else if (frac <= 5) nice = 5;\r\n else nice = 10;\r\n }\r\n return nice * Math.pow(10, exp);\r\n}\r\n\r\nexport function computeTickStep(min: number, max: number, maxTicks: number): number {\r\n const range = niceNumber(max - min, false);\r\n return niceNumber(range / (maxTicks - 1), true);\r\n}\r\n","import type { OHLCBar, DataSeries } from '../types/ohlc.js';\r\n\r\n/**\r\n * Normalize bar timestamp to milliseconds.\r\n * Auto-detects: time > 1e12 is already ms, otherwise treats as seconds.\r\n */\r\nexport function normalizeBarTime(time: number): number {\r\n return time > 1e12 ? time : time * 1000;\r\n}\r\n\r\n/**\r\n * Normalize a bar's timestamp field to milliseconds.\r\n * Accepts either { time } (ms or s) or { t, o, h, l, c, v } wire format.\r\n */\r\nexport function normalizeBar(raw: Record<string, number>): OHLCBar {\r\n const time = normalizeBarTime(raw.time ?? raw.t ?? 0);\r\n return {\r\n time,\r\n open: raw.open ?? raw.o ?? 0,\r\n high: raw.high ?? raw.h ?? 0,\r\n low: raw.low ?? raw.l ?? 0,\r\n close: raw.close ?? raw.c ?? 0,\r\n volume: raw.volume ?? raw.v ?? 0,\r\n };\r\n}\r\n\r\nexport function sliceVisibleData(\r\n data: DataSeries,\r\n from: number,\r\n to: number,\r\n): DataSeries {\r\n const startIdx = Math.max(0, from);\r\n const endIdx = Math.min(data.length, to + 1);\r\n return data.slice(startIdx, endIdx);\r\n}\r\n\r\nexport function findBarIndex(data: DataSeries, timestamp: number): number {\r\n let lo = 0;\r\n let hi = data.length - 1;\r\n while (lo <= hi) {\r\n const mid = (lo + hi) >>> 1;\r\n if (data[mid].time < timestamp) lo = mid + 1;\r\n else if (data[mid].time > timestamp) hi = mid - 1;\r\n else return mid;\r\n }\r\n return lo;\r\n}\r\n\r\nexport function computePriceRange(\r\n data: DataSeries,\r\n from: number,\r\n to: number,\r\n padding = 0.05,\r\n): { min: number; max: number } {\r\n if (data.length === 0) return { min: 0, max: 1 };\r\n const startIdx = Math.max(0, from);\r\n const endIdx = Math.min(data.length - 1, to);\r\n let min = Infinity;\r\n let max = -Infinity;\r\n for (let i = startIdx; i <= endIdx; i++) {\r\n if (data[i].low < min) min = data[i].low;\r\n if (data[i].high > max) max = data[i].high;\r\n }\r\n if (min === Infinity) return { min: 0, max: 1 };\r\n const range = max - min || 1;\r\n return {\r\n min: min - range * padding,\r\n max: max + range * padding,\r\n };\r\n}\r\n\r\nexport function mergeBar(existing: OHLCBar, tick: { price: number; volume?: number; time: number }): OHLCBar {\r\n return {\r\n ...existing,\r\n high: Math.max(existing.high, tick.price),\r\n low: Math.min(existing.low, tick.price),\r\n close: tick.price,\r\n volume: existing.volume + (tick.volume ?? 0),\r\n time: tick.time,\r\n };\r\n}\r\n","export function hexToRgba(hex: string, alpha = 1): string {\r\n const r = parseInt(hex.slice(1, 3), 16);\r\n const g = parseInt(hex.slice(3, 5), 16);\r\n const b = parseInt(hex.slice(5, 7), 16);\r\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\r\n}\r\n\r\nexport function withAlpha(color: string, alpha: number): string {\r\n if (color.startsWith('#')) {\r\n return hexToRgba(color, alpha);\r\n }\r\n const rgbaMatch = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);\r\n if (rgbaMatch) {\r\n return `rgba(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]}, ${alpha})`;\r\n }\r\n return color;\r\n}\r\n\r\nexport function lerpColor(colorA: string, colorB: string, t: number): string {\r\n const parseHex = (hex: string) => {\r\n hex = hex.replace('#', '');\r\n if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];\r\n return {\r\n r: parseInt(hex.slice(0, 2), 16),\r\n g: parseInt(hex.slice(2, 4), 16),\r\n b: parseInt(hex.slice(4, 6), 16),\r\n };\r\n };\r\n const a = parseHex(colorA);\r\n const b = parseHex(colorB);\r\n const r = Math.round(a.r + (b.r - a.r) * t);\r\n const g = Math.round(a.g + (b.g - a.g) * t);\r\n const bl = Math.round(a.b + (b.b - a.b) * t);\r\n return `rgb(${r},${g},${bl})`;\r\n}\r\n","import type { TimeFrame } from '../types/ohlc.js';\r\n\r\nconst TIMEFRAME_MS: Record<TimeFrame, number> = {\r\n '1s': 1_000,\r\n '5s': 5_000,\r\n '15s': 15_000,\r\n '30s': 30_000,\r\n '1m': 60_000,\r\n '3m': 180_000,\r\n '5m': 300_000,\r\n '15m': 900_000,\r\n '30m': 1_800_000,\r\n '45m': 2_700_000,\r\n '1h': 3_600_000,\r\n '2h': 7_200_000,\r\n '3h': 10_800_000,\r\n '4h': 14_400_000,\r\n '6h': 21_600_000,\r\n '8h': 28_800_000,\r\n '12h': 43_200_000,\r\n '1d': 86_400_000,\r\n '2d': 172_800_000,\r\n '3d': 259_200_000,\r\n '1w': 604_800_000,\r\n '2w': 1_209_600_000,\r\n '1M': 2_592_000_000,\r\n '3M': 7_776_000_000,\r\n '6M': 15_552_000_000,\r\n '12M': 31_536_000_000,\r\n};\r\n\r\nexport function timeframeToMs(tf: TimeFrame): number {\r\n return TIMEFRAME_MS[tf];\r\n}\r\n\r\nexport function formatTimestamp(timestamp: number, tf: TimeFrame): string {\r\n const d = new Date(timestamp);\r\n const ms = TIMEFRAME_MS[tf];\r\n if (ms >= 86_400_000) {\r\n return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\r\n }\r\n if (ms >= 3_600_000) {\r\n return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });\r\n }\r\n return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\r\n}\r\n\r\nexport function alignToTimeframe(timestamp: number, tf: TimeFrame): number {\r\n const ms = TIMEFRAME_MS[tf];\r\n return Math.floor(timestamp / ms) * ms;\r\n}\r\n\r\nexport interface TimeParts {\r\n year: number;\r\n month: number; // 1-12\r\n day: number;\r\n hours: number;\r\n minutes: number;\r\n}\r\n\r\n/**\r\n * Calendar parts of a timestamp in either the browser-local timezone\r\n * (`tzOffsetMinutes === null`) or a fixed UTC offset (e.g. -300 for EST).\r\n */\r\nexport function timeParts(timeMs: number, tzOffsetMinutes: number | null): TimeParts {\r\n if (tzOffsetMinutes === null) {\r\n const d = new Date(timeMs);\r\n return {\r\n year: d.getFullYear(),\r\n month: d.getMonth() + 1,\r\n day: d.getDate(),\r\n hours: d.getHours(),\r\n minutes: d.getMinutes(),\r\n };\r\n }\r\n const d = new Date(timeMs + tzOffsetMinutes * 60_000);\r\n return {\r\n year: d.getUTCFullYear(),\r\n month: d.getUTCMonth() + 1,\r\n day: d.getUTCDate(),\r\n hours: d.getUTCHours(),\r\n minutes: d.getUTCMinutes(),\r\n };\r\n}\r\n\r\n/**\r\n * A bar with no time-of-day component (a daily, weekly, monthly or yearly candle — always anchored\r\n * to a calendar-day boundary, so hours/minutes read 00:00) needs the YEAR in its label instead: a\r\n * bare `month/day` is ambiguous across years, and `00:00` tells a trader nothing. Detected from the\r\n * parts themselves rather than a separate timeframe parameter, so every caller that formats a bar's\r\n * time gets the same rule for free. The one false positive this accepts — a genuine intraday bar\r\n * landing exactly on midnight — is acceptable: real trade-driven timestamps essentially never do.\r\n */\r\nexport function isDateOnly(parts: Pick<TimeParts, 'hours' | 'minutes'>): boolean {\r\n return parts.hours === 0 && parts.minutes === 0;\r\n}\r\n\r\n/** A short timezone label like `UTC-5` or `UTC+5:30` (browser-local when null). */\r\nexport function tzLabel(tzOffsetMinutes: number | null): string {\r\n const min = tzOffsetMinutes === null ? -new Date().getTimezoneOffset() : tzOffsetMinutes;\r\n const sign = min >= 0 ? '+' : '-';\r\n const abs = Math.abs(min);\r\n const h = Math.floor(abs / 60);\r\n const m = abs % 60;\r\n return m === 0 ? `UTC${sign}${h}` : `UTC${sign}${h}:${String(m).padStart(2, '0')}`;\r\n}\r\n\r\n/** Day-of-week for a Monday-anchored reference week (1970-01-05 was a Monday, UTC). */\r\nconst WEEK_ANCHOR_MS = Date.UTC(1970, 0, 5);\r\n\r\nfunction parseTimeframe(tf: TimeFrame): { count: number; unit: 's' | 'm' | 'h' | 'd' | 'w' | 'M' } {\r\n const match = /^(\\d+)([smhdwM])$/.exec(tf);\r\n if (!match) return { count: 1, unit: 'm' };\r\n return { count: parseInt(match[1], 10), unit: match[2] as 's' | 'm' | 'h' | 'd' | 'w' | 'M' };\r\n}\r\n\r\n/**\r\n * Start of the bucket a timestamp falls into for a given timeframe.\r\n *\r\n * Intraday and daily frames are anchored to the Unix epoch (UTC) via fixed-ms\r\n * flooring. Weekly frames are anchored to Monday (or `weekStartsOn`), monthly\r\n * and yearly frames use calendar boundaries (1st of month, Jan for years) so\r\n * 3M aligns to quarters and 12M to calendar years.\r\n */\r\nexport function timeframeBucketStart(\r\n timestamp: number,\r\n tf: TimeFrame,\r\n weekStartsOn: 0 | 1 = 1,\r\n): number {\r\n const { count, unit } = parseTimeframe(tf);\r\n\r\n if (unit === 's' || unit === 'm' || unit === 'h' || unit === 'd') {\r\n const ms = TIMEFRAME_MS[tf];\r\n return Math.floor(timestamp / ms) * ms;\r\n }\r\n\r\n const d = new Date(timestamp);\r\n\r\n if (unit === 'w') {\r\n const dayMs = TIMEFRAME_MS['1d'];\r\n const dow = d.getUTCDay();\r\n const shift = (dow - weekStartsOn + 7) % 7;\r\n const weekStart = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - shift);\r\n if (count <= 1) return weekStart;\r\n const weekIndex = Math.floor((weekStart - WEEK_ANCHOR_MS) / (7 * dayMs));\r\n const grouped = Math.floor(weekIndex / count) * count;\r\n return WEEK_ANCHOR_MS + grouped * 7 * dayMs;\r\n }\r\n\r\n // months / quarters / years\r\n const totalMonths = d.getUTCFullYear() * 12 + d.getUTCMonth();\r\n const grouped = Math.floor(totalMonths / count) * count;\r\n return Date.UTC(Math.floor(grouped / 12), grouped % 12, 1);\r\n}\r\n","const NUMBER_FORMAT_CACHE_LIMIT = 64;\r\nconst numberFormatCache = new Map<string, Intl.NumberFormat>();\r\n\r\n/**\r\n * `Number#toLocaleString(locale, options)` constructs a fresh\r\n * `Intl.NumberFormat` on every call — ~20µs in V8 versus ~0.4µs to reuse one.\r\n * Axis labels, the legend, crosshair pills and panel axes format dozens of\r\n * numbers per frame, so formatters are cached per (locale, precision). The\r\n * output is identical: per spec, `toLocaleString` is defined as exactly this.\r\n */\r\nfunction numberFormat(locale: string, precision: number): Intl.NumberFormat {\r\n const key = `${locale}|${precision}`;\r\n let nf = numberFormatCache.get(key);\r\n if (!nf) {\r\n if (numberFormatCache.size >= NUMBER_FORMAT_CACHE_LIMIT) numberFormatCache.clear();\r\n nf = new Intl.NumberFormat(locale, {\r\n minimumFractionDigits: precision,\r\n maximumFractionDigits: precision,\r\n });\r\n numberFormatCache.set(key, nf);\r\n }\r\n return nf;\r\n}\r\n\r\nexport function formatPrice(value: number, precision = 2, locale = 'en-US'): string {\r\n return numberFormat(locale, precision).format(value);\r\n}\r\n\r\n/**\r\n * Decimals the price axis uses for a visible price range: one more than its\r\n * tick step needs, 2 at minimum. Everything that prints a price next to the\r\n * axis (legend, crosshair pill, last-price tag) shares it, so a sub-cent\r\n * asset reads \"0.000004349\" everywhere instead of \"0.00\" off the axis.\r\n */\r\nexport function autoPricePrecision(min: number, max: number): number {\r\n if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return 2;\r\n const step = computeTickStep(min, max, 8);\r\n return step > 0 && step < 1 ? Math.ceil(-Math.log10(step)) + 1 : 2;\r\n}\r\n\r\nimport type { PriceScaleMode } from '../types/rendering.js';\r\nimport { computeTickStep } from './math.js';\r\n\r\n/**\r\n * Format a price-axis label for a given scale mode.\r\n *\r\n * - `regular` / `logarithmic`: the raw price.\r\n * - `percentage`: change from `baseline`, e.g. `+12.34%`.\r\n * - `indexedTo100`: the price rebased so `baseline` reads as 100.\r\n *\r\n * Falls back to a plain price when a baseline is required but missing/zero.\r\n */\r\nexport function formatPriceScaleLabel(\r\n price: number,\r\n mode: PriceScaleMode,\r\n baseline: number | undefined,\r\n precision = 2,\r\n locale = 'en-US',\r\n): string {\r\n if ((mode === 'percentage' || mode === 'indexedTo100') && baseline && baseline !== 0) {\r\n if (mode === 'percentage') {\r\n const pct = (price / baseline - 1) * 100;\r\n const sign = pct > 0 ? '+' : '';\r\n return `${sign}${pct.toFixed(2)}%`;\r\n }\r\n const indexed = (price / baseline) * 100;\r\n return formatPrice(indexed, 2, locale);\r\n }\r\n return formatPrice(price, precision, locale);\r\n}\r\n\r\nexport function formatVolume(value: number): string {\r\n if (value >= 1_000_000_000) return (value / 1_000_000_000).toFixed(2) + 'B';\r\n if (value >= 1_000_000) return (value / 1_000_000).toFixed(2) + 'M';\r\n if (value >= 1_000) return (value / 1_000).toFixed(2) + 'K';\r\n return value.toFixed(0);\r\n}\r\n\r\nexport function detectPrecision(values: number[]): number {\r\n let maxDecimals = 0;\r\n for (const v of values) {\r\n const str = v.toString();\r\n const dot = str.indexOf('.');\r\n if (dot >= 0) {\r\n maxDecimals = Math.max(maxDecimals, str.length - dot - 1);\r\n }\r\n }\r\n return Math.min(maxDecimals, 8);\r\n}\r\n","import type { ChartOptions } from '../types/chart.js';\r\n\r\nexport const DEFAULT_CHART_OPTIONS: Required<Pick<ChartOptions, 'autoScale' | 'rightMargin' | 'minBarSpacing' | 'maxBarSpacing'>> & Pick<ChartOptions, 'grid' | 'crosshair'> = {\r\n autoScale: true,\r\n rightMargin: 5,\r\n minBarSpacing: 2,\r\n maxBarSpacing: 30,\r\n grid: {\r\n visible: true,\r\n hLineStyle: 'solid',\r\n vLineStyle: 'solid',\r\n },\r\n crosshair: {\r\n mode: 'magnet',\r\n },\r\n};\r\n\r\n// Standard timeframe presets for different market types\r\nimport type { TimeFrame } from '../types/ohlc.js';\r\n\r\n/** Crypto: all timeframes including seconds */\r\nexport const TIMEFRAMES_CRYPTO: TimeFrame[] = [\r\n '1s', '1m', '3m', '5m', '15m', '30m',\r\n '1h', '2h', '4h', '6h', '8h', '12h',\r\n '1d', '3d', '1w', '1M',\r\n];\r\n\r\n/** Stocks: minute-level and above (no seconds) */\r\nexport const TIMEFRAMES_STOCK: TimeFrame[] = [\r\n '1m', '5m', '15m', '30m',\r\n '1h', '2h', '4h',\r\n '1d', '1w', '1M', '3M', '6M', '12M',\r\n];\r\n\r\n/** Forex: common forex timeframes */\r\nexport const TIMEFRAMES_FOREX: TimeFrame[] = [\r\n '1m', '5m', '15m', '30m',\r\n '1h', '4h',\r\n '1d', '1w', '1M',\r\n];\r\n\r\n/** Default favorites shown in quick-access bar */\r\nexport const DEFAULT_TIMEFRAME_FAVORITES: TimeFrame[] = [\r\n '1m', '5m', '15m', '1h', '4h', '1d', '1w',\r\n];\r\n\r\nexport const DEFAULT_BAR_WIDTH = 8;\r\nexport const DEFAULT_BAR_SPACING = 2;\r\nexport const PRICE_AXIS_WIDTH = 70;\r\nexport const TIME_AXIS_HEIGHT = 30;\r\nexport const MIN_PANEL_HEIGHT = 60;\r\nexport const DEFAULT_PANEL_HEIGHT = 120;\r\n","import type { Theme } from '../types/theme.js';\r\n\r\nconst DEFAULT_FONT = {\r\n family: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n sizeSmall: 10,\r\n sizeMedium: 12,\r\n sizeLarge: 14,\r\n};\r\n\r\n/**\r\n * TradeCanvas palette. Themes pick their own candle colours for their\r\n * background; everything drawn on top of either theme (indicators, orders,\r\n * signals, drawings) uses the theme-neutral values below, which keep at\r\n * least 3:1 contrast on both the dark ink and a white background. `amber` is\r\n * the brand accent for dark surfaces (2:1 on white); light surfaces use\r\n * `LIGHT_THEME.lineColor` / `ochre` instead.\r\n */\r\nexport const TC_PALETTE = {\r\n /** Brand amber — the default series line and the accent. */\r\n amber: '#f2a93b',\r\n /** Rising / bullish, legible on dark and light. */\r\n up: '#1fa874',\r\n /** Falling / bearish, legible on dark and light. */\r\n down: '#e8505b',\r\n /** Amber deep enough for white backgrounds; auto-coloured series use it. */\r\n ochre: '#c98316',\r\n /** Secondary series, signal lines. */\r\n blue: '#4c8dff',\r\n violet: '#a57cff',\r\n cyan: '#1398a8',\r\n rose: '#e25592',\r\n /** Zero lines, neutral guides. */\r\n neutral: '#7d8696',\r\n /** Ink ground of the dark theme. */\r\n ink: '#0c1016',\r\n} as const;\r\n\r\n/** Order in which overlay indicators are coloured when no colour is given. */\r\nexport const TC_SERIES_COLORS: readonly string[] = [\r\n TC_PALETTE.blue,\r\n TC_PALETTE.ochre,\r\n TC_PALETTE.violet,\r\n TC_PALETTE.cyan,\r\n TC_PALETTE.rose,\r\n];\r\n\r\nexport const DARK_THEME: Theme = {\r\n name: 'dark',\r\n background: TC_PALETTE.ink,\r\n text: '#d9dde4',\r\n textSecondary: '#7d8696',\r\n grid: '#161b23',\r\n crosshair: '#8a93a3',\r\n candleUp: '#3ccf91',\r\n candleDown: '#f0616d',\r\n candleUpWick: '#3ccf91',\r\n candleDownWick: '#f0616d',\r\n lineColor: TC_PALETTE.amber,\r\n areaTopColor: 'rgba(242, 169, 59, 0.32)',\r\n areaBottomColor: 'rgba(242, 169, 59, 0.0)',\r\n volumeUp: 'rgba(60, 207, 145, 0.28)',\r\n volumeDown: 'rgba(240, 97, 109, 0.28)',\r\n axisLine: '#1f2630',\r\n axisLabel: '#c3c9d3',\r\n axisLabelBackground: '#1f2630',\r\n font: DEFAULT_FONT,\r\n};\r\n\r\nexport const LIGHT_THEME: Theme = {\r\n name: 'light',\r\n background: '#ffffff',\r\n text: '#0f131a',\r\n textSecondary: '#6b7380',\r\n grid: '#eef0f3',\r\n crosshair: '#8a93a3',\r\n candleUp: '#16a36a',\r\n candleDown: '#d9414f',\r\n candleUpWick: '#16a36a',\r\n candleDownWick: '#d9414f',\r\n lineColor: '#c77a0a',\r\n areaTopColor: 'rgba(199, 122, 10, 0.24)',\r\n areaBottomColor: 'rgba(199, 122, 10, 0.0)',\r\n volumeUp: 'rgba(22, 163, 106, 0.25)',\r\n volumeDown: 'rgba(217, 65, 79, 0.25)',\r\n axisLine: '#dde1e7',\r\n axisLabel: '#3f4754',\r\n axisLabelBackground: '#eef0f3',\r\n font: DEFAULT_FONT,\r\n};\r\n\r\nexport const DARK_TERMINAL: Theme = {\r\n name: 'terminal',\r\n background: '#0E0E0E',\r\n text: '#C0C0C0',\r\n textSecondary: '#8A8A8A',\r\n grid: '#1A1A1A',\r\n crosshair: '#666666',\r\n candleUp: '#00FF87',\r\n candleDown: '#FF3B4D',\r\n candleUpWick: '#00FF87',\r\n candleDownWick: '#FF3B4D',\r\n lineColor: '#3D8BFD',\r\n areaTopColor: 'rgba(61, 139, 253, 0.3)',\r\n areaBottomColor: 'rgba(61, 139, 253, 0.0)',\r\n volumeUp: 'rgba(0, 255, 135, 0.2)',\r\n volumeDown: 'rgba(255, 59, 77, 0.2)',\r\n axisLine: '#1A1A1A',\r\n axisLabel: '#8A8A8A',\r\n axisLabelBackground: '#1A1A1A',\r\n font: {\r\n family: \"'Roboto Mono', 'JetBrains Mono', 'SF Mono', Consolas, monospace\",\r\n sizeSmall: 10,\r\n sizeMedium: 12,\r\n sizeLarge: 14,\r\n },\r\n};\r\n","import type { LocaleStrings } from './types.js';\r\n\r\nexport const en: LocaleStrings = {\r\n // Chart types\r\n candlestick: 'Candlestick',\r\n line: 'Line',\r\n area: 'Area',\r\n bar: 'OHLC Bar',\r\n\r\n // Axes\r\n price: 'Price',\r\n volume: 'Volume',\r\n time: 'Time',\r\n open: 'Open',\r\n high: 'High',\r\n low: 'Low',\r\n close: 'Close',\r\n\r\n // Indicators - overlays\r\n sma: 'SMA',\r\n ema: 'EMA',\r\n bollingerBands: 'Bollinger Bands',\r\n vwap: 'VWAP',\r\n ichimoku: 'Ichimoku Cloud',\r\n parabolicSAR: 'Parabolic SAR',\r\n supertrend: 'Supertrend',\r\n keltnerChannel: 'Keltner Channel',\r\n donchianChannel: 'Donchian Channel',\r\n\r\n // Indicators - panels\r\n rsi: 'RSI',\r\n macd: 'MACD',\r\n stochastic: 'Stochastic',\r\n atr: 'ATR',\r\n adx: 'ADX',\r\n obv: 'OBV',\r\n williamsR: 'Williams %R',\r\n cci: 'CCI',\r\n mfi: 'MFI',\r\n aroon: 'Aroon',\r\n roc: 'ROC',\r\n tsi: 'TSI',\r\n cmf: 'CMF',\r\n stddev: 'Std Dev',\r\n volumeProfile: 'Volume Profile',\r\n accumulationDistribution: 'A/D Line',\r\n vroc: 'VROC',\r\n\r\n // Drawing tools\r\n trendLine: 'Trend Line',\r\n horizontalLine: 'Horizontal Line',\r\n verticalLine: 'Vertical Line',\r\n ray: 'Ray',\r\n extendedLine: 'Extended Line',\r\n parallelChannel: 'Parallel Channel',\r\n regressionChannel: 'Regression Channel',\r\n fibRetracement: 'Fibonacci Retracement',\r\n fibExtension: 'Fibonacci Extension',\r\n rectangle: 'Rectangle',\r\n ellipse: 'Ellipse',\r\n triangle: 'Triangle',\r\n pitchfork: \"Andrews' Pitchfork\",\r\n elliottWave: 'Elliott Wave',\r\n priceRange: 'Price Range',\r\n dateRange: 'Date Range',\r\n measure: 'Measure',\r\n textTool: 'Text',\r\n arrow: 'Arrow',\r\n clearAll: 'Clear All',\r\n\r\n // Trading\r\n buy: 'Buy',\r\n sell: 'Sell',\r\n buyLimit: 'Buy Limit',\r\n sellLimit: 'Sell Limit',\r\n buyStop: 'Buy Stop',\r\n sellStop: 'Sell Stop',\r\n stopLoss: 'Stop Loss',\r\n takeProfit: 'Take Profit',\r\n market: 'Market',\r\n limit: 'Limit',\r\n stop: 'Stop',\r\n cancel: 'Cancel',\r\n modify: 'Modify',\r\n quantity: 'Qty',\r\n pnl: 'P&L',\r\n activeOrders: 'Active Orders',\r\n positions: 'Positions',\r\n noOrders: 'No active orders',\r\n noPositions: 'No open positions',\r\n placeOrder: 'Place Order',\r\n rightClickToTrade: 'Right-click chart to place orders',\r\n\r\n // Market\r\n ceiling: 'Ceiling',\r\n floor: 'Floor',\r\n reference: 'Reference',\r\n session: 'Session',\r\n preOpen: 'Pre-Open',\r\n continuous: 'Continuous',\r\n preClose: 'Pre-Close',\r\n closed: 'Closed',\r\n\r\n // UI\r\n settings: 'Settings',\r\n theme: 'Theme',\r\n darkTheme: 'Dark',\r\n lightTheme: 'Light',\r\n tools: 'Tools',\r\n indicators: 'Indicators',\r\n overlays: 'Overlays',\r\n panels: 'Panels',\r\n orders: 'Orders',\r\n autoScale: 'Auto Scale',\r\n crosshair: 'Crosshair',\r\n grid: 'Grid',\r\n loading: 'Loading...',\r\n error: 'Error',\r\n\r\n numberDecimalSeparator: '.',\r\n numberGroupSeparator: ',',\r\n};\r\n","import type { LocaleStrings } from './types.js';\r\n\r\nexport const vi: LocaleStrings = {\r\n // Chart types\r\n candlestick: 'Nến',\r\n line: 'Đường',\r\n area: 'Vùng',\r\n bar: 'Thanh OHLC',\r\n\r\n // Axes\r\n price: 'Giá',\r\n volume: 'Khối lượng',\r\n time: 'Thời gian',\r\n open: 'Mở',\r\n high: 'Cao',\r\n low: 'Thấp',\r\n close: 'Đóng',\r\n\r\n // Indicators - overlays\r\n sma: 'SMA',\r\n ema: 'EMA',\r\n bollingerBands: 'Dải Bollinger',\r\n vwap: 'VWAP',\r\n ichimoku: 'Mây Ichimoku',\r\n parabolicSAR: 'Parabolic SAR',\r\n supertrend: 'Supertrend',\r\n keltnerChannel: 'Kênh Keltner',\r\n donchianChannel: 'Kênh Donchian',\r\n\r\n // Indicators - panels\r\n rsi: 'RSI',\r\n macd: 'MACD',\r\n stochastic: 'Stochastic',\r\n atr: 'ATR',\r\n adx: 'ADX',\r\n obv: 'OBV',\r\n williamsR: 'Williams %R',\r\n cci: 'CCI',\r\n mfi: 'MFI',\r\n aroon: 'Aroon',\r\n roc: 'ROC',\r\n tsi: 'TSI',\r\n cmf: 'CMF',\r\n stddev: 'Độ lệch chuẩn',\r\n volumeProfile: 'Phân bổ KL',\r\n accumulationDistribution: 'Tích lũy/Phân phối',\r\n vroc: 'VROC',\r\n\r\n // Drawing tools\r\n trendLine: 'Đường xu hướng',\r\n horizontalLine: 'Đường ngang',\r\n verticalLine: 'Đường dọc',\r\n ray: 'Tia',\r\n extendedLine: 'Đường kéo dài',\r\n parallelChannel: 'Kênh song song',\r\n regressionChannel: 'Kênh hồi quy',\r\n fibRetracement: 'Fibonacci thoái lui',\r\n fibExtension: 'Fibonacci mở rộng',\r\n rectangle: 'Hình chữ nhật',\r\n ellipse: 'Hình elip',\r\n triangle: 'Tam giác',\r\n pitchfork: 'Chĩa ba Andrews',\r\n elliottWave: 'Sóng Elliott',\r\n priceRange: 'Khoảng giá',\r\n dateRange: 'Khoảng thời gian',\r\n measure: 'Đo lường',\r\n textTool: 'Chữ',\r\n arrow: 'Mũi tên',\r\n clearAll: 'Xóa tất cả',\r\n\r\n // Trading\r\n buy: 'Mua',\r\n sell: 'Bán',\r\n buyLimit: 'Mua giới hạn',\r\n sellLimit: 'Bán giới hạn',\r\n buyStop: 'Mua chặn',\r\n sellStop: 'Bán chặn',\r\n stopLoss: 'Cắt lỗ',\r\n takeProfit: 'Chốt lời',\r\n market: 'Thị trường',\r\n limit: 'Giới hạn',\r\n stop: 'Dừng',\r\n cancel: 'Hủy',\r\n modify: 'Sửa',\r\n quantity: 'KL',\r\n pnl: 'Lãi/Lỗ',\r\n activeOrders: 'Lệnh chờ',\r\n positions: 'Vị thế',\r\n noOrders: 'Không có lệnh chờ',\r\n noPositions: 'Không có vị thế mở',\r\n placeOrder: 'Đặt lệnh',\r\n rightClickToTrade: 'Nhấp chuột phải để đặt lệnh',\r\n\r\n // Market\r\n ceiling: 'Trần',\r\n floor: 'Sàn',\r\n reference: 'Tham chiếu',\r\n session: 'Phiên',\r\n preOpen: 'Trước giờ mở',\r\n continuous: 'Liên tục',\r\n preClose: 'Trước giờ đóng',\r\n closed: 'Đóng cửa',\r\n\r\n // UI\r\n settings: 'Cài đặt',\r\n theme: 'Giao diện',\r\n darkTheme: 'Tối',\r\n lightTheme: 'Sáng',\r\n tools: 'Công cụ',\r\n indicators: 'Chỉ báo',\r\n overlays: 'Phủ lên',\r\n panels: 'Bảng',\r\n orders: 'Lệnh',\r\n autoScale: 'Tự co giãn',\r\n crosshair: 'Chữ thập',\r\n grid: 'Lưới',\r\n loading: 'Đang tải...',\r\n error: 'Lỗi',\r\n\r\n numberDecimalSeparator: ',',\r\n numberGroupSeparator: '.',\r\n};\r\n","export type { Locale, LocaleStrings, NumberFormatConfig, DateFormatConfig } from './types.js';\r\nexport { en } from './en.js';\r\nexport { vi } from './vi.js';\r\n\r\nimport type { Locale, LocaleStrings } from './types.js';\r\nimport { en } from './en.js';\r\nimport { vi } from './vi.js';\r\n\r\nconst locales = new Map<string, LocaleStrings>([\r\n ['en', en],\r\n ['vi', vi],\r\n]);\r\n\r\nlet currentLocale: Locale = 'en';\r\nlet currentStrings: LocaleStrings = en;\r\n\r\nexport function setLocale(locale: Locale): void {\r\n currentLocale = locale;\r\n currentStrings = locales.get(locale) ?? en;\r\n}\r\n\r\nexport function getLocale(): Locale {\r\n return currentLocale;\r\n}\r\n\r\nexport function t(key: keyof LocaleStrings): string {\r\n return currentStrings[key] ?? (en as any)[key] ?? key;\r\n}\r\n\r\nexport function registerLocale(locale: string, strings: LocaleStrings): void {\r\n locales.set(locale, strings);\r\n}\r\n\r\nexport function getLocaleStrings(locale?: string): LocaleStrings {\r\n return locales.get(locale ?? currentLocale) ?? en;\r\n}\r\n\r\n// Number formatting\r\nexport function formatNumber(value: number, precision = 2, locale?: string): string {\r\n const strings = locales.get(locale ?? currentLocale) ?? en;\r\n const dec = strings.numberDecimalSeparator;\r\n const grp = strings.numberGroupSeparator;\r\n\r\n const fixed = value.toFixed(precision);\r\n const [intPart, decPart] = fixed.split('.');\r\n\r\n // Group integer part\r\n const negative = intPart.startsWith('-');\r\n const digits = negative ? intPart.slice(1) : intPart;\r\n let grouped = '';\r\n for (let i = digits.length - 1, count = 0; i >= 0; i--, count++) {\r\n if (count > 0 && count % 3 === 0) grouped = grp + grouped;\r\n grouped = digits[i] + grouped;\r\n }\r\n if (negative) grouped = '-' + grouped;\r\n\r\n return decPart ? grouped + dec + decPart : grouped;\r\n}\r\n\r\nexport function formatVND(value: number): string {\r\n return formatNumber(value, 0, 'vi');\r\n}\r\n\r\nexport function formatVolumeLoc(value: number, locale?: string): string {\r\n if (value >= 1e9) return formatNumber(value / 1e9, 2, locale ?? currentLocale) + 'B';\r\n if (value >= 1e6) return formatNumber(value / 1e6, 2, locale ?? currentLocale) + 'M';\r\n if (value >= 1e3) return formatNumber(value / 1e3, 2, locale ?? currentLocale) + 'K';\r\n return formatNumber(value, 0, locale ?? currentLocale);\r\n}\r\n","import type { MarketConfig, MarketColorScheme, TradingSession } from './types.js';\r\nimport type { Theme } from '../types/theme.js';\r\n\r\n// Vietnam stock color convention:\r\n// Purple/Red = ceiling (trần) - max up\r\n// Green/Cyan = floor (sàn) - max down\r\n// Yellow = reference (tham chiếu)\r\n// Red = up, Blue = down (common VN convention)\r\nexport const VN_COLORS: MarketColorScheme = {\r\n up: '#FF0000', // Đỏ - tăng\r\n down: '#0000FF', // Xanh dương - giảm\r\n unchanged: '#FFD700', // Vàng - tham chiếu\r\n ceiling: '#FF00FF', // Tím - trần\r\n floor: '#00FFFF', // Xanh lam - sàn\r\n reference: '#FFD700', // Vàng - tham chiếu\r\n};\r\n\r\nexport const HOSE_SESSIONS: TradingSession[] = [\r\n { name: 'ATO', startTime: '09:00', endTime: '09:15', type: 'preOpen' },\r\n { name: 'Phiên 1', startTime: '09:15', endTime: '11:30', type: 'continuous' },\r\n { name: 'Nghỉ trưa', startTime: '11:30', endTime: '13:00', type: 'closed' },\r\n { name: 'Phiên 2', startTime: '13:00', endTime: '14:30', type: 'continuous' },\r\n { name: 'ATC', startTime: '14:30', endTime: '14:45', type: 'preClose' },\r\n];\r\n\r\nexport const HNX_SESSIONS: TradingSession[] = [\r\n { name: 'Phiên 1', startTime: '09:00', endTime: '11:30', type: 'continuous' },\r\n { name: 'Nghỉ trưa', startTime: '11:30', endTime: '13:00', type: 'closed' },\r\n { name: 'Phiên 2', startTime: '13:00', endTime: '14:30', type: 'continuous' },\r\n { name: 'ATC', startTime: '14:30', endTime: '14:45', type: 'preClose' },\r\n];\r\n\r\n// Market presets\r\nexport const MARKET_HOSE: MarketConfig = {\r\n type: 'stock',\r\n exchange: 'HOSE',\r\n currency: 'VND',\r\n pricePrecision: 2,\r\n volumeUnit: 10,\r\n priceStep: 0.05,\r\n priceLimits: { enabled: true, ceilingPercent: 7, floorPercent: 7 },\r\n sessions: HOSE_SESSIONS,\r\n colorScheme: VN_COLORS,\r\n};\r\n\r\nexport const MARKET_HNX: MarketConfig = {\r\n type: 'stock',\r\n exchange: 'HNX',\r\n currency: 'VND',\r\n pricePrecision: 1,\r\n volumeUnit: 100,\r\n priceStep: 0.1,\r\n priceLimits: { enabled: true, ceilingPercent: 10, floorPercent: 10 },\r\n sessions: HNX_SESSIONS,\r\n colorScheme: VN_COLORS,\r\n};\r\n\r\nexport const MARKET_UPCOM: MarketConfig = {\r\n type: 'stock',\r\n exchange: 'UPCOM',\r\n currency: 'VND',\r\n pricePrecision: 1,\r\n volumeUnit: 100,\r\n priceStep: 0.1,\r\n priceLimits: { enabled: true, ceilingPercent: 15, floorPercent: 15 },\r\n sessions: HNX_SESSIONS,\r\n colorScheme: VN_COLORS,\r\n};\r\n\r\nexport const MARKET_CRYPTO: MarketConfig = {\r\n type: 'crypto',\r\n currency: 'USDT',\r\n pricePrecision: 2,\r\n priceLimits: { enabled: false },\r\n};\r\n\r\nexport const MARKET_NYSE: MarketConfig = {\r\n type: 'stock',\r\n exchange: 'NYSE',\r\n currency: 'USD',\r\n pricePrecision: 2,\r\n priceStep: 0.01,\r\n priceLimits: { enabled: false },\r\n sessions: [\r\n { name: 'Pre-Market', startTime: '04:00', endTime: '09:30', type: 'preOpen' },\r\n { name: 'Regular', startTime: '09:30', endTime: '16:00', type: 'continuous' },\r\n { name: 'After-Hours', startTime: '16:00', endTime: '20:00', type: 'preClose' },\r\n ],\r\n};\r\n\r\n// Build a theme variant for VN stock market\r\nexport function createVNTheme(base: Theme): Theme {\r\n return {\r\n ...base,\r\n candleUp: VN_COLORS.up,\r\n candleDown: VN_COLORS.down,\r\n candleUpWick: VN_COLORS.up,\r\n candleDownWick: VN_COLORS.down,\r\n volumeUp: 'rgba(255, 0, 0, 0.3)',\r\n volumeDown: 'rgba(0, 0, 255, 0.3)',\r\n };\r\n}\r\n\r\nexport function computePriceLimits(referencePrice: number, config: MarketConfig): { ceiling: number; floor: number; reference: number } | null {\r\n if (!config.priceLimits?.enabled || !config.priceLimits.ceilingPercent) return null;\r\n const ceilPct = config.priceLimits.ceilingPercent / 100;\r\n const floorPct = (config.priceLimits.floorPercent ?? config.priceLimits.ceilingPercent) / 100;\r\n return {\r\n ceiling: referencePrice * (1 + ceilPct),\r\n floor: referencePrice * (1 - floorPct),\r\n reference: referencePrice,\r\n };\r\n}\r\n\r\nexport function getCurrentSession(sessions: TradingSession[]): TradingSession | null {\r\n const now = new Date();\r\n const hhmm = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;\r\n for (const session of sessions) {\r\n if (hhmm >= session.startTime && hhmm < session.endTime) return session;\r\n }\r\n return null;\r\n}\r\n"],"mappings":"mEAkEA,IAAY,EAAL,SAAA,EAAA,OACL,GAAA,EAAA,WAAa,GAAA,aACb,EAAA,EAAA,KAAO,GAAA,OACP,EAAA,EAAA,MAAQ,GAAA,QACR,EAAA,EAAA,QAAU,GAAA,UACV,EAAA,EAAA,GAAK,GAAA,KACL,EAAA,EAAA,MAAQ,GAAA,cACT,CCFY,EAAsC,CACjD,MAAO,UACP,UAAW,EACX,UAAW,QACX,UAAW,0BACX,YAAa,GACb,SAAU,GACX,CCsCY,EAAwC,CACnD,QAAS,GACT,YAAa,CAAE,IAAK,UAAW,KAAM,UAAW,CAChD,eAAgB,CAAE,OAAQ,UAAW,KAAM,UAAW,MAAO,UAAW,CACxE,aAAc,CAAE,QAAS,GAAO,SAAU,0BAA2B,SAAU,yBAA0B,SAAU,IAAK,CACxH,YAAa,CAAE,QAAS,GAAM,CAC9B,eAAgB,EAChB,cAAe,EAChB,CCpGY,EAA0C,CACrD,UAAW,UACX,WAAY,UACZ,aAAc,UACd,UAAW,GACX,UAAW,GACX,eAAgB,GACjB,CA2BY,EAA2C,CACtD,YAAa,UACb,UAAW,UACX,YAAa,UACb,YAAa,IACb,YAAa,EACb,UAAW,GACX,QAAS,GACV,CC6CY,EAAqC,CAChD,QAAS,GACT,WAAY,IACZ,UAAW,IACX,SAAU,IACV,kBAAmB,EACpB,CAEY,EAA+C,CAC1D,aAAc,IACd,WAAY,GACZ,qBAAsB,GACtB,eAAgB,GACjB,CC5HD,SAAgB,EAAM,EAAe,EAAa,EAAqB,CACrE,OAAO,KAAK,IAAI,EAAK,KAAK,IAAI,EAAK,EAAM,CAAC,CAG5C,SAAgB,EAAK,EAAW,EAAW,EAAmB,CAC5D,OAAO,GAAK,EAAI,GAAK,EAGvB,SAAgB,EAAY,EAAW,EAAW,EAAuB,CAEvE,OADI,IAAM,EAAU,GACZ,EAAQ,IAAM,EAAI,GAG5B,SAAgB,EAAY,EAAe,EAAsB,CAC/D,OAAO,KAAK,MAAM,EAAQ,EAAK,CAAG,EAGpC,SAAgB,EAAW,EAAe,EAAwB,CAChE,IAAM,EAAM,KAAK,MAAM,KAAK,MAAM,EAAM,CAAC,CACnC,EAAO,EAAiB,IAAI,EAC9B,EAYJ,MAXA,CASO,EATH,EACE,EAAO,IAAY,EACd,EAAO,EAAU,EACjB,EAAO,EAAU,EACd,GAER,GAAQ,EAAU,EACb,GAAQ,EAAU,EAClB,GAAQ,EAAU,EACf,GAEP,EAAgB,IAAI,EAG7B,SAAgB,EAAgB,EAAa,EAAa,EAA0B,CAElF,OAAO,EADO,EAAW,EAAM,EAAK,GAClB,EAAS,EAAW,GAAI,GAAK,CC/BjD,SAAgB,EAAiB,EAAsB,CACrD,OAAO,EAAO,aAAO,EAAO,EAAO,IAOrC,SAAgB,EAAa,EAAsC,CAEjE,MAAO,CACL,KAFW,EAAiB,EAAI,MAAQ,EAAI,GAAK,EAEjD,CACA,KAAM,EAAI,MAAQ,EAAI,GAAK,EAC3B,KAAM,EAAI,MAAQ,EAAI,GAAK,EAC3B,IAAK,EAAI,KAAO,EAAI,GAAK,EACzB,MAAO,EAAI,OAAS,EAAI,GAAK,EAC7B,OAAQ,EAAI,QAAU,EAAI,GAAK,EAChC,CAGH,SAAgB,GACd,EACA,EACA,EACY,CACZ,IAAM,EAAW,KAAK,IAAI,EAAG,EAAK,CAC5B,EAAS,KAAK,IAAI,EAAK,OAAQ,EAAK,EAAE,CAC5C,OAAO,EAAK,MAAM,EAAU,EAAO,CAGrC,SAAgB,GAAa,EAAkB,EAA2B,CACxE,IAAI,EAAK,EACL,EAAK,EAAK,OAAS,EACvB,KAAO,GAAM,GAAI,CACf,IAAM,EAAO,EAAK,IAAQ,EAC1B,GAAI,EAAK,GAAK,KAAO,EAAW,EAAK,EAAM,UAClC,EAAK,GAAK,KAAO,EAAW,EAAK,EAAM,OAC3C,OAAO,EAEd,OAAO,EAGT,SAAgB,EACd,EACA,EACA,EACA,EAAU,IACoB,CAC9B,GAAI,EAAK,SAAW,EAAG,MAAO,CAAE,IAAK,EAAG,IAAK,EAAG,CAChD,IAAM,EAAW,KAAK,IAAI,EAAG,EAAK,CAC5B,EAAS,KAAK,IAAI,EAAK,OAAS,EAAG,EAAG,CACxC,EAAM,IACN,EAAM,KACV,IAAK,IAAI,EAAI,EAAU,GAAK,EAAQ,IAC9B,EAAK,GAAG,IAAM,IAAK,EAAM,EAAK,GAAG,KACjC,EAAK,GAAG,KAAO,IAAK,EAAM,EAAK,GAAG,MAExC,GAAI,IAAQ,IAAU,MAAO,CAAE,IAAK,EAAG,IAAK,EAAG,CAC/C,IAAM,EAAQ,EAAM,GAAO,EAC3B,MAAO,CACL,IAAK,EAAM,EAAQ,EACnB,IAAK,EAAM,EAAQ,EACpB,CAGH,SAAgB,EAAS,EAAmB,EAAiE,CAC3G,MAAO,CACL,GAAG,EACH,KAAM,KAAK,IAAI,EAAS,KAAM,EAAK,MAAM,CACzC,IAAK,KAAK,IAAI,EAAS,IAAK,EAAK,MAAM,CACvC,MAAO,EAAK,MACZ,OAAQ,EAAS,QAAU,EAAK,QAAU,GAC1C,KAAM,EAAK,KACZ,CC/EH,SAAgB,EAAU,EAAa,EAAQ,EAAW,CAIxD,MAAO,QAHG,SAAS,EAAI,MAAM,EAAG,EAAE,CAAE,GAGrB,CAAE,IAFP,SAAS,EAAI,MAAM,EAAG,EAAE,CAAE,GAEf,CAAE,IADb,SAAS,EAAI,MAAM,EAAG,EAAE,CAAE,GACT,CAAE,IAAI,EAAM,GAGzC,SAAgB,EAAU,EAAe,EAAuB,CAC9D,GAAI,EAAM,WAAW,IAAI,CACvB,OAAO,EAAU,EAAO,EAAM,CAEhC,IAAM,EAAY,EAAM,MAAM,iCAAiC,CAI/D,OAHI,EACK,QAAQ,EAAU,GAAG,IAAI,EAAU,GAAG,IAAI,EAAU,GAAG,IAAI,EAAM,GAEnE,EAGT,SAAgB,GAAU,EAAgB,EAAgB,EAAmB,CAC3E,IAAM,EAAY,IAChB,EAAM,EAAI,QAAQ,IAAK,GAAG,CACtB,EAAI,SAAW,IAAG,EAAM,EAAI,GAAK,EAAI,GAAK,EAAI,GAAK,EAAI,GAAK,EAAI,GAAK,EAAI,IACtE,CACL,EAAG,SAAS,EAAI,MAAM,EAAG,EAAE,CAAE,GAAG,CAChC,EAAG,SAAS,EAAI,MAAM,EAAG,EAAE,CAAE,GAAG,CAChC,EAAG,SAAS,EAAI,MAAM,EAAG,EAAE,CAAE,GAAG,CACjC,EAEG,EAAI,EAAS,EAAO,CACpB,EAAI,EAAS,EAAO,CAI1B,MAAO,OAHG,KAAK,MAAM,EAAE,GAAK,EAAE,EAAI,EAAE,GAAK,EAG3B,CAAE,GAFN,KAAK,MAAM,EAAE,GAAK,EAAE,EAAI,EAAE,GAAK,EAEtB,CAAE,GADV,KAAK,MAAM,EAAE,GAAK,EAAE,EAAI,EAAE,GAAK,EAClB,CAAG,GC/B7B,IAAM,EAA0C,CAC9C,KAAM,IACN,KAAM,IACN,MAAO,KACP,MAAO,IACP,KAAM,IACN,KAAM,KACN,KAAM,IACN,MAAO,IACP,MAAO,KACP,MAAO,KACP,KAAM,KACN,KAAM,KACN,KAAM,MACN,KAAM,MACN,KAAM,MACN,KAAM,MACN,MAAO,MACP,KAAM,MACN,KAAM,OACN,KAAM,OACN,KAAM,OACN,KAAM,QACN,KAAM,OACN,KAAM,OACN,KAAM,QACN,MAAO,QACR,CAED,SAAgB,GAAc,EAAuB,CACnD,OAAO,EAAa,GAGtB,SAAgB,GAAgB,EAAmB,EAAuB,CACxE,IAAM,EAAI,IAAI,KAAK,EAAU,CACvB,EAAK,EAAa,GAOxB,OANI,GAAM,MACD,EAAE,mBAAmB,IAAA,GAAW,CAAE,MAAO,QAAS,IAAK,UAAW,CAAC,CAExE,GAAM,KACD,EAAE,mBAAmB,IAAA,GAAW,CAAE,KAAM,UAAW,OAAQ,UAAW,CAAC,CAEzE,EAAE,mBAAmB,IAAA,GAAW,CAAE,KAAM,UAAW,OAAQ,UAAW,OAAQ,UAAW,CAAC,CAGnG,SAAgB,GAAiB,EAAmB,EAAuB,CACzE,IAAM,EAAK,EAAa,GACxB,OAAO,KAAK,MAAM,EAAY,EAAG,CAAG,EAetC,SAAgB,GAAU,EAAgB,EAA2C,CACnF,GAAI,IAAoB,KAAM,CAC5B,IAAM,EAAI,IAAI,KAAK,EAAO,CAC1B,MAAO,CACL,KAAM,EAAE,aAAa,CACrB,MAAO,EAAE,UAAU,CAAG,EACtB,IAAK,EAAE,SAAS,CAChB,MAAO,EAAE,UAAU,CACnB,QAAS,EAAE,YAAY,CACxB,CAEH,IAAM,EAAI,IAAI,KAAK,EAAS,EAAkB,IAAO,CACrD,MAAO,CACL,KAAM,EAAE,gBAAgB,CACxB,MAAO,EAAE,aAAa,CAAG,EACzB,IAAK,EAAE,YAAY,CACnB,MAAO,EAAE,aAAa,CACtB,QAAS,EAAE,eAAe,CAC3B,CAWH,SAAgB,GAAW,EAAsD,CAC/E,OAAO,EAAM,QAAU,GAAK,EAAM,UAAY,EAIhD,SAAgB,GAAQ,EAAwC,CAC9D,IAAM,EAAM,IAAoB,KAAO,CAAC,IAAI,MAAM,CAAC,mBAAmB,CAAG,EACnE,EAAO,GAAO,EAAI,IAAM,IACxB,EAAM,KAAK,IAAI,EAAI,CACnB,EAAI,KAAK,MAAM,EAAM,GAAG,CACxB,EAAI,EAAM,GAChB,OAAO,IAAM,EAAI,MAAM,IAAO,IAAM,MAAM,IAAO,EAAE,GAAG,OAAO,EAAE,CAAC,SAAS,EAAG,IAAI,GAIlF,IAAM,EAAiB,KAAK,IAAI,KAAM,EAAG,EAAE,CAE3C,SAAS,GAAe,EAA2E,CACjG,IAAM,EAAQ,oBAAoB,KAAK,EAAG,CAE1C,OADK,EACE,CAAE,MAAO,SAAS,EAAM,GAAI,GAAG,CAAE,KAAM,EAAM,GAAyC,CAD1E,CAAE,MAAO,EAAG,KAAM,IAAK,CAY5C,SAAgB,GACd,EACA,EACA,EAAsB,EACd,CACR,GAAM,CAAE,QAAO,QAAS,GAAe,EAAG,CAE1C,GAAI,IAAS,KAAO,IAAS,KAAO,IAAS,KAAO,IAAS,IAAK,CAChE,IAAM,EAAK,EAAa,GACxB,OAAO,KAAK,MAAM,EAAY,EAAG,CAAG,EAGtC,IAAM,EAAI,IAAI,KAAK,EAAU,CAE7B,GAAI,IAAS,IAAK,CAChB,IAAM,EAAQ,EAAa,MAErB,GADM,EAAE,WACC,CAAM,EAAe,GAAK,EACnC,EAAY,KAAK,IAAI,EAAE,gBAAgB,CAAE,EAAE,aAAa,CAAE,EAAE,YAAY,CAAG,EAAM,CACvF,GAAI,GAAS,EAAG,OAAO,EACvB,IAAM,EAAY,KAAK,OAAO,EAAY,IAAmB,EAAI,GAAO,CAExE,OAAO,EADS,KAAK,MAAM,EAAY,EAAM,CAAG,EACd,EAAI,EAIxC,IAAM,EAAc,EAAE,gBAAgB,CAAG,GAAK,EAAE,aAAa,CACvD,EAAU,KAAK,MAAM,EAAc,EAAM,CAAG,EAClD,OAAO,KAAK,IAAI,KAAK,MAAM,EAAU,GAAG,CAAE,EAAU,GAAI,EAAE,CCxJ5D,IAAM,EAA4B,GAC5B,EAAoB,IAAI,IAS9B,SAAS,EAAa,EAAgB,EAAsC,CAC1E,IAAM,EAAM,GAAG,EAAO,GAAG,IACrB,EAAK,EAAkB,IAAI,EAAI,CASnC,OARK,IACC,EAAkB,MAAQ,GAA2B,EAAkB,OAAO,CAClF,EAAK,IAAI,KAAK,aAAa,EAAQ,CACjC,sBAAuB,EACvB,sBAAuB,EACxB,CAAC,CACF,EAAkB,IAAI,EAAK,EAAG,EAEzB,EAGT,SAAgB,EAAY,EAAe,EAAY,EAAG,EAAS,QAAiB,CAClF,OAAO,EAAa,EAAQ,EAAU,CAAC,OAAO,EAAM,CAStD,SAAgB,EAAmB,EAAa,EAAqB,CACnE,GAAI,EAAE,EAAM,IAAQ,CAAC,OAAO,SAAS,EAAI,EAAI,CAAC,OAAO,SAAS,EAAI,CAAE,MAAO,GAC3E,IAAM,EAAO,EAAgB,EAAK,EAAK,EAAE,CACzC,OAAO,EAAO,GAAK,EAAO,EAAI,KAAK,KAAK,CAAC,KAAK,MAAM,EAAK,CAAC,CAAG,EAAI,EAenE,SAAgB,EACd,EACA,EACA,EACA,EAAY,EACZ,EAAS,QACD,CACR,IAAK,IAAS,cAAgB,IAAS,iBAAmB,GAAY,IAAa,EAAG,CACpF,GAAI,IAAS,aAAc,CACzB,IAAM,GAAO,EAAQ,EAAW,GAAK,IAErC,MAAO,GADM,EAAM,EAAI,IAAM,KACZ,EAAI,QAAQ,EAAE,CAAC,GAGlC,OAAO,EADU,EAAQ,EAAY,IACT,EAAG,EAAO,CAExC,OAAO,EAAY,EAAO,EAAW,EAAO,CAG9C,SAAgB,EAAa,EAAuB,CAIlD,OAHI,GAAS,KAAuB,EAAQ,KAAe,QAAQ,EAAE,CAAG,IACpE,GAAS,KAAmB,EAAQ,KAAW,QAAQ,EAAE,CAAG,IAC5D,GAAS,KAAe,EAAQ,KAAO,QAAQ,EAAE,CAAG,IACjD,EAAM,QAAQ,EAAE,CAGzB,SAAgB,EAAgB,EAA0B,CACxD,IAAI,EAAc,EAClB,IAAK,IAAM,KAAK,EAAQ,CACtB,IAAM,EAAM,EAAE,UAAU,CAClB,EAAM,EAAI,QAAQ,IAAI,CACxB,GAAO,IACT,EAAc,KAAK,IAAI,EAAa,EAAI,OAAS,EAAM,EAAE,EAG7D,OAAO,KAAK,IAAI,EAAa,EAAE,CCrFjC,IAAa,EAAkK,CAC7K,UAAW,GACX,YAAa,EACb,cAAe,EACf,cAAe,GACf,KAAM,CACJ,QAAS,GACT,WAAY,QACZ,WAAY,QACb,CACD,UAAW,CACT,KAAM,SACP,CACF,CAMY,EAAiC,CAC5C,KAAM,KAAM,KAAM,KAAM,MAAO,MAC/B,KAAM,KAAM,KAAM,KAAM,KAAM,MAC9B,KAAM,KAAM,KAAM,KACnB,CAGY,EAAgC,CAC3C,KAAM,KAAM,MAAO,MACnB,KAAM,KAAM,KACZ,KAAM,KAAM,KAAM,KAAM,KAAM,MAC/B,CAGY,EAAgC,CAC3C,KAAM,KAAM,MAAO,MACnB,KAAM,KACN,KAAM,KAAM,KACb,CAGY,EAA2C,CACtD,KAAM,KAAM,MAAO,KAAM,KAAM,KAAM,KACtC,CAEY,EAAoB,EACpB,EAAsB,EACtB,EAAmB,GACnB,EAAmB,GACnB,EAAmB,GACnB,EAAuB,ICjD9B,EAAe,CACnB,OAAQ,oEACR,UAAW,GACX,WAAY,GACZ,UAAW,GACZ,CAUY,EAAa,CAExB,MAAO,UAEP,GAAI,UAEJ,KAAM,UAEN,MAAO,UAEP,KAAM,UACN,OAAQ,UACR,KAAM,UACN,KAAM,UAEN,QAAS,UAET,IAAK,UACN,CAGY,EAAsC,CACjD,EAAW,KACX,EAAW,MACX,EAAW,OACX,EAAW,KACX,EAAW,KACZ,CAEY,EAAoB,CAC/B,KAAM,OACN,WAAY,EAAW,IACvB,KAAM,UACN,cAAe,UACf,KAAM,UACN,UAAW,UACX,SAAU,UACV,WAAY,UACZ,aAAc,UACd,eAAgB,UAChB,UAAW,EAAW,MACtB,aAAc,2BACd,gBAAiB,0BACjB,SAAU,2BACV,WAAY,2BACZ,SAAU,UACV,UAAW,UACX,oBAAqB,UACrB,KAAM,EACP,CAEY,GAAqB,CAChC,KAAM,QACN,WAAY,UACZ,KAAM,UACN,cAAe,UACf,KAAM,UACN,UAAW,UACX,SAAU,UACV,WAAY,UACZ,aAAc,UACd,eAAgB,UAChB,UAAW,UACX,aAAc,2BACd,gBAAiB,0BACjB,SAAU,2BACV,WAAY,0BACZ,SAAU,UACV,UAAW,UACX,oBAAqB,UACrB,KAAM,EACP,CAEY,GAAuB,CAClC,KAAM,WACN,WAAY,UACZ,KAAM,UACN,cAAe,UACf,KAAM,UACN,UAAW,UACX,SAAU,UACV,WAAY,UACZ,aAAc,UACd,eAAgB,UAChB,UAAW,UACX,aAAc,0BACd,gBAAiB,0BACjB,SAAU,yBACV,WAAY,yBACZ,SAAU,UACV,UAAW,UACX,oBAAqB,UACrB,KAAM,CACJ,OAAQ,kEACR,UAAW,GACX,WAAY,GACZ,UAAW,GACZ,CACF,CCjHY,EAAoB,CAE/B,YAAa,cACb,KAAM,OACN,KAAM,OACN,IAAK,WAGL,MAAO,QACP,OAAQ,SACR,KAAM,OACN,KAAM,OACN,KAAM,OACN,IAAK,MACL,MAAO,QAGP,IAAK,MACL,IAAK,MACL,eAAgB,kBAChB,KAAM,OACN,SAAU,iBACV,aAAc,gBACd,WAAY,aACZ,eAAgB,kBAChB,gBAAiB,mBAGjB,IAAK,MACL,KAAM,OACN,WAAY,aACZ,IAAK,MACL,IAAK,MACL,IAAK,MACL,UAAW,cACX,IAAK,MACL,IAAK,MACL,MAAO,QACP,IAAK,MACL,IAAK,MACL,IAAK,MACL,OAAQ,UACR,cAAe,iBACf,yBAA0B,WAC1B,KAAM,OAGN,UAAW,aACX,eAAgB,kBAChB,aAAc,gBACd,IAAK,MACL,aAAc,gBACd,gBAAiB,mBACjB,kBAAmB,qBACnB,eAAgB,wBAChB,aAAc,sBACd,UAAW,YACX,QAAS,UACT,SAAU,WACV,UAAW,qBACX,YAAa,eACb,WAAY,cACZ,UAAW,aACX,QAAS,UACT,SAAU,OACV,MAAO,QACP,SAAU,YAGV,IAAK,MACL,KAAM,OACN,SAAU,YACV,UAAW,aACX,QAAS,WACT,SAAU,YACV,SAAU,YACV,WAAY,cACZ,OAAQ,SACR,MAAO,QACP,KAAM,OACN,OAAQ,SACR,OAAQ,SACR,SAAU,MACV,IAAK,MACL,aAAc,gBACd,UAAW,YACX,SAAU,mBACV,YAAa,oBACb,WAAY,cACZ,kBAAmB,oCAGnB,QAAS,UACT,MAAO,QACP,UAAW,YACX,QAAS,UACT,QAAS,WACT,WAAY,aACZ,SAAU,YACV,OAAQ,SAGR,SAAU,WACV,MAAO,QACP,UAAW,OACX,WAAY,QACZ,MAAO,QACP,WAAY,aACZ,SAAU,WACV,OAAQ,SACR,OAAQ,SACR,UAAW,aACX,UAAW,YACX,KAAM,OACN,QAAS,aACT,MAAO,QAEP,uBAAwB,IACxB,qBAAsB,IACvB,CCvHY,EAAoB,CAE/B,YAAa,MACb,KAAM,QACN,KAAM,OACN,IAAK,aAGL,MAAO,MACP,OAAQ,aACR,KAAM,YACN,KAAM,KACN,KAAM,MACN,IAAK,OACL,MAAO,OAGP,IAAK,MACL,IAAK,MACL,eAAgB,gBAChB,KAAM,OACN,SAAU,eACV,aAAc,gBACd,WAAY,aACZ,eAAgB,eAChB,gBAAiB,gBAGjB,IAAK,MACL,KAAM,OACN,WAAY,aACZ,IAAK,MACL,IAAK,MACL,IAAK,MACL,UAAW,cACX,IAAK,MACL,IAAK,MACL,MAAO,QACP,IAAK,MACL,IAAK,MACL,IAAK,MACL,OAAQ,gBACR,cAAe,aACf,yBAA0B,qBAC1B,KAAM,OAGN,UAAW,iBACX,eAAgB,cAChB,aAAc,YACd,IAAK,MACL,aAAc,gBACd,gBAAiB,iBACjB,kBAAmB,eACnB,eAAgB,sBAChB,aAAc,oBACd,UAAW,gBACX,QAAS,YACT,SAAU,WACV,UAAW,kBACX,YAAa,eACb,WAAY,aACZ,UAAW,mBACX,QAAS,WACT,SAAU,MACV,MAAO,UACP,SAAU,aAGV,IAAK,MACL,KAAM,MACN,SAAU,eACV,UAAW,eACX,QAAS,WACT,SAAU,WACV,SAAU,SACV,WAAY,WACZ,OAAQ,aACR,MAAO,WACP,KAAM,OACN,OAAQ,MACR,OAAQ,MACR,SAAU,KACV,IAAK,SACL,aAAc,WACd,UAAW,SACX,SAAU,oBACV,YAAa,qBACb,WAAY,WACZ,kBAAmB,8BAGnB,QAAS,OACT,MAAO,MACP,UAAW,aACX,QAAS,QACT,QAAS,eACT,WAAY,WACZ,SAAU,iBACV,OAAQ,WAGR,SAAU,UACV,MAAO,YACP,UAAW,MACX,WAAY,OACZ,MAAO,UACP,WAAY,UACZ,SAAU,UACV,OAAQ,OACR,OAAQ,OACR,UAAW,aACX,UAAW,WACX,KAAM,OACN,QAAS,cACT,MAAO,MAEP,uBAAwB,IACxB,qBAAsB,IACvB,CCjHK,EAAU,IAAI,IAA2B,CAC7C,CAAC,KAAM,EAAG,CACV,CAAC,KAAM,EAAG,CACX,CAAC,CAEE,EAAwB,KACxB,EAAgC,EAEpC,SAAgB,GAAU,EAAsB,CAC9C,EAAgB,EAChB,EAAiB,EAAQ,IAAI,EAAO,EAAI,EAG1C,SAAgB,IAAoB,CAClC,OAAO,EAGT,SAAgB,GAAE,EAAkC,CAClD,OAAO,EAAe,IAAS,EAAW,IAAQ,EAGpD,SAAgB,GAAe,EAAgB,EAA8B,CAC3E,EAAQ,IAAI,EAAQ,EAAQ,CAG9B,SAAgB,GAAiB,EAAgC,CAC/D,OAAO,EAAQ,IAAI,GAAU,EAAc,EAAI,EAIjD,SAAgB,EAAa,EAAe,EAAY,EAAG,EAAyB,CAClF,IAAM,EAAU,EAAQ,IAAI,GAAU,EAAc,EAAI,EAClD,EAAM,EAAQ,uBACd,EAAM,EAAQ,qBAGd,CAAC,EAAS,GADF,EAAM,QAAQ,EACD,CAAM,MAAM,IAAI,CAGrC,EAAW,EAAQ,WAAW,IAAI,CAClC,EAAS,EAAW,EAAQ,MAAM,EAAE,CAAG,EACzC,EAAU,GACd,IAAK,IAAI,EAAI,EAAO,OAAS,EAAG,EAAQ,EAAG,GAAK,EAAG,IAAK,IAClD,EAAQ,GAAK,EAAQ,GAAM,IAAG,EAAU,EAAM,GAClD,EAAU,EAAO,GAAK,EAIxB,OAFI,IAAU,EAAU,IAAM,GAEvB,EAAU,EAAU,EAAM,EAAU,EAG7C,SAAgB,GAAU,EAAuB,CAC/C,OAAO,EAAa,EAAO,EAAG,KAAK,CAGrC,SAAgB,GAAgB,EAAe,EAAyB,CAItE,OAHI,GAAS,IAAY,EAAa,EAAQ,IAAK,EAAG,GAAU,EAAc,CAAG,IAC7E,GAAS,IAAY,EAAa,EAAQ,IAAK,EAAG,GAAU,EAAc,CAAG,IAC7E,GAAS,IAAY,EAAa,EAAQ,IAAK,EAAG,GAAU,EAAc,CAAG,IAC1E,EAAa,EAAO,EAAG,GAAU,EAAc,CC3DxD,IAAa,EAA+B,CAC1C,GAAI,UACJ,KAAM,UACN,UAAW,UACX,QAAS,UACT,MAAO,UACP,UAAW,UACZ,CAEY,EAAkC,CAC7C,CAAE,KAAM,MAAO,UAAW,QAAS,QAAS,QAAS,KAAM,UAAW,CACtE,CAAE,KAAM,UAAW,UAAW,QAAS,QAAS,QAAS,KAAM,aAAc,CAC7E,CAAE,KAAM,YAAa,UAAW,QAAS,QAAS,QAAS,KAAM,SAAU,CAC3E,CAAE,KAAM,UAAW,UAAW,QAAS,QAAS,QAAS,KAAM,aAAc,CAC7E,CAAE,KAAM,MAAO,UAAW,QAAS,QAAS,QAAS,KAAM,WAAY,CACxE,CAEY,EAAiC,CAC5C,CAAE,KAAM,UAAW,UAAW,QAAS,QAAS,QAAS,KAAM,aAAc,CAC7E,CAAE,KAAM,YAAa,UAAW,QAAS,QAAS,QAAS,KAAM,SAAU,CAC3E,CAAE,KAAM,UAAW,UAAW,QAAS,QAAS,QAAS,KAAM,aAAc,CAC7E,CAAE,KAAM,MAAO,UAAW,QAAS,QAAS,QAAS,KAAM,WAAY,CACxE,CAGY,GAA4B,CACvC,KAAM,QACN,SAAU,OACV,SAAU,MACV,eAAgB,EAChB,WAAY,GACZ,UAAW,IACX,YAAa,CAAE,QAAS,GAAM,eAAgB,EAAG,aAAc,EAAG,CAClE,SAAU,EACV,YAAa,EACd,CAEY,GAA2B,CACtC,KAAM,QACN,SAAU,MACV,SAAU,MACV,eAAgB,EAChB,WAAY,IACZ,UAAW,GACX,YAAa,CAAE,QAAS,GAAM,eAAgB,GAAI,aAAc,GAAI,CACpE,SAAU,EACV,YAAa,EACd,CAEY,GAA6B,CACxC,KAAM,QACN,SAAU,QACV,SAAU,MACV,eAAgB,EAChB,WAAY,IACZ,UAAW,GACX,YAAa,CAAE,QAAS,GAAM,eAAgB,GAAI,aAAc,GAAI,CACpE,SAAU,EACV,YAAa,EACd,CAEY,GAA8B,CACzC,KAAM,SACN,SAAU,OACV,eAAgB,EAChB,YAAa,CAAE,QAAS,GAAO,CAChC,CAEY,EAA4B,CACvC,KAAM,QACN,SAAU,OACV,SAAU,MACV,eAAgB,EAChB,UAAW,IACX,YAAa,CAAE,QAAS,GAAO,CAC/B,SAAU,CACR,CAAE,KAAM,aAAc,UAAW,QAAS,QAAS,QAAS,KAAM,UAAW,CAC7E,CAAE,KAAM,UAAW,UAAW,QAAS,QAAS,QAAS,KAAM,aAAc,CAC7E,CAAE,KAAM,cAAe,UAAW,QAAS,QAAS,QAAS,KAAM,WAAY,CAChF,CACF,CAGD,SAAgB,GAAc,EAAoB,CAChD,MAAO,CACL,GAAG,EACH,SAAU,EAAU,GACpB,WAAY,EAAU,KACtB,aAAc,EAAU,GACxB,eAAgB,EAAU,KAC1B,SAAU,uBACV,WAAY,uBACb,CAGH,SAAgB,GAAmB,EAAwB,EAAoF,CAC7I,GAAI,CAAC,EAAO,aAAa,SAAW,CAAC,EAAO,YAAY,eAAgB,OAAO,KAC/E,IAAM,EAAU,EAAO,YAAY,eAAiB,IAC9C,GAAY,EAAO,YAAY,cAAgB,EAAO,YAAY,gBAAkB,IAC1F,MAAO,CACL,QAAS,GAAkB,EAAI,GAC/B,MAAO,GAAkB,EAAI,GAC7B,UAAW,EACZ,CAGH,SAAgB,GAAkB,EAAmD,CACnF,IAAM,EAAM,IAAI,KACV,EAAO,GAAG,OAAO,EAAI,UAAU,CAAC,CAAC,SAAS,EAAG,IAAI,CAAC,GAAG,OAAO,EAAI,YAAY,CAAC,CAAC,SAAS,EAAG,IAAI,GACpG,IAAK,IAAM,KAAW,EACpB,GAAI,GAAQ,EAAQ,WAAa,EAAO,EAAQ,QAAS,OAAO,EAElE,OAAO"}
|