@rioaxyz/sonrchart 0.1.0 → 0.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/README.md +14 -4
- package/dist/index.cjs +238 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -3
- package/dist/index.d.ts +72 -3
- package/dist/index.js +231 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13665,14 +13665,26 @@ var SESSION_INDICATOR = "SONR_SESSION";
|
|
|
13665
13665
|
var PATTERN_INDICATOR = "SONR_PATTERNS";
|
|
13666
13666
|
var WATERMARK_INDICATOR = "SONR_WATERMARK";
|
|
13667
13667
|
var SERIES_INDICATOR = "SONR_SERIES";
|
|
13668
|
+
var HALT_INDICATOR = "SONR_HALT";
|
|
13668
13669
|
var VWAP_INDICATOR = "VWAP";
|
|
13669
13670
|
var Z_ORDER = {
|
|
13670
13671
|
watermark: -3,
|
|
13671
13672
|
session: -2,
|
|
13672
13673
|
series: -1,
|
|
13673
13674
|
compare: 0,
|
|
13674
|
-
pattern: 10
|
|
13675
|
+
pattern: 10,
|
|
13676
|
+
halt: 11
|
|
13675
13677
|
};
|
|
13678
|
+
var DEFAULT_HALT_BADGE = { label: "Halt", color: "#2196f3", textColor: "#ffffff" };
|
|
13679
|
+
function normalizeHalt(prop) {
|
|
13680
|
+
if (!prop) return null;
|
|
13681
|
+
if (prop === true) return DEFAULT_HALT_BADGE;
|
|
13682
|
+
return {
|
|
13683
|
+
label: prop.label ?? DEFAULT_HALT_BADGE.label,
|
|
13684
|
+
color: prop.color ?? DEFAULT_HALT_BADGE.color,
|
|
13685
|
+
textColor: prop.textColor ?? DEFAULT_HALT_BADGE.textColor
|
|
13686
|
+
};
|
|
13687
|
+
}
|
|
13676
13688
|
var VWAP_COLOR = "#a970ff";
|
|
13677
13689
|
function computeVWAP(dataList) {
|
|
13678
13690
|
let day = Number.NaN;
|
|
@@ -13924,6 +13936,41 @@ function registerSonrIndicators() {
|
|
|
13924
13936
|
return false;
|
|
13925
13937
|
}
|
|
13926
13938
|
});
|
|
13939
|
+
registerIndicator({
|
|
13940
|
+
name: HALT_INDICATOR,
|
|
13941
|
+
shortName: "",
|
|
13942
|
+
series: "price",
|
|
13943
|
+
figures: [],
|
|
13944
|
+
calc: (dataList) => dataList.map(() => ({})),
|
|
13945
|
+
draw: ({ ctx, bounding, indicator }) => {
|
|
13946
|
+
const cfg = indicator.extendData;
|
|
13947
|
+
if (!cfg || !cfg.label) return false;
|
|
13948
|
+
const padX = 8;
|
|
13949
|
+
const h = 20;
|
|
13950
|
+
const inset = 8;
|
|
13951
|
+
ctx.save();
|
|
13952
|
+
ctx.font = '600 11px ui-sans-serif, -apple-system, "Segoe UI", sans-serif';
|
|
13953
|
+
const w = Math.ceil(ctx.measureText(cfg.label).width) + padX * 2;
|
|
13954
|
+
const x = bounding.width - w - inset;
|
|
13955
|
+
const y = (bounding.top ?? 0) + inset;
|
|
13956
|
+
const r = h / 2;
|
|
13957
|
+
ctx.beginPath();
|
|
13958
|
+
ctx.moveTo(x + r, y);
|
|
13959
|
+
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
13960
|
+
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
13961
|
+
ctx.arcTo(x, y + h, x, y, r);
|
|
13962
|
+
ctx.arcTo(x, y, x + w, y, r);
|
|
13963
|
+
ctx.closePath();
|
|
13964
|
+
ctx.fillStyle = cfg.color;
|
|
13965
|
+
ctx.fill();
|
|
13966
|
+
ctx.fillStyle = cfg.textColor;
|
|
13967
|
+
ctx.textAlign = "center";
|
|
13968
|
+
ctx.textBaseline = "middle";
|
|
13969
|
+
ctx.fillText(cfg.label, x + w / 2, y + h / 2 + 0.5);
|
|
13970
|
+
ctx.restore();
|
|
13971
|
+
return false;
|
|
13972
|
+
}
|
|
13973
|
+
});
|
|
13927
13974
|
registerIndicator({
|
|
13928
13975
|
name: PATTERN_INDICATOR,
|
|
13929
13976
|
shortName: "",
|
|
@@ -15276,6 +15323,25 @@ function formatWithUnit(text2, quote) {
|
|
|
15276
15323
|
return out;
|
|
15277
15324
|
}
|
|
15278
15325
|
|
|
15326
|
+
// src/volumeTrend.ts
|
|
15327
|
+
var DEFAULT_VOLUME_TREND_BARS = 3;
|
|
15328
|
+
function volumeTrend(list, endIndex, bars = DEFAULT_VOLUME_TREND_BARS) {
|
|
15329
|
+
const n = Math.max(1, Math.floor(bars));
|
|
15330
|
+
if (endIndex < n || endIndex >= list.length) return null;
|
|
15331
|
+
let up = 0;
|
|
15332
|
+
let down = 0;
|
|
15333
|
+
for (let i = endIndex - n + 1; i <= endIndex; i++) {
|
|
15334
|
+
const v = list[i]?.volume;
|
|
15335
|
+
const pv = list[i - 1]?.volume;
|
|
15336
|
+
if (typeof v !== "number" || typeof pv !== "number") return null;
|
|
15337
|
+
if (v > pv) up++;
|
|
15338
|
+
else if (v < pv) down++;
|
|
15339
|
+
}
|
|
15340
|
+
if (up === n) return "increasing";
|
|
15341
|
+
if (down === n) return "decreasing";
|
|
15342
|
+
return "mixed";
|
|
15343
|
+
}
|
|
15344
|
+
|
|
15279
15345
|
// src/SonrChart.tsx
|
|
15280
15346
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
15281
15347
|
registerSonrIndicators();
|
|
@@ -15285,7 +15351,7 @@ registerShapeOverlays();
|
|
|
15285
15351
|
registerExtendedDrawings();
|
|
15286
15352
|
registerTradeReview();
|
|
15287
15353
|
registerTextOverlay();
|
|
15288
|
-
var INTERNAL_INDICATORS = /* @__PURE__ */ new Set([SESSION_INDICATOR, PATTERN_INDICATOR, COMPARE_INDICATOR, WATERMARK_INDICATOR, SERIES_INDICATOR]);
|
|
15354
|
+
var INTERNAL_INDICATORS = /* @__PURE__ */ new Set([SESSION_INDICATOR, PATTERN_INDICATOR, COMPARE_INDICATOR, WATERMARK_INDICATOR, SERIES_INDICATOR, HALT_INDICATOR]);
|
|
15289
15355
|
var isUserIndicator = (name) => !INTERNAL_INDICATORS.has(name);
|
|
15290
15356
|
function magnetMode(m) {
|
|
15291
15357
|
if (m === true || m === "weak") return "weak_magnet";
|
|
@@ -15385,6 +15451,7 @@ var SonrChart = forwardRef(function SonrChart2({
|
|
|
15385
15451
|
sessions: sessionsProp,
|
|
15386
15452
|
watermark: watermarkProp,
|
|
15387
15453
|
patterns,
|
|
15454
|
+
halted,
|
|
15388
15455
|
priceLines,
|
|
15389
15456
|
orderLines,
|
|
15390
15457
|
priceScale: priceScaleProp,
|
|
@@ -15502,14 +15569,24 @@ var SonrChart = forwardRef(function SonrChart2({
|
|
|
15502
15569
|
const legendRef = useRef2(legend);
|
|
15503
15570
|
legendRef.current = legend;
|
|
15504
15571
|
const headerSubs = useRef2(/* @__PURE__ */ new Set());
|
|
15572
|
+
const trendSubs = useRef2(/* @__PURE__ */ new Set());
|
|
15573
|
+
const trendBarsRef = useRef2(DEFAULT_VOLUME_TREND_BARS);
|
|
15574
|
+
const trendBarsSubs = useRef2(/* @__PURE__ */ new Set());
|
|
15575
|
+
const trendOnRef = useRef2(false);
|
|
15576
|
+
const trendOnSubs = useRef2(/* @__PURE__ */ new Set());
|
|
15505
15577
|
const crosshairActiveRef = useRef2(false);
|
|
15578
|
+
const headerIdxRef = useRef2(-1);
|
|
15579
|
+
const emitHeaderAt = (list, idx) => {
|
|
15580
|
+
headerIdxRef.current = idx;
|
|
15581
|
+
const current = idx >= 0 ? list[idx] ?? null : null;
|
|
15582
|
+
const prev = idx > 0 ? list[idx - 1] : null;
|
|
15583
|
+
headerSubs.current.forEach((cb) => cb(current, prev));
|
|
15584
|
+
trendSubs.current.forEach((s) => s.cb(volumeTrend(list, idx, s.bars ?? trendBarsRef.current)));
|
|
15585
|
+
};
|
|
15506
15586
|
const emitLatestHeader = () => {
|
|
15507
15587
|
if (crosshairActiveRef.current) return;
|
|
15508
15588
|
const list = chartRef.current?.getDataList() ?? [];
|
|
15509
|
-
|
|
15510
|
-
const current = n ? list[n - 1] : null;
|
|
15511
|
-
const prev = n > 1 ? list[n - 2] : null;
|
|
15512
|
-
headerSubs.current.forEach((cb) => cb(current, prev));
|
|
15589
|
+
emitHeaderAt(list, list.length - 1);
|
|
15513
15590
|
};
|
|
15514
15591
|
const haRef = useRef2(new HaTransformer());
|
|
15515
15592
|
const [textEdit, setTextEdit] = useState(null);
|
|
@@ -15777,6 +15854,40 @@ var SonrChart = forwardRef(function SonrChart2({
|
|
|
15777
15854
|
const n = list.length;
|
|
15778
15855
|
cb(n ? list[n - 1] : null, n > 1 ? list[n - 2] : null);
|
|
15779
15856
|
return () => headerSubs.current.delete(cb);
|
|
15857
|
+
},
|
|
15858
|
+
// Volume trend (#79) ---------------------------------------------------
|
|
15859
|
+
subscribeVolumeTrend: (cb, bars) => {
|
|
15860
|
+
const sub = { bars: bars === void 0 ? void 0 : Math.max(1, Math.floor(bars)), cb };
|
|
15861
|
+
trendSubs.current.add(sub);
|
|
15862
|
+
const list = chartRef.current?.getDataList() ?? [];
|
|
15863
|
+
cb(volumeTrend(list, list.length - 1, sub.bars ?? trendBarsRef.current));
|
|
15864
|
+
return () => trendSubs.current.delete(sub);
|
|
15865
|
+
},
|
|
15866
|
+
getVolumeTrendBars: () => trendBarsRef.current,
|
|
15867
|
+
setVolumeTrendBars: (bars) => {
|
|
15868
|
+
const n = Math.max(1, Math.floor(bars));
|
|
15869
|
+
if (!Number.isFinite(n) || n === trendBarsRef.current) return;
|
|
15870
|
+
trendBarsRef.current = n;
|
|
15871
|
+
const list = chartRef.current?.getDataList() ?? [];
|
|
15872
|
+
const idx = crosshairActiveRef.current ? headerIdxRef.current : list.length - 1;
|
|
15873
|
+
trendSubs.current.forEach((s) => {
|
|
15874
|
+
if (s.bars === void 0) s.cb(volumeTrend(list, idx, n));
|
|
15875
|
+
});
|
|
15876
|
+
trendBarsSubs.current.forEach((cb) => cb(n));
|
|
15877
|
+
},
|
|
15878
|
+
subscribeVolumeTrendBars: (cb) => {
|
|
15879
|
+
trendBarsSubs.current.add(cb);
|
|
15880
|
+
return () => trendBarsSubs.current.delete(cb);
|
|
15881
|
+
},
|
|
15882
|
+
getVolumeTrendEnabled: () => trendOnRef.current,
|
|
15883
|
+
setVolumeTrendEnabled: (on) => {
|
|
15884
|
+
if (on === trendOnRef.current) return;
|
|
15885
|
+
trendOnRef.current = on;
|
|
15886
|
+
trendOnSubs.current.forEach((cb) => cb(on));
|
|
15887
|
+
},
|
|
15888
|
+
subscribeVolumeTrendEnabled: (cb) => {
|
|
15889
|
+
trendOnSubs.current.add(cb);
|
|
15890
|
+
return () => trendOnSubs.current.delete(cb);
|
|
15780
15891
|
}
|
|
15781
15892
|
},
|
|
15782
15893
|
// paneHeights/priceScale captured by closure; recreated per render is fine.
|
|
@@ -15840,16 +15951,29 @@ var SonrChart = forwardRef(function SonrChart2({
|
|
|
15840
15951
|
chart.setStyles(mergeStyles(themeStyleObj, styles2));
|
|
15841
15952
|
chart.subscribeAction("onCrosshairChange", (d) => {
|
|
15842
15953
|
const cross = d;
|
|
15843
|
-
const
|
|
15844
|
-
|
|
15954
|
+
const list = chart.getDataList();
|
|
15955
|
+
let k = cross?.kLineData ?? null;
|
|
15956
|
+
let idx = typeof cross?.dataIndex === "number" ? cross.dataIndex : -1;
|
|
15957
|
+
if (!k && typeof cross?.x === "number" && list.length > 0) {
|
|
15958
|
+
const [p] = chart.convertFromPixel([{ x: cross.x }], { paneId: CANDLE_PANE });
|
|
15959
|
+
if (typeof p?.dataIndex === "number") {
|
|
15960
|
+
idx = Math.max(0, Math.min(p.dataIndex, list.length - 1));
|
|
15961
|
+
k = list[idx] ?? null;
|
|
15962
|
+
}
|
|
15963
|
+
}
|
|
15964
|
+
if (k && idx >= 0) {
|
|
15845
15965
|
crosshairActiveRef.current = true;
|
|
15846
|
-
|
|
15847
|
-
headerSubs.current.forEach((cb) => cb(k, cross.dataIndex > 0 ? list[cross.dataIndex - 1] : null));
|
|
15966
|
+
emitHeaderAt(list, idx);
|
|
15848
15967
|
} else {
|
|
15849
15968
|
crosshairActiveRef.current = false;
|
|
15850
15969
|
emitLatestHeader();
|
|
15851
15970
|
}
|
|
15852
15971
|
});
|
|
15972
|
+
const onPointerLeave = () => {
|
|
15973
|
+
crosshairActiveRef.current = false;
|
|
15974
|
+
emitLatestHeader();
|
|
15975
|
+
};
|
|
15976
|
+
el.addEventListener("mouseleave", onPointerLeave);
|
|
15853
15977
|
applyCandleStyle(chart, candleStyleRef.current, themeStyleObj, baseTheme);
|
|
15854
15978
|
applyPriceLabels(chart, priceLabelsRef.current);
|
|
15855
15979
|
applyFormatter(chart);
|
|
@@ -15959,6 +16083,7 @@ var SonrChart = forwardRef(function SonrChart2({
|
|
|
15959
16083
|
return () => {
|
|
15960
16084
|
disposed = true;
|
|
15961
16085
|
unsub();
|
|
16086
|
+
el.removeEventListener("mouseleave", onPointerLeave);
|
|
15962
16087
|
dispose(idRef.current);
|
|
15963
16088
|
chartRef.current = null;
|
|
15964
16089
|
overlayIds.current = [];
|
|
@@ -16030,6 +16155,19 @@ var SonrChart = forwardRef(function SonrChart2({
|
|
|
16030
16155
|
chart.removeIndicator({ name: PATTERN_INDICATOR });
|
|
16031
16156
|
}
|
|
16032
16157
|
}, [patterns, chartEpoch]);
|
|
16158
|
+
useEffect(() => {
|
|
16159
|
+
const chart = chartRef.current;
|
|
16160
|
+
if (!chart) return;
|
|
16161
|
+
const present = chart.getIndicators({ name: HALT_INDICATOR }).length > 0;
|
|
16162
|
+
const badge = normalizeHalt(halted);
|
|
16163
|
+
if (badge && !present) {
|
|
16164
|
+
chart.createIndicator({ name: HALT_INDICATOR, paneId: CANDLE_PANE, zLevel: Z_ORDER.halt, extendData: badge }, true);
|
|
16165
|
+
} else if (badge && present) {
|
|
16166
|
+
chart.overrideIndicator({ name: HALT_INDICATOR, extendData: badge });
|
|
16167
|
+
} else if (!badge && present) {
|
|
16168
|
+
chart.removeIndicator({ name: HALT_INDICATOR });
|
|
16169
|
+
}
|
|
16170
|
+
}, [halted, chartEpoch]);
|
|
16033
16171
|
useEffect(() => {
|
|
16034
16172
|
const chart = chartRef.current;
|
|
16035
16173
|
if (!chart) return;
|
|
@@ -16283,7 +16421,7 @@ var CSS = `
|
|
|
16283
16421
|
:where(.sonr-mkt-handle){position:absolute;top:50%;width:12px;height:12px;border-radius:50%;background:#fff;border:2px solid #22a06b;transform:translate(-50%,-50%);}
|
|
16284
16422
|
:where(.sonr-mkt-ticks){grid-column:2;display:flex;justify-content:space-between;font-size:10px;color:#888;}
|
|
16285
16423
|
:where(.sonr-mkt-tz){margin-top:12px;font-size:11px;color:#888;text-align:center;}
|
|
16286
|
-
:where(.sonr-hd){display:inline-flex;align-items:center;gap:10px;font-family:inherit;font-size:12px;color:#000;white-space:nowrap;}
|
|
16424
|
+
:where(.sonr-hd){display:inline-flex;flex-wrap:wrap;align-items:center;gap:10px;font-family:inherit;font-size:12px;color:#000;white-space:nowrap;}
|
|
16287
16425
|
:where(.sonr-hd-sym){font-weight:700;color:#000;}
|
|
16288
16426
|
:where(.sonr-hd-ohlc){display:inline-flex;align-items:center;gap:8px;font-variant-numeric:tabular-nums;}
|
|
16289
16427
|
:where(.sonr-hd-cell){display:inline-flex;align-items:baseline;gap:3px;}
|
|
@@ -16291,6 +16429,8 @@ var CSS = `
|
|
|
16291
16429
|
:where(.sonr-hd-v){color:#555;}
|
|
16292
16430
|
:where(.sonr-hd-up){color:#22a06b;}
|
|
16293
16431
|
:where(.sonr-hd-down){color:#d23c3c;}
|
|
16432
|
+
:where(.sonr-hd-trend){flex-basis:100%;display:inline-flex;align-items:baseline;gap:4px;margin-top:2px;}
|
|
16433
|
+
:where(.sonr-hd-mixed){color:#c98a1b;}
|
|
16294
16434
|
:where(.sonr-brand){position:absolute;z-index:3;pointer-events:none;font-family:inherit;font-weight:700;font-size:13px;letter-spacing:0.02em;color:#000;user-select:none;line-height:1;}
|
|
16295
16435
|
:where(.sonr-brand-img){display:block;}
|
|
16296
16436
|
:where(.sonr-brand-link){pointer-events:auto;color:inherit;text-decoration:none;}
|
|
@@ -16466,6 +16606,10 @@ var DEFAULT_LABELS = {
|
|
|
16466
16606
|
zoomIn: "Zoom in",
|
|
16467
16607
|
fitContent: "Fit all data",
|
|
16468
16608
|
goToRealtime: "Go to realtime",
|
|
16609
|
+
volumeTrend: "Vol trend",
|
|
16610
|
+
volumeTrendIncreasing: "Increasing",
|
|
16611
|
+
volumeTrendDecreasing: "Decreasing",
|
|
16612
|
+
volumeTrendMixed: "Mixed",
|
|
16469
16613
|
marketPre: "Pre-market",
|
|
16470
16614
|
marketOpen: "Open",
|
|
16471
16615
|
marketAfter: "After-hours",
|
|
@@ -16496,6 +16640,7 @@ function mergeLabels(over) {
|
|
|
16496
16640
|
|
|
16497
16641
|
// src/IndicatorMenu.tsx
|
|
16498
16642
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
16643
|
+
var VOLUME_TREND_OPTION = "VOLTREND";
|
|
16499
16644
|
var DEFAULT_INDICATOR_OPTIONS = [
|
|
16500
16645
|
// Price-pane overlays.
|
|
16501
16646
|
{ name: "MA", label: "Moving Average", pane: "main" },
|
|
@@ -16508,6 +16653,7 @@ var DEFAULT_INDICATOR_OPTIONS = [
|
|
|
16508
16653
|
{ name: "VWAP", label: "Volume Weighted Avg Price", pane: "main" },
|
|
16509
16654
|
// Sub-pane oscillators & volume studies.
|
|
16510
16655
|
{ name: "VOL", label: "Volume", pane: "new" },
|
|
16656
|
+
{ name: VOLUME_TREND_OPTION, label: "Volume Trend (header row)" },
|
|
16511
16657
|
{ name: "MACD", label: "MACD", pane: "new" },
|
|
16512
16658
|
{ name: "RSI", label: "Relative Strength Index", pane: "new" },
|
|
16513
16659
|
{ name: "KDJ", label: "KDJ Stochastic", pane: "new" },
|
|
@@ -16538,12 +16684,27 @@ function IndicatorMenu({
|
|
|
16538
16684
|
const [active, setActive] = useState3([]);
|
|
16539
16685
|
const { open, toggle: toggleOpen, pos, triggerRef, panelRef } = useDropdown();
|
|
16540
16686
|
useEffect3(() => {
|
|
16541
|
-
|
|
16542
|
-
|
|
16687
|
+
let unsubOn;
|
|
16688
|
+
const t = setTimeout(() => {
|
|
16689
|
+
const h = chartRef.current;
|
|
16690
|
+
const base = h?.listIndicators() ?? [];
|
|
16691
|
+
setActive(h?.getVolumeTrendEnabled?.() ? [...base, VOLUME_TREND_OPTION] : base);
|
|
16692
|
+
unsubOn = h?.subscribeVolumeTrendEnabled?.(
|
|
16693
|
+
(on) => setActive((a) => on ? a.includes(VOLUME_TREND_OPTION) ? a : [...a, VOLUME_TREND_OPTION] : a.filter((n) => n !== VOLUME_TREND_OPTION))
|
|
16694
|
+
);
|
|
16695
|
+
}, 0);
|
|
16696
|
+
return () => {
|
|
16697
|
+
clearTimeout(t);
|
|
16698
|
+
unsubOn?.();
|
|
16699
|
+
};
|
|
16543
16700
|
}, [chartRef]);
|
|
16544
16701
|
const toggle = (opt) => {
|
|
16545
16702
|
const handle = chartRef.current;
|
|
16546
16703
|
if (!handle) return;
|
|
16704
|
+
if (opt.name === VOLUME_TREND_OPTION) {
|
|
16705
|
+
handle.setVolumeTrendEnabled?.(!active.includes(opt.name));
|
|
16706
|
+
return;
|
|
16707
|
+
}
|
|
16547
16708
|
if (active.includes(opt.name)) {
|
|
16548
16709
|
handle.removeIndicator(opt.name);
|
|
16549
16710
|
setActive((a) => a.filter((n) => n !== opt.name));
|
|
@@ -17984,6 +18145,7 @@ function SettingsModal({
|
|
|
17984
18145
|
const [indicators2, setIndicators] = useState11([]);
|
|
17985
18146
|
const [indColors, setIndColors] = useState11({});
|
|
17986
18147
|
const [indParams, setIndParams] = useState11({});
|
|
18148
|
+
const [trendBars, setTrendBars] = useState11(DEFAULT_VOLUME_TREND_BARS);
|
|
17987
18149
|
const [watermark, setWatermark] = useState11({ symbol: true, interval: true, color: "#e6ebe9", opacity: 0.06 });
|
|
17988
18150
|
const [brushStyles, setBrushStyles] = useState11({
|
|
17989
18151
|
brush: { color: "#e6ebe9", size: 2 },
|
|
@@ -18018,6 +18180,8 @@ function SettingsModal({
|
|
|
18018
18180
|
}
|
|
18019
18181
|
}
|
|
18020
18182
|
setIndParams(params);
|
|
18183
|
+
setTrendBars(chartRef.current?.getVolumeTrendBars?.() ?? DEFAULT_VOLUME_TREND_BARS);
|
|
18184
|
+
const unsubTrendBars = chartRef.current?.subscribeVolumeTrendBars?.(setTrendBars);
|
|
18021
18185
|
const readWm = () => {
|
|
18022
18186
|
const w = chartRef.current?.getWatermark?.();
|
|
18023
18187
|
if (w) setWatermark(w);
|
|
@@ -18060,6 +18224,7 @@ function SettingsModal({
|
|
|
18060
18224
|
document.addEventListener("keydown", onKey);
|
|
18061
18225
|
return () => {
|
|
18062
18226
|
document.removeEventListener("keydown", onKey);
|
|
18227
|
+
unsubTrendBars?.();
|
|
18063
18228
|
unsubWm?.();
|
|
18064
18229
|
unsubBrush?.();
|
|
18065
18230
|
unsubText?.();
|
|
@@ -18168,6 +18333,25 @@ function SettingsModal({
|
|
|
18168
18333
|
) })
|
|
18169
18334
|
] }),
|
|
18170
18335
|
section === "indicators" && /* @__PURE__ */ jsxs14(Fragment4, { children: [
|
|
18336
|
+
/* @__PURE__ */ jsx15("div", { className: "sonr-set-subhead", children: L.volumeTrend }),
|
|
18337
|
+
/* @__PURE__ */ jsx15(Row, { label: "Bars (lookback)", children: /* @__PURE__ */ jsx15(
|
|
18338
|
+
"input",
|
|
18339
|
+
{
|
|
18340
|
+
type: "number",
|
|
18341
|
+
className: "sonr-set-num",
|
|
18342
|
+
min: "1",
|
|
18343
|
+
max: "100",
|
|
18344
|
+
value: trendBars,
|
|
18345
|
+
onChange: (e) => {
|
|
18346
|
+
const v = e.target.valueAsNumber;
|
|
18347
|
+
if (Number.isNaN(v)) return;
|
|
18348
|
+
const n = Math.max(1, Math.min(100, Math.floor(v)));
|
|
18349
|
+
setTrendBars(n);
|
|
18350
|
+
chartRef.current?.setVolumeTrendBars?.(n);
|
|
18351
|
+
}
|
|
18352
|
+
}
|
|
18353
|
+
) }),
|
|
18354
|
+
/* @__PURE__ */ jsx15("div", { className: "sonr-set-subhead", children: "Active indicators" }),
|
|
18171
18355
|
/* @__PURE__ */ jsx15("p", { className: "sonr-set-note", children: "An indicator must be active on the chart to change its settings \u2014 add it from the Indicators menu first." }),
|
|
18172
18356
|
indicators2.length === 0 && /* @__PURE__ */ jsx15("p", { className: "sonr-set-empty", children: "No indicators active." }),
|
|
18173
18357
|
indicators2.map((name) => {
|
|
@@ -18797,18 +18981,28 @@ var fmtVolume = (v) => {
|
|
|
18797
18981
|
if (n >= 1e3) return `${(n / 1e3).toFixed(2)}K`;
|
|
18798
18982
|
return `${n}`;
|
|
18799
18983
|
};
|
|
18800
|
-
function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, labels, className }) {
|
|
18984
|
+
function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, volumeTrend: volumeTrend2, labels, className }) {
|
|
18801
18985
|
useEffect16(ensureDefaultStyles, []);
|
|
18802
18986
|
const L = useMemo12(() => mergeLabels(labels), [labels]);
|
|
18803
18987
|
const [bar, setBar] = useState14({ current: null, prev: null });
|
|
18988
|
+
const [trend, setTrend] = useState14(null);
|
|
18989
|
+
const [trendOn, setTrendOn] = useState14(false);
|
|
18990
|
+
const seedOn = volumeTrend2 === true || typeof volumeTrend2 === "number" && volumeTrend2 > 0;
|
|
18804
18991
|
useEffect16(() => {
|
|
18805
18992
|
let cancelled = false;
|
|
18806
18993
|
let unsub;
|
|
18994
|
+
let unsubOn;
|
|
18807
18995
|
const attach = () => {
|
|
18808
18996
|
if (cancelled) return;
|
|
18809
18997
|
const h = chartRef.current;
|
|
18810
18998
|
if (h?.subscribeHeaderBar) {
|
|
18811
18999
|
unsub = h.subscribeHeaderBar((current, prev) => setBar({ current, prev }));
|
|
19000
|
+
if (seedOn) {
|
|
19001
|
+
if (typeof volumeTrend2 === "number") h.setVolumeTrendBars?.(volumeTrend2);
|
|
19002
|
+
h.setVolumeTrendEnabled?.(true);
|
|
19003
|
+
}
|
|
19004
|
+
setTrendOn(h.getVolumeTrendEnabled?.() ?? seedOn);
|
|
19005
|
+
unsubOn = h.subscribeVolumeTrendEnabled?.(setTrendOn);
|
|
18812
19006
|
} else {
|
|
18813
19007
|
setTimeout(attach, 50);
|
|
18814
19008
|
}
|
|
@@ -18817,8 +19011,17 @@ function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, l
|
|
|
18817
19011
|
return () => {
|
|
18818
19012
|
cancelled = true;
|
|
18819
19013
|
unsub?.();
|
|
19014
|
+
unsubOn?.();
|
|
18820
19015
|
};
|
|
18821
|
-
}, [chartRef]);
|
|
19016
|
+
}, [chartRef, volumeTrend2, seedOn]);
|
|
19017
|
+
useEffect16(() => {
|
|
19018
|
+
if (!trendOn) {
|
|
19019
|
+
setTrend(null);
|
|
19020
|
+
return;
|
|
19021
|
+
}
|
|
19022
|
+
const unsubTrend = chartRef.current?.subscribeVolumeTrend?.((t) => setTrend(t));
|
|
19023
|
+
return () => unsubTrend?.();
|
|
19024
|
+
}, [chartRef, trendOn]);
|
|
18822
19025
|
const k = bar.current;
|
|
18823
19026
|
const up = (k?.close ?? 0) >= (k?.open ?? 0);
|
|
18824
19027
|
const dirClass = up ? "sonr-hd-up" : "sonr-hd-down";
|
|
@@ -18859,6 +19062,10 @@ function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, l
|
|
|
18859
19062
|
/* @__PURE__ */ jsx18("span", { className: "sonr-hd-k", children: "V" }),
|
|
18860
19063
|
/* @__PURE__ */ jsx18("span", { className: "sonr-hd-v", children: fmtVolume(k.volume) })
|
|
18861
19064
|
] })
|
|
19065
|
+
] }),
|
|
19066
|
+
trendOn && trend && /* @__PURE__ */ jsxs17("span", { className: "sonr-hd-trend", children: [
|
|
19067
|
+
/* @__PURE__ */ jsx18("span", { className: "sonr-hd-k", children: L.volumeTrend }),
|
|
19068
|
+
/* @__PURE__ */ jsx18("span", { className: trend === "increasing" ? "sonr-hd-up" : trend === "decreasing" ? "sonr-hd-down" : "sonr-hd-mixed", children: trend === "increasing" ? L.volumeTrendIncreasing : trend === "decreasing" ? L.volumeTrendDecreasing : L.volumeTrendMixed })
|
|
18862
19069
|
] })
|
|
18863
19070
|
] });
|
|
18864
19071
|
}
|
|
@@ -19321,6 +19528,7 @@ var SonrChartShell = forwardRef2(function SonrChartShell2({
|
|
|
19321
19528
|
ranges,
|
|
19322
19529
|
source,
|
|
19323
19530
|
exchange = source,
|
|
19531
|
+
volumeTrend: volumeTrend2 = true,
|
|
19324
19532
|
onIntervalChange,
|
|
19325
19533
|
onSymbolChange,
|
|
19326
19534
|
brand,
|
|
@@ -19394,7 +19602,7 @@ var SonrChartShell = forwardRef2(function SonrChartShell2({
|
|
|
19394
19602
|
style: { position: "absolute", inset: 0 }
|
|
19395
19603
|
}
|
|
19396
19604
|
),
|
|
19397
|
-
/* @__PURE__ */ jsx25("div", { className: "sonr-tile-header", children: /* @__PURE__ */ jsx25(ChartHeader, { chartRef, symbol, interval, source }) }),
|
|
19605
|
+
/* @__PURE__ */ jsx25("div", { className: "sonr-tile-header", children: /* @__PURE__ */ jsx25(ChartHeader, { chartRef, symbol, interval, source, volumeTrend: volumeTrend2, labels }) }),
|
|
19398
19606
|
/* @__PURE__ */ jsx25("div", { className: "sonr-tile-nav", children: /* @__PURE__ */ jsx25(NavControlBar, { chartRef, labels }) }),
|
|
19399
19607
|
brand != null && /* @__PURE__ */ jsx25(BrandMark, { position: brandCorner, opacity: brandOpacity, children: brand })
|
|
19400
19608
|
] }),
|
|
@@ -19456,6 +19664,7 @@ export {
|
|
|
19456
19664
|
ConfirmDialog,
|
|
19457
19665
|
DEFAULT_CANDLE_STYLES,
|
|
19458
19666
|
DEFAULT_DRAWING_TOOLS,
|
|
19667
|
+
DEFAULT_HALT_BADGE,
|
|
19459
19668
|
DEFAULT_INDICATOR_OPTIONS,
|
|
19460
19669
|
DEFAULT_LABELS,
|
|
19461
19670
|
DEFAULT_RANGES,
|
|
@@ -19463,6 +19672,7 @@ export {
|
|
|
19463
19672
|
DEFAULT_SESSION_COLORS,
|
|
19464
19673
|
DEFAULT_TIMEZONE,
|
|
19465
19674
|
DEFAULT_TIMEZONES,
|
|
19675
|
+
DEFAULT_VOLUME_TREND_BARS,
|
|
19466
19676
|
DataWindow,
|
|
19467
19677
|
DrawingTemplateStore,
|
|
19468
19678
|
DrawingToolbar,
|
|
@@ -19470,6 +19680,7 @@ export {
|
|
|
19470
19680
|
EXTENDED_OVERLAYS,
|
|
19471
19681
|
FIB_EXTENSION_LEVELS,
|
|
19472
19682
|
GoToDateDialog,
|
|
19683
|
+
HALT_INDICATOR,
|
|
19473
19684
|
IndicatorMenu,
|
|
19474
19685
|
MarketStatus,
|
|
19475
19686
|
NavControlBar,
|
|
@@ -19498,6 +19709,7 @@ export {
|
|
|
19498
19709
|
ToolbarButton,
|
|
19499
19710
|
UNIT_MS,
|
|
19500
19711
|
USER_DRAWING_GROUP,
|
|
19712
|
+
VOLUME_TREND_OPTION,
|
|
19501
19713
|
VWAP_INDICATOR,
|
|
19502
19714
|
WATERMARK_INDICATOR,
|
|
19503
19715
|
arrowHeadPoints,
|
|
@@ -19515,6 +19727,7 @@ export {
|
|
|
19515
19727
|
isEditableTarget,
|
|
19516
19728
|
marketStatus,
|
|
19517
19729
|
mergeLabels,
|
|
19730
|
+
normalizeHalt,
|
|
19518
19731
|
parseLocalDateTime,
|
|
19519
19732
|
parseResolution,
|
|
19520
19733
|
priceRangeLabel,
|
|
@@ -19533,7 +19746,8 @@ export {
|
|
|
19533
19746
|
themeStyles,
|
|
19534
19747
|
timezoneLabel,
|
|
19535
19748
|
toHeikinAshi,
|
|
19536
|
-
tradeBoxFigures
|
|
19749
|
+
tradeBoxFigures,
|
|
19750
|
+
volumeTrend
|
|
19537
19751
|
};
|
|
19538
19752
|
/*! Bundled license information:
|
|
19539
19753
|
|