pinets 0.9.18 → 0.9.20

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.
Files changed (30) hide show
  1. package/dist/pinets.min.browser.es.js +30 -30
  2. package/dist/pinets.min.browser.es.js.map +1 -1
  3. package/dist/pinets.min.browser.js +30 -30
  4. package/dist/pinets.min.browser.js.map +1 -1
  5. package/dist/pinets.min.cjs +30 -30
  6. package/dist/pinets.min.cjs.map +1 -1
  7. package/dist/pinets.min.es.js +30 -30
  8. package/dist/pinets.min.es.js.map +1 -1
  9. package/dist/types/Context.class.d.ts +4 -0
  10. package/dist/types/Indicator/Indicator.class.d.ts +100 -0
  11. package/dist/types/Indicator/inputProxy.d.ts +14 -0
  12. package/dist/types/Indicator/keyedProxy.d.ts +43 -0
  13. package/dist/types/Indicator/propProxy.d.ts +16 -0
  14. package/dist/types/Indicator/propsSchema.d.ts +8 -0
  15. package/dist/types/Indicator/scanDeclaration.d.ts +29 -0
  16. package/dist/types/Indicator/scanInputs.d.ts +5 -0
  17. package/dist/types/Indicator/types.d.ts +90 -0
  18. package/dist/types/Indicator.d.ts +3 -5
  19. package/dist/types/PineTS.class.d.ts +1 -20
  20. package/dist/types/index.d.ts +2 -0
  21. package/dist/types/namespaces/strategy/methods/avg_losing_trade_percent.d.ts +6 -2
  22. package/dist/types/namespaces/strategy/methods/convert_to_account.d.ts +10 -4
  23. package/dist/types/namespaces/strategy/methods/convert_to_symbol.d.ts +4 -1
  24. package/dist/types/namespaces/strategy/methods/margin_liquidation_price.d.ts +16 -6
  25. package/dist/types/namespaces/strategy/methods/max_drawdown_percent.d.ts +12 -3
  26. package/dist/types/namespaces/strategy/methods/max_runup_percent.d.ts +15 -3
  27. package/dist/types/namespaces/strategy/types.d.ts +11 -0
  28. package/dist/types/namespaces/strategy/utils.d.ts +95 -3
  29. package/dist/types/transpiler/settings.d.ts +1 -0
  30. package/package.json +1 -1
@@ -36,6 +36,10 @@ export declare class Context {
36
36
  }[];
37
37
  /** Alert mode: 'realtime' = only fire on live bars (TV behavior), 'all' = fire on every bar (backtest). */
38
38
  _alertMode: 'realtime' | 'all';
39
+ /** Name-keyed map of declaration-prop overrides from `Indicator.prop`. Layered on top of
40
+ * the source's `indicator()`/`strategy()` call args inside the per-bar handlers
41
+ * (see Core.indicator and initializeStrategy). Empty object when no overrides are set. */
42
+ _propOverrides: Record<string, unknown>;
39
43
  /** Monotonically increasing counter, incremented each time a bar starts executing.
40
44
  * Used by alertcondition/AlertHelper to detect re-execution of the same bar. */
41
45
  _execTick: number;
@@ -0,0 +1,100 @@
1
+ import type { IPineInput, IPineProp, PreparedScript } from './types';
2
+ /**
3
+ * The single owner of every per-script artifact. Holds the source, lazily
4
+ * transpiles + scans inputs on first `prepare()`, and caches the result so
5
+ * the same Indicator instance can be passed to multiple `pine.run()` calls
6
+ * without re-transpiling.
7
+ *
8
+ * Roles:
9
+ * - `source` — the original Pine string or JS callback.
10
+ * - `input` — live, title-keyed view of input values. Read or
11
+ * mutate per key; the container itself is frozen.
12
+ * - `inputs` — LEGACY title-keyed override map (constructor
13
+ * arg). Kept for back-compat; merged into the
14
+ * prepared inputs by `prepare()`.
15
+ * - `prepare(opts?)` — idempotent: transpiles, scans inputs, runs
16
+ * visible-range static analysis. Caches result.
17
+ * - `usesVisibleRange()`— derived from the cached prepare() result.
18
+ *
19
+ * For JS-function source the AST scan returns `[]`, so `input` is empty and
20
+ * per-key writes throw "unknown title". The legacy `inputs` field still
21
+ * works for that path.
22
+ */
23
+ export declare class Indicator {
24
+ readonly source: Function | string;
25
+ inputs: Record<string, unknown>;
26
+ readonly input: Record<string, unknown>;
27
+ readonly prop: Record<string, unknown>;
28
+ private _prepared;
29
+ private _inputMeta;
30
+ private _inputValues;
31
+ private _explicitOverrides;
32
+ private _inputProxy;
33
+ private _propMeta;
34
+ private _propValues;
35
+ private _propProxy;
36
+ private _declarationType;
37
+ private _sourcePropArgs;
38
+ private _explicitPropOverrides;
39
+ constructor(source: Function | string, inputs?: Record<string, unknown>);
40
+ /**
41
+ * Normalize ANY accepted run() argument into an Indicator. Raw functions
42
+ * and strings get wrapped in a throwaway Indicator; existing Indicators
43
+ * pass through. This is the single entry point used by PineTS's run /
44
+ * stream / update so the rest of the engine only deals with Indicators.
45
+ */
46
+ static from(arg: Indicator | Function | string): Indicator;
47
+ /**
48
+ * Idempotent transpile + scan + viewport detection. The result is cached
49
+ * on the instance, so passing the same Indicator to multiple `pine.run()`
50
+ * calls produces a single transpilation pass.
51
+ *
52
+ * NB: cache is keyed by *instance identity*, not by options. If you need
53
+ * different debug settings, create a new Indicator.
54
+ */
55
+ prepare(opts?: {
56
+ debug?: boolean;
57
+ ln?: boolean;
58
+ }): PreparedScript;
59
+ /** True iff the script references any built-in in `VIEWPORT_DEPENDENT_BUILTINS`. */
60
+ usesVisibleRange(): boolean;
61
+ /**
62
+ * Title-keyed input map used by the runtime (read by
63
+ * `input.utils.resolveInput`). Composed at call time so live mutations
64
+ * to `.input` between `pine.run()` calls are picked up automatically.
65
+ *
66
+ * Precedence:
67
+ * 1. Legacy constructor `inputs` (back-compat)
68
+ * 2. Explicit writes to `.input[...]` (new API; takes priority)
69
+ *
70
+ * Defaults are NOT included — the runtime falls back to `defval` when a
71
+ * title is absent.
72
+ */
73
+ getRuntimeInputs(): Record<string, unknown>;
74
+ /**
75
+ * AST-parsed input metadata. Lazy. Empty array for JS-function source.
76
+ */
77
+ getInputsMeta(): IPineInput[];
78
+ /**
79
+ * Schema metadata for declaration props applicable to this script's type.
80
+ * Includes non-mutable entries (`title`, `shorttitle`) for completeness —
81
+ * UI builders can render them; writes to those keys via `.prop` still throw.
82
+ */
83
+ getPropsMeta(): IPineProp[];
84
+ /**
85
+ * Name-keyed map of user-overridden props for the runtime to merge on top
86
+ * of the source-code's indicator()/strategy() call args. Only explicit
87
+ * writes via `.prop` are included — source-code values flow through the
88
+ * normal Pine call path.
89
+ */
90
+ getRuntimePropOverrides(): Record<string, unknown>;
91
+ /**
92
+ * Detected declaration type, or null if the source code has neither call.
93
+ * `.prop` falls back to the indicator schema in the null case.
94
+ */
95
+ getDeclarationType(): 'indicator' | 'strategy' | null;
96
+ private _ensureInputsScanned;
97
+ private _getInputProxy;
98
+ private _ensurePropsScanned;
99
+ private _getPropProxy;
100
+ }
@@ -0,0 +1,14 @@
1
+ import type { IPineInput } from './types';
2
+ /**
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).
7
+ *
8
+ * Backing machinery lives in `keyedProxy.ts` and is shared with `.prop`.
9
+ */
10
+ export declare function buildInputProxy(metas: IPineInput[], onSet?: (title: string) => void): {
11
+ proxy: Record<string, unknown>;
12
+ values: Record<string, unknown>;
13
+ metaByTitle: Map<string, IPineInput>;
14
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Generic title/name-keyed Proxy with per-key validation. Shared backend for
3
+ * both `Indicator.input` (keyed by input title) and `Indicator.prop` (keyed
4
+ * by declaration arg name).
5
+ *
6
+ * Contract:
7
+ * - Read: returns current value (default OR user-overridden) at the key.
8
+ * - Write: validates against the entry's meta — type, options, minval,
9
+ * maxval — and stores the override. Invalid writes THROW with
10
+ * a tailored message.
11
+ * - Delete: throws.
12
+ * - Unknown key write: throws, listing the known keys.
13
+ * - Object.keys() / spread / console.log show real keys with current values.
14
+ */
15
+ export type KeyedType = 'int' | 'float' | 'bool' | 'string' | 'source' | 'color' | 'enum' | 'price' | 'time' | 'session' | 'symbol' | 'timeframe' | 'text_area';
16
+ export interface KeyedSchemaEntry {
17
+ key: string;
18
+ type: KeyedType;
19
+ defval: unknown;
20
+ options?: unknown[];
21
+ minval?: number;
22
+ maxval?: number;
23
+ }
24
+ export interface BuildKeyedProxyResult {
25
+ proxy: Record<string, unknown>;
26
+ values: Record<string, unknown>;
27
+ entryByKey: Map<string, KeyedSchemaEntry>;
28
+ }
29
+ /**
30
+ * Build a keyed proxy view.
31
+ *
32
+ * @param entries schema entries — defaults seeded into `values`
33
+ * @param label display label for error messages, e.g. 'Indicator.input' or 'Indicator.prop'
34
+ * @param onSet optional callback fired after a successful write (for explicit-override tracking)
35
+ * @param seedValues optional initial values overriding entry defvals (used by .prop to seed
36
+ * source-code defaults on top of spec defaults)
37
+ * @param keyNoun noun for the unknown-key message, defaults to 'key'. Inputs use 'input title'.
38
+ */
39
+ export declare function buildKeyedProxy(entries: KeyedSchemaEntry[], label: string, onSet?: (key: string) => void, seedValues?: Record<string, unknown>, keyNoun?: string): BuildKeyedProxyResult;
40
+ /**
41
+ * Per-entry write validation. Throws on any rule violation.
42
+ */
43
+ export declare function validate(entry: KeyedSchemaEntry, value: unknown, label: string): void;
@@ -0,0 +1,16 @@
1
+ import type { IPineProp } from './types';
2
+ /**
3
+ * Build the live `.prop` view exposed on an `Indicator` instance.
4
+ * Name-keyed; non-mutable entries (`title`, `shorttitle`) are excluded.
5
+ *
6
+ * `sourceArgs` seeds source-code defaults on top of spec defaults so the
7
+ * read view matches what TradingView's runtime would see before any user
8
+ * override. Layer order: spec.defval ← sourceArgs ← user `.prop` writes.
9
+ *
10
+ * Backing machinery lives in `keyedProxy.ts` and is shared with `.input`.
11
+ */
12
+ export declare function buildPropProxy(props: IPineProp[], sourceArgs: Record<string, unknown>, onSet?: (name: string) => void): {
13
+ proxy: Record<string, unknown>;
14
+ values: Record<string, unknown>;
15
+ propByName: Map<string, IPineProp>;
16
+ };
@@ -0,0 +1,8 @@
1
+ import type { IPineProp } from './types';
2
+ export declare const INDICATOR_PROPS: IPineProp[];
3
+ export declare const STRATEGY_PROPS: IPineProp[];
4
+ /**
5
+ * Pick the right schema for a detected declaration type. Returns the
6
+ * indicator schema when type is unknown (sensible fallback per spec).
7
+ */
8
+ export declare function propsForDeclaration(type: 'indicator' | 'strategy' | null): IPineProp[];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Detect whether a script is a `strategy()` or `indicator()`, and extract
3
+ * the actual values passed to that call from source so they can seed
4
+ * `.prop` defaults.
5
+ *
6
+ * Both source kinds are handled:
7
+ * - Pine string → pineToJS() AST → walk for the top-level CallExpression
8
+ * - JS function → acorn.parse(fn.toString()) → walk for CallExpression with
9
+ * callee Identifier matching 'indicator' / 'strategy'
10
+ *
11
+ * Enum-typed args are resolved by the "rightmost-identifier rule": for any
12
+ * `MemberExpression` the rightmost `Identifier` name is the runtime string.
13
+ * Holds for every Pine-namespace constant accepted by indicator()/strategy()
14
+ * (format.percent → "percent", currency.USD → "USD",
15
+ * strategy.percent_of_equity → "percent_of_equity",
16
+ * strategy.commission.percent → "percent").
17
+ *
18
+ * Returns `{ type, args }`:
19
+ * - type = 'indicator' | 'strategy' | null
20
+ * - args = name → resolved value extracted from the source call
21
+ *
22
+ * If the declaration call isn't found, returns `{ type: null, args: {} }`.
23
+ * The Indicator class falls back to the indicator schema in that case.
24
+ */
25
+ export interface ScannedDeclaration {
26
+ type: 'indicator' | 'strategy' | null;
27
+ args: Record<string, unknown>;
28
+ }
29
+ export declare function scanDeclaration(source: unknown): ScannedDeclaration;
@@ -0,0 +1,5 @@
1
+ import type { IPineInput } from './types';
2
+ /**
3
+ * Public entry point. Returns `[]` for invalid Pine or for non-string source.
4
+ */
5
+ export declare function scanInputs(source: unknown): IPineInput[];
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Pine Script `input.*` typing classification. Mirrors TradingView's input
3
+ * widget types 1:1. The bare `input()` wrapper auto-detects from `defval`
4
+ * and dispatches to one of these — there is no `'auto'` member.
5
+ *
6
+ * v6 adds `'enum'`. Everything else exists in both v5 and v6.
7
+ */
8
+ export type PineInputType = 'int' | 'float' | 'bool' | 'string' | 'source' | 'color' | 'enum' | 'price' | 'time' | 'session' | 'symbol' | 'timeframe' | 'text_area';
9
+ /**
10
+ * Value of an input's `display=` argument. Stored as the suffix (without the
11
+ * `display.` prefix), matching what the existing runtime parses into
12
+ * `InputOptions.display` at the call site.
13
+ */
14
+ export type PineInputDisplay = 'none' | 'data_window' | 'status_line' | 'all';
15
+ /**
16
+ * Parsed metadata for a single `input.*` declaration in a Pine script.
17
+ *
18
+ * Field presence matches the Pine reference (v6 superset):
19
+ * - title, tooltip, group, display, active — universal (all 14 fns)
20
+ * - inline — universal except text_area
21
+ * - confirm — universal except bare input()
22
+ * - options — enum, float, int, session, string, timeframe
23
+ * - minval / maxval / step — float, int only
24
+ *
25
+ * `defval` is fully resolved at scan time — for enum inputs we resolve
26
+ * `tz.utc` → "UTC" (the field title) so JS callers see what TradingView's
27
+ * `str.tostring()` would print, never the AST path.
28
+ */
29
+ export interface IPineInput {
30
+ type: PineInputType;
31
+ defval: unknown;
32
+ title?: string;
33
+ tooltip?: string;
34
+ group?: string;
35
+ display?: PineInputDisplay;
36
+ active?: boolean;
37
+ confirm?: boolean;
38
+ inline?: string;
39
+ options?: unknown[];
40
+ minval?: number;
41
+ maxval?: number;
42
+ step?: number;
43
+ }
44
+ /**
45
+ * Pine Script declaration-arg typing classification (used by `IPineProp`).
46
+ *
47
+ * `enum` covers every Pine-namespace constant used as a declaration arg
48
+ * (e.g. `format.percent`, `currency.USD`, `strategy.percent_of_equity`).
49
+ * The scanner resolves these to bare strings via the rightmost-identifier
50
+ * rule, matching what the runtime sees.
51
+ */
52
+ export type PinePropType = 'string' | 'int' | 'float' | 'bool' | 'enum';
53
+ /**
54
+ * Schema entry for a single `indicator()` / `strategy()` declaration argument.
55
+ *
56
+ * The full set of entries is curated from the Pine v6 reference:
57
+ * - https://www.tradingview.com/pine-script-reference/v6/#fun_indicator
58
+ * - https://www.tradingview.com/pine-script-reference/v6/#fun_strategy
59
+ *
60
+ * `title` and `shorttitle` are present in the schema with `mutable: false`
61
+ * so UI consumers can render them, but are filtered out of `.prop` writes.
62
+ *
63
+ * For enum-typed entries, `options` enumerates the accepted runtime strings.
64
+ * The schema source file points to the corresponding exported Pine enum
65
+ * (e.g. `enum format` in Types.ts) so JS callers can import the same source.
66
+ */
67
+ export interface IPineProp {
68
+ name: string;
69
+ type: PinePropType;
70
+ defval: unknown;
71
+ options?: unknown[];
72
+ minval?: number;
73
+ maxval?: number;
74
+ mutable: boolean;
75
+ appliesTo: 'indicator' | 'strategy' | 'both';
76
+ version?: 5 | 6;
77
+ }
78
+ /**
79
+ * Result of `Indicator.prepare()`. The single artifact handed to the engine.
80
+ *
81
+ * `inputs` is the title-keyed map the runtime already expects — built by
82
+ * merging each `IPineInput`'s current value (post-user-override) into a
83
+ * flat `{ [title]: value }` object that `input.utils.resolveInput()` reads.
84
+ */
85
+ export interface PreparedScript {
86
+ fn: Function;
87
+ inputs: Record<string, unknown>;
88
+ usesVisibleRange: boolean;
89
+ ltfSlices?: any[];
90
+ }
@@ -1,5 +1,3 @@
1
- export declare class Indicator {
2
- source: Function | String;
3
- inputs: Record<string, any>;
4
- constructor(source: Function | String, inputs?: Record<string, any>);
5
- }
1
+ export { Indicator } from './Indicator/Indicator.class';
2
+ export { INDICATOR_PROPS, STRATEGY_PROPS, propsForDeclaration } from './Indicator/propsSchema';
3
+ export type { IPineInput, IPineProp, PineInputType, PineInputDisplay, PinePropType, PreparedScript, } from './Indicator/types';
@@ -28,6 +28,7 @@ export declare class PineTS {
28
28
  private _debugSettings;
29
29
  private _transpiledCode;
30
30
  get transpiledCode(): Function | String;
31
+ private _currentIndicator;
31
32
  private _isSecondaryContext;
32
33
  markAsSecondary(): void;
33
34
  private _syminfo;
@@ -254,26 +255,6 @@ export declare class PineTS {
254
255
  * @private
255
256
  */
256
257
  private _initializeContext;
257
- /**
258
- * Transpile the Pine Script code
259
- * @private
260
- */
261
- private _transpileCode;
262
- /**
263
- * Static analysis on the transpiled function body to detect references to
264
- * host-bound built-ins (currently visible-range; extensible via
265
- * VIEWPORT_DEPENDENT_BUILTINS). Comments are stripped during pine2js, so
266
- * scanning the post-transpile output is comment-safe.
267
- *
268
- * Why post-transpile (not regex on Pine source): a `chart.left_visible_bar_time`
269
- * literal inside a // comment would be a false positive at the source level.
270
- * After pine2js, only live code remains.
271
- *
272
- * Why regex (not AST visitor): `chart` is a reserved namespace in
273
- * KNOWN_NAMESPACES — Pine scripts cannot shadow it with a local identifier,
274
- * so a whole-word match on `chart.<prop>` is unambiguous.
275
- */
276
- private _detectViewportUsage;
277
258
  /**
278
259
  * Execute iterations from startIdx to endIdx, updating the context
279
260
  * @private
@@ -12,3 +12,5 @@ export type { Kline, PeriodType } from './marketData/types';
12
12
  export { computeNextPeriodStart, localTimeToUTC, computeSessionClose, TIMEFRAME_SECONDS, TIMEFRAME_PERIOD_INFO } from './marketData/types';
13
13
  export { aggregateCandles, selectSubTimeframe, getAggregationRatio } from './marketData/aggregation';
14
14
  export { PineTS, Context, Provider, Indicator, PineRuntimeError };
15
+ export type { IPineInput, IPineProp, PineInputType, PineInputDisplay, PinePropType, PreparedScript } from './Indicator';
16
+ export { INDICATOR_PROPS, STRATEGY_PROPS, propsForDeclaration } from './Indicator';
@@ -1,5 +1,9 @@
1
1
  /**
2
- * Average per-trade loss (as a positive percent) across losing closed trades.
3
- * NaN when no losers yet. Matches Pine's strategy.avg_losing_trade_percent.
2
+ * Average per-trade loss (as a SIGNED negative percent) across losing
3
+ * closed trades. NaN when no losers yet.
4
+ *
5
+ * Note: `strategy.avg_losing_trade` (the dollar version) is reported as
6
+ * a POSITIVE magnitude by Pine convention, but `_percent` is signed —
7
+ * negative values, since they represent losses.
4
8
  */
5
9
  export declare function avg_losing_trade_percent(context: any): () => number;
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * Convert a value from the symbol's currency to the account currency.
3
3
  *
4
- * For the common same-currency case (e.g. BTCUSDC's quote currency USDC ≈ USD),
5
- * this is an identity. Genuine cross-currency conversion would require an FX
6
- * rate source which we don't model; returns the input unchanged in that case
7
- * with a console warning on first call.
4
+ * Pine semantics:
5
+ * - same currency string return the value unchanged (identity)
6
+ * - different currency strings return `na` (NaN), since no FX
7
+ * rate is available. String equality is used, not economic
8
+ * equivalence — so nominally pegged pairs like USDC vs USD still
9
+ * return NaN when their currency strings differ.
10
+ *
11
+ * When `syminfo.currency` is undefined we fall back to identity rather
12
+ * than NaN, so synthetic / array-fed datasets without a syminfo block
13
+ * don't get poisoned.
8
14
  */
9
15
  export declare function convert_to_account(context: any): (value: number) => number;
@@ -1,5 +1,8 @@
1
1
  /**
2
2
  * Inverse of convert_to_account: from account currency → symbol currency.
3
- * Same identity passthrough for matching-currency case.
3
+ *
4
+ * Mirrors convert_to_account's TV-matching behavior: identity passthrough
5
+ * when account and symbol currencies are the same string, NaN when they
6
+ * differ. See convert_to_account.ts for the full rationale.
4
7
  */
5
8
  export declare function convert_to_symbol(context: any): (value: number) => number;
@@ -2,12 +2,22 @@
2
2
  * Price at which the current leveraged position would be force-liquidated.
3
3
  * Returns NaN when flat or when the relevant margin% is 100 (no leverage).
4
4
  *
5
- * Formula (simplified):
6
- * long → entry_avg_price * (1 - margin_long / 100)
7
- * short → entry_avg_price * (1 + margin_short / 100)
5
+ * Official TV formula, documented at
6
+ * https://www.tradingview.com/support/solutions/43000717375/ :
8
7
  *
9
- * This is the simple peak-loss-tolerable price; real broker liquidation
10
- * depends on maintenance margin schedules which we don't model.
11
- * Matches Pine's strategy.margin_liquidation_price in the common case.
8
+ * MarginLiquidationPriceRaw =
9
+ * ((InitialCapital + NetProfit) / (PointValue * AbsPositionSize)
10
+ * Direction * EntryPrice)
11
+ * / (MarginPercent / 100 − Direction)
12
+ *
13
+ * Where:
14
+ * InitialCapital + NetProfit = realized account equity (initial cash
15
+ * plus closed-trade P&L; excludes openprofit)
16
+ * PointValue = syminfo.pointvalue (1 for crypto, varies
17
+ * for futures contracts)
18
+ * AbsPositionSize = |strategy.position_size|
19
+ * Direction = +1 for long, −1 for short
20
+ * EntryPrice = strategy.position_avg_price
21
+ * MarginPercent = margin_long for longs, margin_short for shorts
12
22
  */
13
23
  export declare function margin_liquidation_price(context: any): () => number;
@@ -1,5 +1,14 @@
1
1
  /**
2
- * Maximum drawdown as a percent of the equity high-water mark.
3
- * Matches Pine's strategy.max_drawdown_percent.
2
+ * Maximum drawdown percent. Pine's semantic is the RUNNING MAX of
3
+ * `(latched_drawdown / equity_at_that_latch) × 100` across all latch
4
+ * events over the strategy's lifetime — NOT a derived
5
+ * `(current_max_drawdown / current_equity_at_peak) × 100`. The two
6
+ * interpretations diverge when a later latch produces a larger absolute
7
+ * drawdown but a smaller percentage (because equity grew faster than
8
+ * the drawdown), in which case the earlier latch's higher ratio is the
9
+ * reported value.
10
+ *
11
+ * The running max is maintained in `updateEquityPeaks` (utils.ts) on
12
+ * every latch event; this getter is a pure read.
4
13
  */
5
- export declare function max_drawdown_percent(context: any): () => number;
14
+ export declare function max_drawdown_percent(context: any): () => any;
@@ -1,5 +1,17 @@
1
1
  /**
2
- * Maximum equity run-up as a percent of initial capital.
3
- * Matches Pine's strategy.max_runup_percent.
2
+ * Maximum equity run-up percent. Mirrors the `max_drawdown_percent`
3
+ * semantic TV reports the RUNNING MAX of the per-latch ratio,
4
+ * `(latched_runup / equity_at_that_latch) × 100`, across all latches
5
+ * in the strategy's lifetime.
6
+ *
7
+ * In most strategies the runup ratio grows monotonically with each
8
+ * latch, so this matches a derived `(current_max_runup /
9
+ * current_equity_at_runup_peak) × 100`. But in scenarios where a
10
+ * later latch produces a larger absolute runup yet a smaller
11
+ * percentage (because equity grew faster than the runup), TV keeps
12
+ * the highest ratio ever seen — same as the drawdown semantic.
13
+ *
14
+ * The running max is maintained in `updateEquityPeaks` whenever
15
+ * max_runup is latched to a new peak — see utils.ts.
4
16
  */
5
- export declare function max_runup_percent(context: any): () => number;
17
+ export declare function max_runup_percent(context: any): () => any;
@@ -111,6 +111,10 @@ export interface Order {
111
111
  immediately?: boolean;
112
112
  trail_peak?: number;
113
113
  trail_armed?: boolean;
114
+ _isReversalEntry?: boolean;
115
+ _attachedAtReversal?: boolean;
116
+ _isPersistent?: boolean;
117
+ _callsiteId?: string;
114
118
  }
115
119
  /**
116
120
  * Strategy state stored on the Context after a backtest run.
@@ -144,6 +148,10 @@ export interface StrategyState {
144
148
  max_runup: number;
145
149
  equity_peak: number;
146
150
  equity_trough: number;
151
+ equity_at_runup_peak: number;
152
+ equity_at_drawdown_peak: number;
153
+ max_drawdown_percent_value: number;
154
+ max_runup_percent_value: number;
147
155
  wintrades: number;
148
156
  losstrades: number;
149
157
  eventrades: number;
@@ -173,4 +181,7 @@ export interface StrategyState {
173
181
  max_position_size?: number;
174
182
  };
175
183
  risk_halted: boolean;
184
+ _exit_call_history?: Map<string, number>;
185
+ _exit_fallback_counter?: number;
186
+ _exit_fallback_last_bar?: number;
176
187
  }
@@ -3,6 +3,58 @@ import { Order, StrategyState } from './types';
3
3
  * Parse strategy() function arguments
4
4
  */
5
5
  export declare function parseStrategyOptions(args: any[]): any;
6
+ /**
7
+ * Round a stop/limit price to the symbol's mintick grid, AWAY from the
8
+ * reference price (typically the current bar's close at order placement).
9
+ *
10
+ * Pine's broker emulator places stop/limit orders on the mintick grid
11
+ * conservatively — a buy stop at 4188.4541 above current 4184 becomes
12
+ * 4188.46 (ceiling), not 4188.45. This makes the order trigger LATER
13
+ * (requires more price movement), mirroring real-broker order placement.
14
+ *
15
+ * The rule:
16
+ * price > referencePrice → ceil to mintick (push price UP)
17
+ * price < referencePrice → floor to mintick (push price DOWN)
18
+ * price === referencePrice → return as-is
19
+ *
20
+ * Covers all four cases naturally:
21
+ * - Buy stop above current → ceil
22
+ * - Sell stop below current → floor
23
+ * - Buy limit below current → floor
24
+ * - Sell limit above current → ceil
25
+ * - Long TP above entry / SL below entry → ceil / floor
26
+ * - Short TP below entry / SL above entry → floor / ceil
27
+ *
28
+ * For mintick === 0 or undefined (defensive), returns the price unchanged.
29
+ */
30
+ export declare function roundToMintick(price: number, referencePrice: number, mintick: number): number;
31
+ /**
32
+ * Margin required to hold a position of `qty` contracts at `price`, given
33
+ * the `marginPct` (% of notional that must be posted as collateral). The
34
+ * pointValue factor converts price units to account-currency dollars
35
+ * (1 for crypto, varies for futures).
36
+ *
37
+ * Pine docs (strategy() declaration): `margin_long` / `margin_short` is
38
+ * the percentage of notional held as collateral. 100 = no leverage, 20 =
39
+ * 5× leverage, etc.
40
+ */
41
+ export declare function computeRequiredMargin(qty: number, price: number, marginPct: number, pointValue: number): number;
42
+ /**
43
+ * Account equity computed AS IF the marketprice were `atPrice` — used to
44
+ * check what equity would be at a hypothetical intra-bar price (e.g. the
45
+ * bar's adverse extreme for a margin-call check).
46
+ *
47
+ * equity_at_price = initial_capital + netprofit + unrealizedPnL_at_price
48
+ *
49
+ * The mark-to-market is computed against EVERY open trade's entry price.
50
+ */
51
+ export declare function computeEquityAtPrice(context: any, atPrice: number): number;
52
+ /**
53
+ * Total margin currently held by all open positions, valued at `atPrice`.
54
+ * Per-position margin uses `margin_long` for longs and `margin_short` for
55
+ * shorts (Pine semantic — see strategy() declaration).
56
+ */
57
+ export declare function computeHeldMargin(context: any, atPrice: number): number;
6
58
  /**
7
59
  * Calculate order quantity based on strategy configuration
8
60
  */
@@ -51,14 +103,31 @@ export declare function evaluateCatastrophicRiskHalt(strategy: StrategyState): v
51
103
  * @param price fill price
52
104
  * @param time fill time (ms)
53
105
  */
54
- export declare function openTrade(context: any, entryId: string, direction: number, qty: number, price: number, time: number): void;
106
+ export declare function openTrade(context: any, entryId: string, direction: number, qty: number, price: number, time: number, entryComment?: string, isReversalOpen?: boolean): void;
55
107
  /**
56
108
  * Close partial or full position.
57
109
  *
58
110
  * FIFO accounting: closes oldest open trades first. Splits a trade if the
59
111
  * close qty is smaller than the trade's remaining qty.
60
112
  */
61
- export declare function closePartialPosition(context: any, qtyToClose: number, exitPrice: number, exitTime: number): void;
113
+ export interface CloseInfo {
114
+ /** Which exit leg triggered ('profit'/'loss'/'trailing'), null otherwise. */
115
+ triggerKind?: 'profit' | 'loss' | 'trailing' | null;
116
+ /** Exit order's id, set onto the closed trade as trade.exit_id. */
117
+ exitId?: string;
118
+ /** Resolved exit comment (the matching comment_profit/loss/trailing). */
119
+ exitComment?: string;
120
+ /**
121
+ * True when this close is part of a single REVERSAL order that will
122
+ * also open a new trade in the opposite direction. Affects
123
+ * `cash_per_order` commission: TV charges the flat fee ONCE per order
124
+ * placement, attributed to the new entry — the implicit close leg of
125
+ * the reversal does NOT incur a second flat charge. Per-leg types
126
+ * (percent, cash_per_contract) are unaffected by this flag.
127
+ */
128
+ isImplicitReversal?: boolean;
129
+ }
130
+ export declare function closePartialPosition(context: any, qtyToClose: number, exitPrice: number, exitTime: number, closeInfo?: CloseInfo): void;
62
131
  /**
63
132
  * FIFO close of `qtyToClose` contracts from open trades, optionally filtered
64
133
  * by `fromEntry` — when set, only trades whose `entry_id === fromEntry` are
@@ -67,7 +136,7 @@ export declare function closePartialPosition(context: any, qtyToClose: number, e
67
136
  * Wraps `closePartialPosition` by temporarily reorganizing `opentrades` so
68
137
  * the matching trades sit at the head of the FIFO queue.
69
138
  */
70
- export declare function closeMatching(context: any, fromEntry: string | undefined, qtyToClose: number, exitPrice: number, exitTime: number): void;
139
+ export declare function closeMatching(context: any, fromEntry: string | undefined, qtyToClose: number, exitPrice: number, exitTime: number, closeInfo?: CloseInfo): void;
71
140
  /**
72
141
  * Process exit-category orders each bar (after entry-order fills, before the
73
142
  * user script runs). Handles:
@@ -78,6 +147,29 @@ export declare function closeMatching(context: any, fromEntry: string | undefine
78
147
  * peak (trade.trail_peak) is updated each bar even when not triggered.
79
148
  */
80
149
  export declare function processExitOrders(context: any): void;
150
+ /**
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".
156
+ *
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.
161
+ *
162
+ * Skipped entirely when the position's `margin_pct >= 100` (no leverage)
163
+ * or when there are no open positions.
164
+ */
165
+ export declare function processMarginCall(context: any): void;
166
+ /**
167
+ * End-of-bar finalize: refresh equity at CLOSE and latch
168
+ * `strategy.max_drawdown` / `strategy.max_runup` using the bar's H/L. Runs
169
+ * UNCONDITIONALLY once per bar (after entry+exit fills are done), regardless
170
+ * of whether the strategy uses exit orders.
171
+ */
172
+ export declare function finalizeStrategyBar(context: any): void;
81
173
  /**
82
174
  * Initialize strategy state
83
175
  */