@hoox-sh/pynets 0.1.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/CHANGELOG.md +30 -0
- package/LICENSE +661 -0
- package/NOTICE +46 -0
- package/README.md +327 -0
- package/dist/browser.js +44 -0
- package/dist/index.d.ts +348 -0
- package/dist/index.js +41741 -0
- package/package.json +77 -0
- package/src/ast/builder.ts +1002 -0
- package/src/ast/dump.ts +43 -0
- package/src/ast/helper.ts +77 -0
- package/src/ast/nodes.ts +570 -0
- package/src/ast/unparse.ts +360 -0
- package/src/cli/rich.ts +378 -0
- package/src/cli.ts +627 -0
- package/src/generated/PinescriptLexer.interp +267 -0
- package/src/generated/PinescriptLexer.tokens +131 -0
- package/src/generated/PinescriptLexer.ts +673 -0
- package/src/generated/PinescriptLexerBase.ts +395 -0
- package/src/generated/PinescriptParser.interp +267 -0
- package/src/generated/PinescriptParser.tokens +131 -0
- package/src/generated/PinescriptParser.ts +8863 -0
- package/src/generated/PinescriptParserBase.ts +21 -0
- package/src/generated/PinescriptParserVisitor.ts +840 -0
- package/src/index.ts +55 -0
- package/src/parser/LexerBase.ts +395 -0
- package/src/parser/ParserBase.ts +21 -0
- package/src/parser/error.ts +33 -0
- package/src/runtime/array.ts +486 -0
- package/src/runtime/color.ts +164 -0
- package/src/runtime/drawings.ts +510 -0
- package/src/runtime/input.ts +102 -0
- package/src/runtime/interpret.ts +3897 -0
- package/src/runtime/library.ts +282 -0
- package/src/runtime/log.ts +69 -0
- package/src/runtime/map.ts +85 -0
- package/src/runtime/math.ts +231 -0
- package/src/runtime/matrix.ts +1154 -0
- package/src/runtime/provider.ts +232 -0
- package/src/runtime/request.ts +372 -0
- package/src/runtime/series.ts +44 -0
- package/src/runtime/str.ts +433 -0
- package/src/runtime/strategy.ts +1105 -0
- package/src/runtime/ta.ts +2093 -0
- package/src/runtime/ticker.ts +81 -0
- package/src/runtime/time.ts +439 -0
- package/src/runtime/timeframe.ts +85 -0
- package/src/runtime/udt.ts +119 -0
|
@@ -0,0 +1,3897 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2024-2026 jango_blockchained
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*
|
|
5
|
+
* Interpret host: indicator/plot, series lookback, var/:=, ta.*,
|
|
6
|
+
* tuple unpack, ternary, for-to / for-in / while, switch, and/or,
|
|
7
|
+
* arrays, color/str, strategy events + position series, input*, math.*,
|
|
8
|
+
* request.security, UDF FunctionDef, matrix.*, extra TA, drawings/alert,
|
|
9
|
+
* derived prices, calendar, Pine na-compare.
|
|
10
|
+
* Not a port of runtime/host.py — same public envelope, interpret only.
|
|
11
|
+
*/
|
|
12
|
+
import { parse } from "../ast/helper.ts";
|
|
13
|
+
import type {
|
|
14
|
+
AST,
|
|
15
|
+
Arg,
|
|
16
|
+
Assign,
|
|
17
|
+
Attribute,
|
|
18
|
+
BoolOp,
|
|
19
|
+
Call,
|
|
20
|
+
Conditional,
|
|
21
|
+
ForIn,
|
|
22
|
+
ForTo,
|
|
23
|
+
FunctionDef,
|
|
24
|
+
TypeDef,
|
|
25
|
+
EnumDef,
|
|
26
|
+
Import,
|
|
27
|
+
Param,
|
|
28
|
+
ReAssign,
|
|
29
|
+
Script,
|
|
30
|
+
Switch,
|
|
31
|
+
Tuple,
|
|
32
|
+
While,
|
|
33
|
+
expr,
|
|
34
|
+
stmt,
|
|
35
|
+
} from "../ast/nodes.ts";
|
|
36
|
+
import { PineArray } from "./array.ts";
|
|
37
|
+
import {
|
|
38
|
+
asColor,
|
|
39
|
+
colorByName,
|
|
40
|
+
colorFromGradient,
|
|
41
|
+
colorNew,
|
|
42
|
+
colorB,
|
|
43
|
+
colorG,
|
|
44
|
+
colorR,
|
|
45
|
+
colorRgb,
|
|
46
|
+
colorT,
|
|
47
|
+
parseColor,
|
|
48
|
+
type Color,
|
|
49
|
+
} from "./color.ts";
|
|
50
|
+
import {
|
|
51
|
+
EnumMember,
|
|
52
|
+
EnumType,
|
|
53
|
+
UdtInstance,
|
|
54
|
+
UdtType,
|
|
55
|
+
enumTypeFromNames,
|
|
56
|
+
udtTypeFromAssigns,
|
|
57
|
+
} from "./udt.ts";
|
|
58
|
+
import {
|
|
59
|
+
timeframeFromSeconds,
|
|
60
|
+
timeframeInSeconds,
|
|
61
|
+
timeframeIsDaily,
|
|
62
|
+
timeframeIsIntraday,
|
|
63
|
+
timeframeIsMonthly,
|
|
64
|
+
timeframeIsWeekly,
|
|
65
|
+
timeframeMultiplier as tfMultiplier,
|
|
66
|
+
} from "./timeframe.ts";
|
|
67
|
+
import { DrawingBook, type DrawingEvent } from "./drawings.ts";
|
|
68
|
+
import { LogBook, formatLogParts, runtimeError, type LogRecord } from "./log.ts";
|
|
69
|
+
import { MemoryProvider, type BarProvider } from "./provider.ts";
|
|
70
|
+
import {
|
|
71
|
+
TickerId,
|
|
72
|
+
tickerHeikinashi,
|
|
73
|
+
tickerKagi,
|
|
74
|
+
tickerLinebreak,
|
|
75
|
+
tickerModify,
|
|
76
|
+
tickerNew,
|
|
77
|
+
tickerPointfigure,
|
|
78
|
+
tickerRenko,
|
|
79
|
+
tickerStandard,
|
|
80
|
+
} from "./ticker.ts";
|
|
81
|
+
import { isInputBuiltin, resolveInputDefault, inputAsCell, type InputValue } from "./input.ts";
|
|
82
|
+
import {
|
|
83
|
+
mathAbs,
|
|
84
|
+
mathAcos,
|
|
85
|
+
mathAsin,
|
|
86
|
+
mathAtan,
|
|
87
|
+
mathAvg,
|
|
88
|
+
mathCeil,
|
|
89
|
+
mathCos,
|
|
90
|
+
mathExp,
|
|
91
|
+
mathFloor,
|
|
92
|
+
mathIsFinite,
|
|
93
|
+
mathLog,
|
|
94
|
+
mathLog10,
|
|
95
|
+
mathMax,
|
|
96
|
+
mathMin,
|
|
97
|
+
mathPow,
|
|
98
|
+
mathRandom,
|
|
99
|
+
mathRound,
|
|
100
|
+
mathRoundToMintick,
|
|
101
|
+
mathSign,
|
|
102
|
+
mathSin,
|
|
103
|
+
mathSqrt,
|
|
104
|
+
mathSum,
|
|
105
|
+
mathTan,
|
|
106
|
+
mathToDegrees,
|
|
107
|
+
mathToRadians,
|
|
108
|
+
} from "./math.ts";
|
|
109
|
+
import { utcPartsFromMs, timestamp, timeTradingDay, weekOfYear } from "./time.ts";
|
|
110
|
+
import {
|
|
111
|
+
LibraryModule,
|
|
112
|
+
LibraryRegistry,
|
|
113
|
+
STUB_KNOWN_EXPORTS,
|
|
114
|
+
createStubModule,
|
|
115
|
+
type StubKwargs,
|
|
116
|
+
} from "./library.ts";
|
|
117
|
+
import { isRequestBuiltin, resolveRequest, resolveSecurity } from "./request.ts";
|
|
118
|
+
import { NA, PineSeries, type Cell } from "./series.ts";
|
|
119
|
+
import {
|
|
120
|
+
strContains,
|
|
121
|
+
strEndsWith,
|
|
122
|
+
strFormat,
|
|
123
|
+
strJoin,
|
|
124
|
+
strLength,
|
|
125
|
+
strLower,
|
|
126
|
+
strMatch,
|
|
127
|
+
strPos,
|
|
128
|
+
strRepeat,
|
|
129
|
+
strReplace,
|
|
130
|
+
strReplaceAll,
|
|
131
|
+
strSplit,
|
|
132
|
+
strStartsWith,
|
|
133
|
+
strSubstring,
|
|
134
|
+
strToNumber,
|
|
135
|
+
strFormatTime,
|
|
136
|
+
strTostring,
|
|
137
|
+
strTrim,
|
|
138
|
+
strUpper,
|
|
139
|
+
} from "./str.ts";
|
|
140
|
+
import { StrategyBook, type BrokerSettings, type StrategyEvent, type StrategySummary } from "./strategy.ts";
|
|
141
|
+
import { PineMap } from "./map.ts";
|
|
142
|
+
import { PineMatrix } from "./matrix.ts";
|
|
143
|
+
import { TaEngine } from "./ta.ts";
|
|
144
|
+
|
|
145
|
+
export type { DrawingEvent };
|
|
146
|
+
|
|
147
|
+
export type { StrategyEvent };
|
|
148
|
+
|
|
149
|
+
export interface OHLCVBar {
|
|
150
|
+
open?: number;
|
|
151
|
+
high?: number;
|
|
152
|
+
low?: number;
|
|
153
|
+
close?: number;
|
|
154
|
+
volume?: number;
|
|
155
|
+
time?: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type InputOverrides = Record<string, number | string | boolean>;
|
|
159
|
+
|
|
160
|
+
export interface RuntimeOptions {
|
|
161
|
+
inputs?: InputOverrides;
|
|
162
|
+
broker?: BrokerSettings;
|
|
163
|
+
timeframe?: string | null;
|
|
164
|
+
libraries?: LibraryRegistry;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface RuntimeFill {
|
|
168
|
+
type: "fill";
|
|
169
|
+
bar: number;
|
|
170
|
+
id: string;
|
|
171
|
+
side: "buy" | "sell";
|
|
172
|
+
qty: number;
|
|
173
|
+
price: number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface RuntimeResult {
|
|
177
|
+
series: Record<string, Array<number | null>>;
|
|
178
|
+
plots: Array<number | null>;
|
|
179
|
+
plot_meta: Array<{ title: string }>;
|
|
180
|
+
count: number;
|
|
181
|
+
script_name: string | null;
|
|
182
|
+
script_type: string;
|
|
183
|
+
mode: "interpret";
|
|
184
|
+
events?: StrategyEvent[];
|
|
185
|
+
fills?: RuntimeFill[];
|
|
186
|
+
drawings?: DrawingEvent[];
|
|
187
|
+
logs?: LogRecord[];
|
|
188
|
+
/** Titled plot series as `{ data: [{ value, time? }] }` for host/stream consumers. */
|
|
189
|
+
plot_data?: Record<string, { data: Array<{ value: number | null; time?: number }> }>;
|
|
190
|
+
/** Book-level strategy scalars when the script is a strategy (or any fills ran). */
|
|
191
|
+
strategy?: StrategySummary;
|
|
192
|
+
error?: string;
|
|
193
|
+
error_kind?: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Eval-only: Cell, tagged tuple/array, color, or strategy.long/short strings. */
|
|
197
|
+
type TupleVal = { __tuple: true; elts: Value[] };
|
|
198
|
+
type ArrayVal = { __array: true; elts: Value[] };
|
|
199
|
+
type Value =
|
|
200
|
+
| Cell
|
|
201
|
+
| TupleVal
|
|
202
|
+
| ArrayVal
|
|
203
|
+
| Color
|
|
204
|
+
| string
|
|
205
|
+
| PineArray
|
|
206
|
+
| PineMap
|
|
207
|
+
| PineMatrix
|
|
208
|
+
| TickerId
|
|
209
|
+
| UdtType
|
|
210
|
+
| UdtInstance
|
|
211
|
+
| LibraryModule
|
|
212
|
+
| UdfDef
|
|
213
|
+
| EnumType
|
|
214
|
+
| EnumMember;
|
|
215
|
+
|
|
216
|
+
const LOOP_BREAK = Object.freeze({ __loop: "break" as const });
|
|
217
|
+
const LOOP_CONTINUE = Object.freeze({ __loop: "continue" as const });
|
|
218
|
+
const WHILE_CAP = 10_000;
|
|
219
|
+
/** Python for-to / for-in safety cap (visit_ForTo / visit_ForIn). */
|
|
220
|
+
const FOR_CAP = 1_000_000;
|
|
221
|
+
|
|
222
|
+
interface WhileNode {
|
|
223
|
+
kind: "While";
|
|
224
|
+
test: expr;
|
|
225
|
+
body: stmt[] | stmt | null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface CaseNode {
|
|
229
|
+
kind?: string;
|
|
230
|
+
pattern?: expr | null;
|
|
231
|
+
body: stmt[] | stmt | null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface SwitchNode {
|
|
235
|
+
kind: "Switch";
|
|
236
|
+
subject?: expr | null;
|
|
237
|
+
cases?: CaseNode[] | CaseNode | null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
interface BoolOpNode {
|
|
241
|
+
kind: "BoolOp";
|
|
242
|
+
op?: { kind?: string } | string | null;
|
|
243
|
+
values?: expr[] | null;
|
|
244
|
+
left?: expr;
|
|
245
|
+
right?: expr;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface UdfDef {
|
|
249
|
+
__udf: true;
|
|
250
|
+
params: Param[];
|
|
251
|
+
body: stmt[];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface UdfSiteState {
|
|
255
|
+
ctx: Record<string, Value>;
|
|
256
|
+
series: Map<string, PineSeries>;
|
|
257
|
+
varInited: Set<string>;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface Env {
|
|
261
|
+
ctx: Record<string, Value>;
|
|
262
|
+
series: Map<string, PineSeries>;
|
|
263
|
+
varInited: Set<string>;
|
|
264
|
+
barIndex: number;
|
|
265
|
+
ta: TaEngine;
|
|
266
|
+
callSites: WeakMap<object, string>;
|
|
267
|
+
plotKeys: WeakMap<object, string>;
|
|
268
|
+
nextSite: number;
|
|
269
|
+
plots: Record<string, Array<number | null>>;
|
|
270
|
+
plot_meta: Array<{ title: string }>;
|
|
271
|
+
book: StrategyBook;
|
|
272
|
+
drawings: DrawingBook;
|
|
273
|
+
symbol: string;
|
|
274
|
+
timeframe: string | null;
|
|
275
|
+
inputs: InputOverrides;
|
|
276
|
+
inputStore: Map<string, InputValue>;
|
|
277
|
+
udfs: Map<string, UdfDef>;
|
|
278
|
+
udfSites: Map<string, UdfSiteState>;
|
|
279
|
+
frames: UdfSiteState[];
|
|
280
|
+
barCount: number;
|
|
281
|
+
lastBarTime: number;
|
|
282
|
+
logs: LogBook;
|
|
283
|
+
barTimes: number[];
|
|
284
|
+
libraries: LibraryRegistry;
|
|
285
|
+
pendingExports: Map<string, Value>;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export class Runtime {
|
|
289
|
+
readonly inputs: InputOverrides;
|
|
290
|
+
readonly broker: BrokerSettings;
|
|
291
|
+
readonly timeframe: string | null;
|
|
292
|
+
readonly libraries: LibraryRegistry;
|
|
293
|
+
|
|
294
|
+
constructor(
|
|
295
|
+
public readonly symbol = "AAPL",
|
|
296
|
+
options?: RuntimeOptions,
|
|
297
|
+
) {
|
|
298
|
+
this.inputs = options?.inputs ?? {};
|
|
299
|
+
this.broker = options?.broker ?? {};
|
|
300
|
+
this.timeframe = options?.timeframe ?? null;
|
|
301
|
+
this.libraries = options?.libraries ?? new LibraryRegistry();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Store Pine source for `import namespace/name/version`. */
|
|
305
|
+
registerLibrarySource(namespace: string, name: string, version: number, source: string): void {
|
|
306
|
+
this.libraries.registerSource(namespace, name, version, source);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
run(
|
|
310
|
+
source: string,
|
|
311
|
+
ohlcv: OHLCVBar[],
|
|
312
|
+
extra?: RuntimeOptions | InputOverrides,
|
|
313
|
+
): RuntimeResult {
|
|
314
|
+
let tree: AST;
|
|
315
|
+
try {
|
|
316
|
+
tree = parse(source);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
return {
|
|
319
|
+
series: {},
|
|
320
|
+
plots: [],
|
|
321
|
+
plot_meta: [],
|
|
322
|
+
count: 0,
|
|
323
|
+
script_name: null,
|
|
324
|
+
script_type: "indicator",
|
|
325
|
+
mode: "interpret",
|
|
326
|
+
error: formatRunError(err),
|
|
327
|
+
error_kind: "parse",
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const extraOpts = extra && !isPlainInputs(extra) ? extra : undefined;
|
|
331
|
+
try {
|
|
332
|
+
return interpretTree(tree, ohlcv, {
|
|
333
|
+
symbol: this.symbol,
|
|
334
|
+
inputs: mergeInputs(this.inputs, extra),
|
|
335
|
+
broker: extraOpts?.broker ?? this.broker,
|
|
336
|
+
timeframe: extraOpts?.timeframe ?? this.timeframe,
|
|
337
|
+
libraries: extraOpts?.libraries ?? this.libraries,
|
|
338
|
+
});
|
|
339
|
+
} catch (err) {
|
|
340
|
+
return {
|
|
341
|
+
series: {},
|
|
342
|
+
plots: [],
|
|
343
|
+
plot_meta: [],
|
|
344
|
+
count: 0,
|
|
345
|
+
script_name: null,
|
|
346
|
+
script_type: "indicator",
|
|
347
|
+
mode: "interpret",
|
|
348
|
+
error: formatRunError(err),
|
|
349
|
+
error_kind: "runtime",
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Push-driven re-eval: each `push` runs the script on all bars so far. */
|
|
355
|
+
stream(source: string): RuntimeStream {
|
|
356
|
+
return new RuntimeStream(this, source);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async runProvider(
|
|
360
|
+
source: string,
|
|
361
|
+
provider: BarProvider,
|
|
362
|
+
extra?: RuntimeOptions & { limit?: number },
|
|
363
|
+
): Promise<RuntimeResult> {
|
|
364
|
+
const bars = await provider.fetch({
|
|
365
|
+
symbol: this.symbol,
|
|
366
|
+
timeframe: extra?.timeframe ?? this.timeframe,
|
|
367
|
+
limit: extra?.limit,
|
|
368
|
+
});
|
|
369
|
+
return this.run(source, bars, extra);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export type StreamEvent = "bar" | "error" | "end";
|
|
374
|
+
|
|
375
|
+
export class RuntimeStream {
|
|
376
|
+
private readonly bars: OHLCVBar[] = [];
|
|
377
|
+
private readonly barHandlers: Array<(out: RuntimeResult) => void> = [];
|
|
378
|
+
private readonly errorHandlers: Array<(err: Error) => void> = [];
|
|
379
|
+
private readonly endHandlers: Array<(out: RuntimeResult) => void> = [];
|
|
380
|
+
private closed = false;
|
|
381
|
+
|
|
382
|
+
constructor(
|
|
383
|
+
private readonly runtime: Runtime,
|
|
384
|
+
private readonly source: string,
|
|
385
|
+
) {}
|
|
386
|
+
|
|
387
|
+
on(event: "bar", handler: (out: RuntimeResult) => void): this;
|
|
388
|
+
on(event: "error", handler: (err: Error) => void): this;
|
|
389
|
+
on(event: "end", handler: (out: RuntimeResult) => void): this;
|
|
390
|
+
on(
|
|
391
|
+
event: StreamEvent,
|
|
392
|
+
handler: ((out: RuntimeResult) => void) | ((err: Error) => void),
|
|
393
|
+
): this {
|
|
394
|
+
if (event === "bar") this.barHandlers.push(handler as (out: RuntimeResult) => void);
|
|
395
|
+
else if (event === "error") this.errorHandlers.push(handler as (err: Error) => void);
|
|
396
|
+
else this.endHandlers.push(handler as (out: RuntimeResult) => void);
|
|
397
|
+
return this;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
push(bar: OHLCVBar): RuntimeResult | undefined {
|
|
401
|
+
if (this.closed) return undefined;
|
|
402
|
+
this.bars.push(bar);
|
|
403
|
+
try {
|
|
404
|
+
const out = this.runtime.run(this.source, this.bars);
|
|
405
|
+
for (const h of this.barHandlers) h(out);
|
|
406
|
+
return out;
|
|
407
|
+
} catch (err) {
|
|
408
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
409
|
+
for (const h of this.errorHandlers) h(e);
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
close(): RuntimeResult {
|
|
415
|
+
this.closed = true;
|
|
416
|
+
const out = this.runtime.run(this.source, this.bars);
|
|
417
|
+
for (const h of this.endHandlers) h(out);
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function interpret(source: string, ohlcv: OHLCVBar[]): { plots: Array<number | null> } {
|
|
423
|
+
const out = new Runtime().run(source, ohlcv);
|
|
424
|
+
if (out.error) throw new Error(out.error);
|
|
425
|
+
return { plots: out.plots };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function interpretTree(
|
|
429
|
+
tree: AST,
|
|
430
|
+
ohlcv: OHLCVBar[],
|
|
431
|
+
host?: {
|
|
432
|
+
symbol?: string;
|
|
433
|
+
inputs?: InputOverrides;
|
|
434
|
+
broker?: BrokerSettings;
|
|
435
|
+
timeframe?: string | null;
|
|
436
|
+
libraries?: LibraryRegistry;
|
|
437
|
+
},
|
|
438
|
+
): RuntimeResult {
|
|
439
|
+
const scriptNode = tree.kind === "Script" ? (tree as Script) : null;
|
|
440
|
+
const body = scriptNode?.body ?? [];
|
|
441
|
+
const plots: Record<string, Array<number | null>> = {};
|
|
442
|
+
const plot_meta: Array<{ title: string }> = [];
|
|
443
|
+
let script_name: string | null = null;
|
|
444
|
+
let script_type = "indicator";
|
|
445
|
+
|
|
446
|
+
const env: Env = {
|
|
447
|
+
ctx: {},
|
|
448
|
+
series: new Map(),
|
|
449
|
+
varInited: new Set(),
|
|
450
|
+
barIndex: 0,
|
|
451
|
+
ta: new TaEngine(),
|
|
452
|
+
callSites: new WeakMap(),
|
|
453
|
+
plotKeys: new WeakMap(),
|
|
454
|
+
nextSite: 0,
|
|
455
|
+
plots,
|
|
456
|
+
plot_meta,
|
|
457
|
+
book: new StrategyBook(host?.broker),
|
|
458
|
+
drawings: new DrawingBook(),
|
|
459
|
+
symbol: host?.symbol ?? "AAPL",
|
|
460
|
+
timeframe: host?.timeframe ?? null,
|
|
461
|
+
inputs: host?.inputs ?? {},
|
|
462
|
+
inputStore: new Map(),
|
|
463
|
+
udfs: new Map(),
|
|
464
|
+
udfSites: new Map(),
|
|
465
|
+
frames: [],
|
|
466
|
+
barCount: ohlcv.length,
|
|
467
|
+
lastBarTime: ohlcv.length === 0 ? 0 : (ohlcv[ohlcv.length - 1]!.time ?? (ohlcv.length - 1) * 60_000),
|
|
468
|
+
logs: new LogBook(),
|
|
469
|
+
barTimes: ohlcv.map((b, i) => b.time ?? i * 60_000),
|
|
470
|
+
libraries: host?.libraries ?? new LibraryRegistry(),
|
|
471
|
+
pendingExports: new Map(),
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const n = ohlcv.length;
|
|
475
|
+
let runError: { error: string; error_kind: string } | undefined;
|
|
476
|
+
try {
|
|
477
|
+
for (let i = 0; i < n; i++) {
|
|
478
|
+
beginBar(env, ohlcv[i]!, i);
|
|
479
|
+
env.book.processPending(i, {
|
|
480
|
+
open: num(env.ctx.open) ?? undefined,
|
|
481
|
+
high: num(env.ctx.high) ?? undefined,
|
|
482
|
+
low: num(env.ctx.low) ?? undefined,
|
|
483
|
+
close: num(env.ctx.close) ?? undefined,
|
|
484
|
+
});
|
|
485
|
+
env.book.markOpenTrades(
|
|
486
|
+
num(env.ctx.high) ?? num(env.ctx.close) ?? 0,
|
|
487
|
+
num(env.ctx.low) ?? num(env.ctx.close) ?? 0,
|
|
488
|
+
num(env.ctx.close) ?? 0,
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
for (const stmt of body) {
|
|
493
|
+
const decl = captureDecl(stmt);
|
|
494
|
+
if (decl) {
|
|
495
|
+
if (script_name == null) script_name = decl.name;
|
|
496
|
+
if (decl.kind === "strategy") {
|
|
497
|
+
script_type = "strategy";
|
|
498
|
+
applyStrategyDecl(stmt, env);
|
|
499
|
+
} else if (decl.kind === "library") {
|
|
500
|
+
script_type = "library";
|
|
501
|
+
applyDrawingDecl(stmt, env);
|
|
502
|
+
} else {
|
|
503
|
+
applyDrawingDecl(stmt, env);
|
|
504
|
+
}
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
execStmt(stmt, env);
|
|
508
|
+
}
|
|
509
|
+
if (i === 0 && script_type === "library" && script_name) {
|
|
510
|
+
finalizeLibrary(env, script_name);
|
|
511
|
+
}
|
|
512
|
+
} catch (err) {
|
|
513
|
+
if (err === LOOP_BREAK || err === LOOP_CONTINUE) continue;
|
|
514
|
+
runError = { error: formatRunError(err), error_kind: "runtime" };
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} catch (err) {
|
|
519
|
+
if (err !== LOOP_BREAK && err !== LOOP_CONTINUE) {
|
|
520
|
+
runError = { error: formatRunError(err), error_kind: "runtime" };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const firstTitle = plot_meta[0]?.title;
|
|
525
|
+
return {
|
|
526
|
+
series: plots,
|
|
527
|
+
plots: firstTitle ? (plots[firstTitle] ?? []) : [],
|
|
528
|
+
plot_meta,
|
|
529
|
+
count: n,
|
|
530
|
+
script_name,
|
|
531
|
+
script_type,
|
|
532
|
+
mode: "interpret",
|
|
533
|
+
events: env.book.events,
|
|
534
|
+
fills: packFills(env.book),
|
|
535
|
+
drawings: packDrawings(env.drawings),
|
|
536
|
+
logs: env.logs.records.length ? env.logs.records : undefined,
|
|
537
|
+
plot_data: packPlotData(plots, env.barTimes),
|
|
538
|
+
...(script_type === "strategy" || env.book.fills.length > 0
|
|
539
|
+
? { strategy: env.book.summary(num(env.ctx.close) ?? 0) }
|
|
540
|
+
: {}),
|
|
541
|
+
...(runError ?? {}),
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function packPlotData(
|
|
546
|
+
plots: Record<string, Array<number | null>>,
|
|
547
|
+
times: number[],
|
|
548
|
+
): Record<string, { data: Array<{ value: number | null; time?: number }> }> {
|
|
549
|
+
const out: Record<string, { data: Array<{ value: number | null; time?: number }> }> = {};
|
|
550
|
+
for (const [title, series] of Object.entries(plots)) {
|
|
551
|
+
out[title] = {
|
|
552
|
+
data: series.map((value, i) => {
|
|
553
|
+
const time = times[i];
|
|
554
|
+
return time == null ? { value } : { value, time };
|
|
555
|
+
}),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return out;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function pushNamedSeries(env: Env, id: string, value: Cell): void {
|
|
562
|
+
env.ctx[id] = value;
|
|
563
|
+
let s = env.series.get(id);
|
|
564
|
+
if (!s) {
|
|
565
|
+
s = new PineSeries();
|
|
566
|
+
env.series.set(id, s);
|
|
567
|
+
}
|
|
568
|
+
s.push(value);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function beginBar(env: Env, bar: OHLCVBar, i: number): void {
|
|
572
|
+
env.barIndex = i;
|
|
573
|
+
const o = bar.open ?? 0;
|
|
574
|
+
const h = bar.high ?? 0;
|
|
575
|
+
const l = bar.low ?? 0;
|
|
576
|
+
const c = bar.close ?? 0;
|
|
577
|
+
const vol = bar.volume ?? 1;
|
|
578
|
+
const t = bar.time ?? i * 60_000;
|
|
579
|
+
pushNamedSeries(env, "open", o);
|
|
580
|
+
pushNamedSeries(env, "high", h);
|
|
581
|
+
pushNamedSeries(env, "low", l);
|
|
582
|
+
pushNamedSeries(env, "close", c);
|
|
583
|
+
pushNamedSeries(env, "volume", vol);
|
|
584
|
+
pushNamedSeries(env, "time", t);
|
|
585
|
+
env.ctx.bar_index = i;
|
|
586
|
+
env.ctx.last_bar_index = env.barCount - 1;
|
|
587
|
+
env.ctx.last_bar_time = env.lastBarTime;
|
|
588
|
+
|
|
589
|
+
bindDerived(env, "hl2", (h + l) / 2);
|
|
590
|
+
bindDerived(env, "hlc3", (h + l + c) / 3);
|
|
591
|
+
bindDerived(env, "ohlc4", (o + h + l + c) / 4);
|
|
592
|
+
bindDerived(env, "hlcc4", (h + l + c + c) / 4);
|
|
593
|
+
|
|
594
|
+
const parts = utcPartsFromMs(t);
|
|
595
|
+
env.ctx.year = parts.year;
|
|
596
|
+
env.ctx.month = parts.month;
|
|
597
|
+
env.ctx.dayofmonth = parts.dayofmonth;
|
|
598
|
+
env.ctx.dayofweek = parts.dayofweek;
|
|
599
|
+
env.ctx.hour = parts.hour;
|
|
600
|
+
env.ctx.minute = parts.minute;
|
|
601
|
+
env.ctx.second = parts.second;
|
|
602
|
+
|
|
603
|
+
for (const id of env.varInited) {
|
|
604
|
+
const s = env.series.get(id);
|
|
605
|
+
if (s == null) continue;
|
|
606
|
+
if (s.length === i) s.push(s.current);
|
|
607
|
+
env.ctx[id] = s.current;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function bindDerived(env: Env, id: string, value: Cell): void {
|
|
612
|
+
env.ctx[id] = value;
|
|
613
|
+
let s = env.series.get(id);
|
|
614
|
+
if (!s) {
|
|
615
|
+
s = new PineSeries();
|
|
616
|
+
env.series.set(id, s);
|
|
617
|
+
}
|
|
618
|
+
if (s.length === env.barIndex) s.push(value);
|
|
619
|
+
else if (s.length === env.barIndex + 1) s.setCurrent(value);
|
|
620
|
+
else {
|
|
621
|
+
while (s.length < env.barIndex) s.push(NA);
|
|
622
|
+
s.push(value);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function attrPath(node: expr): string | null {
|
|
627
|
+
if (node.kind === "Name") return node.id;
|
|
628
|
+
if (node.kind === "Attribute") {
|
|
629
|
+
const base = attrPath(node.value);
|
|
630
|
+
return base ? `${base}.${node.attr}` : node.attr;
|
|
631
|
+
}
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function callName(node: Call): string | null {
|
|
636
|
+
if (node.func.kind === "Name") return node.func.id;
|
|
637
|
+
return attrPath(node.func);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function asArgs(args: Arg | Arg[] | null | undefined): Arg[] {
|
|
641
|
+
if (args == null) return [];
|
|
642
|
+
return Array.isArray(args) ? args : [args];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function firstStringArg(args: Arg[] | Arg | null | undefined): string | null {
|
|
646
|
+
for (const a of asArgs(args)) {
|
|
647
|
+
if (a.value.kind === "Constant" && typeof a.value.value === "string") return a.value.value;
|
|
648
|
+
}
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** ASDL `Arg.name`, plus assign-like / keyword aliases if the builder omitted `name`. */
|
|
653
|
+
function argKeyword(a: Arg): string | null {
|
|
654
|
+
if (a.name != null && a.name !== "") return a.name;
|
|
655
|
+
const extra = a as Arg & { arg?: unknown; keyword?: unknown };
|
|
656
|
+
if (typeof extra.arg === "string" && extra.arg !== "") return extra.arg;
|
|
657
|
+
if (typeof extra.keyword === "string" && extra.keyword !== "") return extra.keyword;
|
|
658
|
+
const rec = recoverAssignLike(a);
|
|
659
|
+
return rec?.name ?? null;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function recoverAssignLike(a: Arg): { name: string; value: expr } | null {
|
|
663
|
+
if (a.name != null && a.name !== "") return null;
|
|
664
|
+
const v = a.value as unknown as {
|
|
665
|
+
kind?: string;
|
|
666
|
+
target?: { kind?: string; id?: string };
|
|
667
|
+
value?: expr | null;
|
|
668
|
+
};
|
|
669
|
+
if ((v.kind === "Assign" || v.kind === "ReAssign") && v.target?.kind === "Name" && v.value != null) {
|
|
670
|
+
return { name: v.target.id!, value: v.value };
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function argValue(a: Arg): expr {
|
|
676
|
+
return recoverAssignLike(a)?.value ?? a.value;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function namedStringArg(args: Arg[] | Arg | null | undefined, name: string): string | null {
|
|
680
|
+
for (const a of asArgs(args)) {
|
|
681
|
+
if (argKeyword(a) !== name) continue;
|
|
682
|
+
const v = argValue(a);
|
|
683
|
+
if (v.kind === "Constant" && typeof v.value === "string") return v.value;
|
|
684
|
+
}
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function siteKey(node: Call, env: Env): string {
|
|
689
|
+
const existing = env.callSites.get(node);
|
|
690
|
+
if (existing != null) return existing;
|
|
691
|
+
const loc =
|
|
692
|
+
node.lineno != null && node.col_offset != null ? `${node.lineno}:${node.col_offset}` : "";
|
|
693
|
+
const id = `${loc}#${env.nextSite++}`;
|
|
694
|
+
env.callSites.set(node, id);
|
|
695
|
+
return id;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function callArg(
|
|
699
|
+
args: Arg[] | Arg | null | undefined,
|
|
700
|
+
index: number,
|
|
701
|
+
names: string[],
|
|
702
|
+
): expr | undefined {
|
|
703
|
+
const list = asArgs(args);
|
|
704
|
+
for (const a of list) {
|
|
705
|
+
const kw = argKeyword(a);
|
|
706
|
+
if (kw != null && names.includes(kw)) return argValue(a);
|
|
707
|
+
}
|
|
708
|
+
let i = 0;
|
|
709
|
+
for (const a of list) {
|
|
710
|
+
if (argKeyword(a) != null) continue;
|
|
711
|
+
if (i === index) return a.value;
|
|
712
|
+
i++;
|
|
713
|
+
}
|
|
714
|
+
return undefined;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function defaultPlotTitle(fname: string): string {
|
|
718
|
+
if (fname === "hline") return "hline";
|
|
719
|
+
if (fname === "plotshape") return "shape";
|
|
720
|
+
if (fname === "plotchar") return "char";
|
|
721
|
+
if (fname === "plotarrow") return "arrow";
|
|
722
|
+
if (fname === "bgcolor") return "bgcolor";
|
|
723
|
+
if (fname === "barcolor") return "barcolor";
|
|
724
|
+
return "plot";
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function resolvePlotTitle(args: Arg[], fname: string): string {
|
|
728
|
+
const named = namedStringArg(args, "title");
|
|
729
|
+
if (named != null) {
|
|
730
|
+
const t = named.trim();
|
|
731
|
+
if (t) return t;
|
|
732
|
+
}
|
|
733
|
+
const positional: Arg[] = [];
|
|
734
|
+
for (const a of args) {
|
|
735
|
+
if (argKeyword(a) != null) continue;
|
|
736
|
+
positional.push(a);
|
|
737
|
+
}
|
|
738
|
+
if (positional.length > 1) {
|
|
739
|
+
const second = argValue(positional[1]!);
|
|
740
|
+
if (second.kind === "Constant" && typeof second.value === "string") {
|
|
741
|
+
const t = second.value.trim();
|
|
742
|
+
if (t) return t;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
const after = firstStringArg(args.slice(1));
|
|
746
|
+
if (after != null) {
|
|
747
|
+
const t = after.trim();
|
|
748
|
+
if (t) return t;
|
|
749
|
+
}
|
|
750
|
+
return defaultPlotTitle(fname);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function uniquifyPlotKey(env: Env, node: object, title: string): string {
|
|
754
|
+
const existing = env.plotKeys.get(node);
|
|
755
|
+
if (existing != null) return existing;
|
|
756
|
+
const base = title.trim() || "plot";
|
|
757
|
+
let key = base;
|
|
758
|
+
let n = 2;
|
|
759
|
+
while (Object.prototype.hasOwnProperty.call(env.plots, key)) {
|
|
760
|
+
key = `${base}_${n++}`;
|
|
761
|
+
}
|
|
762
|
+
env.plotKeys.set(node, key);
|
|
763
|
+
return key;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function isVarMode(stmt: Assign): boolean {
|
|
767
|
+
return stmt.mode?.kind === "Var";
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function isTuple(v: Value): v is TupleVal {
|
|
771
|
+
return typeof v === "object" && v !== null && (v as TupleVal).__tuple === true;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function tupleOf(elts: Value[]): TupleVal {
|
|
775
|
+
return { __tuple: true, elts };
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function isCell(value: Value): value is Cell {
|
|
779
|
+
return value === null || (typeof value === "number" && Number.isFinite(value));
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function activeFrame(env: Env): UdfSiteState | null {
|
|
783
|
+
return env.frames.length > 0 ? env.frames[env.frames.length - 1]! : null;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function varSet(env: Env): Set<string> {
|
|
787
|
+
return activeFrame(env)?.varInited ?? env.varInited;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function ctxGet(env: Env, id: string): Value | undefined {
|
|
791
|
+
for (let i = env.frames.length - 1; i >= 0; i--) {
|
|
792
|
+
const ctx = env.frames[i]!.ctx;
|
|
793
|
+
if (Object.prototype.hasOwnProperty.call(ctx, id)) return ctx[id];
|
|
794
|
+
}
|
|
795
|
+
if (Object.prototype.hasOwnProperty.call(env.ctx, id)) return env.ctx[id];
|
|
796
|
+
return undefined;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function seriesGet(env: Env, id: string): PineSeries | undefined {
|
|
800
|
+
for (let i = env.frames.length - 1; i >= 0; i--) {
|
|
801
|
+
const s = env.frames[i]!.series.get(id);
|
|
802
|
+
if (s) return s;
|
|
803
|
+
}
|
|
804
|
+
return env.series.get(id);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function bindName(env: Env, id: string, value: Value): void {
|
|
808
|
+
const stored: Value = typeof value === "number" && !Number.isFinite(value) ? NA : value;
|
|
809
|
+
const frame = activeFrame(env);
|
|
810
|
+
const ctx = frame ? frame.ctx : env.ctx;
|
|
811
|
+
const seriesMap = frame ? frame.series : env.series;
|
|
812
|
+
ctx[id] = stored;
|
|
813
|
+
if (!isCell(stored)) return;
|
|
814
|
+
let s = seriesMap.get(id);
|
|
815
|
+
if (!s) {
|
|
816
|
+
s = new PineSeries();
|
|
817
|
+
seriesMap.set(id, s);
|
|
818
|
+
}
|
|
819
|
+
if (s.length === env.barIndex + 1) s.setCurrent(stored);
|
|
820
|
+
else {
|
|
821
|
+
while (s.length < env.barIndex) s.push(NA);
|
|
822
|
+
s.push(stored);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function unpackTuple(target: Tuple, value: Value, env: Env): void {
|
|
827
|
+
if (!isTuple(value)) return;
|
|
828
|
+
const n = Math.min(target.elts.length, value.elts.length);
|
|
829
|
+
for (let i = 0; i < n; i++) {
|
|
830
|
+
const elt = target.elts[i];
|
|
831
|
+
if (elt?.kind === "Name") bindName(env, elt.id, unwrap(value.elts[i]!));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function evalAssign(stmt: Assign, env: Env): Value {
|
|
836
|
+
if (stmt.value == null) return NA;
|
|
837
|
+
const inited = varSet(env);
|
|
838
|
+
if (stmt.target.kind === "Tuple") {
|
|
839
|
+
if (isVarMode(stmt)) {
|
|
840
|
+
const already =
|
|
841
|
+
stmt.target.elts.some((e) => e.kind === "Name" && inited.has(e.id)) ||
|
|
842
|
+
(env.barIndex !== 0 &&
|
|
843
|
+
stmt.target.elts.some((e) => e.kind === "Name" && ctxGet(env, e.id) !== undefined));
|
|
844
|
+
if (already) {
|
|
845
|
+
for (const e of stmt.target.elts) {
|
|
846
|
+
if (e.kind === "Name") inited.add(e.id);
|
|
847
|
+
}
|
|
848
|
+
return NA;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
const packed = evalExpr(stmt.value, env);
|
|
852
|
+
unpackTuple(stmt.target, packed, env);
|
|
853
|
+
if (isVarMode(stmt)) {
|
|
854
|
+
for (const e of stmt.target.elts) {
|
|
855
|
+
if (e.kind === "Name") inited.add(e.id);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return packed;
|
|
859
|
+
}
|
|
860
|
+
if (stmt.target.kind !== "Name") return NA;
|
|
861
|
+
const id = stmt.target.id;
|
|
862
|
+
// var: init on first execution (bar_index==0, or first time the name is missing).
|
|
863
|
+
if (isVarMode(stmt) && (inited.has(id) || (env.barIndex !== 0 && ctxGet(env, id) !== undefined))) {
|
|
864
|
+
inited.add(id);
|
|
865
|
+
const cur = ctxGet(env, id);
|
|
866
|
+
return cur === undefined ? NA : cur;
|
|
867
|
+
}
|
|
868
|
+
const value = evalExpr(stmt.value, env);
|
|
869
|
+
bindName(env, id, value);
|
|
870
|
+
if (isVarMode(stmt)) inited.add(id);
|
|
871
|
+
if (stmt.export) env.pendingExports.set(id, value);
|
|
872
|
+
return value;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function evalReAssign(stmt: ReAssign, env: Env): Value {
|
|
876
|
+
if (stmt.target.kind === "Tuple") {
|
|
877
|
+
const packed = evalExpr(stmt.value, env);
|
|
878
|
+
unpackTuple(stmt.target, packed, env);
|
|
879
|
+
return packed;
|
|
880
|
+
}
|
|
881
|
+
if (stmt.target.kind === "Attribute") {
|
|
882
|
+
const obj = evalExpr(stmt.target.value, env);
|
|
883
|
+
const value = evalExpr(stmt.value, env);
|
|
884
|
+
if (obj instanceof UdtInstance) {
|
|
885
|
+
obj.set(stmt.target.attr, value);
|
|
886
|
+
return value;
|
|
887
|
+
}
|
|
888
|
+
return NA;
|
|
889
|
+
}
|
|
890
|
+
if (stmt.target.kind !== "Name") return NA;
|
|
891
|
+
const value = evalExpr(stmt.value, env);
|
|
892
|
+
bindName(env, stmt.target.id, value);
|
|
893
|
+
return value;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function applyStrategyDecl(stmt: { kind: string; value?: expr | null }, env: Env): void {
|
|
897
|
+
if (stmt.kind !== "Expr" || stmt.value?.kind !== "Call") return;
|
|
898
|
+
const settings: BrokerSettings = {};
|
|
899
|
+
const comm = unwrap(evalExpr(callArg(stmt.value.args, -1, ["commission", "commission_value"]), env));
|
|
900
|
+
const slip = unwrap(evalExpr(callArg(stmt.value.args, -1, ["slippage"]), env));
|
|
901
|
+
const pyr = unwrap(evalExpr(callArg(stmt.value.args, -1, ["pyramiding"]), env));
|
|
902
|
+
if (comm != null) settings.commission = comm;
|
|
903
|
+
if (slip != null) settings.slippage = slip;
|
|
904
|
+
if (pyr != null) settings.pyramiding = Math.trunc(pyr);
|
|
905
|
+
if (Object.keys(settings).length) env.book.configure(settings);
|
|
906
|
+
const capital = unwrap(evalExpr(callArg(stmt.value.args, -1, ["initial_capital"]), env));
|
|
907
|
+
if (capital != null && Number.isFinite(capital)) env.book.initialCapital = capital;
|
|
908
|
+
applyDrawingDecl(stmt, env);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function applyDrawingDecl(stmt: { kind: string; value?: expr | null }, env: Env): void {
|
|
912
|
+
if (stmt.kind !== "Expr" || stmt.value?.kind !== "Call") return;
|
|
913
|
+
const lines = unwrap(evalExpr(callArg(stmt.value.args, -1, ["max_lines_count"]), env));
|
|
914
|
+
const labels = unwrap(evalExpr(callArg(stmt.value.args, -1, ["max_labels_count"]), env));
|
|
915
|
+
const boxes = unwrap(evalExpr(callArg(stmt.value.args, -1, ["max_boxes_count"]), env));
|
|
916
|
+
const polylines = unwrap(evalExpr(callArg(stmt.value.args, -1, ["max_polylines_count"]), env));
|
|
917
|
+
if (lines == null && labels == null && boxes == null && polylines == null) return;
|
|
918
|
+
env.drawings.configure({
|
|
919
|
+
max_lines_count: lines ?? undefined,
|
|
920
|
+
max_labels_count: labels ?? undefined,
|
|
921
|
+
max_boxes_count: boxes ?? undefined,
|
|
922
|
+
max_polylines_count: polylines ?? undefined,
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function captureDecl(stmt: { kind: string; value?: expr | null }): { name: string | null; kind: string } | null {
|
|
927
|
+
if (stmt.kind !== "Expr" || stmt.value?.kind !== "Call") return null;
|
|
928
|
+
const fname = callName(stmt.value);
|
|
929
|
+
if (fname !== "indicator" && fname !== "strategy" && fname !== "library") return null;
|
|
930
|
+
return { name: firstStringArg(stmt.value.args), kind: fname };
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function execStmt(
|
|
934
|
+
stmt: { kind: string; value?: expr } | Assign | ReAssign | FunctionDef,
|
|
935
|
+
env: Env,
|
|
936
|
+
): Value {
|
|
937
|
+
if (stmt.kind === "FunctionDef") {
|
|
938
|
+
registerUdf(stmt as FunctionDef, env);
|
|
939
|
+
return NA;
|
|
940
|
+
}
|
|
941
|
+
if (stmt.kind === "TypeDef") {
|
|
942
|
+
registerTypeDef(stmt as TypeDef, env);
|
|
943
|
+
return NA;
|
|
944
|
+
}
|
|
945
|
+
if (stmt.kind === "EnumDef") {
|
|
946
|
+
registerEnumDef(stmt as EnumDef, env);
|
|
947
|
+
return NA;
|
|
948
|
+
}
|
|
949
|
+
if (stmt.kind === "Import") {
|
|
950
|
+
execImport(stmt as Import, env);
|
|
951
|
+
return NA;
|
|
952
|
+
}
|
|
953
|
+
if (stmt.kind === "Assign") {
|
|
954
|
+
return evalAssign(stmt as Assign, env);
|
|
955
|
+
}
|
|
956
|
+
if (stmt.kind === "ReAssign") {
|
|
957
|
+
return evalReAssign(stmt as ReAssign, env);
|
|
958
|
+
}
|
|
959
|
+
if (stmt.kind === "Break") throw LOOP_BREAK;
|
|
960
|
+
if (stmt.kind === "Continue") throw LOOP_CONTINUE;
|
|
961
|
+
if (stmt.kind === "Switch" || stmt.kind === "While" || stmt.kind === "If") {
|
|
962
|
+
return evalExpr(stmt as unknown as expr, env);
|
|
963
|
+
}
|
|
964
|
+
if (stmt.kind !== "Expr" || !("value" in stmt) || stmt.value == null) return NA;
|
|
965
|
+
const value = stmt.value;
|
|
966
|
+
if (value.kind === "Call") {
|
|
967
|
+
const fname = callName(value);
|
|
968
|
+
if (fname === "indicator" || fname === "strategy" || fname === "library") return NA;
|
|
969
|
+
if (
|
|
970
|
+
fname === "plot" ||
|
|
971
|
+
fname === "plotshape" ||
|
|
972
|
+
fname === "hline" ||
|
|
973
|
+
fname === "plotchar" ||
|
|
974
|
+
fname === "plotarrow" ||
|
|
975
|
+
fname === "bgcolor" ||
|
|
976
|
+
fname === "barcolor"
|
|
977
|
+
) {
|
|
978
|
+
const plotArgs = asArgs(value.args);
|
|
979
|
+
const title = resolvePlotTitle(plotArgs, fname);
|
|
980
|
+
const key = uniquifyPlotKey(env, value, title);
|
|
981
|
+
const seriesExpr = callArg(value.args, 0, ["series", "source"]) ?? plotArgs[0]?.value;
|
|
982
|
+
const cell = unwrap(evalExpr(seriesExpr, env));
|
|
983
|
+
if (!env.plots[key]) {
|
|
984
|
+
env.plots[key] = [];
|
|
985
|
+
env.plot_meta.push({ title: key });
|
|
986
|
+
}
|
|
987
|
+
const dest = env.plots[key]!;
|
|
988
|
+
while (dest.length < env.barIndex) dest.push(null);
|
|
989
|
+
dest.push(cell);
|
|
990
|
+
return cell;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return evalExpr(value, env);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function isTruthy(v: Cell): boolean {
|
|
997
|
+
return v != null && v !== 0;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function evalExpr(node: expr | undefined, env: Env): Value {
|
|
1001
|
+
if (node == null) return NA;
|
|
1002
|
+
switch (node.kind) {
|
|
1003
|
+
case "Name": {
|
|
1004
|
+
const v = ctxGet(env, node.id);
|
|
1005
|
+
return v === undefined ? NA : v;
|
|
1006
|
+
}
|
|
1007
|
+
case "Constant": {
|
|
1008
|
+
const v = node.value;
|
|
1009
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : NA;
|
|
1010
|
+
if (typeof v === "string") return v;
|
|
1011
|
+
if (v === true) return 1;
|
|
1012
|
+
if (v === false) return 0;
|
|
1013
|
+
return NA;
|
|
1014
|
+
}
|
|
1015
|
+
case "BinOp": {
|
|
1016
|
+
const leftV = evalExpr(node.left, env);
|
|
1017
|
+
const rightV = evalExpr(node.right, env);
|
|
1018
|
+
if (node.op.kind === "Add" && (typeof leftV === "string" || typeof rightV === "string")) {
|
|
1019
|
+
return stringifyVal(leftV) + stringifyVal(rightV);
|
|
1020
|
+
}
|
|
1021
|
+
const l = unwrap(leftV);
|
|
1022
|
+
const r = unwrap(rightV);
|
|
1023
|
+
if (l == null || r == null) return NA;
|
|
1024
|
+
switch (node.op.kind) {
|
|
1025
|
+
case "Add":
|
|
1026
|
+
return l + r;
|
|
1027
|
+
case "Sub":
|
|
1028
|
+
return l - r;
|
|
1029
|
+
case "Mult":
|
|
1030
|
+
return l * r;
|
|
1031
|
+
case "Div":
|
|
1032
|
+
return r === 0 ? NA : l / r;
|
|
1033
|
+
case "Mod":
|
|
1034
|
+
return r === 0 ? NA : l % r;
|
|
1035
|
+
}
|
|
1036
|
+
return NA;
|
|
1037
|
+
}
|
|
1038
|
+
case "UnaryOp": {
|
|
1039
|
+
const v = unwrap(evalExpr(node.operand, env));
|
|
1040
|
+
if (node.op.kind === "Not") return isTruthy(v) ? 0 : 1;
|
|
1041
|
+
if (v == null) return NA;
|
|
1042
|
+
if (node.op.kind === "USub") return -v;
|
|
1043
|
+
if (node.op.kind === "UAdd") return v;
|
|
1044
|
+
if (node.op.kind === "Invert") return ~Math.trunc(v);
|
|
1045
|
+
return NA;
|
|
1046
|
+
}
|
|
1047
|
+
case "Subscript": {
|
|
1048
|
+
const raw = unwrap(evalExpr(node.slice ?? undefined, env));
|
|
1049
|
+
if (raw == null || !Number.isFinite(raw)) return NA;
|
|
1050
|
+
const offset = Math.trunc(raw);
|
|
1051
|
+
if (offset < 0) return NA;
|
|
1052
|
+
if (node.value.kind === "Name") {
|
|
1053
|
+
const s = seriesGet(env, node.value.id);
|
|
1054
|
+
if (s) return s.get(offset);
|
|
1055
|
+
}
|
|
1056
|
+
return NA;
|
|
1057
|
+
}
|
|
1058
|
+
case "Call":
|
|
1059
|
+
return evalCall(node, env);
|
|
1060
|
+
case "Compare": {
|
|
1061
|
+
let left = evalExpr(node.left, env);
|
|
1062
|
+
for (let i = 0; i < node.ops.length; i++) {
|
|
1063
|
+
const right = evalExpr(node.comparators[i], env);
|
|
1064
|
+
const ok = cmp(left, node.ops[i]!.kind, right);
|
|
1065
|
+
if (ok == null) return NA;
|
|
1066
|
+
if (!ok) return 0;
|
|
1067
|
+
left = right;
|
|
1068
|
+
}
|
|
1069
|
+
return 1;
|
|
1070
|
+
}
|
|
1071
|
+
case "If": {
|
|
1072
|
+
const t = unwrap(evalExpr(node.test, env));
|
|
1073
|
+
const branch = isTruthy(t) ? node.body : node.orelse;
|
|
1074
|
+
let last: Value = NA;
|
|
1075
|
+
for (const s of branch) last = execStmt(s, env);
|
|
1076
|
+
return last;
|
|
1077
|
+
}
|
|
1078
|
+
case "Attribute":
|
|
1079
|
+
return evalAttribute(node, env);
|
|
1080
|
+
case "Conditional":
|
|
1081
|
+
return evalConditional(node, env);
|
|
1082
|
+
case "ForTo":
|
|
1083
|
+
return evalForTo(node, env);
|
|
1084
|
+
case "Tuple":
|
|
1085
|
+
return tupleOf(node.elts.map((e) => evalExpr(e, env)));
|
|
1086
|
+
case "ForIn":
|
|
1087
|
+
return evalForIn(node, env);
|
|
1088
|
+
case "While":
|
|
1089
|
+
return evalWhile(node, env);
|
|
1090
|
+
case "Switch":
|
|
1091
|
+
return evalSwitch(node, env);
|
|
1092
|
+
case "BoolOp":
|
|
1093
|
+
return evalBoolOp(node, env);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function evalConditional(node: Conditional, env: Env): Value {
|
|
1098
|
+
const t = unwrap(evalExpr(node.test, env));
|
|
1099
|
+
return isTruthy(t) ? evalExpr(node.body, env) : evalExpr(node.orelse, env);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function evalForTo(node: ForTo, env: Env): Value {
|
|
1103
|
+
const startV = unwrap(evalExpr(node.start, env));
|
|
1104
|
+
const endV = unwrap(evalExpr(node.end, env));
|
|
1105
|
+
if (startV == null || endV == null || !Number.isFinite(startV) || !Number.isFinite(endV)) {
|
|
1106
|
+
return NA;
|
|
1107
|
+
}
|
|
1108
|
+
let step: number;
|
|
1109
|
+
if (node.step != null) {
|
|
1110
|
+
const stepV = unwrap(evalExpr(node.step, env));
|
|
1111
|
+
if (stepV == null || !Number.isFinite(stepV) || stepV === 0) return NA;
|
|
1112
|
+
step = Math.trunc(stepV);
|
|
1113
|
+
if (step === 0) return NA;
|
|
1114
|
+
} else {
|
|
1115
|
+
// Pine / Python: omitted `by` is +1 when from <= to, else -1.
|
|
1116
|
+
step = startV <= endV ? 1 : -1;
|
|
1117
|
+
}
|
|
1118
|
+
const start = Math.trunc(startV);
|
|
1119
|
+
const end = Math.trunc(endV);
|
|
1120
|
+
const target = node.target.kind === "Name" ? node.target.id : null;
|
|
1121
|
+
let last: Value = NA;
|
|
1122
|
+
let n = 0;
|
|
1123
|
+
const run = (i: number): "break" | "ok" => {
|
|
1124
|
+
if (target) bindName(env, target, i);
|
|
1125
|
+
try {
|
|
1126
|
+
for (const s of node.body) last = execStmt(s, env);
|
|
1127
|
+
} catch (err) {
|
|
1128
|
+
if (err === LOOP_BREAK) return "break";
|
|
1129
|
+
if (err === LOOP_CONTINUE) return "ok";
|
|
1130
|
+
throw err;
|
|
1131
|
+
}
|
|
1132
|
+
return "ok";
|
|
1133
|
+
};
|
|
1134
|
+
if (step > 0) {
|
|
1135
|
+
for (let i = start; i <= end; i += step) {
|
|
1136
|
+
if (++n > FOR_CAP) break;
|
|
1137
|
+
if (run(i) === "break") break;
|
|
1138
|
+
}
|
|
1139
|
+
} else {
|
|
1140
|
+
for (let i = start; i >= end; i += step) {
|
|
1141
|
+
if (++n > FOR_CAP) break;
|
|
1142
|
+
if (run(i) === "break") break;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return last;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function evalWhile(node: While, env: Env): Value {
|
|
1149
|
+
let last: Value = NA;
|
|
1150
|
+
let n = 0;
|
|
1151
|
+
while (isTruthy(unwrap(evalExpr(node.test, env)))) {
|
|
1152
|
+
if (++n > WHILE_CAP) break;
|
|
1153
|
+
try {
|
|
1154
|
+
for (const s of asStmts(node.body)) last = execStmt(s, env);
|
|
1155
|
+
} catch (err) {
|
|
1156
|
+
if (err === LOOP_BREAK) break;
|
|
1157
|
+
if (err === LOOP_CONTINUE) continue;
|
|
1158
|
+
throw err;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return last;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function evalSwitch(node: Switch, env: Env): Value {
|
|
1165
|
+
const cases = Array.isArray(node.cases) ? node.cases : [];
|
|
1166
|
+
const subjectPresent = node.subject != null;
|
|
1167
|
+
const subject = subjectPresent ? evalExpr(node.subject!, env) : null;
|
|
1168
|
+
for (const c of cases) {
|
|
1169
|
+
if (c.pattern == null) return execBlock(c.body, env);
|
|
1170
|
+
if (subjectPresent) {
|
|
1171
|
+
const pat = evalExpr(c.pattern, env);
|
|
1172
|
+
if (valuesEq(pat, subject)) return execBlock(c.body, env);
|
|
1173
|
+
} else if (isTruthy(unwrap(evalExpr(c.pattern, env)))) {
|
|
1174
|
+
return execBlock(c.body, env);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return NA;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function execBlock(body: stmt[] | stmt | null | undefined, env: Env): Value {
|
|
1181
|
+
let last: Value = NA;
|
|
1182
|
+
for (const s of asStmts(body)) last = execStmt(s, env);
|
|
1183
|
+
return last;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function evalBoolOp(node: BoolOp, env: Env): Cell {
|
|
1187
|
+
const values = node.values ?? [];
|
|
1188
|
+
const kind = typeof node.op === "string" ? node.op : node.op?.kind;
|
|
1189
|
+
if (kind === "And") {
|
|
1190
|
+
for (const v of values) {
|
|
1191
|
+
const x = unwrap(evalExpr(v, env));
|
|
1192
|
+
if (x == null) return NA;
|
|
1193
|
+
if (!isTruthy(x)) return 0;
|
|
1194
|
+
}
|
|
1195
|
+
return 1;
|
|
1196
|
+
}
|
|
1197
|
+
if (kind === "Or") {
|
|
1198
|
+
let sawNa = false;
|
|
1199
|
+
for (const v of values) {
|
|
1200
|
+
const x = unwrap(evalExpr(v, env));
|
|
1201
|
+
if (x == null) {
|
|
1202
|
+
sawNa = true;
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
if (isTruthy(x)) return 1;
|
|
1206
|
+
}
|
|
1207
|
+
return sawNa ? NA : 0;
|
|
1208
|
+
}
|
|
1209
|
+
return NA;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function evalForIn(node: ForIn, env: Env): Value {
|
|
1213
|
+
const iter = evalExpr(node.iter, env);
|
|
1214
|
+
let items: Value[] = [];
|
|
1215
|
+
if (isTuple(iter)) items = iter.elts;
|
|
1216
|
+
else if (iter instanceof PineArray) items = iter.toValues();
|
|
1217
|
+
const target = node.target.kind === "Name" ? node.target.id : null;
|
|
1218
|
+
let last: Value = NA;
|
|
1219
|
+
const limit = Math.min(items.length, FOR_CAP);
|
|
1220
|
+
for (let i = 0; i < limit; i++) {
|
|
1221
|
+
const item = items[i]!;
|
|
1222
|
+
if (target) bindName(env, target, item);
|
|
1223
|
+
try {
|
|
1224
|
+
for (const s of node.body) last = execStmt(s, env);
|
|
1225
|
+
} catch (err) {
|
|
1226
|
+
if (err === LOOP_BREAK) break;
|
|
1227
|
+
if (err === LOOP_CONTINUE) continue;
|
|
1228
|
+
throw err;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return last;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/** `strategy.long` / `strategy.short` → "long"/"short"; other attrs are na. */
|
|
1235
|
+
function evalAttribute(node: Attribute, env: Env): Value {
|
|
1236
|
+
if (node.value.kind === "Name") {
|
|
1237
|
+
if (node.value.id === "strategy") {
|
|
1238
|
+
if (node.attr === "long" || node.attr === "short") return node.attr;
|
|
1239
|
+
if (node.attr === "position_size") return env.book.position.qty;
|
|
1240
|
+
if (node.attr === "position_avg_price") return env.book.position.avgPrice;
|
|
1241
|
+
if (node.attr === "netprofit") return env.book.realizedPnl;
|
|
1242
|
+
if (node.attr === "openprofit") {
|
|
1243
|
+
const q = env.book.position.qty;
|
|
1244
|
+
const avg = env.book.position.avgPrice;
|
|
1245
|
+
const mark = num(env.ctx.close);
|
|
1246
|
+
if (q === 0 || avg == null || mark == null) return 0;
|
|
1247
|
+
return q * (mark - avg);
|
|
1248
|
+
}
|
|
1249
|
+
if (node.attr === "equity") return env.book.equity(num(env.ctx.close) ?? 0);
|
|
1250
|
+
if (node.attr === "opentrades") return env.book.opentrades;
|
|
1251
|
+
if (node.attr === "closedtrades") return env.book.closedtrades;
|
|
1252
|
+
if (node.attr === "wintrades") return env.book.wintrades;
|
|
1253
|
+
if (node.attr === "losstrades") return env.book.losstrades;
|
|
1254
|
+
if (node.attr === "eventrades") return env.book.eventrades;
|
|
1255
|
+
if (node.attr === "grossprofit") return env.book.grossprofit;
|
|
1256
|
+
if (node.attr === "grossloss") return env.book.grossloss;
|
|
1257
|
+
if (node.attr === "avg_trade") return env.book.avgTrade();
|
|
1258
|
+
if (node.attr === "avg_winning_trade") return env.book.avgWinningTrade();
|
|
1259
|
+
if (node.attr === "avg_losing_trade") return env.book.avgLosingTrade();
|
|
1260
|
+
if (node.attr === "netprofit_percent") {
|
|
1261
|
+
return env.book.netprofitPercent(num(env.ctx.close) ?? 0);
|
|
1262
|
+
}
|
|
1263
|
+
if (node.attr === "openprofit_percent") {
|
|
1264
|
+
return env.book.openprofitPercent(num(env.ctx.close) ?? 0);
|
|
1265
|
+
}
|
|
1266
|
+
if (node.attr === "grossprofit_percent") return env.book.grossprofitPercent();
|
|
1267
|
+
if (node.attr === "grossloss_percent") return env.book.grosslossPercent();
|
|
1268
|
+
if (node.attr === "avg_trade_percent") return env.book.avgTradePercent();
|
|
1269
|
+
if (node.attr === "avg_winning_trade_percent") return env.book.avgWinningTradePercent();
|
|
1270
|
+
if (node.attr === "avg_losing_trade_percent") return env.book.avgLosingTradePercent();
|
|
1271
|
+
if (node.attr === "max_drawdown") return env.book.maxDrawdown();
|
|
1272
|
+
if (node.attr === "max_drawdown_percent") return env.book.maxDrawdownPercent();
|
|
1273
|
+
if (node.attr === "max_runup") return env.book.maxRunup();
|
|
1274
|
+
if (node.attr === "max_runup_percent") return env.book.maxRunupPercent();
|
|
1275
|
+
if (node.attr === "percent_profitable" || node.attr === "winrate") {
|
|
1276
|
+
return env.book.percentProfitable();
|
|
1277
|
+
}
|
|
1278
|
+
if (node.attr === "profitfactor" || node.attr === "profit_factor") {
|
|
1279
|
+
return env.book.profitFactor();
|
|
1280
|
+
}
|
|
1281
|
+
if (node.attr === "initial_capital") return env.book.initialCapital;
|
|
1282
|
+
if (node.attr === "commission") return 0;
|
|
1283
|
+
if (node.attr === "cash") return "cash";
|
|
1284
|
+
if (node.attr === "fixed") return "fixed";
|
|
1285
|
+
if (node.attr === "percent_of_equity") return "percent_of_equity";
|
|
1286
|
+
}
|
|
1287
|
+
if (node.value.id === "syminfo") {
|
|
1288
|
+
if (node.attr === "ticker" || node.attr === "tickerid") return env.symbol;
|
|
1289
|
+
if (node.attr === "timezone") return "Etc/UTC";
|
|
1290
|
+
if (node.attr === "currency") return "USD";
|
|
1291
|
+
if (node.attr === "mintick") return 0.01;
|
|
1292
|
+
if (node.attr === "type") return "stock";
|
|
1293
|
+
}
|
|
1294
|
+
if (node.value.id === "timeframe") {
|
|
1295
|
+
if (node.attr === "period") return env.timeframe ?? "";
|
|
1296
|
+
if (node.attr === "multiplier") return tfMultiplier(env.timeframe);
|
|
1297
|
+
if (node.attr === "isintraday") return timeframeIsIntraday(env.timeframe) ? 1 : 0;
|
|
1298
|
+
if (node.attr === "isdaily") return timeframeIsDaily(env.timeframe) ? 1 : 0;
|
|
1299
|
+
if (node.attr === "isweekly") return timeframeIsWeekly(env.timeframe) ? 1 : 0;
|
|
1300
|
+
if (node.attr === "ismonthly") return timeframeIsMonthly(env.timeframe) ? 1 : 0;
|
|
1301
|
+
}
|
|
1302
|
+
if (node.value.id === "color") {
|
|
1303
|
+
const named = colorByName(node.attr);
|
|
1304
|
+
if (named) return named;
|
|
1305
|
+
}
|
|
1306
|
+
if (node.value.id === "barstate") {
|
|
1307
|
+
const last = env.barIndex === env.barCount - 1;
|
|
1308
|
+
if (node.attr === "isfirst") return env.barIndex === 0 ? 1 : 0;
|
|
1309
|
+
if (node.attr === "islast") return last ? 1 : 0;
|
|
1310
|
+
if (node.attr === "ishistory") return last ? 0 : 1;
|
|
1311
|
+
if (node.attr === "isconfirmed") return 1;
|
|
1312
|
+
if (node.attr === "isnew") return 1;
|
|
1313
|
+
if (node.attr === "isrealtime") return 0;
|
|
1314
|
+
if (node.attr === "islastconfirmedhistory") return last ? 1 : 0;
|
|
1315
|
+
}
|
|
1316
|
+
if (node.value.id === "session") {
|
|
1317
|
+
if (node.attr === "regular") return "regular";
|
|
1318
|
+
if (node.attr === "extended") return "extended";
|
|
1319
|
+
if (node.attr === "ismarket") return 1;
|
|
1320
|
+
if (node.attr === "ispremarket" || node.attr === "ispostmarket") return 0;
|
|
1321
|
+
if (node.attr === "isfirstbar" || node.attr === "isfirstbar_regular") {
|
|
1322
|
+
return env.barIndex === 0 ? 1 : 0;
|
|
1323
|
+
}
|
|
1324
|
+
if (node.attr === "islastbar" || node.attr === "islastbar_regular") {
|
|
1325
|
+
return env.barIndex === env.barCount - 1 ? 1 : 0;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
if (node.value.id === "chart") {
|
|
1329
|
+
if (node.attr === "is_heikinashi" || node.attr === "is_renko" || node.attr === "is_kagi") {
|
|
1330
|
+
return 0;
|
|
1331
|
+
}
|
|
1332
|
+
if (node.attr === "fg_color") return "#ffffff";
|
|
1333
|
+
if (node.attr === "bg_color") return "#131722";
|
|
1334
|
+
}
|
|
1335
|
+
if (node.value.id === "ta") {
|
|
1336
|
+
const taAttr = evalTaAttr(node.attr, env);
|
|
1337
|
+
if (taAttr !== undefined) return taAttr;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
const obj = evalExpr(node.value, env);
|
|
1341
|
+
if (obj instanceof UdtInstance) {
|
|
1342
|
+
const v = obj.get(node.attr);
|
|
1343
|
+
if (typeof v === "number" || v === null || typeof v === "string") return v as Value;
|
|
1344
|
+
if (v instanceof UdtInstance || v instanceof EnumMember) return v;
|
|
1345
|
+
return NA;
|
|
1346
|
+
}
|
|
1347
|
+
if (obj instanceof UdtType && node.attr === "new") return obj;
|
|
1348
|
+
if (obj instanceof EnumType) {
|
|
1349
|
+
return obj.members.get(node.attr) ?? NA;
|
|
1350
|
+
}
|
|
1351
|
+
if (obj instanceof LibraryModule) {
|
|
1352
|
+
const exp = obj.get(node.attr);
|
|
1353
|
+
if (exp === undefined) return NA;
|
|
1354
|
+
if (isUdfDef(exp)) return exp;
|
|
1355
|
+
if (typeof exp === "function") return NA;
|
|
1356
|
+
return exp as Value;
|
|
1357
|
+
}
|
|
1358
|
+
return NA;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function evalTaAttr(attr: string, env: Env): Value | undefined {
|
|
1362
|
+
const ta = env.ta;
|
|
1363
|
+
const site = `attr:ta.${attr}`;
|
|
1364
|
+
if (attr === "accdist" || attr === "ad") {
|
|
1365
|
+
return ta.accdist(
|
|
1366
|
+
site,
|
|
1367
|
+
num(env.ctx.high),
|
|
1368
|
+
num(env.ctx.low),
|
|
1369
|
+
num(env.ctx.close),
|
|
1370
|
+
num(env.ctx.volume),
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
if (attr === "obv" && typeof ta.obv === "function") {
|
|
1374
|
+
return (ta.obv as TaEngine["obv"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
1375
|
+
}
|
|
1376
|
+
if (attr === "tr" && typeof ta.tr === "function") {
|
|
1377
|
+
return (ta.tr as TaEngine["tr"]).call(
|
|
1378
|
+
env.ta,
|
|
1379
|
+
site,
|
|
1380
|
+
num(env.ctx.high),
|
|
1381
|
+
num(env.ctx.low),
|
|
1382
|
+
num(env.ctx.close),
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
if (attr === "vwap" && typeof ta.vwap === "function") {
|
|
1386
|
+
const h = num(env.ctx.high);
|
|
1387
|
+
const l = num(env.ctx.low);
|
|
1388
|
+
const c = num(env.ctx.close);
|
|
1389
|
+
const src = h != null && l != null && c != null ? (h + l + c) / 3 : c;
|
|
1390
|
+
return (ta.vwap as TaEngine["vwap"]).call(env.ta, site, src, num(env.ctx.volume));
|
|
1391
|
+
}
|
|
1392
|
+
if (attr === "pvt" && typeof ta.pvt === "function") {
|
|
1393
|
+
return (ta.pvt as TaEngine["pvt"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
1394
|
+
}
|
|
1395
|
+
if (attr === "nvi" && typeof ta.nvi === "function") {
|
|
1396
|
+
return (ta.nvi as TaEngine["nvi"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
1397
|
+
}
|
|
1398
|
+
if (attr === "pvi" && typeof ta.pvi === "function") {
|
|
1399
|
+
return (ta.pvi as TaEngine["pvi"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
1400
|
+
}
|
|
1401
|
+
return undefined;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function evalDirection(node: expr | undefined, env: Env): string {
|
|
1405
|
+
if (node == null) return "long";
|
|
1406
|
+
if (node.kind === "Constant" && typeof node.value === "string") return node.value;
|
|
1407
|
+
const v = evalExpr(node, env);
|
|
1408
|
+
if (typeof v === "string") return v;
|
|
1409
|
+
const n = unwrap(v);
|
|
1410
|
+
if (n === 1) return "long";
|
|
1411
|
+
if (n === -1) return "short";
|
|
1412
|
+
return "long";
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function evalId(node: expr | undefined): string {
|
|
1416
|
+
if (node == null) return "entry";
|
|
1417
|
+
if (node.kind === "Constant" && typeof node.value === "string") return node.value;
|
|
1418
|
+
if (node.kind === "Name") return node.id;
|
|
1419
|
+
return "entry";
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function stringifyVal(value: Value): string {
|
|
1423
|
+
if (typeof value === "string") return value;
|
|
1424
|
+
if (value instanceof TickerId) return value.toString();
|
|
1425
|
+
const cell = unwrap(value);
|
|
1426
|
+
return cell == null ? "na" : String(cell);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
function isNaVal(value: Value): boolean {
|
|
1430
|
+
if (value == null) return true;
|
|
1431
|
+
if (typeof value === "number") return !Number.isFinite(value);
|
|
1432
|
+
return false;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function valuesEq(left: Value, right: Value): boolean {
|
|
1436
|
+
if (isNaVal(left) && isNaVal(right)) return true;
|
|
1437
|
+
if (isNaVal(left) || isNaVal(right)) return false;
|
|
1438
|
+
if (left instanceof EnumMember) return left.equals(right);
|
|
1439
|
+
if (right instanceof EnumMember) return right.equals(left);
|
|
1440
|
+
if (typeof left === "string" || typeof right === "string") return left === right;
|
|
1441
|
+
if (typeof left === "number" && typeof right === "number") return left === right;
|
|
1442
|
+
return left === right;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
function cmp(left: Value, op: string, right: Value): boolean | null {
|
|
1446
|
+
const lNa = isNaVal(left);
|
|
1447
|
+
const rNa = isNaVal(right);
|
|
1448
|
+
switch (op) {
|
|
1449
|
+
case "Eq":
|
|
1450
|
+
return valuesEq(left, right);
|
|
1451
|
+
case "NotEq":
|
|
1452
|
+
// Python compile `numba_pine_ne`: any comparison involving na is False.
|
|
1453
|
+
if (lNa || rNa) return false;
|
|
1454
|
+
return !valuesEq(left, right);
|
|
1455
|
+
case "Lt":
|
|
1456
|
+
case "LtE":
|
|
1457
|
+
case "Gt":
|
|
1458
|
+
case "GtE": {
|
|
1459
|
+
const l = unwrap(left);
|
|
1460
|
+
const r = unwrap(right);
|
|
1461
|
+
if (l == null || r == null) return false;
|
|
1462
|
+
if (op === "Lt") return l < r;
|
|
1463
|
+
if (op === "LtE") return l <= r;
|
|
1464
|
+
if (op === "Gt") return l > r;
|
|
1465
|
+
return l >= r;
|
|
1466
|
+
}
|
|
1467
|
+
default:
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function evalCall(node: Call, env: Env): Value {
|
|
1473
|
+
if (node.func.kind === "Attribute") {
|
|
1474
|
+
const obj = evalExpr(node.func.value, env);
|
|
1475
|
+
if (obj instanceof LibraryModule) {
|
|
1476
|
+
return evalLibraryMemberCall(obj, node.func.attr, node, env);
|
|
1477
|
+
}
|
|
1478
|
+
if (obj instanceof UdtInstance) {
|
|
1479
|
+
const meth = obj.getMethod(node.func.attr);
|
|
1480
|
+
if (meth && isUdfDef(meth.body)) return evalUdf(meth.body, node, env, obj);
|
|
1481
|
+
}
|
|
1482
|
+
if (obj instanceof UdtType && node.func.attr === "new") {
|
|
1483
|
+
const overrides: Record<string, unknown> = {};
|
|
1484
|
+
for (const a of asArgs(node.args)) {
|
|
1485
|
+
const key = argKeyword(a);
|
|
1486
|
+
if (key) overrides[key] = evalExpr(a.value, env);
|
|
1487
|
+
}
|
|
1488
|
+
return obj.newInstance(overrides);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
const fname = callName(node);
|
|
1492
|
+
const site = siteKey(node, env);
|
|
1493
|
+
if (fname === "ta.sma" || fname === "sma") {
|
|
1494
|
+
return env.ta.sma(site, srcArg(node, env), lenArg(node, env, 1));
|
|
1495
|
+
}
|
|
1496
|
+
if (fname === "ta.ema" || fname === "ema") {
|
|
1497
|
+
return env.ta.ema(site, srcArg(node, env), lenArg(node, env, 1));
|
|
1498
|
+
}
|
|
1499
|
+
if (fname === "ta.rsi" || fname === "rsi") {
|
|
1500
|
+
return env.ta.rsi(site, srcArg(node, env), lenArg(node, env, 1));
|
|
1501
|
+
}
|
|
1502
|
+
if (fname === "ta.rma" || fname === "rma") {
|
|
1503
|
+
return env.ta.rma(site, srcArg(node, env), lenArg(node, env, 1));
|
|
1504
|
+
}
|
|
1505
|
+
if (fname === "ta.atr" || fname === "atr") {
|
|
1506
|
+
const period = lenArg(node, env, 0);
|
|
1507
|
+
return env.ta.atr(site, num(env.ctx.high), num(env.ctx.low), num(env.ctx.close), period);
|
|
1508
|
+
}
|
|
1509
|
+
if (fname === "ta.kc" || fname === "kc") {
|
|
1510
|
+
return evalKc(node, env, site);
|
|
1511
|
+
}
|
|
1512
|
+
if (fname === "ta.supertrend" || fname === "supertrend") {
|
|
1513
|
+
return evalSupertrend(node, env, site);
|
|
1514
|
+
}
|
|
1515
|
+
if (fname === "ta.tr" || fname === "tr") {
|
|
1516
|
+
return evalTr(node, env, site);
|
|
1517
|
+
}
|
|
1518
|
+
const extraTa = evalExtraTa(fname, node, env, site);
|
|
1519
|
+
if (extraTa !== undefined) return extraTa;
|
|
1520
|
+
const mathVal = evalMathCall(fname, node, env);
|
|
1521
|
+
if (mathVal !== undefined) return mathVal;
|
|
1522
|
+
if (isInputBuiltin(fname)) return evalInput(node, env, fname!);
|
|
1523
|
+
if (isRequestBuiltin(fname) || fname === "request.security") {
|
|
1524
|
+
return evalRequestCall(fname!, node, env);
|
|
1525
|
+
}
|
|
1526
|
+
const arrVal = evalArrayCall(fname, node, env);
|
|
1527
|
+
if (arrVal !== undefined) return arrVal;
|
|
1528
|
+
const colorVal = evalColorCall(fname, node, env);
|
|
1529
|
+
if (colorVal !== undefined) return colorVal;
|
|
1530
|
+
const strVal = evalStrCall(fname, node, env);
|
|
1531
|
+
if (strVal !== undefined) return strVal;
|
|
1532
|
+
if (fname === "na") {
|
|
1533
|
+
const arg = callArg(node.args, 0, ["x", "source"]);
|
|
1534
|
+
if (arg == null) return NA;
|
|
1535
|
+
return isNaVal(evalExpr(arg, env)) ? 1 : 0;
|
|
1536
|
+
}
|
|
1537
|
+
if (fname === "iff") {
|
|
1538
|
+
const t = unwrap(evalExpr(callArg(node.args, 0, ["condition", "cond"]), env));
|
|
1539
|
+
if (t == null) return NA;
|
|
1540
|
+
return isTruthy(t)
|
|
1541
|
+
? evalExpr(callArg(node.args, 1, ["then", "if_true"]), env)
|
|
1542
|
+
: evalExpr(callArg(node.args, 2, ["else", "if_false"]), env);
|
|
1543
|
+
}
|
|
1544
|
+
if (fname === "fixnan") {
|
|
1545
|
+
const v = unwrap(evalExpr(callArg(node.args, 0, ["source", "x"]), env));
|
|
1546
|
+
return v == null ? 0 : v;
|
|
1547
|
+
}
|
|
1548
|
+
if (fname === "int") {
|
|
1549
|
+
const v = unwrap(evalExpr(callArg(node.args, 0, ["x", "source"]), env));
|
|
1550
|
+
return v == null ? NA : Math.trunc(v);
|
|
1551
|
+
}
|
|
1552
|
+
if (fname === "float") {
|
|
1553
|
+
return unwrap(evalExpr(callArg(node.args, 0, ["x", "source"]), env));
|
|
1554
|
+
}
|
|
1555
|
+
if (fname === "bool") {
|
|
1556
|
+
const v = unwrap(evalExpr(callArg(node.args, 0, ["x", "source"]), env));
|
|
1557
|
+
return isTruthy(v) ? 1 : 0;
|
|
1558
|
+
}
|
|
1559
|
+
if (fname === "string" || fname === "str") {
|
|
1560
|
+
return stringifyVal(evalExpr(callArg(node.args, 0, ["x", "source"]), env));
|
|
1561
|
+
}
|
|
1562
|
+
if (fname === "log.info" || fname === "log.warning" || fname === "log.error") {
|
|
1563
|
+
const parts = asArgs(node.args).map((a) => {
|
|
1564
|
+
const v = evalExpr(a.value, env);
|
|
1565
|
+
if (v instanceof TickerId) return v.toString();
|
|
1566
|
+
return typeof v === "string" ? v : unwrap(v);
|
|
1567
|
+
});
|
|
1568
|
+
const msg = formatLogParts(parts);
|
|
1569
|
+
if (fname === "log.info") env.logs.info(env.barIndex, msg);
|
|
1570
|
+
else if (fname === "log.warning") env.logs.warning(env.barIndex, msg);
|
|
1571
|
+
else env.logs.error(env.barIndex, msg);
|
|
1572
|
+
return NA;
|
|
1573
|
+
}
|
|
1574
|
+
const timeVal = evalTimeCall(fname, node, env);
|
|
1575
|
+
if (timeVal !== undefined) return timeVal;
|
|
1576
|
+
if (fname === "timeframe.in_seconds") {
|
|
1577
|
+
const p = evalAsString(callArg(node.args, 0, ["timeframe", "period"]), env) ?? env.timeframe;
|
|
1578
|
+
return timeframeInSeconds(p);
|
|
1579
|
+
}
|
|
1580
|
+
if (fname === "timeframe.from_seconds") {
|
|
1581
|
+
const s = unwrap(evalExpr(callArg(node.args, 0, ["seconds"]), env));
|
|
1582
|
+
return s == null ? NA : timeframeFromSeconds(s);
|
|
1583
|
+
}
|
|
1584
|
+
if (fname === "runtime.error") {
|
|
1585
|
+
const parts = asArgs(node.args).map((a) => {
|
|
1586
|
+
const v = evalExpr(a.value, env);
|
|
1587
|
+
return typeof v === "string" ? v : unwrap(v);
|
|
1588
|
+
});
|
|
1589
|
+
runtimeError(formatLogParts(parts) || "runtime.error");
|
|
1590
|
+
}
|
|
1591
|
+
if (fname === "ticker.new") {
|
|
1592
|
+
const sym = evalAsString(callArg(node.args, 0, ["symbol"]), env) ?? env.symbol;
|
|
1593
|
+
const session = evalAsString(callArg(node.args, 1, ["session"]), env) ?? undefined;
|
|
1594
|
+
return tickerNew(sym, session);
|
|
1595
|
+
}
|
|
1596
|
+
if (fname === "ticker.heikinashi") {
|
|
1597
|
+
const raw = evalExpr(callArg(node.args, 0, ["symbol"]), env);
|
|
1598
|
+
const sym = raw instanceof TickerId ? raw.symbol : (typeof raw === "string" ? raw : env.symbol);
|
|
1599
|
+
return tickerHeikinashi(sym);
|
|
1600
|
+
}
|
|
1601
|
+
if (fname === "ticker.standard") {
|
|
1602
|
+
const raw = evalExpr(callArg(node.args, 0, ["symbol"]), env);
|
|
1603
|
+
const sym = raw instanceof TickerId ? raw.symbol : (typeof raw === "string" ? raw : env.symbol);
|
|
1604
|
+
return tickerStandard(sym);
|
|
1605
|
+
}
|
|
1606
|
+
if (fname === "ticker.renko") {
|
|
1607
|
+
const raw = evalExpr(callArg(node.args, 0, ["symbol"]), env);
|
|
1608
|
+
const sym = raw instanceof TickerId ? raw.symbol : (typeof raw === "string" ? raw : env.symbol);
|
|
1609
|
+
return tickerRenko(sym);
|
|
1610
|
+
}
|
|
1611
|
+
if (fname === "ticker.kagi") {
|
|
1612
|
+
const raw = evalExpr(callArg(node.args, 0, ["symbol"]), env);
|
|
1613
|
+
const sym = raw instanceof TickerId ? raw.symbol : (typeof raw === "string" ? raw : env.symbol);
|
|
1614
|
+
return tickerKagi(sym);
|
|
1615
|
+
}
|
|
1616
|
+
if (fname === "ticker.linebreak") {
|
|
1617
|
+
const raw = evalExpr(callArg(node.args, 0, ["symbol"]), env);
|
|
1618
|
+
const sym = raw instanceof TickerId ? raw.symbol : (typeof raw === "string" ? raw : env.symbol);
|
|
1619
|
+
return tickerLinebreak(sym);
|
|
1620
|
+
}
|
|
1621
|
+
if (fname === "ticker.pointfigure") {
|
|
1622
|
+
const raw = evalExpr(callArg(node.args, 0, ["symbol"]), env);
|
|
1623
|
+
const sym = raw instanceof TickerId ? raw.symbol : (typeof raw === "string" ? raw : env.symbol);
|
|
1624
|
+
return tickerPointfigure(sym);
|
|
1625
|
+
}
|
|
1626
|
+
if (fname === "ticker.modify") {
|
|
1627
|
+
const raw = evalExpr(callArg(node.args, 0, ["ticker", "tickerid"]), env);
|
|
1628
|
+
const base = raw instanceof TickerId ? raw : (typeof raw === "string" ? raw : env.symbol);
|
|
1629
|
+
return tickerModify(base, {
|
|
1630
|
+
symbol: evalAsString(callArg(node.args, 1, ["symbol"]), env) ?? undefined,
|
|
1631
|
+
session: evalAsString(callArg(node.args, 2, ["session"]), env) ?? undefined,
|
|
1632
|
+
adjust:
|
|
1633
|
+
evalAsString(callArg(node.args, 3, ["adjust", "adjustment"]), env) ?? undefined,
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
const mapVal = evalMapCall(fname, node, env);
|
|
1637
|
+
if (mapVal !== undefined) return mapVal;
|
|
1638
|
+
const matrixVal = evalMatrixCall(fname, node, env);
|
|
1639
|
+
if (matrixVal !== undefined) return matrixVal;
|
|
1640
|
+
const drawVal = evalDrawingCall(fname, node, env);
|
|
1641
|
+
if (drawVal !== undefined) return drawVal;
|
|
1642
|
+
if (fname === "nz") {
|
|
1643
|
+
const v = unwrap(evalExpr(callArg(node.args, 0, ["source", "x"]), env));
|
|
1644
|
+
const fallback = unwrap(evalExpr(callArg(node.args, 1, ["replacement"]), env));
|
|
1645
|
+
return v == null ? (fallback ?? 0) : v;
|
|
1646
|
+
}
|
|
1647
|
+
if (fname === "strategy.entry") {
|
|
1648
|
+
const id = evalId(callArg(node.args, 0, ["id"]));
|
|
1649
|
+
const dir = evalDirection(callArg(node.args, 1, ["direction"]), env);
|
|
1650
|
+
const qtyArg = callArg(node.args, 2, ["qty"]);
|
|
1651
|
+
const qtyRaw = qtyArg == null ? 1 : unwrap(evalExpr(qtyArg, env));
|
|
1652
|
+
const qty = qtyRaw == null || !Number.isFinite(qtyRaw) ? Number.NaN : qtyRaw;
|
|
1653
|
+
const limit = unwrap(evalExpr(callArg(node.args, -1, ["limit"]), env));
|
|
1654
|
+
const stop = unwrap(evalExpr(callArg(node.args, -1, ["stop"]), env));
|
|
1655
|
+
const mark = num(env.ctx.close);
|
|
1656
|
+
env.book.placeEntry(env.barIndex, id, dir, qty, {
|
|
1657
|
+
limit,
|
|
1658
|
+
stop,
|
|
1659
|
+
price: mark == null || !Number.isFinite(mark) ? undefined : mark,
|
|
1660
|
+
time: num(env.ctx.time) ?? undefined,
|
|
1661
|
+
oca_name: evalAsString(callArg(node.args, -1, ["oca_name"]), env),
|
|
1662
|
+
oca_type: evalAsString(callArg(node.args, -1, ["oca_type"]), env),
|
|
1663
|
+
});
|
|
1664
|
+
return NA;
|
|
1665
|
+
}
|
|
1666
|
+
if (fname === "strategy.close") {
|
|
1667
|
+
const id = evalId(callArg(node.args, 0, ["id"]));
|
|
1668
|
+
const mark = num(env.ctx.close);
|
|
1669
|
+
if (env.book.position.qty !== 0 && mark != null) {
|
|
1670
|
+
env.book.fillClose(env.barIndex, id, mark, { time: num(env.ctx.time) ?? undefined });
|
|
1671
|
+
} else env.book.close(env.barIndex, id);
|
|
1672
|
+
return NA;
|
|
1673
|
+
}
|
|
1674
|
+
if (fname === "strategy.exit") {
|
|
1675
|
+
env.book.exit(env.barIndex, evalId(callArg(node.args, 0, ["id"])));
|
|
1676
|
+
return NA;
|
|
1677
|
+
}
|
|
1678
|
+
if (fname === "strategy.close_all") {
|
|
1679
|
+
env.book.closeAll(env.barIndex, num(env.ctx.close) ?? undefined);
|
|
1680
|
+
return NA;
|
|
1681
|
+
}
|
|
1682
|
+
if (fname === "strategy.cancel") {
|
|
1683
|
+
env.book.cancel(env.barIndex, evalId(callArg(node.args, 0, ["id"])));
|
|
1684
|
+
return NA;
|
|
1685
|
+
}
|
|
1686
|
+
if (fname === "strategy.cancel_all") {
|
|
1687
|
+
env.book.cancelAll(env.barIndex);
|
|
1688
|
+
return NA;
|
|
1689
|
+
}
|
|
1690
|
+
if (fname === "strategy.order") {
|
|
1691
|
+
const id = evalId(callArg(node.args, 0, ["id"]));
|
|
1692
|
+
const dir = evalDirection(callArg(node.args, 1, ["direction"]), env);
|
|
1693
|
+
const qtyRaw = unwrap(evalExpr(callArg(node.args, 2, ["qty"]), env));
|
|
1694
|
+
const qty = qtyRaw == null || !Number.isFinite(qtyRaw) ? Number.NaN : qtyRaw;
|
|
1695
|
+
env.book.order(env.barIndex, id, dir, qty, {
|
|
1696
|
+
limit: unwrap(evalExpr(callArg(node.args, -1, ["limit"]), env)),
|
|
1697
|
+
stop: unwrap(evalExpr(callArg(node.args, -1, ["stop"]), env)),
|
|
1698
|
+
price: num(env.ctx.close) ?? undefined,
|
|
1699
|
+
time: num(env.ctx.time) ?? undefined,
|
|
1700
|
+
oca_name: evalAsString(callArg(node.args, -1, ["oca_name"]), env),
|
|
1701
|
+
oca_type: evalAsString(callArg(node.args, -1, ["oca_type"]), env),
|
|
1702
|
+
});
|
|
1703
|
+
return NA;
|
|
1704
|
+
}
|
|
1705
|
+
const riskVal = evalStrategyRiskCall(fname, node, env);
|
|
1706
|
+
if (riskVal !== undefined) return riskVal;
|
|
1707
|
+
const tradeVal = evalStrategyTradeCall(fname, node, env);
|
|
1708
|
+
if (tradeVal !== undefined) return tradeVal;
|
|
1709
|
+
if (fname != null && fname.endsWith(".new")) {
|
|
1710
|
+
const typeName = fname.slice(0, -4);
|
|
1711
|
+
const t = ctxGet(env, typeName);
|
|
1712
|
+
if (t instanceof UdtType) {
|
|
1713
|
+
const overrides: Record<string, unknown> = {};
|
|
1714
|
+
for (const a of asArgs(node.args)) {
|
|
1715
|
+
const key = argKeyword(a);
|
|
1716
|
+
if (key) overrides[key] = evalExpr(a.value, env);
|
|
1717
|
+
}
|
|
1718
|
+
return t.newInstance(overrides);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
if (fname != null) {
|
|
1722
|
+
const udf = env.udfs.get(fname);
|
|
1723
|
+
if (udf) return evalUdf(udf, node, env);
|
|
1724
|
+
}
|
|
1725
|
+
return NA;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
function evalKc(node: Call, env: Env, site: string): Value {
|
|
1729
|
+
const positional = asArgs(node.args).filter((a) => argKeyword(a) == null);
|
|
1730
|
+
let high = num(env.ctx.high);
|
|
1731
|
+
let low = num(env.ctx.low);
|
|
1732
|
+
let close = srcArg(node, env);
|
|
1733
|
+
let length = lenArg(node, env, 1);
|
|
1734
|
+
let mult = numArg(node, env, 2, ["mult", "multiplier"], 1);
|
|
1735
|
+
if (positional.length >= 4) {
|
|
1736
|
+
high = unwrap(evalExpr(positional[0]!.value, env));
|
|
1737
|
+
low = unwrap(evalExpr(positional[1]!.value, env));
|
|
1738
|
+
close = unwrap(evalExpr(positional[2]!.value, env));
|
|
1739
|
+
length = lenArg(node, env, 3);
|
|
1740
|
+
mult = numArg(node, env, 4, ["mult", "multiplier"], 1);
|
|
1741
|
+
}
|
|
1742
|
+
const kc = taKc(env, site, high, low, close, length, mult);
|
|
1743
|
+
return tupleOf([kc.mid, kc.up, kc.lo]);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
function evalSupertrend(node: Call, env: Env, site: string): Value {
|
|
1747
|
+
const factor = numArg(node, env, 0, ["factor"], 3);
|
|
1748
|
+
const period = namedOrLen(node, env, 1, ["atrPeriod", "length"], 10);
|
|
1749
|
+
const st = taSupertrend(
|
|
1750
|
+
env,
|
|
1751
|
+
site,
|
|
1752
|
+
num(env.ctx.high),
|
|
1753
|
+
num(env.ctx.low),
|
|
1754
|
+
num(env.ctx.close),
|
|
1755
|
+
factor,
|
|
1756
|
+
period,
|
|
1757
|
+
);
|
|
1758
|
+
return tupleOf([st.st, st.dir]);
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function evalTr(node: Call, env: Env, site: string): Cell {
|
|
1762
|
+
const positional = asArgs(node.args).filter((a) => argKeyword(a) == null);
|
|
1763
|
+
if (positional.length >= 3) {
|
|
1764
|
+
return taTr(
|
|
1765
|
+
env,
|
|
1766
|
+
site,
|
|
1767
|
+
unwrap(evalExpr(positional[0]!.value, env)),
|
|
1768
|
+
unwrap(evalExpr(positional[1]!.value, env)),
|
|
1769
|
+
unwrap(evalExpr(positional[2]!.value, env)),
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
return taTr(env, site, num(env.ctx.high), num(env.ctx.low), num(env.ctx.close));
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
function taKc(
|
|
1776
|
+
env: Env,
|
|
1777
|
+
site: string,
|
|
1778
|
+
high: Cell,
|
|
1779
|
+
low: Cell,
|
|
1780
|
+
close: Cell,
|
|
1781
|
+
length: number,
|
|
1782
|
+
mult: number,
|
|
1783
|
+
): { mid: Cell; up: Cell; lo: Cell } {
|
|
1784
|
+
const fn = (env.ta as TaEngine & { kc?: TaEngine["kc"] }).kc;
|
|
1785
|
+
if (typeof fn === "function") return fn.call(env.ta, site, high, low, close, length, mult);
|
|
1786
|
+
const mid = env.ta.ema(`${site}:ema`, close, length);
|
|
1787
|
+
const atrVal = env.ta.atr(`${site}:atr`, high, low, close, length);
|
|
1788
|
+
if (mid == null) return { mid: null, up: null, lo: null };
|
|
1789
|
+
const width = (atrVal ?? 0) * mult;
|
|
1790
|
+
return { mid, up: mid + width, lo: mid - width };
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
function taSupertrend(
|
|
1794
|
+
env: Env,
|
|
1795
|
+
site: string,
|
|
1796
|
+
high: Cell,
|
|
1797
|
+
low: Cell,
|
|
1798
|
+
close: Cell,
|
|
1799
|
+
factor: number,
|
|
1800
|
+
atrPeriod: number,
|
|
1801
|
+
): { st: Cell; dir: Cell } {
|
|
1802
|
+
const fn = (env.ta as TaEngine & { supertrend?: TaEngine["supertrend"] }).supertrend;
|
|
1803
|
+
if (typeof fn === "function") {
|
|
1804
|
+
return fn.call(env.ta, site, high, low, close, factor, atrPeriod);
|
|
1805
|
+
}
|
|
1806
|
+
const atrVal = env.ta.atr(`${site}:atr`, high, low, close, atrPeriod);
|
|
1807
|
+
const atrF = atrVal == null || !Number.isFinite(atrVal) ? 0 : atrVal;
|
|
1808
|
+
const h = high ?? 0;
|
|
1809
|
+
const l = low ?? 0;
|
|
1810
|
+
const c = close ?? h;
|
|
1811
|
+
const mid = (h + l) / 2;
|
|
1812
|
+
const upper = mid + factor * atrF;
|
|
1813
|
+
const lower = mid - factor * atrF;
|
|
1814
|
+
const dir = c >= mid ? -1 : 1;
|
|
1815
|
+
return { st: dir < 0 ? lower : upper, dir };
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
function taTr(env: Env, site: string, high: Cell, low: Cell, close: Cell): Cell {
|
|
1819
|
+
const fn = (env.ta as TaEngine & { tr?: TaEngine["tr"] }).tr;
|
|
1820
|
+
if (typeof fn === "function") return fn.call(env.ta, site, high, low, close);
|
|
1821
|
+
return env.ta.atr(site, high, low, close, 1) == null && env.barIndex === 0 ? NA : NA;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
function num(v: Value | undefined): Cell {
|
|
1825
|
+
if (v === undefined) return NA;
|
|
1826
|
+
return unwrap(v);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
function srcArg(node: Call, env: Env): Cell {
|
|
1830
|
+
return unwrap(evalExpr(callArg(node.args, 0, ["source", "series"]), env));
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
function lenArg(node: Call, env: Env, index: number): number {
|
|
1834
|
+
const raw = unwrap(evalExpr(callArg(node.args, index, ["length"]), env));
|
|
1835
|
+
return raw == null || !Number.isFinite(raw) ? 0 : raw;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
function namedOrLen(node: Call, env: Env, index: number, names: string[], fallback: number): number {
|
|
1839
|
+
const raw = unwrap(evalExpr(callArg(node.args, index, names), env));
|
|
1840
|
+
return raw == null || !Number.isFinite(raw) ? fallback : raw;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
function numArg(node: Call, env: Env, index: number, names: string[], fallback: number): number {
|
|
1844
|
+
const raw = unwrap(evalExpr(callArg(node.args, index, names), env));
|
|
1845
|
+
return raw == null || !Number.isFinite(raw) ? fallback : raw;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
function unwrap(value: Value): Cell {
|
|
1849
|
+
if (value == null) return NA;
|
|
1850
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : NA;
|
|
1851
|
+
// Objects (PineArray / Matrix / Color / tuples) are not cells — not na.
|
|
1852
|
+
return NA;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
function formatRunError(err: unknown): string {
|
|
1856
|
+
if (err instanceof Error) {
|
|
1857
|
+
const msg = (err.message || err.name || "runtime error").split("\n")[0]!.trim();
|
|
1858
|
+
return msg || "runtime error";
|
|
1859
|
+
}
|
|
1860
|
+
if (typeof err === "string") {
|
|
1861
|
+
const msg = err.split("\n")[0]!.trim();
|
|
1862
|
+
return msg || "runtime error";
|
|
1863
|
+
}
|
|
1864
|
+
return "runtime error";
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function isPlainInputs(extra: RuntimeOptions | InputOverrides): extra is InputOverrides {
|
|
1868
|
+
return !("inputs" in extra) && !("broker" in extra) && !("timeframe" in extra) && !("libraries" in extra);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
function mergeInputs(
|
|
1872
|
+
base: InputOverrides,
|
|
1873
|
+
extra?: RuntimeOptions | InputOverrides,
|
|
1874
|
+
): InputOverrides {
|
|
1875
|
+
const more = pickInputs(extra);
|
|
1876
|
+
if (more == null) return base;
|
|
1877
|
+
return { ...base, ...more };
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
function pickInputs(extra?: RuntimeOptions | InputOverrides): InputOverrides | undefined {
|
|
1881
|
+
if (extra == null || typeof extra !== "object") return undefined;
|
|
1882
|
+
if ("inputs" in extra) {
|
|
1883
|
+
const nested = extra.inputs;
|
|
1884
|
+
if (nested != null && typeof nested === "object" && !Array.isArray(nested)) return nested;
|
|
1885
|
+
return undefined;
|
|
1886
|
+
}
|
|
1887
|
+
return extra as InputOverrides;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
function packFills(book: StrategyBook): RuntimeFill[] | undefined {
|
|
1891
|
+
const fills = (book as StrategyBook & {
|
|
1892
|
+
fills?: Array<{ bar: number; id: string; side: "buy" | "sell"; qty: number; price: number }>;
|
|
1893
|
+
}).fills;
|
|
1894
|
+
if (fills == null || fills.length === 0) return undefined;
|
|
1895
|
+
return fills.map((f) => ({ type: "fill" as const, ...f }));
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
function packDrawings(book: DrawingBook): DrawingEvent[] | undefined {
|
|
1899
|
+
return book.items.length === 0 ? undefined : book.items;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
function inputTitle(node: Call): string | null {
|
|
1903
|
+
const named = namedStringArg(node.args, "title");
|
|
1904
|
+
if (named != null) return named;
|
|
1905
|
+
const list = asArgs(node.args);
|
|
1906
|
+
let i = 0;
|
|
1907
|
+
for (const a of list) {
|
|
1908
|
+
if (argKeyword(a) != null) continue;
|
|
1909
|
+
if (i === 1 && a.value.kind === "Constant" && typeof a.value.value === "string") {
|
|
1910
|
+
return a.value.value;
|
|
1911
|
+
}
|
|
1912
|
+
i++;
|
|
1913
|
+
}
|
|
1914
|
+
return null;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
function rawArgValue(node: expr, env: Env): unknown {
|
|
1918
|
+
if (node.kind === "Constant") return node.value;
|
|
1919
|
+
const v = evalExpr(node, env);
|
|
1920
|
+
if (typeof v === "string") return v;
|
|
1921
|
+
return unwrap(v);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
function evalInput(node: Call, env: Env, fname: string): Value {
|
|
1925
|
+
const title = inputTitle(node);
|
|
1926
|
+
const key = title ?? siteKey(node, env);
|
|
1927
|
+
const stored = env.inputStore.get(key);
|
|
1928
|
+
if (stored !== undefined) return inputAsCell(stored);
|
|
1929
|
+
|
|
1930
|
+
const positional: unknown[] = [];
|
|
1931
|
+
const named: Record<string, unknown> = {};
|
|
1932
|
+
for (const a of asArgs(node.args)) {
|
|
1933
|
+
const raw = rawArgValue(argValue(a), env);
|
|
1934
|
+
const kw = argKeyword(a);
|
|
1935
|
+
if (kw != null) named[kw] = raw;
|
|
1936
|
+
else positional.push(raw);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
let value = resolveInputDefault(fname, positional, named);
|
|
1940
|
+
if (title != null && env.inputs[title] !== undefined) {
|
|
1941
|
+
value = env.inputs[title] as InputValue;
|
|
1942
|
+
}
|
|
1943
|
+
env.inputStore.set(key, value);
|
|
1944
|
+
return inputAsCell(value);
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
function evalAsString(node: expr | undefined, env: Env): string | null {
|
|
1948
|
+
if (node == null) return null;
|
|
1949
|
+
if (node.kind === "Constant" && typeof node.value === "string") return node.value;
|
|
1950
|
+
const v = evalExpr(node, env);
|
|
1951
|
+
return typeof v === "string" ? v : null;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
function evalStrategyRiskCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
1955
|
+
if (fname == null || !fname.startsWith("strategy.risk.")) return undefined;
|
|
1956
|
+
const book = env.book;
|
|
1957
|
+
if (fname === "strategy.risk.allow_entry_in") {
|
|
1958
|
+
const raw = evalExpr(callArg(node.args, 0, ["value"]), env);
|
|
1959
|
+
book.riskAllowEntryIn(typeof raw === "string" ? raw : unwrap(raw));
|
|
1960
|
+
return NA;
|
|
1961
|
+
}
|
|
1962
|
+
if (fname === "strategy.risk.max_position_size") {
|
|
1963
|
+
book.riskMaxPositionSize(unwrap(evalExpr(callArg(node.args, 0, ["percent", "value"]), env)));
|
|
1964
|
+
return NA;
|
|
1965
|
+
}
|
|
1966
|
+
if (fname === "strategy.risk.max_drawdown") {
|
|
1967
|
+
const value = unwrap(evalExpr(callArg(node.args, 0, ["value"]), env));
|
|
1968
|
+
const typeRaw = evalExpr(callArg(node.args, 1, ["type"]), env);
|
|
1969
|
+
book.riskMaxDrawdown(value, typeof typeRaw === "string" ? typeRaw : unwrap(typeRaw));
|
|
1970
|
+
return NA;
|
|
1971
|
+
}
|
|
1972
|
+
if (fname === "strategy.risk.max_cons_loss_days") {
|
|
1973
|
+
book.riskMaxConsLossDays(unwrap(evalExpr(callArg(node.args, 0, ["days", "value"]), env)));
|
|
1974
|
+
return NA;
|
|
1975
|
+
}
|
|
1976
|
+
if (fname === "strategy.risk.max_intraday_loss") {
|
|
1977
|
+
book.riskMaxIntradayLoss(unwrap(evalExpr(callArg(node.args, 0, ["percent", "value"]), env)));
|
|
1978
|
+
return NA;
|
|
1979
|
+
}
|
|
1980
|
+
if (fname === "strategy.risk.max_intraday_filled_orders") {
|
|
1981
|
+
book.riskMaxIntradayFilledOrders(
|
|
1982
|
+
unwrap(evalExpr(callArg(node.args, 0, ["max", "max_orders", "value"]), env)),
|
|
1983
|
+
);
|
|
1984
|
+
return NA;
|
|
1985
|
+
}
|
|
1986
|
+
return undefined;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
function evalStrategyTradeCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
1990
|
+
if (fname == null || !fname.startsWith("strategy.")) return undefined;
|
|
1991
|
+
const idx = unwrap(evalExpr(callArg(node.args, 0, ["trade_num", "trade_index", "index"]), env));
|
|
1992
|
+
const i = idx == null ? 0 : Math.trunc(idx);
|
|
1993
|
+
const mark = num(env.ctx.close) ?? 0;
|
|
1994
|
+
const book = env.book;
|
|
1995
|
+
switch (fname) {
|
|
1996
|
+
case "strategy.closedtrades.entry_bar_index":
|
|
1997
|
+
return book.closedEntryBar(i);
|
|
1998
|
+
case "strategy.closedtrades.entry_price":
|
|
1999
|
+
return book.closedEntryPrice(i);
|
|
2000
|
+
case "strategy.closedtrades.exit_bar_index":
|
|
2001
|
+
return book.closedExitBar(i);
|
|
2002
|
+
case "strategy.closedtrades.exit_price":
|
|
2003
|
+
return book.closedExitPrice(i);
|
|
2004
|
+
case "strategy.closedtrades.profit":
|
|
2005
|
+
return book.closedProfit(i);
|
|
2006
|
+
case "strategy.closedtrades.size":
|
|
2007
|
+
return book.closedSize(i);
|
|
2008
|
+
case "strategy.closedtrades.entry_id":
|
|
2009
|
+
case "strategy.closedtrades.exit_id":
|
|
2010
|
+
return book.closedId(i);
|
|
2011
|
+
case "strategy.closedtrades.commission":
|
|
2012
|
+
return book.closedCommission(i);
|
|
2013
|
+
case "strategy.opentrades.entry_bar_index":
|
|
2014
|
+
return book.openEntryBar(i);
|
|
2015
|
+
case "strategy.opentrades.entry_price":
|
|
2016
|
+
return book.openEntryPrice(i);
|
|
2017
|
+
case "strategy.opentrades.size":
|
|
2018
|
+
return book.openSize(i);
|
|
2019
|
+
case "strategy.opentrades.entry_id":
|
|
2020
|
+
return book.openId(i);
|
|
2021
|
+
case "strategy.opentrades.profit":
|
|
2022
|
+
return book.openProfit(i, mark);
|
|
2023
|
+
default:
|
|
2024
|
+
return undefined;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function evalRequestSecurity(node: Call, env: Env): Cell {
|
|
2029
|
+
const symbol = evalAsString(callArg(node.args, 0, ["symbol"]), env);
|
|
2030
|
+
const timeframe = evalAsString(callArg(node.args, 1, ["timeframe"]), env);
|
|
2031
|
+
const exprNode = callArg(node.args, 2, ["expression"]);
|
|
2032
|
+
const sameSymbolValue = unwrap(evalExpr(exprNode, env));
|
|
2033
|
+
return resolveSecurity(
|
|
2034
|
+
env.symbol,
|
|
2035
|
+
env.timeframe,
|
|
2036
|
+
{ symbol, timeframe, expression: exprNode },
|
|
2037
|
+
sameSymbolValue,
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
function evalRequestCall(fname: string, node: Call, env: Env): Cell {
|
|
2042
|
+
if (fname === "request.security" || fname === "request.security_lower_tf") {
|
|
2043
|
+
return evalRequestSecurity(node, env);
|
|
2044
|
+
}
|
|
2045
|
+
const host = { symbol: env.symbol, timeframe: env.timeframe };
|
|
2046
|
+
if (fname === "request.currency_rate") {
|
|
2047
|
+
const from = evalAsString(callArg(node.args, 0, ["from", "from_currency"]), env);
|
|
2048
|
+
const to = evalAsString(callArg(node.args, 1, ["to", "to_currency"]), env);
|
|
2049
|
+
const v = resolveRequest(fname, host, { from, to });
|
|
2050
|
+
return typeof v === "number" || v === null ? v : NA;
|
|
2051
|
+
}
|
|
2052
|
+
if (fname === "request.seed") {
|
|
2053
|
+
const seed = unwrap(evalExpr(callArg(node.args, 0, ["seed"]), env));
|
|
2054
|
+
const v = resolveRequest(fname, host, { seed });
|
|
2055
|
+
return typeof v === "number" || v === null ? v : NA;
|
|
2056
|
+
}
|
|
2057
|
+
const v = resolveRequest(fname, host, {});
|
|
2058
|
+
return typeof v === "number" || v === null ? v : NA;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
function evalMathCall(fname: string | null, node: Call, env: Env): Cell | undefined {
|
|
2062
|
+
if (fname === "math.abs" || fname === "abs") {
|
|
2063
|
+
return mathAbs(cellArg(node, env, 0, ["number", "x"]));
|
|
2064
|
+
}
|
|
2065
|
+
if (fname === "math.max" || fname === "max") return mathMax(...allCellArgs(node, env));
|
|
2066
|
+
if (fname === "math.min" || fname === "min") return mathMin(...allCellArgs(node, env));
|
|
2067
|
+
if (fname === "math.sqrt" || fname === "sqrt") return mathSqrt(cellArg(node, env, 0, ["number", "x"]));
|
|
2068
|
+
if (fname === "math.log" || fname === "log") return mathLog(cellArg(node, env, 0, ["number", "x"]));
|
|
2069
|
+
if (fname === "math.log10" || fname === "log10") return mathLog10(cellArg(node, env, 0, ["number", "x"]));
|
|
2070
|
+
if (fname === "math.exp" || fname === "exp") return mathExp(cellArg(node, env, 0, ["number", "x"]));
|
|
2071
|
+
if (fname === "math.pow" || fname === "pow") {
|
|
2072
|
+
return mathPow(
|
|
2073
|
+
cellArg(node, env, 0, ["base", "number", "x"]),
|
|
2074
|
+
cellArg(node, env, 1, ["exponent", "exp", "y"]),
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
if (fname === "math.round" || fname === "round") {
|
|
2078
|
+
const prec = callArg(node.args, 1, ["precision"]);
|
|
2079
|
+
return mathRound(cellArg(node, env, 0, ["number", "x"]), prec == null ? undefined : unwrap(evalExpr(prec, env)));
|
|
2080
|
+
}
|
|
2081
|
+
if (fname === "math.floor" || fname === "floor") return mathFloor(cellArg(node, env, 0, ["number", "x"]));
|
|
2082
|
+
if (fname === "math.ceil" || fname === "ceil") return mathCeil(cellArg(node, env, 0, ["number", "x"]));
|
|
2083
|
+
if (fname === "math.sign" || fname === "sign") return mathSign(cellArg(node, env, 0, ["number", "x"]));
|
|
2084
|
+
if (fname === "math.avg" || fname === "avg") return mathAvg(...allCellArgs(node, env));
|
|
2085
|
+
if (fname === "math.sum") {
|
|
2086
|
+
const first = evalExpr(callArg(node.args, 0, ["source", "id", "array"]), env);
|
|
2087
|
+
const arr = asArray(first);
|
|
2088
|
+
if (arr) return arr.sum();
|
|
2089
|
+
return mathSum(...allCellArgs(node, env));
|
|
2090
|
+
}
|
|
2091
|
+
if (fname === "math.sin" || fname === "sin") return mathSin(cellArg(node, env, 0, ["angle", "x"]));
|
|
2092
|
+
if (fname === "math.cos" || fname === "cos") return mathCos(cellArg(node, env, 0, ["angle", "x"]));
|
|
2093
|
+
if (fname === "math.tan" || fname === "tan") return mathTan(cellArg(node, env, 0, ["angle", "x"]));
|
|
2094
|
+
if (fname === "math.asin" || fname === "asin") return mathAsin(cellArg(node, env, 0, ["x"]));
|
|
2095
|
+
if (fname === "math.acos" || fname === "acos") return mathAcos(cellArg(node, env, 0, ["x"]));
|
|
2096
|
+
if (fname === "math.atan" || fname === "atan") return mathAtan(cellArg(node, env, 0, ["x"]));
|
|
2097
|
+
if (fname === "math.todegrees") return mathToDegrees(cellArg(node, env, 0, ["radians", "x"]));
|
|
2098
|
+
if (fname === "math.toradians") return mathToRadians(cellArg(node, env, 0, ["degrees", "x"]));
|
|
2099
|
+
if (fname === "math.isfinite") return mathIsFinite(cellArg(node, env, 0, ["number", "x"]));
|
|
2100
|
+
if (fname === "math.random") {
|
|
2101
|
+
const a0 = callArg(node.args, 0, ["min"]);
|
|
2102
|
+
const a1 = callArg(node.args, 1, ["max"]);
|
|
2103
|
+
return mathRandom(
|
|
2104
|
+
a0 == null ? undefined : unwrap(evalExpr(a0, env)),
|
|
2105
|
+
a1 == null ? undefined : unwrap(evalExpr(a1, env)),
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
if (fname === "math.round_to_mintick") {
|
|
2109
|
+
const tick = typeof env.ctx["syminfo.mintick"] === "number" ? (env.ctx["syminfo.mintick"] as number) : 0.01;
|
|
2110
|
+
return mathRoundToMintick(cellArg(node, env, 0, ["number", "x"]), tick);
|
|
2111
|
+
}
|
|
2112
|
+
return undefined;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
function cellArg(node: Call, env: Env, index: number, names: string[]): Cell {
|
|
2116
|
+
return unwrap(evalExpr(callArg(node.args, index, names), env));
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
function allCellArgs(node: Call, env: Env): Cell[] {
|
|
2120
|
+
return asArgs(node.args).map((a) => unwrap(evalExpr(a.value, env)));
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
function lenOrDefault(
|
|
2124
|
+
node: Call,
|
|
2125
|
+
env: Env,
|
|
2126
|
+
index: number,
|
|
2127
|
+
names: string[],
|
|
2128
|
+
fallback: number,
|
|
2129
|
+
): number {
|
|
2130
|
+
const arg = callArg(node.args, index, names);
|
|
2131
|
+
if (arg == null) return fallback;
|
|
2132
|
+
const raw = unwrap(evalExpr(arg, env));
|
|
2133
|
+
return raw == null || !Number.isFinite(raw) ? fallback : raw;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
type TaAny = TaEngine & Record<string, unknown>;
|
|
2137
|
+
|
|
2138
|
+
function evalExtraTa(fname: string | null, node: Call, env: Env, site: string): Value | undefined {
|
|
2139
|
+
const ta = env.ta as TaAny;
|
|
2140
|
+
if (fname === "ta.highest" || fname === "highest") {
|
|
2141
|
+
if (typeof ta.highest !== "function") return NA;
|
|
2142
|
+
return (ta.highest as TaEngine["highest"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2143
|
+
}
|
|
2144
|
+
if (fname === "ta.lowest" || fname === "lowest") {
|
|
2145
|
+
if (typeof ta.lowest !== "function") return NA;
|
|
2146
|
+
return (ta.lowest as TaEngine["lowest"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2147
|
+
}
|
|
2148
|
+
if (fname === "ta.stdev" || fname === "stdev") {
|
|
2149
|
+
if (typeof ta.stdev !== "function") return NA;
|
|
2150
|
+
return (ta.stdev as TaEngine["stdev"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2151
|
+
}
|
|
2152
|
+
if (fname === "ta.change" || fname === "change") {
|
|
2153
|
+
if (typeof ta.change !== "function") return NA;
|
|
2154
|
+
return (ta.change as TaEngine["change"]).call(
|
|
2155
|
+
env.ta,
|
|
2156
|
+
site,
|
|
2157
|
+
srcArg(node, env),
|
|
2158
|
+
lenOrDefault(node, env, 1, ["length"], 1),
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
if (fname === "ta.wma" || fname === "wma") {
|
|
2162
|
+
if (typeof ta.wma !== "function") return NA;
|
|
2163
|
+
return (ta.wma as TaEngine["wma"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2164
|
+
}
|
|
2165
|
+
if (fname === "ta.crossover" || fname === "crossover") {
|
|
2166
|
+
if (typeof ta.crossover !== "function") return NA;
|
|
2167
|
+
return (ta.crossover as TaEngine["crossover"]).call(
|
|
2168
|
+
env.ta,
|
|
2169
|
+
site,
|
|
2170
|
+
srcArg(node, env),
|
|
2171
|
+
unwrap(evalExpr(callArg(node.args, 1, ["source2", "b", "series2"]), env)),
|
|
2172
|
+
);
|
|
2173
|
+
}
|
|
2174
|
+
if (fname === "ta.crossunder" || fname === "crossunder") {
|
|
2175
|
+
if (typeof ta.crossunder !== "function") return NA;
|
|
2176
|
+
return (ta.crossunder as TaEngine["crossunder"]).call(
|
|
2177
|
+
env.ta,
|
|
2178
|
+
site,
|
|
2179
|
+
srcArg(node, env),
|
|
2180
|
+
unwrap(evalExpr(callArg(node.args, 1, ["source2", "b", "series2"]), env)),
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
if (fname === "ta.macd" || fname === "macd") {
|
|
2184
|
+
if (typeof ta.macd !== "function") return NA;
|
|
2185
|
+
const r = (ta.macd as TaEngine["macd"]).call(
|
|
2186
|
+
env.ta,
|
|
2187
|
+
site,
|
|
2188
|
+
srcArg(node, env),
|
|
2189
|
+
lenOrDefault(node, env, 1, ["fastlen", "fastLength", "fast"], 12),
|
|
2190
|
+
lenOrDefault(node, env, 2, ["slowlen", "slowLength", "slow"], 26),
|
|
2191
|
+
lenOrDefault(node, env, 3, ["signal", "signalLength", "siglen"], 9),
|
|
2192
|
+
);
|
|
2193
|
+
return tupleOf([r.macd, r.signal, r.hist]);
|
|
2194
|
+
}
|
|
2195
|
+
if (fname === "ta.bb" || fname === "bb") {
|
|
2196
|
+
if (typeof ta.bb !== "function") return NA;
|
|
2197
|
+
const r = (ta.bb as TaEngine["bb"]).call(
|
|
2198
|
+
env.ta,
|
|
2199
|
+
site,
|
|
2200
|
+
srcArg(node, env),
|
|
2201
|
+
lenArg(node, env, 1),
|
|
2202
|
+
lenOrDefault(node, env, 2, ["mult", "multiplier"], 2),
|
|
2203
|
+
);
|
|
2204
|
+
return tupleOf([r.mid, r.up, r.lo]);
|
|
2205
|
+
}
|
|
2206
|
+
if (fname === "ta.sum" || fname === "sum") {
|
|
2207
|
+
if (typeof ta.sum !== "function") return NA;
|
|
2208
|
+
return (ta.sum as TaEngine["sum"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2209
|
+
}
|
|
2210
|
+
if (fname === "ta.roc" || fname === "roc") {
|
|
2211
|
+
if (typeof ta.roc !== "function") return NA;
|
|
2212
|
+
return (ta.roc as TaEngine["roc"]).call(
|
|
2213
|
+
env.ta,
|
|
2214
|
+
site,
|
|
2215
|
+
srcArg(node, env),
|
|
2216
|
+
lenOrDefault(node, env, 1, ["length"], 1),
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2219
|
+
if (fname === "ta.mom" || fname === "mom") {
|
|
2220
|
+
if (typeof ta.mom !== "function") return NA;
|
|
2221
|
+
return (ta.mom as TaEngine["mom"]).call(
|
|
2222
|
+
env.ta,
|
|
2223
|
+
site,
|
|
2224
|
+
srcArg(node, env),
|
|
2225
|
+
lenOrDefault(node, env, 1, ["length"], 1),
|
|
2226
|
+
);
|
|
2227
|
+
}
|
|
2228
|
+
if (fname === "ta.vwma" || fname === "vwma") {
|
|
2229
|
+
if (typeof ta.vwma !== "function") return NA;
|
|
2230
|
+
return (ta.vwma as TaEngine["vwma"]).call(
|
|
2231
|
+
env.ta,
|
|
2232
|
+
site,
|
|
2233
|
+
srcArg(node, env),
|
|
2234
|
+
num(env.ctx.volume),
|
|
2235
|
+
lenArg(node, env, 1),
|
|
2236
|
+
);
|
|
2237
|
+
}
|
|
2238
|
+
if (fname === "ta.cci" || fname === "cci") {
|
|
2239
|
+
if (typeof ta.cci !== "function") return NA;
|
|
2240
|
+
const h = num(env.ctx.high);
|
|
2241
|
+
const l = num(env.ctx.low);
|
|
2242
|
+
const c = srcArg(node, env);
|
|
2243
|
+
const tp = h != null && l != null && c != null ? (h + l + c) / 3 : c;
|
|
2244
|
+
return (ta.cci as TaEngine["cci"]).call(env.ta, site, tp, lenArg(node, env, 1));
|
|
2245
|
+
}
|
|
2246
|
+
if (fname === "ta.wpr" || fname === "wpr") {
|
|
2247
|
+
if (typeof ta.willr !== "function") return NA;
|
|
2248
|
+
return (ta.willr as TaEngine["willr"]).call(
|
|
2249
|
+
env.ta,
|
|
2250
|
+
site,
|
|
2251
|
+
num(env.ctx.high),
|
|
2252
|
+
num(env.ctx.low),
|
|
2253
|
+
num(env.ctx.close),
|
|
2254
|
+
lenArg(node, env, 0),
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
if (fname === "ta.willr" || fname === "willr") {
|
|
2258
|
+
if (typeof ta.willr !== "function") return NA;
|
|
2259
|
+
return (ta.willr as TaEngine["willr"]).call(
|
|
2260
|
+
env.ta,
|
|
2261
|
+
site,
|
|
2262
|
+
num(env.ctx.high),
|
|
2263
|
+
num(env.ctx.low),
|
|
2264
|
+
num(env.ctx.close),
|
|
2265
|
+
lenArg(node, env, 0),
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
if (fname === "ta.stoch" || fname === "stoch") {
|
|
2269
|
+
if (typeof ta.stoch !== "function") return NA;
|
|
2270
|
+
const positional = asArgs(node.args).filter((a) => argKeyword(a) == null);
|
|
2271
|
+
const highArg = callArg(node.args, 1, ["high"]);
|
|
2272
|
+
if (highArg == null && positional.length <= 1) {
|
|
2273
|
+
return (ta.stoch as TaEngine["stoch"]).call(
|
|
2274
|
+
env.ta,
|
|
2275
|
+
site,
|
|
2276
|
+
num(env.ctx.close),
|
|
2277
|
+
num(env.ctx.high),
|
|
2278
|
+
num(env.ctx.low),
|
|
2279
|
+
lenArg(node, env, 0),
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
return (ta.stoch as TaEngine["stoch"]).call(
|
|
2283
|
+
env.ta,
|
|
2284
|
+
site,
|
|
2285
|
+
srcArg(node, env),
|
|
2286
|
+
unwrap(evalExpr(highArg, env)),
|
|
2287
|
+
unwrap(evalExpr(callArg(node.args, 2, ["low"]), env)),
|
|
2288
|
+
lenArg(node, env, 3),
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
if (fname === "ta.linreg" || fname === "linreg") {
|
|
2292
|
+
if (typeof ta.linreg !== "function") return NA;
|
|
2293
|
+
return (ta.linreg as TaEngine["linreg"]).call(
|
|
2294
|
+
env.ta,
|
|
2295
|
+
site,
|
|
2296
|
+
srcArg(node, env),
|
|
2297
|
+
lenArg(node, env, 1),
|
|
2298
|
+
lenOrDefault(node, env, 2, ["offset"], 0),
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
if (fname === "ta.vwap" || fname === "vwap") {
|
|
2302
|
+
if (typeof ta.vwap !== "function") return NA;
|
|
2303
|
+
const src = callArg(node.args, 0, ["source", "series"]);
|
|
2304
|
+
const source = src != null ? unwrap(evalExpr(src, env)) : hlc3(env);
|
|
2305
|
+
return (ta.vwap as TaEngine["vwap"]).call(env.ta, site, source, num(env.ctx.volume));
|
|
2306
|
+
}
|
|
2307
|
+
if (fname === "ta.rising" || fname === "rising") {
|
|
2308
|
+
if (typeof ta.rising !== "function") return NA;
|
|
2309
|
+
return (ta.rising as TaEngine["rising"]).call(
|
|
2310
|
+
env.ta,
|
|
2311
|
+
site,
|
|
2312
|
+
srcArg(node, env),
|
|
2313
|
+
lenOrDefault(node, env, 1, ["length"], 1),
|
|
2314
|
+
);
|
|
2315
|
+
}
|
|
2316
|
+
if (fname === "ta.falling" || fname === "falling") {
|
|
2317
|
+
if (typeof ta.falling !== "function") return NA;
|
|
2318
|
+
return (ta.falling as TaEngine["falling"]).call(
|
|
2319
|
+
env.ta,
|
|
2320
|
+
site,
|
|
2321
|
+
srcArg(node, env),
|
|
2322
|
+
lenOrDefault(node, env, 1, ["length"], 1),
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
if (fname === "ta.cross" || fname === "cross") {
|
|
2326
|
+
if (typeof ta.cross !== "function") return NA;
|
|
2327
|
+
return (ta.cross as TaEngine["cross"]).call(
|
|
2328
|
+
env.ta,
|
|
2329
|
+
site,
|
|
2330
|
+
srcArg(node, env),
|
|
2331
|
+
unwrap(evalExpr(callArg(node.args, 1, ["source2", "b", "series2"]), env)),
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
if (fname === "ta.bbw" || fname === "bbw") {
|
|
2335
|
+
if (typeof ta.bbw !== "function") return NA;
|
|
2336
|
+
return (ta.bbw as TaEngine["bbw"]).call(
|
|
2337
|
+
env.ta,
|
|
2338
|
+
site,
|
|
2339
|
+
srcArg(node, env),
|
|
2340
|
+
lenArg(node, env, 1),
|
|
2341
|
+
lenOrDefault(node, env, 2, ["mult", "multiplier"], 2),
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
if (fname === "ta.dema" || fname === "dema") {
|
|
2345
|
+
if (typeof ta.dema !== "function") return NA;
|
|
2346
|
+
return (ta.dema as TaEngine["dema"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2347
|
+
}
|
|
2348
|
+
if (fname === "ta.tema" || fname === "tema") {
|
|
2349
|
+
if (typeof ta.tema !== "function") return NA;
|
|
2350
|
+
return (ta.tema as TaEngine["tema"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2351
|
+
}
|
|
2352
|
+
if (fname === "ta.highestbars" || fname === "highestbars") {
|
|
2353
|
+
if (typeof ta.highestbars !== "function") return NA;
|
|
2354
|
+
return (ta.highestbars as TaEngine["highestbars"]).call(
|
|
2355
|
+
env.ta,
|
|
2356
|
+
site,
|
|
2357
|
+
srcArg(node, env),
|
|
2358
|
+
lenArg(node, env, 1),
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
2361
|
+
if (fname === "ta.lowestbars" || fname === "lowestbars") {
|
|
2362
|
+
if (typeof ta.lowestbars !== "function") return NA;
|
|
2363
|
+
return (ta.lowestbars as TaEngine["lowestbars"]).call(
|
|
2364
|
+
env.ta,
|
|
2365
|
+
site,
|
|
2366
|
+
srcArg(node, env),
|
|
2367
|
+
lenArg(node, env, 1),
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
if (fname === "ta.alma" || fname === "alma") {
|
|
2371
|
+
if (typeof ta.alma !== "function") return NA;
|
|
2372
|
+
return (ta.alma as TaEngine["alma"]).call(
|
|
2373
|
+
env.ta,
|
|
2374
|
+
site,
|
|
2375
|
+
srcArg(node, env),
|
|
2376
|
+
lenArg(node, env, 1),
|
|
2377
|
+
lenOrDefault(node, env, 2, ["offset"], 0.85),
|
|
2378
|
+
lenOrDefault(node, env, 3, ["sigma"], 6),
|
|
2379
|
+
);
|
|
2380
|
+
}
|
|
2381
|
+
if (fname === "ta.cmo" || fname === "cmo") {
|
|
2382
|
+
if (typeof ta.cmo !== "function") return NA;
|
|
2383
|
+
return (ta.cmo as TaEngine["cmo"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2384
|
+
}
|
|
2385
|
+
if (fname === "ta.kama" || fname === "kama") {
|
|
2386
|
+
if (typeof ta.kama !== "function") return NA;
|
|
2387
|
+
return (ta.kama as TaEngine["kama"]).call(
|
|
2388
|
+
env.ta,
|
|
2389
|
+
site,
|
|
2390
|
+
srcArg(node, env),
|
|
2391
|
+
lenArg(node, env, 1),
|
|
2392
|
+
lenOrDefault(node, env, 2, ["fastLength", "fast", "fastlen"], 2),
|
|
2393
|
+
lenOrDefault(node, env, 3, ["slowLength", "slow", "slowlen"], 30),
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
if (fname === "ta.accdist" || fname === "accdist" || fname === "ta.ad" || fname === "ad") {
|
|
2397
|
+
const hArg = callArg(node.args, 0, ["high"]);
|
|
2398
|
+
const lArg = callArg(node.args, 1, ["low"]);
|
|
2399
|
+
const cArg = callArg(node.args, 2, ["close"]);
|
|
2400
|
+
const vArg = callArg(node.args, 3, ["volume"]);
|
|
2401
|
+
return env.ta.accdist(
|
|
2402
|
+
site,
|
|
2403
|
+
hArg == null ? num(env.ctx.high) : unwrap(evalExpr(hArg, env)),
|
|
2404
|
+
lArg == null ? num(env.ctx.low) : unwrap(evalExpr(lArg, env)),
|
|
2405
|
+
cArg == null ? num(env.ctx.close) : unwrap(evalExpr(cArg, env)),
|
|
2406
|
+
vArg == null ? num(env.ctx.volume) : unwrap(evalExpr(vArg, env)),
|
|
2407
|
+
);
|
|
2408
|
+
}
|
|
2409
|
+
if (fname === "ta.obv" || fname === "obv") {
|
|
2410
|
+
if (typeof ta.obv !== "function") return NA;
|
|
2411
|
+
const cArg = callArg(node.args, 0, ["source", "close"]);
|
|
2412
|
+
const vArg = callArg(node.args, 1, ["volume"]);
|
|
2413
|
+
const close = cArg == null ? num(env.ctx.close) : unwrap(evalExpr(cArg, env));
|
|
2414
|
+
const volume = vArg == null ? num(env.ctx.volume) : unwrap(evalExpr(vArg, env));
|
|
2415
|
+
return (ta.obv as TaEngine["obv"]).call(env.ta, site, close, volume);
|
|
2416
|
+
}
|
|
2417
|
+
if (fname === "ta.pivothigh" || fname === "pivothigh") {
|
|
2418
|
+
if (typeof ta.pivothigh !== "function") return NA;
|
|
2419
|
+
return (ta.pivothigh as TaEngine["pivothigh"]).call(
|
|
2420
|
+
env.ta,
|
|
2421
|
+
site,
|
|
2422
|
+
srcArg(node, env),
|
|
2423
|
+
lenOrDefault(node, env, 1, ["leftbars", "left"], 5),
|
|
2424
|
+
lenOrDefault(node, env, 2, ["rightbars", "right"], 5),
|
|
2425
|
+
);
|
|
2426
|
+
}
|
|
2427
|
+
if (fname === "ta.pivotlow" || fname === "pivotlow") {
|
|
2428
|
+
if (typeof ta.pivotlow !== "function") return NA;
|
|
2429
|
+
return (ta.pivotlow as TaEngine["pivotlow"]).call(
|
|
2430
|
+
env.ta,
|
|
2431
|
+
site,
|
|
2432
|
+
srcArg(node, env),
|
|
2433
|
+
lenOrDefault(node, env, 1, ["leftbars", "left"], 5),
|
|
2434
|
+
lenOrDefault(node, env, 2, ["rightbars", "right"], 5),
|
|
2435
|
+
);
|
|
2436
|
+
}
|
|
2437
|
+
if (fname === "ta.hma" || fname === "hma") {
|
|
2438
|
+
if (typeof ta.hma !== "function") return NA;
|
|
2439
|
+
return (ta.hma as TaEngine["hma"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2440
|
+
}
|
|
2441
|
+
if (fname === "ta.mfi" || fname === "mfi") {
|
|
2442
|
+
if (typeof ta.mfi !== "function") return NA;
|
|
2443
|
+
const positional = asArgs(node.args).filter((a) => a.name == null);
|
|
2444
|
+
if (positional.length <= 1) {
|
|
2445
|
+
return (ta.mfi as TaEngine["mfi"]).call(
|
|
2446
|
+
env.ta,
|
|
2447
|
+
site,
|
|
2448
|
+
num(env.ctx.high),
|
|
2449
|
+
num(env.ctx.low),
|
|
2450
|
+
num(env.ctx.close),
|
|
2451
|
+
num(env.ctx.volume),
|
|
2452
|
+
lenArg(node, env, 0),
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
if (positional.length === 2) {
|
|
2456
|
+
return (ta.mfi as TaEngine["mfi"]).call(
|
|
2457
|
+
env.ta,
|
|
2458
|
+
site,
|
|
2459
|
+
srcArg(node, env),
|
|
2460
|
+
srcArg(node, env),
|
|
2461
|
+
srcArg(node, env),
|
|
2462
|
+
num(env.ctx.volume),
|
|
2463
|
+
lenArg(node, env, 1),
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
return (ta.mfi as TaEngine["mfi"]).call(
|
|
2467
|
+
env.ta,
|
|
2468
|
+
site,
|
|
2469
|
+
unwrap(evalExpr(callArg(node.args, 0, ["high"]), env)),
|
|
2470
|
+
unwrap(evalExpr(callArg(node.args, 1, ["low"]), env)),
|
|
2471
|
+
unwrap(evalExpr(callArg(node.args, 2, ["close"]), env)),
|
|
2472
|
+
unwrap(evalExpr(callArg(node.args, 3, ["volume"]), env)),
|
|
2473
|
+
lenArg(node, env, 4),
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
if (fname === "ta.sar" || fname === "sar") {
|
|
2477
|
+
if (typeof ta.sar !== "function") return NA;
|
|
2478
|
+
return (ta.sar as TaEngine["sar"]).call(
|
|
2479
|
+
env.ta,
|
|
2480
|
+
site,
|
|
2481
|
+
num(env.ctx.high),
|
|
2482
|
+
num(env.ctx.low),
|
|
2483
|
+
lenOrDefault(node, env, 0, ["start"], 0.02),
|
|
2484
|
+
lenOrDefault(node, env, 1, ["increment"], 0.02),
|
|
2485
|
+
lenOrDefault(node, env, 2, ["maximum", "max"], 0.2),
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2488
|
+
if (fname === "ta.dmi" || fname === "dmi") {
|
|
2489
|
+
if (typeof ta.dmi !== "function") return NA;
|
|
2490
|
+
const r = (ta.dmi as TaEngine["dmi"]).call(
|
|
2491
|
+
env.ta,
|
|
2492
|
+
site,
|
|
2493
|
+
num(env.ctx.high),
|
|
2494
|
+
num(env.ctx.low),
|
|
2495
|
+
num(env.ctx.close),
|
|
2496
|
+
lenOrDefault(node, env, 0, ["diLength", "length"], 14),
|
|
2497
|
+
lenOrDefault(node, env, 1, ["adxSmoothing", "adxlen"], 14),
|
|
2498
|
+
);
|
|
2499
|
+
return tupleOf([r.plus, r.minus, r.adx]);
|
|
2500
|
+
}
|
|
2501
|
+
if (fname === "ta.adx" || fname === "adx") {
|
|
2502
|
+
if (typeof ta.adx !== "function") return NA;
|
|
2503
|
+
return (ta.adx as TaEngine["adx"]).call(
|
|
2504
|
+
env.ta,
|
|
2505
|
+
site,
|
|
2506
|
+
num(env.ctx.high),
|
|
2507
|
+
num(env.ctx.low),
|
|
2508
|
+
num(env.ctx.close),
|
|
2509
|
+
lenOrDefault(node, env, 0, ["length"], 14),
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
if (fname === "ta.correlation" || fname === "correlation") {
|
|
2513
|
+
if (typeof ta.correlation !== "function") return NA;
|
|
2514
|
+
return (ta.correlation as TaEngine["correlation"]).call(
|
|
2515
|
+
env.ta,
|
|
2516
|
+
site,
|
|
2517
|
+
srcArg(node, env),
|
|
2518
|
+
unwrap(evalExpr(callArg(node.args, 1, ["source2", "b", "series2"]), env)),
|
|
2519
|
+
lenArg(node, env, 2),
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
if (fname === "ta.swma" || fname === "swma") {
|
|
2523
|
+
if (typeof ta.swma !== "function") return NA;
|
|
2524
|
+
const len = callArg(node.args, 1, ["length"]);
|
|
2525
|
+
return (ta.swma as TaEngine["swma"]).call(
|
|
2526
|
+
env.ta,
|
|
2527
|
+
site,
|
|
2528
|
+
srcArg(node, env),
|
|
2529
|
+
len == null ? undefined : lenArg(node, env, 1),
|
|
2530
|
+
);
|
|
2531
|
+
}
|
|
2532
|
+
if (fname === "ta.cog" || fname === "cog") {
|
|
2533
|
+
if (typeof ta.cog !== "function") return NA;
|
|
2534
|
+
return (ta.cog as TaEngine["cog"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2535
|
+
}
|
|
2536
|
+
if (fname === "ta.tsi" || fname === "tsi") {
|
|
2537
|
+
if (typeof ta.tsi !== "function") return NA;
|
|
2538
|
+
const positional = asArgs(node.args).filter((a) => a.name == null);
|
|
2539
|
+
if (positional.length === 2) {
|
|
2540
|
+
return (ta.tsi as TaEngine["tsi"]).call(
|
|
2541
|
+
env.ta,
|
|
2542
|
+
site,
|
|
2543
|
+
num(env.ctx.close),
|
|
2544
|
+
lenOrDefault(node, env, 1, ["long", "long_length"], 25),
|
|
2545
|
+
lenOrDefault(node, env, 0, ["short", "short_length"], 13),
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
return (ta.tsi as TaEngine["tsi"]).call(
|
|
2549
|
+
env.ta,
|
|
2550
|
+
site,
|
|
2551
|
+
srcArg(node, env),
|
|
2552
|
+
lenOrDefault(node, env, 2, ["long", "long_length"], 25),
|
|
2553
|
+
lenOrDefault(node, env, 1, ["short", "short_length"], 13),
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
if (fname === "ta.kcw" || fname === "kcw") {
|
|
2557
|
+
if (typeof ta.kcw !== "function") return NA;
|
|
2558
|
+
return (ta.kcw as TaEngine["kcw"]).call(
|
|
2559
|
+
env.ta,
|
|
2560
|
+
site,
|
|
2561
|
+
num(env.ctx.high),
|
|
2562
|
+
num(env.ctx.low),
|
|
2563
|
+
num(env.ctx.close),
|
|
2564
|
+
lenArg(node, env, 0),
|
|
2565
|
+
lenOrDefault(node, env, 1, ["mult", "multiplier"], 2),
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
if (fname === "ta.dev" || fname === "dev") {
|
|
2569
|
+
if (typeof ta.dev !== "function") return NA;
|
|
2570
|
+
return (ta.dev as TaEngine["dev"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2571
|
+
}
|
|
2572
|
+
if (fname === "ta.variance" || fname === "variance") {
|
|
2573
|
+
if (typeof ta.variance !== "function") return NA;
|
|
2574
|
+
return (ta.variance as TaEngine["variance"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2575
|
+
}
|
|
2576
|
+
if (fname === "ta.median" || fname === "median") {
|
|
2577
|
+
if (typeof ta.median !== "function") return NA;
|
|
2578
|
+
return (ta.median as TaEngine["median"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2579
|
+
}
|
|
2580
|
+
if (fname === "ta.mode" || fname === "mode") {
|
|
2581
|
+
if (typeof ta.mode !== "function") return NA;
|
|
2582
|
+
return (ta.mode as TaEngine["mode"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2583
|
+
}
|
|
2584
|
+
if (fname === "ta.percentrank" || fname === "percentrank") {
|
|
2585
|
+
if (typeof ta.percentrank !== "function") return NA;
|
|
2586
|
+
return (ta.percentrank as TaEngine["percentrank"]).call(
|
|
2587
|
+
env.ta,
|
|
2588
|
+
site,
|
|
2589
|
+
srcArg(node, env),
|
|
2590
|
+
lenArg(node, env, 1),
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2593
|
+
if (fname === "ta.percentile_nearest_rank") {
|
|
2594
|
+
if (typeof ta.percentileNearest !== "function") return NA;
|
|
2595
|
+
return (ta.percentileNearest as TaEngine["percentileNearest"]).call(
|
|
2596
|
+
env.ta,
|
|
2597
|
+
site,
|
|
2598
|
+
srcArg(node, env),
|
|
2599
|
+
lenArg(node, env, 1),
|
|
2600
|
+
lenOrDefault(node, env, 2, ["percentage", "percent"], 50),
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
if (fname === "ta.percentile_linear_interpolation") {
|
|
2604
|
+
if (typeof ta.percentileLinear !== "function") return NA;
|
|
2605
|
+
return (ta.percentileLinear as TaEngine["percentileLinear"]).call(
|
|
2606
|
+
env.ta,
|
|
2607
|
+
site,
|
|
2608
|
+
srcArg(node, env),
|
|
2609
|
+
lenArg(node, env, 1),
|
|
2610
|
+
lenOrDefault(node, env, 2, ["percentage", "percent"], 50),
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
if (fname === "ta.cum" || fname === "cum") {
|
|
2614
|
+
if (typeof ta.cum !== "function") return NA;
|
|
2615
|
+
return (ta.cum as TaEngine["cum"]).call(env.ta, site, srcArg(node, env));
|
|
2616
|
+
}
|
|
2617
|
+
if (fname === "ta.barssince" || fname === "barssince") {
|
|
2618
|
+
if (typeof ta.barssince !== "function") return NA;
|
|
2619
|
+
return (ta.barssince as TaEngine["barssince"]).call(
|
|
2620
|
+
env.ta,
|
|
2621
|
+
site,
|
|
2622
|
+
unwrap(evalExpr(callArg(node.args, 0, ["condition", "cond"]), env)),
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2625
|
+
if (fname === "ta.valuewhen" || fname === "valuewhen") {
|
|
2626
|
+
if (typeof ta.valuewhen !== "function") return NA;
|
|
2627
|
+
return (ta.valuewhen as TaEngine["valuewhen"]).call(
|
|
2628
|
+
env.ta,
|
|
2629
|
+
site,
|
|
2630
|
+
unwrap(evalExpr(callArg(node.args, 0, ["condition", "cond"]), env)),
|
|
2631
|
+
unwrap(evalExpr(callArg(node.args, 1, ["source"]), env)),
|
|
2632
|
+
lenOrDefault(node, env, 2, ["occurrence"], 0),
|
|
2633
|
+
);
|
|
2634
|
+
}
|
|
2635
|
+
if (fname === "ta.range") {
|
|
2636
|
+
if (typeof ta.range !== "function") return NA;
|
|
2637
|
+
return (ta.range as TaEngine["range"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2638
|
+
}
|
|
2639
|
+
if ((fname === "ta.max" || fname === "ta.min") && typeof ta.max === "function") {
|
|
2640
|
+
const fn = fname === "ta.max" ? ta.max : ta.min;
|
|
2641
|
+
return (fn as TaEngine["max"]).call(env.ta, site, srcArg(node, env), lenArg(node, env, 1));
|
|
2642
|
+
}
|
|
2643
|
+
if (fname === "ta.accdist" || fname === "accdist") {
|
|
2644
|
+
if (typeof ta.accdist !== "function") return NA;
|
|
2645
|
+
return (ta.accdist as TaEngine["accdist"]).call(
|
|
2646
|
+
env.ta,
|
|
2647
|
+
site,
|
|
2648
|
+
num(env.ctx.high),
|
|
2649
|
+
num(env.ctx.low),
|
|
2650
|
+
num(env.ctx.close),
|
|
2651
|
+
num(env.ctx.volume),
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
if (fname === "ta.pvt" || fname === "pvt") {
|
|
2655
|
+
if (typeof ta.pvt !== "function") return NA;
|
|
2656
|
+
return (ta.pvt as TaEngine["pvt"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
2657
|
+
}
|
|
2658
|
+
if (fname === "ta.wad" || fname === "wad") {
|
|
2659
|
+
if (typeof ta.wad !== "function") return NA;
|
|
2660
|
+
return (ta.wad as TaEngine["wad"]).call(
|
|
2661
|
+
env.ta,
|
|
2662
|
+
site,
|
|
2663
|
+
num(env.ctx.high),
|
|
2664
|
+
num(env.ctx.low),
|
|
2665
|
+
num(env.ctx.close),
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
if (fname === "ta.nvi" || fname === "nvi") {
|
|
2669
|
+
if (typeof ta.nvi !== "function") return NA;
|
|
2670
|
+
return (ta.nvi as TaEngine["nvi"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
2671
|
+
}
|
|
2672
|
+
if (fname === "ta.pvi" || fname === "pvi") {
|
|
2673
|
+
if (typeof ta.pvi !== "function") return NA;
|
|
2674
|
+
return (ta.pvi as TaEngine["pvi"]).call(env.ta, site, num(env.ctx.close), num(env.ctx.volume));
|
|
2675
|
+
}
|
|
2676
|
+
if (fname === "ta.iii" || fname === "iii") {
|
|
2677
|
+
if (typeof ta.iii !== "function") return NA;
|
|
2678
|
+
return (ta.iii as TaEngine["iii"]).call(
|
|
2679
|
+
env.ta,
|
|
2680
|
+
site,
|
|
2681
|
+
num(env.ctx.high),
|
|
2682
|
+
num(env.ctx.low),
|
|
2683
|
+
num(env.ctx.close),
|
|
2684
|
+
num(env.ctx.volume),
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
if (fname === "ta.wvad" || fname === "wvad") {
|
|
2688
|
+
if (typeof ta.wvad !== "function") return NA;
|
|
2689
|
+
return (ta.wvad as TaEngine["wvad"]).call(
|
|
2690
|
+
env.ta,
|
|
2691
|
+
site,
|
|
2692
|
+
num(env.ctx.open),
|
|
2693
|
+
num(env.ctx.high),
|
|
2694
|
+
num(env.ctx.low),
|
|
2695
|
+
num(env.ctx.close),
|
|
2696
|
+
num(env.ctx.volume),
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2699
|
+
if (fname === "ta.pivot_point_levels") {
|
|
2700
|
+
if (typeof ta.pivotPoints !== "function") return NA;
|
|
2701
|
+
const r = (ta.pivotPoints as TaEngine["pivotPoints"]).call(
|
|
2702
|
+
env.ta,
|
|
2703
|
+
site,
|
|
2704
|
+
num(env.ctx.high),
|
|
2705
|
+
num(env.ctx.low),
|
|
2706
|
+
num(env.ctx.close),
|
|
2707
|
+
);
|
|
2708
|
+
return tupleOf([r.pp, r.r1, r.s1, r.r2, r.s2]);
|
|
2709
|
+
}
|
|
2710
|
+
return undefined;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
function hlc3(env: Env): Cell {
|
|
2714
|
+
const h = num(env.ctx.high);
|
|
2715
|
+
const l = num(env.ctx.low);
|
|
2716
|
+
const c = num(env.ctx.close);
|
|
2717
|
+
if (h == null || l == null || c == null) return c ?? h ?? l;
|
|
2718
|
+
return (h + l + c) / 3;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
function asArray(value: Value): PineArray | null {
|
|
2722
|
+
if (value instanceof PineArray) return value;
|
|
2723
|
+
if (value && typeof value === "object" && "__array" in value) {
|
|
2724
|
+
const elts = (value as ArrayVal).elts;
|
|
2725
|
+
const arr = new PineArray();
|
|
2726
|
+
for (const e of elts) arr.push(unwrap(e));
|
|
2727
|
+
return arr;
|
|
2728
|
+
}
|
|
2729
|
+
return null;
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
function evalArrayCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
2733
|
+
if (
|
|
2734
|
+
fname === "array.new_float" ||
|
|
2735
|
+
fname === "array.new" ||
|
|
2736
|
+
fname === "array.new_int"
|
|
2737
|
+
) {
|
|
2738
|
+
const size = unwrap(evalExpr(callArg(node.args, 0, ["size"]), env)) ?? 0;
|
|
2739
|
+
const initial = unwrap(evalExpr(callArg(node.args, 1, ["initial_value"]), env));
|
|
2740
|
+
return new PineArray(size, initial);
|
|
2741
|
+
}
|
|
2742
|
+
if (fname === "array.push") {
|
|
2743
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2744
|
+
const v = unwrap(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
2745
|
+
arr?.push(v);
|
|
2746
|
+
return NA;
|
|
2747
|
+
}
|
|
2748
|
+
if (fname === "array.get") {
|
|
2749
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2750
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2751
|
+
return arr ? arr.get(i ?? 0) : NA;
|
|
2752
|
+
}
|
|
2753
|
+
if (fname === "array.set") {
|
|
2754
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2755
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2756
|
+
const v = unwrap(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
2757
|
+
if (arr && i != null) arr.set(i, v);
|
|
2758
|
+
return NA;
|
|
2759
|
+
}
|
|
2760
|
+
if (fname === "array.size") {
|
|
2761
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2762
|
+
return arr ? arr.size() : NA;
|
|
2763
|
+
}
|
|
2764
|
+
if (fname === "array.pop") {
|
|
2765
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2766
|
+
return arr ? arr.pop() : NA;
|
|
2767
|
+
}
|
|
2768
|
+
if (fname === "array.clear") {
|
|
2769
|
+
asArray(evalExpr(callArg(node.args, 0, ["id"]), env))?.clear();
|
|
2770
|
+
return NA;
|
|
2771
|
+
}
|
|
2772
|
+
if (fname === "array.unshift") {
|
|
2773
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2774
|
+
arr?.unshift(unwrap(evalExpr(callArg(node.args, 1, ["value"]), env)));
|
|
2775
|
+
return NA;
|
|
2776
|
+
}
|
|
2777
|
+
if (fname === "array.shift") {
|
|
2778
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2779
|
+
return arr ? arr.shift() : NA;
|
|
2780
|
+
}
|
|
2781
|
+
if (fname === "array.includes") {
|
|
2782
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2783
|
+
return arr?.includes(unwrap(evalExpr(callArg(node.args, 1, ["value"]), env))) ? 1 : 0;
|
|
2784
|
+
}
|
|
2785
|
+
if (fname === "array.first") {
|
|
2786
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2787
|
+
return arr ? arr.first() : NA;
|
|
2788
|
+
}
|
|
2789
|
+
if (fname === "array.last") {
|
|
2790
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2791
|
+
return arr ? arr.last() : NA;
|
|
2792
|
+
}
|
|
2793
|
+
if (fname === "array.insert") {
|
|
2794
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2795
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2796
|
+
const v = unwrap(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
2797
|
+
if (arr && i != null) arr.insert(i, v);
|
|
2798
|
+
return NA;
|
|
2799
|
+
}
|
|
2800
|
+
if (fname === "array.remove") {
|
|
2801
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2802
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["index"]), env));
|
|
2803
|
+
return arr && i != null ? arr.remove(i) : NA;
|
|
2804
|
+
}
|
|
2805
|
+
if (fname === "array.fill") {
|
|
2806
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2807
|
+
arr?.fill(unwrap(evalExpr(callArg(node.args, 1, ["value"]), env)));
|
|
2808
|
+
return NA;
|
|
2809
|
+
}
|
|
2810
|
+
if (fname === "array.slice") {
|
|
2811
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2812
|
+
const from = unwrap(evalExpr(callArg(node.args, 1, ["index_from", "from"]), env)) ?? 0;
|
|
2813
|
+
const toArg = callArg(node.args, 2, ["index_to", "to"]);
|
|
2814
|
+
const to = toArg == null ? undefined : unwrap(evalExpr(toArg, env)) ?? undefined;
|
|
2815
|
+
return arr ? arr.slice(from, to) : NA;
|
|
2816
|
+
}
|
|
2817
|
+
if (fname === "array.copy") {
|
|
2818
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2819
|
+
return arr ? arr.copy() : NA;
|
|
2820
|
+
}
|
|
2821
|
+
if (fname === "array.reverse") {
|
|
2822
|
+
asArray(evalExpr(callArg(node.args, 0, ["id"]), env))?.reverse();
|
|
2823
|
+
return NA;
|
|
2824
|
+
}
|
|
2825
|
+
if (fname === "array.sort") {
|
|
2826
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2827
|
+
const orderRaw = evalExpr(callArg(node.args, 1, ["order"]), env);
|
|
2828
|
+
const order = typeof orderRaw === "string" && orderRaw.toLowerCase().includes("desc") ? "desc" : "asc";
|
|
2829
|
+
arr?.sort(order);
|
|
2830
|
+
return NA;
|
|
2831
|
+
}
|
|
2832
|
+
if (fname === "array.indexof") {
|
|
2833
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2834
|
+
return arr ? arr.indexof(unwrap(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
2835
|
+
}
|
|
2836
|
+
if (fname === "array.avg") {
|
|
2837
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2838
|
+
return arr ? arr.avg() : NA;
|
|
2839
|
+
}
|
|
2840
|
+
if (fname === "array.min") {
|
|
2841
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2842
|
+
return arr ? arr.min() : NA;
|
|
2843
|
+
}
|
|
2844
|
+
if (fname === "array.max") {
|
|
2845
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2846
|
+
return arr ? arr.max() : NA;
|
|
2847
|
+
}
|
|
2848
|
+
if (fname === "array.sum") {
|
|
2849
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2850
|
+
return arr ? arr.sum() : NA;
|
|
2851
|
+
}
|
|
2852
|
+
if (fname === "array.join") {
|
|
2853
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2854
|
+
const sep = evalAsString(callArg(node.args, 1, ["separator", "sep"]), env);
|
|
2855
|
+
return arr ? arr.join(sep ?? ",") : NA;
|
|
2856
|
+
}
|
|
2857
|
+
if (fname === "array.new_bool" || fname === "array.new_string") {
|
|
2858
|
+
const size = unwrap(evalExpr(callArg(node.args, 0, ["size"]), env)) ?? 0;
|
|
2859
|
+
const initial = unwrap(evalExpr(callArg(node.args, 1, ["initial_value"]), env));
|
|
2860
|
+
return new PineArray(size, initial);
|
|
2861
|
+
}
|
|
2862
|
+
if (fname === "array.from") {
|
|
2863
|
+
const arr = new PineArray();
|
|
2864
|
+
for (const a of asArgs(node.args)) arr.push(unwrap(evalExpr(a.value, env)));
|
|
2865
|
+
return arr;
|
|
2866
|
+
}
|
|
2867
|
+
if (fname === "array.lastindexof") {
|
|
2868
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2869
|
+
return arr ? arr.lastIndexOf(unwrap(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
2870
|
+
}
|
|
2871
|
+
if (fname === "array.concat") {
|
|
2872
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2873
|
+
const other = asArray(evalExpr(callArg(node.args, 1, ["other", "id2"]), env));
|
|
2874
|
+
return arr && other ? arr.concat(other) : NA;
|
|
2875
|
+
}
|
|
2876
|
+
if (fname === "array.abs") {
|
|
2877
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2878
|
+
return arr ? arr.abs() : NA;
|
|
2879
|
+
}
|
|
2880
|
+
if (fname === "array.every") {
|
|
2881
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2882
|
+
return arr && arr.every() ? 1 : 0;
|
|
2883
|
+
}
|
|
2884
|
+
if (fname === "array.some") {
|
|
2885
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2886
|
+
return arr && arr.some() ? 1 : 0;
|
|
2887
|
+
}
|
|
2888
|
+
if (fname === "array.median") {
|
|
2889
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2890
|
+
return arr ? arr.median() : NA;
|
|
2891
|
+
}
|
|
2892
|
+
if (fname === "array.range") {
|
|
2893
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2894
|
+
return arr ? arr.range() : NA;
|
|
2895
|
+
}
|
|
2896
|
+
if (fname === "array.mode") {
|
|
2897
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2898
|
+
return arr ? arr.mode() : NA;
|
|
2899
|
+
}
|
|
2900
|
+
if (fname === "array.binary_search") {
|
|
2901
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2902
|
+
return arr ? arr.binarySearch(unwrap(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
2903
|
+
}
|
|
2904
|
+
if (fname === "array.binary_search_leftmost") {
|
|
2905
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2906
|
+
return arr ? arr.binarySearchLeftmost(unwrap(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
2907
|
+
}
|
|
2908
|
+
if (fname === "array.binary_search_rightmost") {
|
|
2909
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2910
|
+
return arr ? arr.binarySearchRightmost(unwrap(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
2911
|
+
}
|
|
2912
|
+
if (fname === "array.stdev") {
|
|
2913
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2914
|
+
const biased = biasedArg(node, env);
|
|
2915
|
+
return arr ? arr.stdev(biased) : NA;
|
|
2916
|
+
}
|
|
2917
|
+
if (fname === "array.variance") {
|
|
2918
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2919
|
+
return arr ? arr.variance(biasedArg(node, env)) : NA;
|
|
2920
|
+
}
|
|
2921
|
+
if (fname === "array.covariance") {
|
|
2922
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2923
|
+
const other = asArray(evalExpr(callArg(node.args, 1, ["id2", "other"]), env));
|
|
2924
|
+
return arr && other ? arr.covariance(other, biasedArg(node, env, 2)) : NA;
|
|
2925
|
+
}
|
|
2926
|
+
if (fname === "array.percentile_linear_interpolation") {
|
|
2927
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2928
|
+
const p = unwrap(evalExpr(callArg(node.args, 1, ["percentage", "percent"]), env));
|
|
2929
|
+
return arr && p != null ? arr.percentileLinearInterpolation(p) : NA;
|
|
2930
|
+
}
|
|
2931
|
+
if (fname === "array.percentile_nearest_rank") {
|
|
2932
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2933
|
+
const p = unwrap(evalExpr(callArg(node.args, 1, ["percentage", "percent"]), env));
|
|
2934
|
+
return arr && p != null ? arr.percentileNearestRank(p) : NA;
|
|
2935
|
+
}
|
|
2936
|
+
if (fname === "array.percentrank") {
|
|
2937
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2938
|
+
return arr ? arr.percentrank(unwrap(evalExpr(callArg(node.args, 1, ["value"]), env))) : NA;
|
|
2939
|
+
}
|
|
2940
|
+
if (fname === "array.standardize") {
|
|
2941
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2942
|
+
return arr ? arr.standardize() : NA;
|
|
2943
|
+
}
|
|
2944
|
+
if (fname === "array.sort_indices") {
|
|
2945
|
+
const arr = asArray(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2946
|
+
const orderRaw = evalExpr(callArg(node.args, 1, ["order"]), env);
|
|
2947
|
+
const order = typeof orderRaw === "string" && orderRaw.toLowerCase().includes("desc") ? "desc" : "asc";
|
|
2948
|
+
if (!arr) return NA;
|
|
2949
|
+
const idx = arr.sortIndices(order);
|
|
2950
|
+
if (idx == null) return NA;
|
|
2951
|
+
const out = new PineArray();
|
|
2952
|
+
for (const i of idx) out.push(i);
|
|
2953
|
+
return out;
|
|
2954
|
+
}
|
|
2955
|
+
return undefined;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
function biasedArg(node: Call, env: Env, pos = 1): boolean {
|
|
2959
|
+
const raw = unwrap(evalExpr(callArg(node.args, pos, ["biased"]), env));
|
|
2960
|
+
if (raw == null) return true;
|
|
2961
|
+
return raw !== 0;
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
function asMap(value: Value): PineMap | null {
|
|
2965
|
+
return value instanceof PineMap ? value : null;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
function evalMapCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
2969
|
+
if (fname === "map.new" || fname === "map.new<float,float>" || fname === "map.new_string_float") {
|
|
2970
|
+
return new PineMap();
|
|
2971
|
+
}
|
|
2972
|
+
if (fname === "map.put") {
|
|
2973
|
+
const m = asMap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2974
|
+
const key = evalExpr(callArg(node.args, 1, ["key"]), env);
|
|
2975
|
+
const val = unwrap(evalExpr(callArg(node.args, 2, ["value"]), env));
|
|
2976
|
+
m?.put(typeof key === "string" || typeof key === "number" ? key : unwrap(key), val);
|
|
2977
|
+
return NA;
|
|
2978
|
+
}
|
|
2979
|
+
if (fname === "map.get") {
|
|
2980
|
+
const m = asMap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2981
|
+
const key = evalExpr(callArg(node.args, 1, ["key"]), env);
|
|
2982
|
+
return m ? m.get(typeof key === "string" || typeof key === "number" ? key : unwrap(key)) : NA;
|
|
2983
|
+
}
|
|
2984
|
+
if (fname === "map.contains") {
|
|
2985
|
+
const m = asMap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2986
|
+
const key = evalExpr(callArg(node.args, 1, ["key"]), env);
|
|
2987
|
+
const ok = m?.contains(typeof key === "string" || typeof key === "number" ? key : unwrap(key));
|
|
2988
|
+
return ok ? 1 : 0;
|
|
2989
|
+
}
|
|
2990
|
+
if (fname === "map.size") {
|
|
2991
|
+
const m = asMap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2992
|
+
return m ? m.size() : NA;
|
|
2993
|
+
}
|
|
2994
|
+
if (fname === "map.remove") {
|
|
2995
|
+
const m = asMap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
2996
|
+
const key = evalExpr(callArg(node.args, 1, ["key"]), env);
|
|
2997
|
+
return m ? m.remove(typeof key === "string" || typeof key === "number" ? key : unwrap(key)) : NA;
|
|
2998
|
+
}
|
|
2999
|
+
if (fname === "map.clear") {
|
|
3000
|
+
asMap(evalExpr(callArg(node.args, 0, ["id"]), env))?.clear();
|
|
3001
|
+
return NA;
|
|
3002
|
+
}
|
|
3003
|
+
if (fname === "map.put_all") {
|
|
3004
|
+
const m = asMap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3005
|
+
const other = asMap(evalExpr(callArg(node.args, 1, ["id2", "other"]), env));
|
|
3006
|
+
if (m && other) m.putAll(other);
|
|
3007
|
+
return NA;
|
|
3008
|
+
}
|
|
3009
|
+
if (fname === "map.keys") {
|
|
3010
|
+
const m = asMap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3011
|
+
if (!m) return NA;
|
|
3012
|
+
const arr = new PineArray();
|
|
3013
|
+
for (const k of m.keys()) arr.push(typeof k === "number" ? k : null);
|
|
3014
|
+
return arr;
|
|
3015
|
+
}
|
|
3016
|
+
if (fname === "map.values") {
|
|
3017
|
+
const m = asMap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3018
|
+
if (!m) return NA;
|
|
3019
|
+
const arr = new PineArray();
|
|
3020
|
+
for (const v of m.values()) arr.push(v);
|
|
3021
|
+
return arr;
|
|
3022
|
+
}
|
|
3023
|
+
if (fname === "map.copy") {
|
|
3024
|
+
const m = asMap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3025
|
+
return m ? m.copy() : NA;
|
|
3026
|
+
}
|
|
3027
|
+
return undefined;
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
function matrixBuiltin(fname: string | null): string | null {
|
|
3031
|
+
if (fname == null || !fname.startsWith("matrix.")) return null;
|
|
3032
|
+
const lt = fname.indexOf("<");
|
|
3033
|
+
return lt >= 0 ? fname.slice(0, lt) : fname;
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
function asMatrix(value: Value): PineMatrix | null {
|
|
3037
|
+
return value instanceof PineMatrix ? value : null;
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
function isMatrixNew(name: string): boolean {
|
|
3041
|
+
return name === "matrix.new" || name.startsWith("matrix.new_");
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
function evalMatrixCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
3045
|
+
const name = matrixBuiltin(fname);
|
|
3046
|
+
if (name == null) return undefined;
|
|
3047
|
+
if (isMatrixNew(name)) {
|
|
3048
|
+
const rows = unwrap(evalExpr(callArg(node.args, 0, ["rows"]), env)) ?? 0;
|
|
3049
|
+
const cols = unwrap(evalExpr(callArg(node.args, 1, ["columns", "cols"]), env)) ?? 0;
|
|
3050
|
+
const initialArg = callArg(node.args, 2, ["initial_value", "initial"]);
|
|
3051
|
+
const initial = initialArg == null ? undefined : unwrap(evalExpr(initialArg, env));
|
|
3052
|
+
return new PineMatrix(rows, cols, initial);
|
|
3053
|
+
}
|
|
3054
|
+
if (name === "matrix.get") {
|
|
3055
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3056
|
+
const row = unwrap(evalExpr(callArg(node.args, 1, ["row"]), env));
|
|
3057
|
+
const col = unwrap(evalExpr(callArg(node.args, 2, ["column", "col"]), env));
|
|
3058
|
+
return m ? m.get(row ?? 0, col ?? 0) : NA;
|
|
3059
|
+
}
|
|
3060
|
+
if (name === "matrix.set") {
|
|
3061
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3062
|
+
const row = unwrap(evalExpr(callArg(node.args, 1, ["row"]), env));
|
|
3063
|
+
const col = unwrap(evalExpr(callArg(node.args, 2, ["column", "col"]), env));
|
|
3064
|
+
const v = unwrap(evalExpr(callArg(node.args, 3, ["value"]), env));
|
|
3065
|
+
if (m && row != null && col != null) m.set(row, col, v);
|
|
3066
|
+
return NA;
|
|
3067
|
+
}
|
|
3068
|
+
if (name === "matrix.rows") {
|
|
3069
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3070
|
+
return m ? m.rows() : NA;
|
|
3071
|
+
}
|
|
3072
|
+
if (name === "matrix.columns") {
|
|
3073
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3074
|
+
return m ? m.columns() : NA;
|
|
3075
|
+
}
|
|
3076
|
+
if (name === "matrix.fill") {
|
|
3077
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3078
|
+
const v = unwrap(evalExpr(callArg(node.args, 1, ["value"]), env));
|
|
3079
|
+
m?.fill(v);
|
|
3080
|
+
return NA;
|
|
3081
|
+
}
|
|
3082
|
+
if (name === "matrix.transpose") {
|
|
3083
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3084
|
+
return m ? m.transpose() : NA;
|
|
3085
|
+
}
|
|
3086
|
+
if (name === "matrix.copy") {
|
|
3087
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3088
|
+
return m ? m.copy() : NA;
|
|
3089
|
+
}
|
|
3090
|
+
if (name === "matrix.elements_count") {
|
|
3091
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3092
|
+
return m ? m.elementsCount() : NA;
|
|
3093
|
+
}
|
|
3094
|
+
if (name === "matrix.is_square") {
|
|
3095
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3096
|
+
return m && m.isSquare() ? 1 : 0;
|
|
3097
|
+
}
|
|
3098
|
+
if (name === "matrix.sum" || name === "matrix.sum_all") {
|
|
3099
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3100
|
+
return m ? m.sum() : NA;
|
|
3101
|
+
}
|
|
3102
|
+
if (name === "matrix.avg" || name === "matrix.avg_all") {
|
|
3103
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3104
|
+
return m ? m.avg() : NA;
|
|
3105
|
+
}
|
|
3106
|
+
if (name === "matrix.min" || name === "matrix.min_all") {
|
|
3107
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3108
|
+
return m ? m.min() : NA;
|
|
3109
|
+
}
|
|
3110
|
+
if (name === "matrix.max" || name === "matrix.max_all") {
|
|
3111
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3112
|
+
return m ? m.max() : NA;
|
|
3113
|
+
}
|
|
3114
|
+
if (name === "matrix.trace") {
|
|
3115
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3116
|
+
return m ? m.trace() : NA;
|
|
3117
|
+
}
|
|
3118
|
+
if (name === "matrix.det") {
|
|
3119
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3120
|
+
return m ? m.det() : NA;
|
|
3121
|
+
}
|
|
3122
|
+
if (name === "matrix.row") {
|
|
3123
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3124
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["row", "index"]), env));
|
|
3125
|
+
if (!m || i == null) return NA;
|
|
3126
|
+
const arr = new PineArray();
|
|
3127
|
+
for (const v of m.row(i)) arr.push(v);
|
|
3128
|
+
return arr;
|
|
3129
|
+
}
|
|
3130
|
+
if (name === "matrix.col" || name === "matrix.column") {
|
|
3131
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3132
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["column", "col", "index"]), env));
|
|
3133
|
+
if (!m || i == null) return NA;
|
|
3134
|
+
const arr = new PineArray();
|
|
3135
|
+
for (const v of m.col(i)) arr.push(v);
|
|
3136
|
+
return arr;
|
|
3137
|
+
}
|
|
3138
|
+
if (name === "matrix.mult") {
|
|
3139
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3140
|
+
const otherVal = evalExpr(callArg(node.args, 1, ["other", "matrix_2"]), env);
|
|
3141
|
+
if (!m) return NA;
|
|
3142
|
+
const otherM = asMatrix(otherVal);
|
|
3143
|
+
return otherM ? m.mult(otherM) : m.mult(unwrap(otherVal) ?? 0);
|
|
3144
|
+
}
|
|
3145
|
+
if (name === "matrix.inv") {
|
|
3146
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3147
|
+
return m ? m.inv() : NA;
|
|
3148
|
+
}
|
|
3149
|
+
if (name === "matrix.pinv") {
|
|
3150
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3151
|
+
return m ? m.pinv() : NA;
|
|
3152
|
+
}
|
|
3153
|
+
if (name === "matrix.eigenvalues") {
|
|
3154
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3155
|
+
const vals = m?.eigenvalues();
|
|
3156
|
+
if (vals == null) return NA;
|
|
3157
|
+
const arr = new PineArray();
|
|
3158
|
+
for (const v of vals) arr.push(v);
|
|
3159
|
+
return arr;
|
|
3160
|
+
}
|
|
3161
|
+
if (name === "matrix.eigenvectors") {
|
|
3162
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3163
|
+
return m ? m.eigenvectors() : NA;
|
|
3164
|
+
}
|
|
3165
|
+
if (name === "matrix.pow") {
|
|
3166
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3167
|
+
const p = unwrap(evalExpr(callArg(node.args, 1, ["power", "n"]), env));
|
|
3168
|
+
return m && p != null ? m.pow(p) : NA;
|
|
3169
|
+
}
|
|
3170
|
+
if (name === "matrix.kron") {
|
|
3171
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3172
|
+
const other = asMatrix(evalExpr(callArg(node.args, 1, ["other"]), env));
|
|
3173
|
+
return m && other ? m.kron(other) : NA;
|
|
3174
|
+
}
|
|
3175
|
+
if (name === "matrix.diff") {
|
|
3176
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3177
|
+
const other = asMatrix(evalExpr(callArg(node.args, 1, ["other"]), env));
|
|
3178
|
+
return m && other ? m.diff(other) : NA;
|
|
3179
|
+
}
|
|
3180
|
+
if (name === "matrix.rank") {
|
|
3181
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3182
|
+
return m ? m.rank() : NA;
|
|
3183
|
+
}
|
|
3184
|
+
if (name === "matrix.swap_rows") {
|
|
3185
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3186
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["row1", "i"]), env));
|
|
3187
|
+
const j = unwrap(evalExpr(callArg(node.args, 2, ["row2", "j"]), env));
|
|
3188
|
+
if (m && i != null && j != null) m.swapRows(i, j);
|
|
3189
|
+
return NA;
|
|
3190
|
+
}
|
|
3191
|
+
if (name === "matrix.swap_columns") {
|
|
3192
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3193
|
+
const i = unwrap(evalExpr(callArg(node.args, 1, ["column1", "i"]), env));
|
|
3194
|
+
const j = unwrap(evalExpr(callArg(node.args, 2, ["column2", "j"]), env));
|
|
3195
|
+
if (m && i != null && j != null) m.swapColumns(i, j);
|
|
3196
|
+
return NA;
|
|
3197
|
+
}
|
|
3198
|
+
if (name === "matrix.is_zero") {
|
|
3199
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3200
|
+
return m && m.isZero() ? 1 : 0;
|
|
3201
|
+
}
|
|
3202
|
+
if (name === "matrix.is_identity") {
|
|
3203
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3204
|
+
return m && m.isIdentity() ? 1 : 0;
|
|
3205
|
+
}
|
|
3206
|
+
if (name === "matrix.is_diagonal") {
|
|
3207
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3208
|
+
return m && m.isDiagonal() ? 1 : 0;
|
|
3209
|
+
}
|
|
3210
|
+
if (name === "matrix.is_symmetric") {
|
|
3211
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3212
|
+
return m && m.isSymmetric() ? 1 : 0;
|
|
3213
|
+
}
|
|
3214
|
+
if (name === "matrix.is_antisymmetric") {
|
|
3215
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3216
|
+
return m && m.isAntisymmetric() ? 1 : 0;
|
|
3217
|
+
}
|
|
3218
|
+
if (name === "matrix.is_triangular") {
|
|
3219
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3220
|
+
return m && m.isTriangular() ? 1 : 0;
|
|
3221
|
+
}
|
|
3222
|
+
if (name === "matrix.add_row") {
|
|
3223
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3224
|
+
const idx = unwrap(evalExpr(callArg(node.args, 1, ["row", "index"]), env));
|
|
3225
|
+
const arr = asArray(evalExpr(callArg(node.args, 2, ["array"]), env));
|
|
3226
|
+
m?.addRow(idx ?? undefined, arr?.toValues());
|
|
3227
|
+
return NA;
|
|
3228
|
+
}
|
|
3229
|
+
if (name === "matrix.add_col" || name === "matrix.add_column") {
|
|
3230
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3231
|
+
const idx = unwrap(evalExpr(callArg(node.args, 1, ["column", "index"]), env));
|
|
3232
|
+
const arr = asArray(evalExpr(callArg(node.args, 2, ["array"]), env));
|
|
3233
|
+
m?.addCol(idx ?? undefined, arr?.toValues());
|
|
3234
|
+
return NA;
|
|
3235
|
+
}
|
|
3236
|
+
if (name === "matrix.remove_row") {
|
|
3237
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3238
|
+
const idx = unwrap(evalExpr(callArg(node.args, 1, ["row", "index"]), env));
|
|
3239
|
+
if (m && idx != null) m.removeRow(idx);
|
|
3240
|
+
return NA;
|
|
3241
|
+
}
|
|
3242
|
+
if (name === "matrix.remove_col" || name === "matrix.remove_column") {
|
|
3243
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3244
|
+
const idx = unwrap(evalExpr(callArg(node.args, 1, ["column", "index"]), env));
|
|
3245
|
+
if (m && idx != null) m.removeCol(idx);
|
|
3246
|
+
return NA;
|
|
3247
|
+
}
|
|
3248
|
+
if (name === "matrix.reshape") {
|
|
3249
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3250
|
+
const r = unwrap(evalExpr(callArg(node.args, 1, ["rows"]), env));
|
|
3251
|
+
const c = unwrap(evalExpr(callArg(node.args, 2, ["columns", "cols"]), env));
|
|
3252
|
+
return m && r != null && c != null ? m.reshape(r, c) : NA;
|
|
3253
|
+
}
|
|
3254
|
+
if (name === "matrix.concat") {
|
|
3255
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3256
|
+
const other = asMatrix(evalExpr(callArg(node.args, 1, ["other"]), env));
|
|
3257
|
+
return m && other ? m.concat(other) : NA;
|
|
3258
|
+
}
|
|
3259
|
+
if (name === "matrix.submatrix") {
|
|
3260
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3261
|
+
if (!m) return NA;
|
|
3262
|
+
return m.submatrix(
|
|
3263
|
+
unwrap(evalExpr(callArg(node.args, 1, ["from_row"]), env)) ?? 0,
|
|
3264
|
+
unwrap(evalExpr(callArg(node.args, 2, ["to_row"]), env)) ?? m.rows(),
|
|
3265
|
+
unwrap(evalExpr(callArg(node.args, 3, ["from_column", "from_col"]), env)) ?? 0,
|
|
3266
|
+
unwrap(evalExpr(callArg(node.args, 4, ["to_column", "to_col"]), env)) ?? m.columns(),
|
|
3267
|
+
);
|
|
3268
|
+
}
|
|
3269
|
+
if (name === "matrix.reverse") {
|
|
3270
|
+
asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env))?.reverse();
|
|
3271
|
+
return NA;
|
|
3272
|
+
}
|
|
3273
|
+
if (name === "matrix.sort") {
|
|
3274
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3275
|
+
const col = unwrap(evalExpr(callArg(node.args, 1, ["column", "col"]), env)) ?? 0;
|
|
3276
|
+
const orderRaw = evalExpr(callArg(node.args, 2, ["order"]), env);
|
|
3277
|
+
const order = typeof orderRaw === "string" && orderRaw.toLowerCase().includes("desc") ? "desc" : "asc";
|
|
3278
|
+
m?.sort(col, order);
|
|
3279
|
+
return NA;
|
|
3280
|
+
}
|
|
3281
|
+
if (name === "matrix.median") {
|
|
3282
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3283
|
+
return m ? m.median() : NA;
|
|
3284
|
+
}
|
|
3285
|
+
if (name === "matrix.mode") {
|
|
3286
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3287
|
+
return m ? m.mode() : NA;
|
|
3288
|
+
}
|
|
3289
|
+
if (name === "matrix.is_binary") {
|
|
3290
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3291
|
+
return m && m.isBinary() ? 1 : 0;
|
|
3292
|
+
}
|
|
3293
|
+
if (name === "matrix.is_stochastic") {
|
|
3294
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3295
|
+
return m && m.isStochastic() ? 1 : 0;
|
|
3296
|
+
}
|
|
3297
|
+
if (name === "matrix.is_antidiagonal") {
|
|
3298
|
+
const m = asMatrix(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3299
|
+
return m && m.isAntidiagonal() ? 1 : 0;
|
|
3300
|
+
}
|
|
3301
|
+
return undefined;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
function evalDrawingCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
3305
|
+
if (fname === "line.new") {
|
|
3306
|
+
return env.drawings.lineNew(env.barIndex);
|
|
3307
|
+
}
|
|
3308
|
+
if (fname === "label.new") {
|
|
3309
|
+
const text = evalAsString(callArg(node.args, 2, ["text"]), env);
|
|
3310
|
+
return env.drawings.labelNew(env.barIndex, text ?? undefined);
|
|
3311
|
+
}
|
|
3312
|
+
if (fname === "box.new") {
|
|
3313
|
+
return env.drawings.boxNew(env.barIndex);
|
|
3314
|
+
}
|
|
3315
|
+
if (fname === "alert") {
|
|
3316
|
+
const msg = evalAlertMessage(callArg(node.args, 0, ["message"]), env);
|
|
3317
|
+
env.drawings.alert(env.barIndex, msg);
|
|
3318
|
+
return NA;
|
|
3319
|
+
}
|
|
3320
|
+
if (fname === "table.new") return env.drawings.tableNew(env.barIndex);
|
|
3321
|
+
if (fname === "polyline.new") return env.drawings.polylineNew(env.barIndex);
|
|
3322
|
+
if (fname === "linefill.new") {
|
|
3323
|
+
const a = unwrap(evalExpr(callArg(node.args, 0, ["id1"]), env));
|
|
3324
|
+
const b = unwrap(evalExpr(callArg(node.args, 1, ["id2"]), env));
|
|
3325
|
+
return env.drawings.linefillNew(
|
|
3326
|
+
env.barIndex,
|
|
3327
|
+
a == null ? undefined : a,
|
|
3328
|
+
b == null ? undefined : b,
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
if (fname === "line.set_xy") {
|
|
3332
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3333
|
+
if (id != null) {
|
|
3334
|
+
env.drawings.lineSetXy(
|
|
3335
|
+
id,
|
|
3336
|
+
unwrap(evalExpr(callArg(node.args, 1, ["x1"]), env)) ?? 0,
|
|
3337
|
+
unwrap(evalExpr(callArg(node.args, 2, ["y1"]), env)) ?? 0,
|
|
3338
|
+
unwrap(evalExpr(callArg(node.args, 3, ["x2"]), env)) ?? 0,
|
|
3339
|
+
unwrap(evalExpr(callArg(node.args, 4, ["y2"]), env)) ?? 0,
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3342
|
+
return NA;
|
|
3343
|
+
}
|
|
3344
|
+
if (fname === "label.set_text") {
|
|
3345
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3346
|
+
if (id != null) env.drawings.labelSetText(id, evalAsString(callArg(node.args, 1, ["text"]), env) ?? "");
|
|
3347
|
+
return NA;
|
|
3348
|
+
}
|
|
3349
|
+
if (fname === "line.delete" || fname === "label.delete" || fname === "box.delete") {
|
|
3350
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3351
|
+
if (id != null) {
|
|
3352
|
+
if (fname === "line.delete") env.drawings.lineDelete(id);
|
|
3353
|
+
else if (fname === "label.delete") env.drawings.labelDelete(id);
|
|
3354
|
+
else env.drawings.boxDelete(id);
|
|
3355
|
+
}
|
|
3356
|
+
return NA;
|
|
3357
|
+
}
|
|
3358
|
+
if (fname === "line.set_color") {
|
|
3359
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3360
|
+
if (id != null) env.drawings.lineSetColor(id, evalAsString(callArg(node.args, 1, ["color"]), env) ?? "");
|
|
3361
|
+
return NA;
|
|
3362
|
+
}
|
|
3363
|
+
if (fname === "line.get_price") {
|
|
3364
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3365
|
+
const x = unwrap(evalExpr(callArg(node.args, 1, ["x"]), env));
|
|
3366
|
+
return id != null && x != null ? env.drawings.lineGetPrice(id, x) : NA;
|
|
3367
|
+
}
|
|
3368
|
+
if (fname === "line.get_x1") {
|
|
3369
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3370
|
+
return id != null ? env.drawings.getX1(id) : NA;
|
|
3371
|
+
}
|
|
3372
|
+
if (fname === "line.get_y1") {
|
|
3373
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3374
|
+
return id != null ? env.drawings.getY1(id) : NA;
|
|
3375
|
+
}
|
|
3376
|
+
if (fname === "line.get_x2") {
|
|
3377
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3378
|
+
return id != null ? env.drawings.getX2(id) : NA;
|
|
3379
|
+
}
|
|
3380
|
+
if (fname === "line.get_y2") {
|
|
3381
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3382
|
+
return id != null ? env.drawings.getY2(id) : NA;
|
|
3383
|
+
}
|
|
3384
|
+
if (fname === "label.get_text") {
|
|
3385
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3386
|
+
return id != null ? env.drawings.labelGetText(id) : NA;
|
|
3387
|
+
}
|
|
3388
|
+
if (fname === "label.set_xy") {
|
|
3389
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3390
|
+
if (id != null) {
|
|
3391
|
+
env.drawings.labelSetXy(
|
|
3392
|
+
id,
|
|
3393
|
+
unwrap(evalExpr(callArg(node.args, 1, ["x"]), env)) ?? 0,
|
|
3394
|
+
unwrap(evalExpr(callArg(node.args, 2, ["y"]), env)) ?? 0,
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
return NA;
|
|
3398
|
+
}
|
|
3399
|
+
if (fname === "box.set_lefttop" || fname === "box.set_corners") {
|
|
3400
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3401
|
+
if (id != null) {
|
|
3402
|
+
env.drawings.boxSetCorners(
|
|
3403
|
+
id,
|
|
3404
|
+
unwrap(evalExpr(callArg(node.args, 1, ["left", "x1"]), env)) ?? 0,
|
|
3405
|
+
unwrap(evalExpr(callArg(node.args, 2, ["top", "y1"]), env)) ?? 0,
|
|
3406
|
+
unwrap(evalExpr(callArg(node.args, 3, ["right", "x2"]), env)) ?? 0,
|
|
3407
|
+
unwrap(evalExpr(callArg(node.args, 4, ["bottom", "y2"]), env)) ?? 0,
|
|
3408
|
+
);
|
|
3409
|
+
}
|
|
3410
|
+
return NA;
|
|
3411
|
+
}
|
|
3412
|
+
if (fname === "label.set_color") {
|
|
3413
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3414
|
+
if (id != null) env.drawings.labelSetColor(id, evalAsString(callArg(node.args, 1, ["color"]), env) ?? "");
|
|
3415
|
+
return NA;
|
|
3416
|
+
}
|
|
3417
|
+
if (fname === "table.cell") {
|
|
3418
|
+
const id = unwrap(evalExpr(callArg(node.args, 0, ["id"]), env));
|
|
3419
|
+
const col = unwrap(evalExpr(callArg(node.args, 1, ["column"]), env));
|
|
3420
|
+
const row = unwrap(evalExpr(callArg(node.args, 2, ["row"]), env));
|
|
3421
|
+
const text = evalAsString(callArg(node.args, 3, ["text"]), env) ?? undefined;
|
|
3422
|
+
if (id != null && col != null && row != null) env.drawings.tableCell(id, col, row, text);
|
|
3423
|
+
return NA;
|
|
3424
|
+
}
|
|
3425
|
+
return undefined;
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
function evalAlertMessage(node: expr | undefined, env: Env): string {
|
|
3429
|
+
if (node == null) return "";
|
|
3430
|
+
const v = evalExpr(node, env);
|
|
3431
|
+
if (typeof v === "string") return v;
|
|
3432
|
+
const cell = unwrap(v);
|
|
3433
|
+
return cell == null ? "" : String(cell);
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
function evalColorCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
3437
|
+
if (fname === "color.r" || fname === "color.g" || fname === "color.b" || fname === "color.t") {
|
|
3438
|
+
const v = evalExpr(callArg(node.args, 0, ["color"]), env);
|
|
3439
|
+
const raw = typeof v === "string" ? v : v && typeof v === "object" && "r" in v ? v : null;
|
|
3440
|
+
if (fname === "color.r") return colorR(raw);
|
|
3441
|
+
if (fname === "color.g") return colorG(raw);
|
|
3442
|
+
if (fname === "color.b") return colorB(raw);
|
|
3443
|
+
return colorT(raw);
|
|
3444
|
+
}
|
|
3445
|
+
if (fname === "color.rgb") {
|
|
3446
|
+
return colorRgb(
|
|
3447
|
+
unwrap(evalExpr(callArg(node.args, 0, ["red", "r"]), env)),
|
|
3448
|
+
unwrap(evalExpr(callArg(node.args, 1, ["green", "g"]), env)),
|
|
3449
|
+
unwrap(evalExpr(callArg(node.args, 2, ["blue", "b"]), env)),
|
|
3450
|
+
unwrap(evalExpr(callArg(node.args, 3, ["transp", "t"]), env)) ?? undefined,
|
|
3451
|
+
);
|
|
3452
|
+
}
|
|
3453
|
+
if (fname === "color.from_gradient") {
|
|
3454
|
+
const c1 = evalExpr(callArg(node.args, 3, ["color1"]), env);
|
|
3455
|
+
const c2 = evalExpr(callArg(node.args, 4, ["color2"]), env);
|
|
3456
|
+
const asColor = (v: Value): Color | null => {
|
|
3457
|
+
if (v && typeof v === "object" && "r" in v && "g" in v) return v as Color;
|
|
3458
|
+
if (typeof v === "string") return parseColor(v);
|
|
3459
|
+
return null;
|
|
3460
|
+
};
|
|
3461
|
+
return colorFromGradient(
|
|
3462
|
+
unwrap(evalExpr(callArg(node.args, 0, ["value"]), env)),
|
|
3463
|
+
unwrap(evalExpr(callArg(node.args, 1, ["bottom_value", "bottom"]), env)),
|
|
3464
|
+
unwrap(evalExpr(callArg(node.args, 2, ["top_value", "top"]), env)),
|
|
3465
|
+
asColor(c1) ?? { r: 0, g: 0, b: 0, a: 255 },
|
|
3466
|
+
asColor(c2) ?? { r: 255, g: 255, b: 255, a: 255 },
|
|
3467
|
+
);
|
|
3468
|
+
}
|
|
3469
|
+
if (fname === "color.new" || fname === "color") {
|
|
3470
|
+
const raw = evalExpr(callArg(node.args, 0, ["color"]), env);
|
|
3471
|
+
const parsed = asColor(raw);
|
|
3472
|
+
if (!parsed) return NA;
|
|
3473
|
+
const t = unwrap(evalExpr(callArg(node.args, 1, ["transp"]), env));
|
|
3474
|
+
return colorNew(parsed.r, parsed.g, parsed.b, t ?? undefined);
|
|
3475
|
+
}
|
|
3476
|
+
return undefined;
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
function evalStrCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
3480
|
+
if (fname === "str.tostring" || fname === "str.tostring") {
|
|
3481
|
+
const v = evalExpr(callArg(node.args, 0, ["value", "source"]), env);
|
|
3482
|
+
if (typeof v === "string") return v;
|
|
3483
|
+
const cell = unwrap(v);
|
|
3484
|
+
return strTostring(cell);
|
|
3485
|
+
}
|
|
3486
|
+
if (fname === "str.length") {
|
|
3487
|
+
const v = evalExpr(callArg(node.args, 0, ["string", "source"]), env);
|
|
3488
|
+
return strLength(typeof v === "string" ? v : null);
|
|
3489
|
+
}
|
|
3490
|
+
if (fname === "str.contains") {
|
|
3491
|
+
const s = evalExpr(callArg(node.args, 0, ["source"]), env);
|
|
3492
|
+
const sub = evalExpr(callArg(node.args, 1, ["str"]), env);
|
|
3493
|
+
const ok = strContains(typeof s === "string" ? s : null, typeof sub === "string" ? sub : null);
|
|
3494
|
+
return ok == null ? NA : ok ? 1 : 0;
|
|
3495
|
+
}
|
|
3496
|
+
if (fname === "str.upper") {
|
|
3497
|
+
const s = evalExpr(callArg(node.args, 0, ["source"]), env);
|
|
3498
|
+
return strUpper(typeof s === "string" ? s : null);
|
|
3499
|
+
}
|
|
3500
|
+
if (fname === "str.lower") {
|
|
3501
|
+
const s = evalExpr(callArg(node.args, 0, ["source"]), env);
|
|
3502
|
+
return strLower(typeof s === "string" ? s : null);
|
|
3503
|
+
}
|
|
3504
|
+
if (fname === "str.replace") {
|
|
3505
|
+
const s = evalExpr(callArg(node.args, 0, ["source"]), env);
|
|
3506
|
+
const target = evalExpr(callArg(node.args, 1, ["target"]), env);
|
|
3507
|
+
const repl = evalExpr(callArg(node.args, 2, ["replacement"]), env);
|
|
3508
|
+
const occ = unwrap(evalExpr(callArg(node.args, 3, ["occurrence"]), env));
|
|
3509
|
+
return strReplace(
|
|
3510
|
+
typeof s === "string" ? s : null,
|
|
3511
|
+
typeof target === "string" ? target : null,
|
|
3512
|
+
typeof repl === "string" ? repl : null,
|
|
3513
|
+
occ,
|
|
3514
|
+
);
|
|
3515
|
+
}
|
|
3516
|
+
if (fname === "str.replace_all") {
|
|
3517
|
+
const s = evalExpr(callArg(node.args, 0, ["source"]), env);
|
|
3518
|
+
const target = evalExpr(callArg(node.args, 1, ["target"]), env);
|
|
3519
|
+
const repl = evalExpr(callArg(node.args, 2, ["replacement"]), env);
|
|
3520
|
+
return strReplaceAll(
|
|
3521
|
+
typeof s === "string" ? s : null,
|
|
3522
|
+
typeof target === "string" ? target : null,
|
|
3523
|
+
typeof repl === "string" ? repl : null,
|
|
3524
|
+
);
|
|
3525
|
+
}
|
|
3526
|
+
if (fname === "str.startswith") {
|
|
3527
|
+
const ok = strStartsWith(evalAsString(callArg(node.args, 0, ["source"]), env), evalAsString(callArg(node.args, 1, ["str"]), env));
|
|
3528
|
+
return ok == null ? NA : ok ? 1 : 0;
|
|
3529
|
+
}
|
|
3530
|
+
if (fname === "str.endswith") {
|
|
3531
|
+
const ok = strEndsWith(evalAsString(callArg(node.args, 0, ["source"]), env), evalAsString(callArg(node.args, 1, ["str"]), env));
|
|
3532
|
+
return ok == null ? NA : ok ? 1 : 0;
|
|
3533
|
+
}
|
|
3534
|
+
if (fname === "str.substring") {
|
|
3535
|
+
const endArg = callArg(node.args, 2, ["end_pos", "end"]);
|
|
3536
|
+
return strSubstring(
|
|
3537
|
+
evalAsString(callArg(node.args, 0, ["source"]), env),
|
|
3538
|
+
unwrap(evalExpr(callArg(node.args, 1, ["begin_pos", "begin"]), env)),
|
|
3539
|
+
endArg == null ? undefined : unwrap(evalExpr(endArg, env)),
|
|
3540
|
+
);
|
|
3541
|
+
}
|
|
3542
|
+
if (fname === "str.repeat") {
|
|
3543
|
+
return strRepeat(
|
|
3544
|
+
evalAsString(callArg(node.args, 0, ["source"]), env),
|
|
3545
|
+
unwrap(evalExpr(callArg(node.args, 1, ["n", "times"]), env)),
|
|
3546
|
+
);
|
|
3547
|
+
}
|
|
3548
|
+
if (fname === "str.trim") {
|
|
3549
|
+
return strTrim(evalAsString(callArg(node.args, 0, ["source"]), env));
|
|
3550
|
+
}
|
|
3551
|
+
if (fname === "str.split") {
|
|
3552
|
+
const parts = strSplit(
|
|
3553
|
+
evalAsString(callArg(node.args, 0, ["source"]), env),
|
|
3554
|
+
evalAsString(callArg(node.args, 1, ["separator", "sep"]), env),
|
|
3555
|
+
);
|
|
3556
|
+
return parts == null ? NA : tupleOf(parts);
|
|
3557
|
+
}
|
|
3558
|
+
if (fname === "str.tonumber" || fname === "tonumber") {
|
|
3559
|
+
return strToNumber(evalAsString(callArg(node.args, 0, ["source", "string"]), env));
|
|
3560
|
+
}
|
|
3561
|
+
if (fname === "tostring") {
|
|
3562
|
+
const v = evalExpr(callArg(node.args, 0, ["value", "source"]), env);
|
|
3563
|
+
return typeof v === "string" ? v : strTostring(unwrap(v));
|
|
3564
|
+
}
|
|
3565
|
+
if (fname === "str.pos") {
|
|
3566
|
+
return strPos(
|
|
3567
|
+
evalAsString(callArg(node.args, 0, ["source"]), env),
|
|
3568
|
+
evalAsString(callArg(node.args, 1, ["str", "substr"]), env),
|
|
3569
|
+
);
|
|
3570
|
+
}
|
|
3571
|
+
if (fname === "str.match") {
|
|
3572
|
+
return strMatch(
|
|
3573
|
+
evalAsString(callArg(node.args, 0, ["source"]), env),
|
|
3574
|
+
evalAsString(callArg(node.args, 1, ["regex", "pattern"]), env),
|
|
3575
|
+
);
|
|
3576
|
+
}
|
|
3577
|
+
if (fname === "str.format_time") {
|
|
3578
|
+
return strFormatTime(
|
|
3579
|
+
unwrap(evalExpr(callArg(node.args, 0, ["time", "timestamp"]), env)) ??
|
|
3580
|
+
evalExpr(callArg(node.args, 0, ["time", "timestamp"]), env),
|
|
3581
|
+
evalAsString(callArg(node.args, 1, ["format"]), env),
|
|
3582
|
+
evalAsString(callArg(node.args, 2, ["timezone"]), env),
|
|
3583
|
+
);
|
|
3584
|
+
}
|
|
3585
|
+
if (fname === "str.format") {
|
|
3586
|
+
const fmt = evalAsString(callArg(node.args, 0, ["formatString", "format"]), env);
|
|
3587
|
+
const args = asArgs(node.args).slice(1).map((a) => {
|
|
3588
|
+
const v = evalExpr(a.value, env);
|
|
3589
|
+
return typeof v === "string" ? v : unwrap(v);
|
|
3590
|
+
});
|
|
3591
|
+
return strFormat(fmt, ...args);
|
|
3592
|
+
}
|
|
3593
|
+
if (fname === "str.join") {
|
|
3594
|
+
const first = evalExpr(callArg(node.args, 0, ["array", "parts"]), env);
|
|
3595
|
+
const sep = evalAsString(callArg(node.args, 1, ["separator", "sep"]), env);
|
|
3596
|
+
const parts = isTuple(first)
|
|
3597
|
+
? first.elts.map((e) => (typeof e === "string" ? e : e == null ? null : String(unwrap(e))))
|
|
3598
|
+
: null;
|
|
3599
|
+
return parts ? strJoin(parts, sep) : NA;
|
|
3600
|
+
}
|
|
3601
|
+
return undefined;
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
function asParams(args: Param | Param[] | null | undefined): Param[] {
|
|
3605
|
+
if (args == null) return [];
|
|
3606
|
+
return Array.isArray(args) ? args : [args];
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
function asStmts(body: stmt | stmt[] | null | undefined): stmt[] {
|
|
3610
|
+
if (body == null) return [];
|
|
3611
|
+
return Array.isArray(body) ? body : [body];
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
function registerUdf(node: FunctionDef, env: Env): void {
|
|
3615
|
+
if (env.barIndex !== 0) return;
|
|
3616
|
+
const def: UdfDef = { __udf: true, params: asParams(node.args), body: asStmts(node.body) };
|
|
3617
|
+
env.udfs.set(node.name, def);
|
|
3618
|
+
if (node.export) env.pendingExports.set(node.name, def);
|
|
3619
|
+
if (node.method) {
|
|
3620
|
+
const first = asParams(node.args)[0];
|
|
3621
|
+
const typeName = typeNameFromExpr(first?.type ?? null);
|
|
3622
|
+
const owner = typeName != null ? ctxGet(env, typeName) : undefined;
|
|
3623
|
+
if (owner instanceof UdtType) {
|
|
3624
|
+
owner.addMethod(node.name, {
|
|
3625
|
+
name: node.name,
|
|
3626
|
+
params: asParams(node.args).map((p) => ({ name: p.name })),
|
|
3627
|
+
body: def,
|
|
3628
|
+
exported: Boolean(node.export),
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
function registerTypeDef(node: TypeDef, env: Env): void {
|
|
3635
|
+
if (env.barIndex !== 0) return;
|
|
3636
|
+
const fields: Array<{ name: string; default: unknown }> = [];
|
|
3637
|
+
for (const s of asStmts(node.body as unknown as stmt[])) {
|
|
3638
|
+
if (s.kind !== "Assign") continue;
|
|
3639
|
+
const a = s as Assign;
|
|
3640
|
+
if (a.target.kind !== "Name") continue;
|
|
3641
|
+
fields.push({
|
|
3642
|
+
name: a.target.id,
|
|
3643
|
+
default: a.value == null ? null : evalExpr(a.value, env),
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
const t = udtTypeFromAssigns(node.name, fields);
|
|
3647
|
+
bindName(env, node.name, t);
|
|
3648
|
+
if (node.export) env.pendingExports.set(node.name, t);
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
function registerEnumDef(node: EnumDef, env: Env): void {
|
|
3652
|
+
if (env.barIndex !== 0) return;
|
|
3653
|
+
const names: string[] = [];
|
|
3654
|
+
for (const s of asStmts(node.body as unknown as stmt[])) {
|
|
3655
|
+
if (s.kind === "Assign") {
|
|
3656
|
+
const t = (s as Assign).target;
|
|
3657
|
+
if (t.kind === "Name") names.push(t.id);
|
|
3658
|
+
} else if (s.kind === "Expr" && (s as { value?: expr }).value?.kind === "Name") {
|
|
3659
|
+
names.push(((s as { value: { id: string } }).value).id);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
const e = enumTypeFromNames(node.name, names);
|
|
3663
|
+
bindName(env, node.name, e);
|
|
3664
|
+
if (node.export) env.pendingExports.set(node.name, e);
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
function pushUdfFrame(env: Env, site: string): void {
|
|
3668
|
+
let state = env.udfSites.get(site);
|
|
3669
|
+
if (!state) {
|
|
3670
|
+
state = { ctx: {}, series: new Map(), varInited: new Set() };
|
|
3671
|
+
env.udfSites.set(site, state);
|
|
3672
|
+
}
|
|
3673
|
+
for (const id of state.varInited) {
|
|
3674
|
+
const s = state.series.get(id);
|
|
3675
|
+
if (s == null) continue;
|
|
3676
|
+
while (s.length < env.barIndex) s.push(s.current);
|
|
3677
|
+
if (s.length === env.barIndex) s.push(s.current);
|
|
3678
|
+
state.ctx[id] = s.current;
|
|
3679
|
+
}
|
|
3680
|
+
env.frames.push(state);
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3683
|
+
function typeNameFromExpr(node: expr | null | undefined): string | null {
|
|
3684
|
+
if (node == null) return null;
|
|
3685
|
+
if (node.kind === "Name") return node.id;
|
|
3686
|
+
if (node.kind === "Attribute") return node.attr;
|
|
3687
|
+
return null;
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
function evalUdf(def: UdfDef, node: Call, env: Env, implicitThis?: Value): Value {
|
|
3691
|
+
const args = asArgs(node.args);
|
|
3692
|
+
const bound = new Set<string>();
|
|
3693
|
+
pushUdfFrame(env, siteKey(node, env));
|
|
3694
|
+
try {
|
|
3695
|
+
let pos = 0;
|
|
3696
|
+
if (implicitThis !== undefined) {
|
|
3697
|
+
const self = def.params[pos++];
|
|
3698
|
+
if (self) {
|
|
3699
|
+
bindName(env, self.name, implicitThis);
|
|
3700
|
+
bound.add(self.name);
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
for (const a of args) {
|
|
3704
|
+
if (argKeyword(a) != null) continue;
|
|
3705
|
+
const p = def.params[pos++];
|
|
3706
|
+
if (p) {
|
|
3707
|
+
bindName(env, p.name, evalExpr(a.value, env));
|
|
3708
|
+
bound.add(p.name);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
for (const a of args) {
|
|
3712
|
+
const kw = argKeyword(a);
|
|
3713
|
+
if (kw == null) continue;
|
|
3714
|
+
if (def.params.some((p) => p.name === kw)) {
|
|
3715
|
+
bindName(env, kw, evalExpr(argValue(a), env));
|
|
3716
|
+
bound.add(kw);
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
for (const p of def.params) {
|
|
3720
|
+
if (!bound.has(p.name) && p.default != null) bindName(env, p.name, evalExpr(p.default, env));
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
let last: Value = NA;
|
|
3724
|
+
for (const s of def.body) {
|
|
3725
|
+
const v = execStmt(s, env);
|
|
3726
|
+
if (s.kind === "Expr" || s.kind === "Assign" || s.kind === "ReAssign") last = v;
|
|
3727
|
+
}
|
|
3728
|
+
return last;
|
|
3729
|
+
} finally {
|
|
3730
|
+
env.frames.pop();
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
function isUdfDef(value: unknown): value is UdfDef {
|
|
3735
|
+
return typeof value === "object" && value !== null && (value as UdfDef).__udf === true;
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
function evalTimeCall(fname: string | null, node: Call, env: Env): Value | undefined {
|
|
3739
|
+
if (fname === "timestamp") {
|
|
3740
|
+
const args = asArgs(node.args).map((a) => {
|
|
3741
|
+
const v = evalExpr(a.value, env);
|
|
3742
|
+
return typeof v === "string" ? v : unwrap(v);
|
|
3743
|
+
});
|
|
3744
|
+
return timestamp(...args);
|
|
3745
|
+
}
|
|
3746
|
+
const part =
|
|
3747
|
+
fname === "year" ||
|
|
3748
|
+
fname === "month" ||
|
|
3749
|
+
fname === "dayofmonth" ||
|
|
3750
|
+
fname === "dayofweek" ||
|
|
3751
|
+
fname === "hour" ||
|
|
3752
|
+
fname === "minute" ||
|
|
3753
|
+
fname === "second"
|
|
3754
|
+
? fname
|
|
3755
|
+
: null;
|
|
3756
|
+
if (part != null) {
|
|
3757
|
+
const ms = calendarMs(node, env);
|
|
3758
|
+
if (ms == null) return NA;
|
|
3759
|
+
const parts = utcPartsFromMs(ms);
|
|
3760
|
+
return parts[part];
|
|
3761
|
+
}
|
|
3762
|
+
if (fname === "weekofyear") {
|
|
3763
|
+
const ms = calendarMs(node, env);
|
|
3764
|
+
return ms == null ? NA : weekOfYear(ms);
|
|
3765
|
+
}
|
|
3766
|
+
if (fname === "time_tradingday") {
|
|
3767
|
+
const ms = calendarMs(node, env);
|
|
3768
|
+
return ms == null ? NA : timeTradingDay(ms);
|
|
3769
|
+
}
|
|
3770
|
+
return undefined;
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
function calendarMs(node: Call, env: Env): number | null {
|
|
3774
|
+
const arg = callArg(node.args, 0, ["time", "timestamp"]);
|
|
3775
|
+
if (arg == null) return num(env.ctx.time);
|
|
3776
|
+
return unwrap(evalExpr(arg, env));
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3779
|
+
function evalLibraryMemberCall(mod: LibraryModule, attr: string, node: Call, env: Env): Value {
|
|
3780
|
+
const exp = mod.get(attr);
|
|
3781
|
+
if (exp === undefined) {
|
|
3782
|
+
const stub = STUB_KNOWN_EXPORTS[attr];
|
|
3783
|
+
if (stub) return callStubExport(stub, node, env);
|
|
3784
|
+
return NA;
|
|
3785
|
+
}
|
|
3786
|
+
if (isUdfDef(exp)) return evalUdf(exp, node, env);
|
|
3787
|
+
if (typeof exp === "function") return callStubExport(exp as (...args: unknown[]) => unknown, node, env);
|
|
3788
|
+
return exp as Value;
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
function callStubExport(fn: (...args: unknown[]) => unknown, node: Call, env: Env): Value {
|
|
3792
|
+
const args: unknown[] = [];
|
|
3793
|
+
const kwargs: StubKwargs = {};
|
|
3794
|
+
for (const a of asArgs(node.args)) {
|
|
3795
|
+
const kw = argKeyword(a);
|
|
3796
|
+
const v = evalExpr(a.value, env);
|
|
3797
|
+
const raw = typeof v === "string" ? v : unwrap(v);
|
|
3798
|
+
if (kw) kwargs[kw] = raw;
|
|
3799
|
+
else args.push(raw);
|
|
3800
|
+
}
|
|
3801
|
+
const result = Object.keys(kwargs).length > 0 ? fn(args, kwargs) : fn(...args);
|
|
3802
|
+
if (result == null) return NA;
|
|
3803
|
+
if (typeof result === "number" && Number.isFinite(result)) return result;
|
|
3804
|
+
if (typeof result === "string") return result;
|
|
3805
|
+
if (Array.isArray(result) && result.every((x) => typeof x === "number" && Number.isFinite(x))) {
|
|
3806
|
+
return tupleOf(result.map((x) => x as number));
|
|
3807
|
+
}
|
|
3808
|
+
return NA;
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
function execImport(node: Import, env: Env): void {
|
|
3812
|
+
if (env.barIndex !== 0) return;
|
|
3813
|
+
const alias = node.alias || node.name;
|
|
3814
|
+
let mod = env.libraries.lookup({
|
|
3815
|
+
namespace: node.namespace,
|
|
3816
|
+
name: node.name,
|
|
3817
|
+
version: node.version,
|
|
3818
|
+
});
|
|
3819
|
+
if (mod == null) {
|
|
3820
|
+
const source = env.libraries.getSource(node.namespace, node.name, node.version);
|
|
3821
|
+
if (source != null) {
|
|
3822
|
+
loadLibrarySource(env, source, node.name);
|
|
3823
|
+
mod = env.libraries.lookup({
|
|
3824
|
+
namespace: node.namespace,
|
|
3825
|
+
name: node.name,
|
|
3826
|
+
version: node.version,
|
|
3827
|
+
});
|
|
3828
|
+
if (mod == null) {
|
|
3829
|
+
mod = env.libraries.lookup({ name: node.name });
|
|
3830
|
+
if (mod != null) {
|
|
3831
|
+
mod.namespace = node.namespace;
|
|
3832
|
+
mod.version = node.version;
|
|
3833
|
+
env.libraries.register(mod);
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
if (mod == null) {
|
|
3839
|
+
const stub = createStubModule(node.name);
|
|
3840
|
+
stub.namespace = node.namespace;
|
|
3841
|
+
stub.version = node.version;
|
|
3842
|
+
bindName(env, alias, stub);
|
|
3843
|
+
return;
|
|
3844
|
+
}
|
|
3845
|
+
if (mod.namespace == null) mod.namespace = node.namespace;
|
|
3846
|
+
if (mod.version == null) mod.version = node.version;
|
|
3847
|
+
bindName(env, alias, mod);
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
function loadLibrarySource(env: Env, source: string, fallbackTitle?: string): void {
|
|
3851
|
+
let tree: AST;
|
|
3852
|
+
try {
|
|
3853
|
+
tree = parse(source);
|
|
3854
|
+
} catch {
|
|
3855
|
+
return;
|
|
3856
|
+
}
|
|
3857
|
+
if (tree.kind !== "Script") return;
|
|
3858
|
+
const script = tree as Script;
|
|
3859
|
+
const saved = new Map(env.pendingExports);
|
|
3860
|
+
env.pendingExports.clear();
|
|
3861
|
+
let title: string | null = null;
|
|
3862
|
+
for (const stmt of script.body) {
|
|
3863
|
+
const decl = captureDecl(stmt);
|
|
3864
|
+
if (decl) {
|
|
3865
|
+
if (decl.kind === "library" && decl.name) title = decl.name;
|
|
3866
|
+
continue;
|
|
3867
|
+
}
|
|
3868
|
+
const kind = stmt.kind;
|
|
3869
|
+
if (
|
|
3870
|
+
kind === "FunctionDef" ||
|
|
3871
|
+
kind === "TypeDef" ||
|
|
3872
|
+
kind === "EnumDef" ||
|
|
3873
|
+
kind === "Import" ||
|
|
3874
|
+
kind === "Assign"
|
|
3875
|
+
) {
|
|
3876
|
+
execStmt(stmt, env);
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
const libTitle = title ?? fallbackTitle;
|
|
3880
|
+
if (libTitle) finalizeLibrary(env, libTitle);
|
|
3881
|
+
env.pendingExports = saved;
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
function finalizeLibrary(env: Env, title: string): void {
|
|
3885
|
+
const existing = env.libraries.lookup({ name: title });
|
|
3886
|
+
const mod = existing ?? new LibraryModule(title);
|
|
3887
|
+
for (const [k, v] of env.pendingExports) {
|
|
3888
|
+
if (isUdfDef(v)) mod.exportFn(k, v);
|
|
3889
|
+
else if (v instanceof UdtType) {
|
|
3890
|
+
v.isExported = true;
|
|
3891
|
+
mod.exportType(k, v);
|
|
3892
|
+
} else if (v instanceof EnumType) mod.exportEnum(k, v);
|
|
3893
|
+
else mod.setExport(k, v);
|
|
3894
|
+
}
|
|
3895
|
+
env.libraries.register(mod);
|
|
3896
|
+
env.pendingExports.clear();
|
|
3897
|
+
}
|