@hoox-sh/pynets 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,15 +6,19 @@
6
6
  * Injected as `__h.strategy`. Wraps StrategyBook (fills + ledger + risk).
7
7
  */
8
8
  import {
9
+ isStrategyCashAmount,
10
+ strategyCashAmount,
9
11
  StrategyBook,
10
12
  type BarOhlc,
11
13
  type BrokerSettings,
12
14
  type PlaceEntryOpts,
13
15
  type PlaceExitOpts,
16
+ type StrategyCashAmount,
14
17
  type StrategyDirection,
15
18
  type StrategyEvent,
16
19
  type StrategySummary,
17
20
  } from "../strategy.ts";
21
+ import { naNum } from "./helpers.ts";
18
22
 
19
23
  function asDir(v: unknown): StrategyDirection | number | string {
20
24
  if (v == null) return "long";
@@ -92,9 +96,13 @@ export class CompileStrategy {
92
96
  if (ml != null) broker.margin_long = ml;
93
97
  const ms = optNum(rec.margin_short);
94
98
  if (ms != null) broker.margin_short = ms;
95
- if (typeof rec.default_qty_type === "string") broker.default_qty_type = rec.default_qty_type;
99
+ // String sentinel or tagged free-cash (`strategy.cash`) both select cash sizing.
100
+ if (typeof rec.default_qty_type === "string" || isStrategyCashAmount(rec.default_qty_type)) {
101
+ broker.default_qty_type = rec.default_qty_type;
102
+ }
96
103
  const dqv = optNum(rec.default_qty_value);
97
104
  if (dqv != null) broker.default_qty_value = dqv;
105
+ if (typeof rec.currency === "string" && rec.currency !== "") broker.currency = rec.currency;
98
106
  if (Object.keys(broker).length) this.book.configure(broker);
99
107
  const capital = optNum(rec.initial_capital);
100
108
  if (capital != null) this.book.initialCapital = capital;
@@ -212,6 +220,250 @@ export class CompileStrategy {
212
220
  return this.book.initialCapital;
213
221
  }
214
222
 
223
+ /** Free cash tagged so `default_qty_type=strategy.cash` still selects cash sizing. */
224
+ cash(): StrategyCashAmount {
225
+ return strategyCashAmount(this.book.cash(this.mark));
226
+ }
227
+
228
+ position_entry_name(): string {
229
+ return this.book.position_entry_name;
230
+ }
231
+
232
+ account_currency(): string {
233
+ return this.book.account_currency;
234
+ }
235
+
236
+ /** Zero-arg form is `percent_equity=100` (interpret `strategy.default_entry_qty`). */
237
+ default_entry_qty(percent?: unknown): number | null {
238
+ const pct = arguments.length === 0 || percent === undefined ? 100 : naNum(percent);
239
+ if (pct == null) return null;
240
+ return (this.book.initialCapital * (pct / 100)) / 100;
241
+ }
242
+
243
+ /** Identity FX stub. Missing arg → 1; na / non-finite → na. */
244
+ convert_to(value?: unknown): number | null {
245
+ if (arguments.length === 0 || value === undefined) return 1;
246
+ return naNum(value);
247
+ }
248
+
249
+ max_contracts_held_all(): number {
250
+ return this.book.maxContractsHeldAll();
251
+ }
252
+
253
+ max_contracts_held_long(): number {
254
+ return this.book.maxContractsHeldLong();
255
+ }
256
+
257
+ max_contracts_held_short(): number {
258
+ return this.book.maxContractsHeldShort();
259
+ }
260
+
261
+ capital_held(): number {
262
+ return this.book.capitalHeld();
263
+ }
264
+
265
+ closedtrades_first_index(): number {
266
+ return this.book.closedtrades_first_index;
267
+ }
268
+
269
+ wintrades(): number {
270
+ return this.book.wintrades;
271
+ }
272
+
273
+ losstrades(): number {
274
+ return this.book.losstrades;
275
+ }
276
+
277
+ eventrades(): number {
278
+ return this.book.eventrades;
279
+ }
280
+
281
+ grossprofit(): number {
282
+ return this.book.grossprofit;
283
+ }
284
+
285
+ grossloss(): number {
286
+ return this.book.grossloss;
287
+ }
288
+
289
+ avg_trade(): number {
290
+ return this.book.avgTrade();
291
+ }
292
+
293
+ avg_winning_trade(): number {
294
+ return this.book.avgWinningTrade();
295
+ }
296
+
297
+ avg_losing_trade(): number {
298
+ return this.book.avgLosingTrade();
299
+ }
300
+
301
+ netprofit_percent(): number {
302
+ return this.book.netprofitPercent(this.mark);
303
+ }
304
+
305
+ openprofit_percent(): number {
306
+ return this.book.openprofitPercent(this.mark);
307
+ }
308
+
309
+ grossprofit_percent(): number {
310
+ return this.book.grossprofitPercent();
311
+ }
312
+
313
+ grossloss_percent(): number {
314
+ return this.book.grosslossPercent();
315
+ }
316
+
317
+ avg_trade_percent(): number {
318
+ return this.book.avgTradePercent();
319
+ }
320
+
321
+ avg_winning_trade_percent(): number {
322
+ return this.book.avgWinningTradePercent();
323
+ }
324
+
325
+ avg_losing_trade_percent(): number {
326
+ return this.book.avgLosingTradePercent();
327
+ }
328
+
329
+ max_drawdown(): number {
330
+ return this.book.maxDrawdown();
331
+ }
332
+
333
+ max_drawdown_percent(): number {
334
+ return this.book.maxDrawdownPercent();
335
+ }
336
+
337
+ max_runup(): number {
338
+ return this.book.maxRunup();
339
+ }
340
+
341
+ max_runup_percent(): number {
342
+ return this.book.maxRunupPercent();
343
+ }
344
+
345
+ percent_profitable(): number {
346
+ return this.book.percentProfitable();
347
+ }
348
+
349
+ winrate(): number {
350
+ return this.book.percentProfitable();
351
+ }
352
+
353
+ profitfactor(): number {
354
+ return this.book.profitFactor();
355
+ }
356
+
357
+ profit_factor(): number {
358
+ return this.book.profitFactor();
359
+ }
360
+
361
+ closedtrades_entry_bar_index(i?: unknown): number {
362
+ return this.book.closedEntryBar(this.idx(i)) ?? 0;
363
+ }
364
+
365
+ closedtrades_entry_price(i?: unknown): number {
366
+ return this.book.closedEntryPrice(this.idx(i)) ?? 0;
367
+ }
368
+
369
+ closedtrades_exit_bar_index(i?: unknown): number {
370
+ return this.book.closedExitBar(this.idx(i)) ?? 0;
371
+ }
372
+
373
+ closedtrades_exit_price(i?: unknown): number {
374
+ return this.book.closedExitPrice(this.idx(i)) ?? 0;
375
+ }
376
+
377
+ closedtrades_profit(i?: unknown): number {
378
+ return this.book.closedProfit(this.idx(i)) ?? 0;
379
+ }
380
+
381
+ closedtrades_size(i?: unknown): number {
382
+ return this.book.closedSize(this.idx(i)) ?? 0;
383
+ }
384
+
385
+ closedtrades_entry_id(i?: unknown): string {
386
+ return this.book.closedId(this.idx(i)) ?? "";
387
+ }
388
+
389
+ closedtrades_exit_id(i?: unknown): string {
390
+ return this.book.closedId(this.idx(i)) ?? "";
391
+ }
392
+
393
+ closedtrades_commission(i?: unknown): number {
394
+ return this.book.closedCommission(this.idx(i)) ?? 0;
395
+ }
396
+
397
+ closedtrades_entry_time(i?: unknown): number {
398
+ return this.book.closedEntryTime(this.idx(i));
399
+ }
400
+
401
+ closedtrades_exit_time(i?: unknown): number {
402
+ return this.book.closedExitTime(this.idx(i));
403
+ }
404
+
405
+ closedtrades_entry_comment(i?: unknown): string {
406
+ return this.book.closedEntryComment(this.idx(i));
407
+ }
408
+
409
+ closedtrades_exit_comment(i?: unknown): string {
410
+ return this.book.closedExitComment(this.idx(i));
411
+ }
412
+
413
+ closedtrades_max_drawdown(i?: unknown): number {
414
+ return this.book.closedMaxDrawdown(this.idx(i));
415
+ }
416
+
417
+ closedtrades_max_runup(i?: unknown): number {
418
+ return this.book.closedMaxRunup(this.idx(i));
419
+ }
420
+
421
+ opentrades_entry_bar_index(i?: unknown): number {
422
+ return this.book.openEntryBar(this.idx(i)) ?? 0;
423
+ }
424
+
425
+ opentrades_entry_price(i?: unknown): number {
426
+ return this.book.openEntryPrice(this.idx(i)) ?? 0;
427
+ }
428
+
429
+ opentrades_size(i?: unknown): number {
430
+ return this.book.openSize(this.idx(i)) ?? 0;
431
+ }
432
+
433
+ opentrades_entry_id(i?: unknown): string {
434
+ return this.book.openId(this.idx(i)) ?? "";
435
+ }
436
+
437
+ opentrades_profit(i?: unknown): number {
438
+ return this.book.openProfit(this.idx(i), this.mark) ?? 0;
439
+ }
440
+
441
+ opentrades_entry_time(i?: unknown): number {
442
+ return this.book.openEntryTime(this.idx(i));
443
+ }
444
+
445
+ opentrades_entry_comment(i?: unknown): string {
446
+ return this.book.openEntryComment(this.idx(i));
447
+ }
448
+
449
+ opentrades_commission(i?: unknown): number {
450
+ return this.book.openCommission(this.idx(i));
451
+ }
452
+
453
+ opentrades_max_drawdown(i?: unknown): number {
454
+ return this.book.openMaxDrawdown(this.idx(i));
455
+ }
456
+
457
+ opentrades_max_runup(i?: unknown): number {
458
+ return this.book.openMaxRunup(this.idx(i));
459
+ }
460
+
461
+ private idx(i: unknown): number {
462
+ if (typeof i === "boolean") return i ? 1 : 0;
463
+ if (typeof i === "number" && Number.isFinite(i)) return Math.trunc(i);
464
+ return 0;
465
+ }
466
+
215
467
  risk_allow_entry_in(value?: unknown): void {
216
468
  this.book.riskAllowEntryIn(value);
217
469
  }
@@ -3951,6 +3951,157 @@ function evalExtraTa(fname: string | null, node: Call, env: Env, site: string):
3951
3951
  const z = env.ta.zigzag(site, srcArg(node, env), th);
3952
3952
  return tupleOf([z.high, z.low, z.dir]);
3953
3953
  }
3954
+ // wave10a-start
3955
+ const wave10aArg = (index: number, names: string[], message: string): expr => {
3956
+ const arg = callArg(node.args, index, names);
3957
+ if (arg == null) runtimeError(message);
3958
+ return arg;
3959
+ };
3960
+ if (fname === "ta.hammer" || fname === "hammer") {
3961
+ const msg = "ta.hammer() requires 4 arguments: open, high, low, close";
3962
+ return env.ta.hammer(
3963
+ unwrap(evalExpr(wave10aArg(0, ["open"], msg), env)),
3964
+ unwrap(evalExpr(wave10aArg(1, ["high"], msg), env)),
3965
+ unwrap(evalExpr(wave10aArg(2, ["low"], msg), env)),
3966
+ unwrap(evalExpr(wave10aArg(3, ["close"], msg), env)),
3967
+ );
3968
+ }
3969
+ if (fname === "ta.engulfing" || fname === "engulfing") {
3970
+ const msg = "ta.engulfing() requires 4 arguments: open, high, low, close";
3971
+ unwrap(evalExpr(wave10aArg(0, ["open"], msg), env));
3972
+ unwrap(evalExpr(wave10aArg(1, ["high"], msg), env));
3973
+ unwrap(evalExpr(wave10aArg(2, ["low"], msg), env));
3974
+ unwrap(evalExpr(wave10aArg(3, ["close"], msg), env));
3975
+ return env.ta.engulfing();
3976
+ }
3977
+ if (fname === "ta.gap_detector" || fname === "gap_detector") {
3978
+ const msg = "ta.gap_detector() requires 3 arguments: high, low, prev_close";
3979
+ return env.ta.gapDetector(
3980
+ unwrap(evalExpr(wave10aArg(0, ["high"], msg), env)),
3981
+ unwrap(evalExpr(wave10aArg(1, ["low"], msg), env)),
3982
+ unwrap(evalExpr(wave10aArg(2, ["prev_close", "prev"], msg), env)),
3983
+ );
3984
+ }
3985
+ if (fname === "ta.double_top_bottom" || fname === "double_top_bottom") {
3986
+ const msg = "ta.double_top_bottom() requires 3 arguments";
3987
+ unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
3988
+ unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
3989
+ return env.ta.doubleTopBottom(unwrap(evalExpr(wave10aArg(2, ["period", "length"], msg), env)));
3990
+ }
3991
+ if (fname === "ta.inside_bar_pattern" || fname === "inside_bar_pattern") {
3992
+ const msg = "ta.inside_bar_pattern() requires 2 arguments";
3993
+ unwrap(evalExpr(wave10aArg(0, ["high"], msg), env));
3994
+ unwrap(evalExpr(wave10aArg(1, ["low"], msg), env));
3995
+ return env.ta.insideBarPattern();
3996
+ }
3997
+ if (fname === "ta.macd_signal" || fname === "macd_signal") {
3998
+ const msg = "ta.macd_signal() requires 2 arguments: macd_line, signal_line";
3999
+ return env.ta.macdSignal(
4000
+ unwrap(evalExpr(wave10aArg(0, ["macd_line", "macd"], msg), env)),
4001
+ unwrap(evalExpr(wave10aArg(1, ["signal_line", "signal"], msg), env)),
4002
+ );
4003
+ }
4004
+ if (fname === "ta.acceleration_factor" || fname === "acceleration_factor") {
4005
+ const msg = "ta.acceleration_factor() requires 2 arguments";
4006
+ return env.ta.accelerationFactor(
4007
+ site,
4008
+ unwrap(evalExpr(wave10aArg(0, ["momentum_list", "momentum", "source", "series"], msg), env)),
4009
+ unwrap(evalExpr(wave10aArg(1, ["period", "length"], msg), env)),
4010
+ );
4011
+ }
4012
+ if (fname === "ta.stoch_smooth" || fname === "stoch_smooth") {
4013
+ const msg = "ta.stoch_smooth() requires 6 arguments: high, low, close, period, smooth_k, smooth_d";
4014
+ return env.ta.stochSmooth(
4015
+ unwrap(evalExpr(wave10aArg(0, ["high"], msg), env)),
4016
+ unwrap(evalExpr(wave10aArg(1, ["low"], msg), env)),
4017
+ unwrap(evalExpr(wave10aArg(2, ["close"], msg), env)),
4018
+ unwrap(evalExpr(wave10aArg(3, ["period", "length"], msg), env)),
4019
+ unwrap(evalExpr(wave10aArg(4, ["smooth_k", "k"], msg), env)),
4020
+ unwrap(evalExpr(wave10aArg(5, ["smooth_d", "d"], msg), env)),
4021
+ );
4022
+ }
4023
+ // wave10a-end
4024
+ // wave10b-start
4025
+ const wave10bList = (index: number, names: string[]): Array<number | null> => {
4026
+ const raw = evalExpr(callArg(node.args, index, names), env);
4027
+ const arr = asArray(raw);
4028
+ if (arr == null) return [unwrap(raw)];
4029
+ const out: Array<number | null> = [];
4030
+ for (let i = 0; i < arr.size(); i++) out.push(arr.get(i));
4031
+ return out;
4032
+ };
4033
+ if (fname === "ta.voi" || fname === "voi") {
4034
+ return env.ta.voi(
4035
+ unwrap(evalExpr(callArg(node.args, 0, ["buy_volume", "buy"]), env)),
4036
+ unwrap(evalExpr(callArg(node.args, 1, ["sell_volume", "sell"]), env)),
4037
+ );
4038
+ }
4039
+ if (fname === "ta.bid_ask_imbalance" || fname === "bid_ask_imbalance") {
4040
+ return env.ta.bidAskImbalance(
4041
+ unwrap(evalExpr(callArg(node.args, 0, ["bid_size", "bid"]), env)),
4042
+ unwrap(evalExpr(callArg(node.args, 1, ["ask_size", "ask"]), env)),
4043
+ unwrap(evalExpr(callArg(node.args, 2, ["bid_price"]), env)),
4044
+ unwrap(evalExpr(callArg(node.args, 3, ["ask_price"]), env)),
4045
+ );
4046
+ }
4047
+ if (fname === "ta.rsi_oversold_overbought" || fname === "rsi_oversold_overbought") {
4048
+ const os = unwrap(evalExpr(callArg(node.args, 1, ["oversold_level", "oversold"]), env));
4049
+ const ob = unwrap(evalExpr(callArg(node.args, 2, ["overbought_level", "overbought"]), env));
4050
+ return env.ta.rsiOversoldOverbought(
4051
+ unwrap(evalExpr(callArg(node.args, 0, ["rsi_series", "rsi", "source"]), env)),
4052
+ os ?? Number.NaN,
4053
+ ob ?? Number.NaN,
4054
+ );
4055
+ }
4056
+ if (fname === "ta.rsi_divergence" || fname === "rsi_divergence") {
4057
+ const period = unwrap(evalExpr(callArg(node.args, 1, ["period", "length"]), env));
4058
+ return env.ta.rsiDivergence(wave10bList(0, ["rsi_series", "series", "source"]), period ?? 0);
4059
+ }
4060
+ if (fname === "ta.cumulative_delta" || fname === "cumulative_delta") {
4061
+ const period = unwrap(evalExpr(callArg(node.args, 2, ["period", "length"]), env));
4062
+ return env.ta.cumulativeDelta(
4063
+ site,
4064
+ unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
4065
+ unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env)),
4066
+ period ?? 0,
4067
+ );
4068
+ }
4069
+ if (fname === "ta.volume_momentum" || fname === "volume_momentum") {
4070
+ const period = unwrap(evalExpr(callArg(node.args, 1, ["period", "length"]), env));
4071
+ return env.ta.volumeMomentum(
4072
+ site,
4073
+ unwrap(evalExpr(callArg(node.args, 0, ["volume", "source"]), env)),
4074
+ period ?? 0,
4075
+ );
4076
+ }
4077
+ if (fname === "ta.volume_profile_high" || fname === "volume_profile_high") {
4078
+ return env.ta.volumeProfileHigh(
4079
+ wave10bList(0, ["close", "price", "source", "series"]),
4080
+ wave10bList(1, ["volume"]),
4081
+ );
4082
+ }
4083
+ if (fname === "ta.volume_profile_low" || fname === "volume_profile_low") {
4084
+ const period = unwrap(evalExpr(callArg(node.args, 2, ["period", "length"]), env));
4085
+ const levels = unwrap(evalExpr(callArg(node.args, 3, ["levels"]), env));
4086
+ return env.ta.volumeProfileLow(
4087
+ site,
4088
+ unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
4089
+ unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env)),
4090
+ period ?? 0,
4091
+ levels ?? 0,
4092
+ );
4093
+ }
4094
+ if (fname === "ta.volume_thrust" || fname === "volume_thrust") {
4095
+ const sens = unwrap(evalExpr(callArg(node.args, 3, ["sensitivity"]), env));
4096
+ return env.ta.volumeThrust(
4097
+ site,
4098
+ unwrap(evalExpr(callArg(node.args, 0, ["close", "source"]), env)),
4099
+ unwrap(evalExpr(callArg(node.args, 1, ["volume"]), env)),
4100
+ unwrap(evalExpr(callArg(node.args, 2, ["volume_sma", "vol_sma", "sma"]), env)),
4101
+ sens ?? 0.3,
4102
+ );
4103
+ }
4104
+ // wave10b-end
3954
4105
  return undefined;
3955
4106
  }
3956
4107