@hoox-sh/pynets 0.1.0 → 0.2.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.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Copyright (C) 2024-2026 jango_blockchained
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
5
+ * na-safe arithmetic / compare / history for the JS compile backend.
6
+ * Python analog: pynescript.compiler.numba_builtins (object-mode subset).
7
+ */
8
+ export const NA = null;
9
+
10
+ export function isNaCell(v: unknown): boolean {
11
+ if (v == null) return true;
12
+ if (typeof v === "number") return !Number.isFinite(v);
13
+ return false;
14
+ }
15
+
16
+ export function naNum(v: unknown): number | null {
17
+ if (isNaCell(v)) return NA;
18
+ if (typeof v === "boolean") return v ? 1 : 0;
19
+ if (typeof v === "number") return v;
20
+ return NA;
21
+ }
22
+
23
+ /** Store cell or object handle. Objects (array/map/matrix/UDT) stay intact. */
24
+ export function hold(v: unknown): unknown {
25
+ if (v != null && typeof v === "object") return v;
26
+ return naNum(v);
27
+ }
28
+
29
+ export function pineEq(a: unknown, b: unknown): boolean {
30
+ const an = isNaCell(a);
31
+ const bn = isNaCell(b);
32
+ if (an && bn) return true;
33
+ if (an || bn) return false;
34
+ return a === b;
35
+ }
36
+
37
+ export function pineNe(a: unknown, b: unknown): boolean {
38
+ const an = isNaCell(a);
39
+ const bn = isNaCell(b);
40
+ if (an || bn) return false;
41
+ return a !== b;
42
+ }
43
+
44
+ export function pineLt(a: unknown, b: unknown): boolean {
45
+ if (isNaCell(a) || isNaCell(b)) return false;
46
+ return (a as number) < (b as number);
47
+ }
48
+
49
+ export function pineLtE(a: unknown, b: unknown): boolean {
50
+ if (isNaCell(a) || isNaCell(b)) return false;
51
+ return (a as number) <= (b as number);
52
+ }
53
+
54
+ export function pineGt(a: unknown, b: unknown): boolean {
55
+ if (isNaCell(a) || isNaCell(b)) return false;
56
+ return (a as number) > (b as number);
57
+ }
58
+
59
+ export function pineGtE(a: unknown, b: unknown): boolean {
60
+ if (isNaCell(a) || isNaCell(b)) return false;
61
+ return (a as number) >= (b as number);
62
+ }
63
+
64
+ function bin(a: unknown, b: unknown, op: (x: number, y: number) => number): number | null {
65
+ const x = naNum(a);
66
+ const y = naNum(b);
67
+ if (x == null || y == null) return NA;
68
+ const r = op(x, y);
69
+ return Number.isFinite(r) ? r : NA;
70
+ }
71
+
72
+ export function safeAdd(a: unknown, b: unknown): number | null {
73
+ return bin(a, b, (x, y) => x + y);
74
+ }
75
+
76
+ export function safeSub(a: unknown, b: unknown): number | null {
77
+ return bin(a, b, (x, y) => x - y);
78
+ }
79
+
80
+ export function safeMul(a: unknown, b: unknown): number | null {
81
+ return bin(a, b, (x, y) => x * y);
82
+ }
83
+
84
+ export function safeDiv(a: unknown, b: unknown): number | null {
85
+ return bin(a, b, (x, y) => x / y);
86
+ }
87
+
88
+ export function safeMod(a: unknown, b: unknown): number | null {
89
+ return bin(a, b, (x, y) => x % y);
90
+ }
91
+
92
+ export function safeNeg(a: unknown): number | null {
93
+ const x = naNum(a);
94
+ if (x == null) return NA;
95
+ const r = -x;
96
+ return Number.isFinite(r) ? r : NA;
97
+ }
98
+
99
+ export function nz(v: unknown, fallback: unknown = 0): number | null {
100
+ if (isNaCell(v)) {
101
+ const f = naNum(fallback);
102
+ return f == null ? 0 : f;
103
+ }
104
+ return naNum(v);
105
+ }
106
+
107
+ /** History: arr[bar - offset]. OOB / negative offset → na. [0] is current. */
108
+ export function histGet(arr: Array<number | null>, bar: number, offset: unknown): number | null {
109
+ const off = naNum(offset);
110
+ if (off == null || off < 0 || !Number.isFinite(off)) return NA;
111
+ const i = bar - Math.trunc(off);
112
+ if (i < 0 || i >= arr.length) return NA;
113
+ return naNum(arr[i]);
114
+ }
115
+
116
+ export function histStore(arr: Array<number | null>, bar: number, value: unknown): void {
117
+ if (bar < 0 || bar >= arr.length) return;
118
+ arr[bar] = naNum(value);
119
+ }
120
+
121
+ export function defaultVolume(n: number): Array<number | null> {
122
+ return Array.from({ length: n }, () => 1);
123
+ }
124
+
125
+ export function defaultTime(n: number): Array<number | null> {
126
+ return Array.from({ length: n }, (_, i) => i * 60_000);
127
+ }
128
+
129
+ export function toFloatArr(
130
+ xs: ArrayLike<number | null | undefined> | null | undefined,
131
+ n: number,
132
+ missing: number | null,
133
+ ): Array<number | null> {
134
+ const out: Array<number | null> = new Array(n);
135
+ for (let i = 0; i < n; i++) {
136
+ const v = xs != null && i < xs.length ? xs[i] : missing;
137
+ out[i] = naNum(v ?? missing);
138
+ }
139
+ return out;
140
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Copyright (C) 2024-2026 jango_blockchained
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
5
+ * Public compile surface. Names match Python pynescript.compiler where they
6
+ * apply: compileScript, transpile, CompiledScript, clearCompileCache.
7
+ * Backend is JS emit only (Python object-mode analog — no Numba).
8
+ */
9
+ export {
10
+ CompileError,
11
+ CompileEmitError,
12
+ CompileIneligibleError,
13
+ CompileLoadError,
14
+ } from "./types.ts";
15
+ export type {
16
+ CompileBackend,
17
+ CompileCacheStats,
18
+ CompileEligibility,
19
+ CompilePlotMeta,
20
+ CompiledScript,
21
+ RuntimeMode,
22
+ } from "./types.ts";
23
+ export {
24
+ compileEligible,
25
+ compileScript,
26
+ clearCompileCache,
27
+ compileCacheStats,
28
+ transpile,
29
+ runScript,
30
+ compileToResult,
31
+ } from "./engine.ts";
32
+ export type { CompileScriptExtras, CompileRunExtras } from "./engine.ts";
33
+ export { emitScript } from "./emit.ts";
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Copyright (C) 2024-2026 jango_blockchained
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
5
+ * Helpers object injected as `__h` into generated execute_script_compiled.
6
+ */
7
+ import {
8
+ asColor,
9
+ colorB,
10
+ colorByName,
11
+ colorFromGradient,
12
+ colorG,
13
+ colorNew,
14
+ colorR,
15
+ colorRgb,
16
+ colorT,
17
+ } from "../color.ts";
18
+ import {
19
+ mathAbs,
20
+ mathAcos,
21
+ mathAsin,
22
+ mathAtan,
23
+ mathAvg,
24
+ mathCeil,
25
+ mathCos,
26
+ mathExp,
27
+ mathFixnan,
28
+ mathFloor,
29
+ mathIff,
30
+ mathLog10,
31
+ mathMax,
32
+ mathMin,
33
+ mathPow,
34
+ mathRound,
35
+ mathSign,
36
+ mathSin,
37
+ mathSqrt,
38
+ mathTan,
39
+ mathToDegrees,
40
+ mathToRadians,
41
+ } from "../math.ts";
42
+ import {
43
+ strContains,
44
+ strEndsWith,
45
+ strFormat,
46
+ strFormatTime,
47
+ strLength,
48
+ strLower,
49
+ strReplace,
50
+ strStartsWith,
51
+ strSubstring,
52
+ strToNumber,
53
+ strTostring,
54
+ strTrim,
55
+ strUpper,
56
+ } from "../str.ts";
57
+ import { LogBook, formatLogParts } from "../log.ts";
58
+ import { resolveCurrencyRate } from "../request.ts";
59
+ import { TaEngine } from "../ta.ts";
60
+ import { utcPartsFromMs, timestamp, weekOfYear, timeTradingDay, type UtcParts } from "../time.ts";
61
+ import { timeframeInSeconds } from "../timeframe.ts";
62
+ import { compileArray, compileMap, compileMatrix } from "./collections.ts";
63
+ import { createCompileDraw } from "./draw.ts";
64
+ import {
65
+ histGet,
66
+ histStore,
67
+ hold,
68
+ isNaCell,
69
+ naNum,
70
+ nz,
71
+ pineEq,
72
+ pineGt,
73
+ pineGtE,
74
+ pineLt,
75
+ pineLtE,
76
+ pineNe,
77
+ safeAdd,
78
+ safeDiv,
79
+ safeMod,
80
+ safeMul,
81
+ safeNeg,
82
+ safeSub,
83
+ } from "./helpers.ts";
84
+ import { createCompileStrategy } from "./strategy.ts";
85
+ import { udtGet, udtNew, udtRegister, udtSet } from "./udt_runtime.ts";
86
+
87
+ export type CompileHelperInputs = Record<string, number | string | boolean>;
88
+
89
+ export type CompileHelperOpts = {
90
+ inputs?: CompileHelperInputs;
91
+ };
92
+
93
+ export function createCompileHelpers(opts?: CompileHelperOpts) {
94
+ const ta = new TaEngine();
95
+ const inputs = opts?.inputs;
96
+ const book = new LogBook();
97
+ return {
98
+ na: NA_FN,
99
+ nz,
100
+ naNum,
101
+ isNa: isNaCell,
102
+ eq: pineEq,
103
+ ne: pineNe,
104
+ lt: pineLt,
105
+ lte: pineLtE,
106
+ gt: pineGt,
107
+ gte: pineGtE,
108
+ add: safeAdd,
109
+ sub: safeSub,
110
+ mul: safeMul,
111
+ div: safeDiv,
112
+ mod: safeMod,
113
+ neg: safeNeg,
114
+ hist: histGet,
115
+ store: histStore,
116
+ ta,
117
+ strategy: createCompileStrategy(),
118
+ site: (id: string) => id,
119
+ input: (key: unknown, def: unknown) => inputLookup(inputs, key, def),
120
+ colorR,
121
+ colorG,
122
+ colorB,
123
+ colorT,
124
+ colorNew: compileColorNew,
125
+ colorRgb,
126
+ colorFromGradient,
127
+ colorByName,
128
+ abs: mathAbs,
129
+ sign: mathSign,
130
+ floor: mathFloor,
131
+ ceil: mathCeil,
132
+ round: mathRound,
133
+ sqrt: mathSqrt,
134
+ exp: mathExp,
135
+ log: {
136
+ info(bar: unknown, ...parts: unknown[]) {
137
+ book.info(logBar(bar), formatLogParts(parts));
138
+ },
139
+ warning(bar: unknown, ...parts: unknown[]) {
140
+ book.warning(logBar(bar), formatLogParts(parts));
141
+ },
142
+ error(bar: unknown, ...parts: unknown[]) {
143
+ book.error(logBar(bar), formatLogParts(parts));
144
+ },
145
+ clear() {
146
+ book.clear();
147
+ },
148
+ extras() {
149
+ return { __logs: book.snapshot() };
150
+ },
151
+ },
152
+ log10: mathLog10,
153
+ pow: mathPow,
154
+ min: mathMin,
155
+ max: mathMax,
156
+ avg: mathAvg,
157
+ sin: mathSin,
158
+ cos: mathCos,
159
+ tan: mathTan,
160
+ asin: mathAsin,
161
+ acos: mathAcos,
162
+ atan: mathAtan,
163
+ todegrees: mathToDegrees,
164
+ toradians: mathToRadians,
165
+ iff,
166
+ fixnan: mathFixnan,
167
+ year: calendarYear,
168
+ month: calendarMonth,
169
+ dayofmonth: calendarDayofmonth,
170
+ hour: calendarHour,
171
+ minute: calendarMinute,
172
+ second: calendarSecond,
173
+ dayofweek: calendarDayofweek,
174
+ timestamp,
175
+ weekOfYear,
176
+ timeTradingDay,
177
+ timeframeInSeconds,
178
+ strLength,
179
+ strContains,
180
+ strStartsWith,
181
+ strEndsWith,
182
+ strLower,
183
+ strUpper,
184
+ strReplace,
185
+ strSubstring,
186
+ strToNumber,
187
+ strTrim,
188
+ strFormat,
189
+ strFormatTime,
190
+ tostring: strTostring,
191
+ str: {
192
+ length: strLength,
193
+ contains: strContains,
194
+ starts_with: strStartsWith,
195
+ ends_with: strEndsWith,
196
+ lower: strLower,
197
+ upper: strUpper,
198
+ replace: strReplace,
199
+ substring: strSubstring,
200
+ tonumber: strToNumber,
201
+ trim: strTrim,
202
+ tostring: strTostring,
203
+ format: strFormat,
204
+ format_time: strFormatTime,
205
+ },
206
+ hold,
207
+ array: compileArray,
208
+ map: compileMap,
209
+ matrix: compileMatrix,
210
+ draw: createCompileDraw(),
211
+ udtNew,
212
+ udtGet,
213
+ udtSet,
214
+ udtRegister,
215
+ currencyRate: resolveCurrencyRate,
216
+ runtimeError(msg: unknown): never {
217
+ throw new Error(String(msg));
218
+ },
219
+ };
220
+ }
221
+
222
+ /** Pine `color.new(color, transp)` — first arg is a color, not r,g,b. */
223
+ function compileColorNew(color: unknown, transp?: unknown) {
224
+ const parsed = asColor(color);
225
+ if (!parsed) return null;
226
+ const t = typeof transp === "number" && Number.isFinite(transp) ? transp : undefined;
227
+ return colorNew(parsed.r, parsed.g, parsed.b, t);
228
+ }
229
+
230
+ /** Override `def` when `inputs` has `key` or `String(key)`. */
231
+ function inputLookup(
232
+ inputs: CompileHelperInputs | undefined,
233
+ key: unknown,
234
+ def: unknown,
235
+ ): unknown {
236
+ if (inputs == null) return def;
237
+ if (hasInput(inputs, key)) return inputs[key as string];
238
+ const asStr = String(key);
239
+ if (hasInput(inputs, asStr)) return inputs[asStr];
240
+ return def;
241
+ }
242
+
243
+ function hasInput(inputs: CompileHelperInputs, key: unknown): boolean {
244
+ return (
245
+ (typeof key === "string" || typeof key === "number") &&
246
+ Object.prototype.hasOwnProperty.call(inputs, key)
247
+ );
248
+ }
249
+
250
+ /** Pine v4 `iff`: na cond → na; 0 / false → else; else then. */
251
+ function iff(cond: unknown, thenV: unknown, elseV: unknown): unknown {
252
+ if (typeof cond === "boolean") return cond ? thenV : elseV;
253
+ return mathIff(cond as number | null, thenV as number | null, elseV as number | null);
254
+ }
255
+
256
+ function NA_FN(v?: unknown): number | null {
257
+ if (arguments.length === 0) return null;
258
+ return isNaCell(v) ? 1 : 0;
259
+ }
260
+
261
+ /** Unix-ms → UTC calendar part. na / non-finite time → null. */
262
+ function utcPart(t: unknown, key: keyof UtcParts): number | null {
263
+ if (typeof t !== "number" || !Number.isFinite(t)) return null;
264
+ return utcPartsFromMs(t)[key];
265
+ }
266
+
267
+ function calendarYear(t: unknown): number | null {
268
+ return utcPart(t, "year");
269
+ }
270
+
271
+ function calendarMonth(t: unknown): number | null {
272
+ return utcPart(t, "month");
273
+ }
274
+
275
+ function calendarDayofmonth(t: unknown): number | null {
276
+ return utcPart(t, "dayofmonth");
277
+ }
278
+
279
+ function calendarHour(t: unknown): number | null {
280
+ return utcPart(t, "hour");
281
+ }
282
+
283
+ function calendarMinute(t: unknown): number | null {
284
+ return utcPart(t, "minute");
285
+ }
286
+
287
+ function calendarSecond(t: unknown): number | null {
288
+ return utcPart(t, "second");
289
+ }
290
+
291
+ function calendarDayofweek(t: unknown): number | null {
292
+ return utcPart(t, "dayofweek");
293
+ }
294
+
295
+ function logBar(bar: unknown): number {
296
+ return typeof bar === "number" && Number.isFinite(bar) ? bar : 0;
297
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Copyright (C) 2024-2026 jango_blockchained
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
5
+ * Object-mode compile broker. Python analog: CompileStrategyBroker.
6
+ * Injected as `__h.strategy`. Wraps StrategyBook (fills + ledger + risk).
7
+ */
8
+ import {
9
+ StrategyBook,
10
+ type BrokerSettings,
11
+ type PlaceEntryOpts,
12
+ type StrategyDirection,
13
+ type StrategyEvent,
14
+ type StrategySummary,
15
+ } from "../strategy.ts";
16
+
17
+ function asDir(v: unknown): StrategyDirection | number | string {
18
+ if (v == null) return "long";
19
+ if (v === 1 || v === "1" || v === "long" || v === "strategy.long" || v === "buy") return "long";
20
+ if (v === -1 || v === "-1" || v === "short" || v === "strategy.short" || v === "sell") return "short";
21
+ return String(v);
22
+ }
23
+
24
+ function num(v: unknown, fallback: number): number {
25
+ if (typeof v === "number" && Number.isFinite(v)) return v;
26
+ if (typeof v === "boolean") return v ? 1 : 0;
27
+ if (typeof v === "string" && v !== "") {
28
+ const n = Number(v);
29
+ if (Number.isFinite(n)) return n;
30
+ }
31
+ return fallback;
32
+ }
33
+
34
+ function str(v: unknown, fallback = ""): string {
35
+ if (v == null) return fallback;
36
+ return String(v);
37
+ }
38
+
39
+ export class CompileStrategy {
40
+ readonly book: StrategyBook;
41
+ private bar = 0;
42
+ private mark = 0;
43
+ private time = 0;
44
+
45
+ constructor(settings?: BrokerSettings) {
46
+ this.book = new StrategyBook(settings);
47
+ }
48
+
49
+ beginBar(
50
+ bar: unknown,
51
+ open: unknown,
52
+ high: unknown,
53
+ low: unknown,
54
+ close: unknown,
55
+ time?: unknown,
56
+ ): void {
57
+ this.bar = num(bar, 0);
58
+ this.mark = num(close, 0);
59
+ this.time = time == null ? this.bar : num(time, this.bar);
60
+ const ohlc = {
61
+ open: num(open, this.mark),
62
+ high: num(high, this.mark),
63
+ low: num(low, this.mark),
64
+ close: this.mark,
65
+ };
66
+ this.book.processPending(this.bar, ohlc);
67
+ this.book.markOpenTrades(ohlc.high, ohlc.low, ohlc.close);
68
+ }
69
+
70
+ entry(id?: unknown, direction?: unknown, qty?: unknown, opts?: PlaceEntryOpts): void {
71
+ const q = qty == null ? 1 : num(qty, 1);
72
+ this.book.placeEntry(this.bar, str(id, "entry"), asDir(direction), q, {
73
+ price: opts?.price ?? this.mark,
74
+ time: opts?.time ?? this.time,
75
+ limit: opts?.limit,
76
+ stop: opts?.stop,
77
+ comment: opts?.comment,
78
+ oca_name: opts?.oca_name,
79
+ oca_type: opts?.oca_type,
80
+ });
81
+ }
82
+
83
+ close(id?: unknown, _qty?: unknown): void {
84
+ this.book.fillClose(this.bar, str(id, ""), this.mark, { time: this.time });
85
+ }
86
+
87
+ close_all(_comment?: unknown): void {
88
+ this.book.closeAll(this.bar, this.mark);
89
+ }
90
+
91
+ order(id?: unknown, direction?: unknown, qty?: unknown, opts?: PlaceEntryOpts): void {
92
+ this.entry(id, direction, qty, opts);
93
+ }
94
+
95
+ cancel(id?: unknown): void {
96
+ this.book.cancel(this.bar, str(id, ""));
97
+ }
98
+
99
+ cancel_all(): void {
100
+ this.book.cancelAll(this.bar);
101
+ }
102
+
103
+ exit(from_entry?: unknown, _qty?: unknown): void {
104
+ this.book.fillClose(this.bar, str(from_entry, ""), this.mark, { time: this.time });
105
+ }
106
+
107
+ position_size(): number {
108
+ return this.book.position.qty;
109
+ }
110
+
111
+ netprofit(): number {
112
+ return this.book.realizedPnl;
113
+ }
114
+
115
+ equity(): number {
116
+ return this.book.equity(this.mark);
117
+ }
118
+
119
+ openprofit(): number {
120
+ const q = this.book.position.qty;
121
+ const avg = this.book.position.avgPrice;
122
+ if (q === 0 || avg == null || !Number.isFinite(avg)) return 0;
123
+ return q * (this.mark - avg);
124
+ }
125
+
126
+ opentrades(): number {
127
+ return this.book.opentrades;
128
+ }
129
+
130
+ closedtrades(): number {
131
+ return this.book.closedtrades;
132
+ }
133
+
134
+ risk_allow_entry_in(value?: unknown): void {
135
+ this.book.riskAllowEntryIn(value);
136
+ }
137
+
138
+ risk_max_position_size(percent?: unknown): void {
139
+ this.book.riskMaxPositionSize(percent);
140
+ }
141
+
142
+ risk_max_drawdown(value?: unknown, type?: unknown): void {
143
+ this.book.riskMaxDrawdown(value, type);
144
+ }
145
+
146
+ extras(): CompileStrategyExtras {
147
+ return {
148
+ __events: this.book.events.slice(),
149
+ __fills: this.book.fills.slice(),
150
+ __position_size: this.book.position.qty,
151
+ __netprofit: this.book.realizedPnl,
152
+ __equity: this.book.equity(this.mark),
153
+ __strategy: this.book.summary(this.mark),
154
+ };
155
+ }
156
+ }
157
+
158
+ export interface CompileStrategyExtras {
159
+ __events: StrategyEvent[];
160
+ __fills: StrategyBook["fills"];
161
+ __position_size: number;
162
+ __netprofit: number;
163
+ __equity: number;
164
+ __strategy: StrategySummary;
165
+ }
166
+
167
+ export function createCompileStrategy(settings?: BrokerSettings): CompileStrategy {
168
+ return new CompileStrategy(settings);
169
+ }