pinets 0.9.21 → 0.9.23

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.
@@ -59,16 +59,17 @@ export declare class Indicator {
59
59
  /** True iff the script references any built-in in `VIEWPORT_DEPENDENT_BUILTINS`. */
60
60
  usesVisibleRange(): boolean;
61
61
  /**
62
- * Title-keyed input map used by the runtime (read by
62
+ * Input override map used by the runtime (read by
63
63
  * `input.utils.resolveInput`). Composed at call time so live mutations
64
64
  * to `.input` between `pine.run()` calls are picked up automatically.
65
65
  *
66
- * Precedence:
67
- * 1. Legacy constructor `inputs` (back-compat)
68
- * 2. Explicit writes to `.input[...]` (new API; takes priority)
66
+ * Keys are mixed by design and resolved in this precedence by the runtime:
67
+ * 1. Legacy constructor `inputs` map — TITLE-keyed (back-compat)
68
+ * 2. Explicit `.input[...]` writes — VARID-keyed (canonical);
69
+ * resolveInput checks varId before title, so these take priority.
69
70
  *
70
- * Defaults are NOT included — the runtime falls back to `defval` when a
71
- * title is absent.
71
+ * Defaults are NOT included — the runtime falls back to `defval` when no
72
+ * override key matches.
72
73
  */
73
74
  getRuntimeInputs(): Record<string, unknown>;
74
75
  /**
@@ -1,14 +1,18 @@
1
1
  import type { IPineInput } from './types';
2
2
  /**
3
3
  * Build the live `.input` view exposed on an `Indicator` instance.
4
- * Title-keyed; entries without an explicit `title=` on the Pine source are
5
- * excluded (Pine's runtime keys overrides by title, so a title-less input is
6
- * not overridable).
4
+ *
5
+ * Keyed by **varId** (the assigned variable name) as the canonical, primary
6
+ * override key, with the input's **title** registered as a secondary alias.
7
+ * This makes every input overridable by a stable, unique handle — robust to
8
+ * empty or duplicated titles — while `.input['Title']` keeps working for the
9
+ * common case (unique, non-empty titles). When two inputs share a title, the
10
+ * title aliases the first; the second is reachable only by its varId.
7
11
  *
8
12
  * Backing machinery lives in `keyedProxy.ts` and is shared with `.prop`.
9
13
  */
10
- export declare function buildInputProxy(metas: IPineInput[], onSet?: (title: string) => void): {
14
+ export declare function buildInputProxy(metas: IPineInput[], onSet?: (key: string) => void): {
11
15
  proxy: Record<string, unknown>;
12
16
  values: Record<string, unknown>;
13
- metaByTitle: Map<string, IPineInput>;
17
+ metaByKey: Map<string, IPineInput>;
14
18
  };
@@ -20,6 +20,14 @@ export interface KeyedSchemaEntry {
20
20
  options?: unknown[];
21
21
  minval?: number;
22
22
  maxval?: number;
23
+ /**
24
+ * Secondary keys that also resolve to this entry (e.g. an input's title
25
+ * aliasing its varId). The canonical `key` always takes priority; an alias
26
+ * is registered only if not already claimed by a canonical key or an
27
+ * earlier entry. Values/overrides are stored under the canonical key, so
28
+ * reading or writing via an alias hits the same slot.
29
+ */
30
+ aliases?: string[];
23
31
  }
24
32
  export interface BuildKeyedProxyResult {
25
33
  proxy: Record<string, unknown>;
@@ -29,6 +29,7 @@ export type PineInputDisplay = 'none' | 'data_window' | 'status_line' | 'all';
29
29
  export interface IPineInput {
30
30
  type: PineInputType;
31
31
  defval: unknown;
32
+ varId?: string;
32
33
  title?: string;
33
34
  tooltip?: string;
34
35
  group?: string;
@@ -1,3 +1,26 @@
1
+ /**
2
+ * Resolve any static color value to its `[r, g, b, a]` components (a in
3
+ * 0..1). Accepts:
4
+ * - `#RRGGBB` / `#RRGGBBAA` hex
5
+ * - `rgb(r,g,b)` / `rgba(r,g,b,a)` strings
6
+ * - a named constant, with or without the namespace: `color.red` / `red`
7
+ * Returns null for anything it can't parse as a color.
8
+ */
9
+ export declare function resolveColorToRgba(value: unknown): [number, number, number, number] | null;
10
+ /**
11
+ * Format `[r, g, b, a]` (a in 0..1) as a canonical 8-digit RGBA hex string
12
+ * `#RRGGBBAA` (uppercase, alpha byte ALWAYS present — `FF` = fully opaque).
13
+ */
14
+ export declare function rgbaToHex8(r: number, g: number, b: number, a: number): string;
15
+ /**
16
+ * Normalize any color value to a canonical 8-digit RGBA hex string
17
+ * `#RRGGBBAA` (see {@link rgbaToHex8}). Accepts every shape a color input
18
+ * can carry (hex, rgb()/rgba(), named constant). Values it can't parse as
19
+ * a color are returned unchanged, so non-color data passes through
20
+ * untouched. Used by `Indicator.getInputsMeta()` to present color-input
21
+ * defaults in a single canonical form.
22
+ */
23
+ export declare function normalizeColorToRgbaHex(value: unknown): unknown;
1
24
  /**
2
25
  * PineColor implements the Pine Script `color` namespace.
3
26
  *
@@ -11,4 +11,11 @@ export type InputOptions = {
11
11
  confirm?: boolean;
12
12
  display?: string;
13
13
  active?: boolean;
14
+ /**
15
+ * Transpiler-injected handle: the variable the input is assigned to
16
+ * (`len = input.int(…)` → "len"). Popped from the call args by
17
+ * parseInputOptions and used as the PRIMARY override key by resolveInput
18
+ * (title is the secondary fallback). Absent for non-transpiled JS calls.
19
+ */
20
+ __varId?: string;
14
21
  };
@@ -68,6 +68,14 @@ export interface Trade {
68
68
  max_drawdown?: number;
69
69
  max_runup?: number;
70
70
  status: 'open' | 'closed';
71
+ /**
72
+ * PHYSICAL entry price of this lot, immutable — used to compute
73
+ * per-lot exit-bracket levels (strategy.exit profit/loss ticks).
74
+ * Distinct from `entry_price`, which is the LEDGER value and can be
75
+ * swapped by FIFO entry/exit pairing when a newer lot's bracket fills
76
+ * before an older lot's (TV ledger convention).
77
+ */
78
+ _bracket_entry?: number;
71
79
  }
72
80
  /**
73
81
  * A pending or filled order tracked internally by the engine.
@@ -115,6 +123,7 @@ export interface Order {
115
123
  _attachedAtReversal?: boolean;
116
124
  _isPersistent?: boolean;
117
125
  _callsiteId?: string;
126
+ _intended_trade_ids?: string[];
118
127
  }
119
128
  /**
120
129
  * Strategy state stored on the Context after a backtest run.
@@ -152,6 +161,10 @@ export interface StrategyState {
152
161
  equity_at_drawdown_peak: number;
153
162
  max_drawdown_percent_value: number;
154
163
  max_runup_percent_value: number;
164
+ sharpe_ratio: number;
165
+ sortino_ratio: number;
166
+ _monthly_equity?: number[];
167
+ _last_month_key?: number;
155
168
  wintrades: number;
156
169
  losstrades: number;
157
170
  eventrades: number;
@@ -136,7 +136,7 @@ export declare function closePartialPosition(context: any, qtyToClose: number, e
136
136
  * Wraps `closePartialPosition` by temporarily reorganizing `opentrades` so
137
137
  * the matching trades sit at the head of the FIFO queue.
138
138
  */
139
- export declare function closeMatching(context: any, fromEntry: string | undefined, qtyToClose: number, exitPrice: number, exitTime: number, closeInfo?: CloseInfo): void;
139
+ export declare function closeMatching(context: any, fromEntry: string | undefined, qtyToClose: number, exitPrice: number, exitTime: number, closeInfo?: CloseInfo, specificTradeId?: string): void;
140
140
  /**
141
141
  * Process exit-category orders each bar (after entry-order fills, before the
142
142
  * user script runs). Handles:
@@ -146,18 +146,57 @@ export declare function closeMatching(context: any, fromEntry: string | undefine
146
146
  * triggers evaluated against current bar's high/low. Trailing-stop
147
147
  * peak (trade.trail_peak) is updated each bar even when not triggered.
148
148
  */
149
- export declare function processExitOrders(context: any): void;
149
+ export declare function processExitOrders(context: any, phase?: 'open' | 'intrabar'): void;
150
+ /**
151
+ * Apply a SECOND margin call scheduled by the phantom re-check (see
152
+ * processMarginCall). TV books that fill at the PREVIOUS bar's close,
153
+ * AFTER the script's on-close evaluation — so the script and any order
154
+ * it queued saw the pre-MC#2 position. PineTS mirrors this by booking
155
+ * the fill at the very start of the NEXT bar, before entries process:
156
+ * a reversal queued at the MC bar's close (qty frozen at queue time)
157
+ * then naturally overshoots by exactly q2, reproducing TV's phantom
158
+ * opposite-side position (xlsx-confirmed: 2021-10-02 reversal long
159
+ * 5.263108 = 5 + 0.263108).
160
+ *
161
+ * Same-direction (non-reversal) entries queued on the MC bar are
162
+ * CANCELED — TV's transient post-MC state rejects them (2022-04-19: the
163
+ * add queued at the 04-18 close never filled; the next add was accepted
164
+ * a bar later). Opposite-direction reversals are unaffected (E1), and a
165
+ * close-MC that flattens the position leaves nothing to gate (IC=900k
166
+ * experiment: next-open entry from flat was admitted).
167
+ */
168
+ export declare function applyPendingCloseMarginCall(context: any): void;
150
169
  /**
151
- * Margin-call check (TV broker emulator). After all entries and user-defined
152
- * exits have processed for the bar, check whether the bar's INTRA-BAR
153
- * adverse movement (low for longs, high for shorts) would have pushed
154
- * account equity below required maintenance margin. If yes, FORCE LIQUIDATE
155
- * all open positions at the bar's adverse extreme with exit_id="Margin call".
170
+ * True when the bar's first intra-bar move is ADVERSE for the current
171
+ * position (TV broker-emulator path assumption: open closer to high
172
+ * open→high→low→close; open closer to low open→low→high→close).
173
+ * Used to path-order the margin-call checkpoint against exit fills.
174
+ */
175
+ export declare function isAdverseFirstBar(context: any): boolean;
176
+ /**
177
+ * Margin-call check (TV broker emulator) at one of two intra-bar
178
+ * CHECKPOINTS along the assumed price path:
179
+ *
180
+ * 'open' — right after entries fill at the bar's open: equity and
181
+ * required margin evaluated AT THE OPEN, liquidation fills
182
+ * at the open price.
183
+ * 'extreme' — at the bar's adverse extreme (low for longs, high for
184
+ * shorts), liquidation fills at the extreme itself — the
185
+ * pessimistic broker model (intra-bar tick order unknown).
186
+ * 'close' — at the bar's close, after all exits: if the (possibly
187
+ * already-trimmed) position still breaches at the closing
188
+ * price, another partial liquidation fills at the close.
189
+ * Evidence: 2021-10-01 (profit QA) shows TWO same-bar MC
190
+ * prices — 4×cover at the high, then a further 0.263108
191
+ * at 48,147.38 (the close).
156
192
  *
157
- * The exit price is the bar's adverse extreme itself, NOT the theoretical
158
- * threshold where equity would exactly equal required margin. This is the
159
- * pessimistic broker model the trader is assumed to be liquidated at
160
- * the worst intra-bar price, since intra-bar tick order is unknown.
193
+ * TV checks margin along the path, interleaved with exit fills proven
194
+ * by the MC-ordering probe (BTCUSDT 1D, 2026-02-05): a 5-lot short
195
+ * entered at the open was split within one bar into MC 0.00228 at the
196
+ * OPEN price, MC 0.0888 at the high, then a TP fill of 4.90892 at the
197
+ * lows. The caller orders the 'extreme' checkpoint BEFORE exit
198
+ * processing on adverse-first bars and AFTER it on favorable-first bars
199
+ * (favorable exits free margin before the adverse extreme is reached).
161
200
  *
162
201
  * Runs for ALL margin percentages including 100%. At 100% margin the
163
202
  * trader still needs full notional collateral; adverse price movement
@@ -166,7 +205,7 @@ export declare function processExitOrders(context: any): void;
166
205
  * (the "Margin calls" stat in the Strategy Tester is non-zero on 100%
167
206
  * margin runs whenever a position's mark-to-market loss exceeds equity).
168
207
  */
169
- export declare function processMarginCall(context: any): void;
208
+ export declare function processMarginCall(context: any, checkpoint?: 'open' | 'extreme' | 'close'): void;
170
209
  /**
171
210
  * End-of-bar finalize: refresh equity at CLOSE and latch
172
211
  * `strategy.max_drawdown` / `strategy.max_runup` using the bar's H/L. Runs
@@ -174,6 +213,29 @@ export declare function processMarginCall(context: any): void;
174
213
  * of whether the strategy uses exit orders.
175
214
  */
176
215
  export declare function finalizeStrategyBar(context: any): void;
216
+ /**
217
+ * End-of-run finalize: compute the risk-adjusted performance ratios
218
+ * (Sharpe / Sortino) from the monthly equity curve captured during the
219
+ * run. Called ONCE after the last bar (see PineTS.class.ts).
220
+ *
221
+ * TV broker-emulator formula (confirmed against the Help Center docs and
222
+ * reverse-engineered to the third decimal across 7 QA datasets,
223
+ * 2026-06-15):
224
+ * - Sample the MARK-TO-MARKET equity at each calendar month's close.
225
+ * - Monthly simple returns rᵢ = Eᵢ / Eᵢ₋₁ − 1, anchored at the initial
226
+ * capital (the first return runs from initial_capital to month 1).
227
+ * - MR = mean(rᵢ); RFR = risk_free_rate / 100 / 12 (annual % → monthly).
228
+ * - Sharpe = (MR − RFR) / SD, SD = √(Σ(rᵢ − MR)² / N) (population).
229
+ * - Sortino = (MR − RFR) / DD, DD = √(Σ min(0, rᵢ − RFR)² / N)
230
+ * (downside deviation over ALL N returns, target = RFR — per TV's
231
+ * documented DD = sqrt(sum(min(0, Xᵢ − T))² / N)).
232
+ * - No annualization.
233
+ *
234
+ * Note: the ratios are only as accurate as the bar-by-bar equity path;
235
+ * they ride on the strategy engine's mark-to-market fidelity. With < 2
236
+ * monthly returns (very short backtests) they are left at 0.
237
+ */
238
+ export declare function finalizeStrategyRun(context: any): void;
177
239
  /**
178
240
  * Initialize strategy state
179
241
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinets",
3
- "version": "0.9.21",
3
+ "version": "0.9.23",
4
4
  "description": "Run Pine Script anywhere. PineTS is an open-source transpiler and runtime that brings Pine Script logic to Node.js and the browser with 1:1 syntax compatibility. Reliably write, port, and run indicators or strategies on your own infrastructure.",
5
5
  "keywords": [
6
6
  "Pine Script",