wickchart 1.2.0 → 1.4.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/types/core.d.ts CHANGED
@@ -747,6 +747,162 @@ export declare function normalizeScenario(spec: any): null | {
747
747
  color: string | null;
748
748
  label: string;
749
749
  };
750
+ /**
751
+ * Validate a risk plan: entry + stop define 1R (the risk unit per trade);
752
+ * reward levels are drawn at R multiples beyond the entry. Invalid input
753
+ * is dropped, never thrown — same contract as setOverlays/setScenario.
754
+ *
755
+ * { entry: 64500, stop: 63800, // stop < entry ⇒ long; else short
756
+ * multiples: [1, 2, 3], // R-multiple levels (default [1,2,3])
757
+ * targets: [65900, 67300], // alternative: explicit prices → kR
758
+ * label: 'breakout plan' } // ≤ 40 chars
759
+ *
760
+ * Explicit `targets` are converted to their (signed) R multiple; levels on
761
+ * the wrong side of the entry (negative or ~zero R) are dropped. `multiples`
762
+ * win when both are given. At most 8 levels, each ≤ 20R.
763
+ *
764
+ * @param {any} spec
765
+ * @returns {null|{entry: number, stop: number, risk: number,
766
+ * direction: 'long'|'short', levels: {k: number, price: number}[],
767
+ * maxK: number, label: string}}
768
+ */
769
+ export declare function normalizeRiskPlan(spec: any): null | {
770
+ entry: number;
771
+ stop: number;
772
+ risk: number;
773
+ direction: 'long' | 'short';
774
+ levels: {
775
+ k: number;
776
+ price: number;
777
+ }[];
778
+ maxK: number;
779
+ label: string;
780
+ };
781
+ /**
782
+ * Turn a bar window into an ordered story: the annotation events (pivot
783
+ * highs/lows, volume spikes, gaps, RSI divergences) plus derived **legs** —
784
+ * the move between consecutive opposite pivots ("+12.4% over 38 bars").
785
+ * The timeline drives the bar-walk player and any caption UI.
786
+ *
787
+ * @param {Bar[]} bars full dataset
788
+ * @param {number} i0 first index of the window
789
+ * @param {number} i1 last index of the window
790
+ * @param {{pivot?: number, volMult?: number, gapMult?: number, rsiPeriod?: number}} [opts]
791
+ * pivot window defaults to 8 (denser than the annotations overlay's 20)
792
+ * @returns {{i: number, time: number, type: string, side: string, note: string,
793
+ * legPct?: number, legBars?: number}[]} sorted by index, capped at 60
794
+ */
795
+ export declare function narrateWindow(bars: Bar[], i0: number, i1: number, opts?: {
796
+ pivot?: number;
797
+ volMult?: number;
798
+ gapMult?: number;
799
+ rsiPeriod?: number;
800
+ }): {
801
+ i: number;
802
+ time: number;
803
+ type: string;
804
+ side: string;
805
+ note: string;
806
+ legPct?: number;
807
+ legBars?: number;
808
+ }[];
809
+ /**
810
+ * Stats for a brushed bar range: net move (open of the first bar → close
811
+ * of the last), extremes, and summed volume. Powers the brush-selection
812
+ * overlay and its `wick:brush` event.
813
+ *
814
+ * @param {Bar[]} bars full dataset
815
+ * @param {number} i0 first selected index
816
+ * @param {number} i1 last selected index
817
+ * @returns {null|{bars: number, from: {index: number, time: number},
818
+ * to: {index: number, time: number}, firstOpen: number,
819
+ * lastClose: number, delta: number, deltaPct: number,
820
+ * high: number, low: number, volume: number}}
821
+ */
822
+ export declare function brushStats(bars: Bar[], i0: number, i1: number): null | {
823
+ bars: number;
824
+ from: {
825
+ index: number;
826
+ time: number;
827
+ };
828
+ to: {
829
+ index: number;
830
+ time: number;
831
+ };
832
+ firstOpen: number;
833
+ lastClose: number;
834
+ delta: number;
835
+ deltaPct: number;
836
+ high: number;
837
+ low: number;
838
+ volume: number;
839
+ };
840
+ /** Smoothest cheap easing for viewport pans: slow in, slow out. */
841
+ export declare function easeInOutCubic(t: any): number;
842
+ /**
843
+ * Validate one story scene. Every field is optional except that a scene
844
+ * must be an object; omitted fields simply don't change that aspect of
845
+ * the chart when played. `scenario`/`riskPlan` use a 'clear' sentinel for
846
+ * explicit "remove it" (null input means clear too when the KEY is present).
847
+ *
848
+ * { title: 'The breakout', note: 'What happened…',
849
+ * range: { from, to }, // times (s or ms) — the camera pans there
850
+ * indicators: 'sma:20 rsi:14', // optional indicator string
851
+ * type: 'candles', // optional series type
852
+ * overlays: [...], // optional zones/levels (normalizeOverlays)
853
+ * scenario: {...} | null, // set / clear a scenario
854
+ * riskPlan: {...} | null, // set / clear a risk plan
855
+ * dwell: 2200 } // ms to hold after the pan (500–30000)
856
+ *
857
+ * @returns {object|null} normalized scene, or null for non-objects
858
+ */
859
+ export declare function normalizeScene(scene: any): object | null;
860
+ /**
861
+ * Validate a whole story: normalize each scene, drop junk, cap at 20.
862
+ * @returns {object[]} possibly empty
863
+ */
864
+ export declare function sceneList(story: any): object[];
865
+ /**
866
+ * Tracks other charts viewing the same room: last-sighting timestamps per
867
+ * peer plus the viewport each one is looking at. Pure bookkeeping — the
868
+ * transport (BroadcastChannel, WebSocket, …) lives in the component/app.
869
+ *
870
+ * Peers expire `ttl` ms after their last sighting, so a closed tab fades
871
+ * out of the room without an explicit goodbye.
872
+ */
873
+ export declare class PresenceTracker {
874
+ ttl: number;
875
+ /** @type {Map<string, {id: string, name: string|null, range: {from:number,to:number}|null, at: number}>} */
876
+ peers: Map<string, {
877
+ id: string;
878
+ name: string | null;
879
+ range: {
880
+ from: number;
881
+ to: number;
882
+ } | null;
883
+ at: number;
884
+ }>;
885
+ /** @param {number} [ttl=12000] ms a peer survives without a sighting */
886
+ constructor(ttl?: number);
887
+ /**
888
+ * Record a sighting. `patch.range` ({from,to} times) is validated and
889
+ * normalized; a sighting without a range keeps the previous one.
890
+ * @returns {boolean} true when this sighting is a join (new peer)
891
+ */
892
+ track(id: any, patch?: {}, now?: number): boolean;
893
+ /** @returns {object|null} the removed peer entry, or null when unknown */
894
+ drop(id: any): object | null;
895
+ /** Expire peers not seen within the ttl.
896
+ * @returns {object[]} the peer entries that left */
897
+ sweep(now?: number): object[];
898
+ /** @returns {{id: string, name: string|null, range: object|null, at: number}[]} copies, oldest sighting first */
899
+ list(): {
900
+ id: string;
901
+ name: string | null;
902
+ range: object | null;
903
+ at: number;
904
+ }[];
905
+ }
750
906
  export declare const tfLabelOf: (dtMs: any) => string;
751
907
  /**
752
908
  * Compact, LLM-friendly summary of a bar window: structured fields plus a
@@ -1,3 +1,4 @@
1
+ import { PresenceTracker } from './core.js';
1
2
  declare const HTMLElementBase: {
2
3
  new (): {};
3
4
  };
@@ -108,6 +109,10 @@ declare class WickChart extends HTMLElementBase {
108
109
  at: number;
109
110
  };
110
111
  _ghostTimer: number;
112
+ _coviewLabel: any;
113
+ _presence: PresenceTracker;
114
+ _coviewBeat: number;
115
+ _coviewViewLast: number;
111
116
  _sonify: boolean;
112
117
  _actx: any;
113
118
  _lastToneIdx: number;
@@ -120,6 +125,36 @@ declare class WickChart extends HTMLElementBase {
120
125
  done: boolean;
121
126
  };
122
127
  _measuring: boolean;
128
+ _walkTimer: number;
129
+ _storyToken: number;
130
+ _story: object[];
131
+ _brush: boolean;
132
+ _brushSel: {
133
+ i0: number;
134
+ i1: number;
135
+ stats: {
136
+ bars: number;
137
+ from: {
138
+ index: number;
139
+ time: number;
140
+ };
141
+ to: {
142
+ index: number;
143
+ time: number;
144
+ };
145
+ firstOpen: number;
146
+ lastClose: number;
147
+ delta: number;
148
+ deltaPct: number;
149
+ high: number;
150
+ low: number;
151
+ volume: number;
152
+ };
153
+ };
154
+ _brushDrag: {
155
+ i0: any;
156
+ i1: any;
157
+ };
123
158
  _ind: {
124
159
  overlays: any[];
125
160
  panes: any[];
@@ -137,6 +172,12 @@ declare class WickChart extends HTMLElementBase {
137
172
  idxAtMid: number;
138
173
  midX: number;
139
174
  };
175
+ _layers: any[];
176
+ _layerClaim: {
177
+ layer: any;
178
+ pointerId: any;
179
+ };
180
+ _layerSeq: number;
140
181
  _loadingMore: boolean;
141
182
  _noMore: boolean;
142
183
  _positions: any[];
@@ -154,6 +195,18 @@ declare class WickChart extends HTMLElementBase {
154
195
  color: string | null;
155
196
  label: string;
156
197
  };
198
+ _riskPlan: {
199
+ entry: number;
200
+ stop: number;
201
+ risk: number;
202
+ direction: 'long' | 'short';
203
+ levels: {
204
+ k: number;
205
+ price: number;
206
+ }[];
207
+ maxK: number;
208
+ label: string;
209
+ };
157
210
  _onResize: () => void;
158
211
  _onPointerDown: (e: any) => void;
159
212
  _onPointerMove: (e: any) => void;
@@ -427,6 +480,26 @@ declare class WickChart extends HTMLElementBase {
427
480
  clearScenario(): void;
428
481
  /** @returns {object|null} a copy of the active scenario */
429
482
  get scenario(): object | null;
483
+ /**
484
+ * Risk plan: an R-multiple grid anchored at entry/stop. 1R = |entry −
485
+ * stop| (the risk unit); reward lines are drawn at kR beyond the entry
486
+ * with the risk/reward zones shaded, so sizing and take-profit choices
487
+ * read directly off the chart.
488
+ *
489
+ * chart.setRiskPlan({ entry: 64500, stop: 63800, multiples: [1, 2, 3] });
490
+ * chart.setRiskPlan({ entry: 64500, stop: 63800, targets: [65900, 67300] });
491
+ *
492
+ * Direction is derived (stop below entry ⇒ long). Targets convert to
493
+ * their R multiple; `multiples` win when both are given. Invalid specs
494
+ * clear the plan (replace semantics, like setScenario); excluded from
495
+ * getState/setState — it is app state, not chart state.
496
+ * @param {object} spec
497
+ * @returns {object|null} the normalized plan, or null when invalid
498
+ */
499
+ setRiskPlan(spec: object): object | null;
500
+ clearRiskPlan(): void;
501
+ /** @returns {object|null} a copy of the active risk plan */
502
+ get riskPlan(): object | null;
430
503
  /** σ-cone for the active scenario, cached per data version. */
431
504
  _scenarioConeCache(): any;
432
505
  /** Tool manifest for LLM control — JSON-safe copy of AI_TOOLS. */
@@ -556,6 +629,75 @@ declare class WickChart extends HTMLElementBase {
556
629
  _updateLegend(): void;
557
630
  /** Position P&L chips (top-right HTML overlay). */
558
631
  _updateHud(): void;
632
+ /**
633
+ * Register a plugin layer. `layer.draw(api)` runs on every render, above
634
+ * chart content and under the crosshair; `layer.onPointer(pev)` is asked
635
+ * first on pointerdown and claims the gesture by returning true — the
636
+ * layer then receives that pointer's move/up/cancel events (plus a
637
+ * 'cancel' on Escape) and the chart suppresses its own pan/measure/brush
638
+ * for the duration.
639
+ * @param {{id?: string, draw: Function, onPointer?: Function}} layer
640
+ * @returns {object|null} the normalized layer handle (with `id`), or null
641
+ * if the layer was rejected (no draw fn, or 16 layers already added)
642
+ */
643
+ addLayer(layer: {
644
+ id?: string;
645
+ draw: Function;
646
+ onPointer?: Function;
647
+ }): object | null;
648
+ /**
649
+ * Remove a layer added via addLayer (pass the returned handle or its id).
650
+ * @param {object|string} idOrLayer
651
+ * @returns {boolean} true if a layer was removed
652
+ */
653
+ removeLayer(idOrLayer: object | string): boolean;
654
+ /** Ask for a repaint on the next frame (interactive layers call this). */
655
+ requestDraw(): void;
656
+ /** Paint every registered layer. Called from _render with live state. */
657
+ _drawLayers(ctx: any, pal: any, ly: any, d: any): void;
658
+ /** Ask layers, in order, whether one claims this pointerdown. */
659
+ _layerHit(e: any, pt: any): any;
660
+ /** Deliver a pointer event to a claiming layer; never throws outward. */
661
+ _routeLayer(layer: any, e: any, pt: any, type: any): void;
662
+ _layerPointerEvent(e: any, pt: any, type: any): {
663
+ type: any;
664
+ x: any;
665
+ y: any;
666
+ pointerId: any;
667
+ button: any;
668
+ shiftKey: boolean;
669
+ ctrlKey: boolean;
670
+ altKey: boolean;
671
+ metaKey: boolean;
672
+ };
673
+ /**
674
+ * x-pixel for a bar time (ms or s, auto-detected). Extrapolates past the
675
+ * last bar into future space using the median bar interval, so layers can
676
+ * anchor trendlines to tomorrow, not just to history.
677
+ * @param {number} time
678
+ * @returns {number|null}
679
+ */
680
+ timeToX(time: number): number | null;
681
+ /**
682
+ * Bar time (ms) at an x-pixel — the inverse of timeToX, interpolating
683
+ * between bars and extrapolating beyond both data edges.
684
+ * @param {number} x
685
+ * @returns {number|null}
686
+ */
687
+ xToTime(x: number): number | null;
688
+ /**
689
+ * y-pixel for a price in the main pane (current scale; log-aware).
690
+ * Unclamped — values off-screen still map, layers decide how to clip.
691
+ * @param {number} price
692
+ * @returns {number|null}
693
+ */
694
+ priceToY(price: number): number | null;
695
+ /**
696
+ * Price at a y-pixel in the main pane — the inverse of priceToY.
697
+ * @param {number} y
698
+ * @returns {number|null}
699
+ */
700
+ yToPrice(y: number): number | null;
559
701
  _localPoint(e: any): {
560
702
  x: number;
561
703
  y: number;
@@ -582,11 +724,126 @@ declare class WickChart extends HTMLElementBase {
582
724
  _emitCrosshair(hover: any): void;
583
725
  /** Join/leave the co-view channel named by the `co-view` attribute. */
584
726
  _setupCoView(): void;
727
+ /** Broadcast our visible range for presence; throttled unless forced. */
728
+ _coviewSendView(force: any): void;
585
729
  _coviewSend(msg: any): void;
586
730
  _onCoMessage(m: any): void;
587
731
  /** Dispatch `wick:name` (canonical) plus the deprecated `hab:name` alias,
588
732
  * so 0.x listeners keep working until 2.0. */
589
733
  _fire(name: any, detail: any): void;
734
+ /**
735
+ * Live co-view peers: who else is in the room and the time window each
736
+ * one is looking at — [{ id, name, range: {from, to}, at }], oldest
737
+ * sighting first. Peers fade out ~12 s after their last sighting.
738
+ * @returns {object[]}
739
+ */
740
+ getPeers(): object[];
741
+ /**
742
+ * Narrated timeline for a window (default: the visible range) — pivot
743
+ * highs/lows, volume spikes, gaps, RSI divergences plus derived legs
744
+ * ("+12.4% over 38 bars"), sorted by index. Pure data, perfect for
745
+ * caption UIs or the walk player.
746
+ * chart.narrate(); // visible range
747
+ * chart.narrate({ from, to }); // times in ms (s accepted)
748
+ * @param {{from?: number, to?: number}} [range]
749
+ * @returns {{i: number, time: number, type: string, side: string, note: string,
750
+ * legPct?: number, legBars?: number}[]}
751
+ */
752
+ narrate(range?: {
753
+ from?: number;
754
+ to?: number;
755
+ }): {
756
+ i: number;
757
+ time: number;
758
+ type: string;
759
+ side: string;
760
+ note: string;
761
+ legPct?: number;
762
+ legBars?: number;
763
+ }[];
764
+ /**
765
+ * Walk the chart through history like a story: the viewport slides
766
+ * from `from` to `to` while `wick:walk` events announce every step and
767
+ * the narrator's events (spikes, gaps, pivots, legs) as they're crossed.
768
+ * Any user interaction — pointer, wheel, keys, double-click — stops it.
769
+ * chart.walk({ from: 0, to: 500, speed: 120, step: 10 });
770
+ * chart.addEventListener('wick:walk', (e) => showCaption(e.detail));
771
+ * // detail: { phase: 'step'|'end'|'stop', index, events: [...], from, to }
772
+ * @param {{from?: number, to?: number, speed?: number, step?: number}} [opts]
773
+ * from/to are bar indices (default: last ~500 bars → the end)
774
+ * @returns {boolean} true when the walk started
775
+ */
776
+ walk(opts?: {
777
+ from?: number;
778
+ to?: number;
779
+ speed?: number;
780
+ step?: number;
781
+ }): boolean;
782
+ /**
783
+ * Stop the running walk (if any). Fires a final `wick:walk`
784
+ * { phase: 'stop' } unless called internally.
785
+ */
786
+ stopWalk(silent: any): void;
787
+ /**
788
+ * Commit a brush selection over [i0, i1]: stores it (draws the band
789
+ * and delta chip) and fires `wick:brush` with the range statistics.
790
+ * @param {number} i0 first index
791
+ * @param {number} i1 last index
792
+ */
793
+ _brushFinish(i0: number, i1: number): void;
794
+ /** Clear the committed brush selection (if any). Escape does the same. */
795
+ clearBrush(): void;
796
+ /** @returns {object|null} the committed selection { i0, i1, stats } */
797
+ get brushSelection(): object | null;
798
+ /**
799
+ * Capture the current chart state as a story scene: view, series type,
800
+ * indicators, overlays, scenario and risk plan, plus a title/note.
801
+ * Build guided tours by capturing several and playing them back.
802
+ * const story = [
803
+ * chart.captureScene('Overview', 'The full picture'),
804
+ * { title: 'The breakout', range: { from, to }, indicators: 'sma:20' },
805
+ * ];
806
+ * chart.playStory(story);
807
+ * @param {string} [title]
808
+ * @param {string} [note]
809
+ * @returns {object} scene (plain data — snapshot of the moment)
810
+ */
811
+ captureScene(title?: string, note?: string): object;
812
+ /** @returns {object[]|null} a copy of the last played story */
813
+ getStory(): object[] | null;
814
+ /**
815
+ * Play a story: each scene applies its state (type / indicators /
816
+ * overlays / scenario / risk plan — set or clear), the camera eases
817
+ * to its range, then holds for its dwell. `wick:story` events narrate:
818
+ * { phase: 'scene' | 'end' | 'stop', index, total, scene, title, note }
819
+ * Any user interaction — pointer, wheel, keys, double-click — stops it.
820
+ * @param {object[]} story scenes (invalid entries dropped, max 20)
821
+ * @param {{dwell?: number, panMs?: number, loop?: boolean}} [opts]
822
+ * panMs clamps 100–5000 (default 900); loop replays forever
823
+ * @returns {boolean} true when playback started
824
+ */
825
+ playStory(story: object[], opts?: {
826
+ dwell?: number;
827
+ panMs?: number;
828
+ loop?: boolean;
829
+ }): boolean;
830
+ /**
831
+ * Stop story playback (if running). Fires a final `wick:story`
832
+ * { phase: 'stop' } unless called internally.
833
+ */
834
+ stopStory(silent: any): void;
835
+ /** Apply a scene's state (only the fields it carries). */
836
+ _applyScene(sc: any): void;
837
+ /** Map a scene's time range to bar indices (null when not applicable). */
838
+ _sceneTarget(sc: any): {
839
+ i0: number;
840
+ i1: number;
841
+ };
842
+ /** Ease the viewport to { i0, i1 } over `ms`; resolves early if the
843
+ * token changes (superseded or stopped). rAF when available. */
844
+ _storyTween(target: any, ms: any, token: any): Promise<any>;
845
+ /** Interrupt narrated playback (walk / story) on user input. */
846
+ _stopPlayback(): void;
590
847
  _emitRange(): void;
591
848
  }
592
849
  export default WickChart;