@tradejs/infra 1.0.5 → 1.0.8

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,132 @@
1
+ // src/aiEndpoints.ts
2
+ var AI_CUSTOM_ENDPOINT_VALUE = "__custom__";
3
+ var AI_ENDPOINT_OPTIONS = [
4
+ {
5
+ label: "OpenAI",
6
+ value: "https://api.openai.com/v1"
7
+ },
8
+ {
9
+ label: "Claude",
10
+ value: "https://api.anthropic.com/v1"
11
+ },
12
+ {
13
+ label: "OpenRouter",
14
+ value: "https://openrouter.ai/api/v1"
15
+ },
16
+ {
17
+ label: "Gemini",
18
+ value: "https://generativelanguage.googleapis.com/v1beta/openai"
19
+ },
20
+ {
21
+ label: "Together AI",
22
+ value: "https://api.together.xyz/v1"
23
+ },
24
+ {
25
+ label: "Groq",
26
+ value: "https://api.groq.com/openai/v1"
27
+ },
28
+ {
29
+ label: "DeepInfra",
30
+ value: "https://api.deepinfra.com/v1/openai"
31
+ },
32
+ {
33
+ label: "xAI",
34
+ value: "https://api.x.ai/v1"
35
+ },
36
+ {
37
+ label: "Qwen (DashScope Intl)",
38
+ value: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
39
+ },
40
+ {
41
+ label: "Qwen (DashScope CN)",
42
+ value: "https://dashscope.aliyuncs.com/compatible-mode/v1"
43
+ },
44
+ {
45
+ label: "Qwen (DashScope US)",
46
+ value: "https://dashscope-us.aliyuncs.com/compatible-mode/v1"
47
+ },
48
+ {
49
+ label: "Perplexity",
50
+ value: "https://api.perplexity.ai"
51
+ },
52
+ {
53
+ label: "Fireworks",
54
+ value: "https://api.fireworks.ai/inference/v1"
55
+ },
56
+ {
57
+ label: "SambaNova",
58
+ value: "https://api.sambanova.ai/v1"
59
+ },
60
+ {
61
+ label: "Hyperbolic",
62
+ value: "https://api.hyperbolic.xyz/v1"
63
+ },
64
+ {
65
+ label: "Kimi",
66
+ value: "https://api.moonshot.ai/v1"
67
+ },
68
+ {
69
+ label: "ProxyAPI",
70
+ value: "https://openai.api.proxyapi.ru/v1"
71
+ },
72
+ {
73
+ label: "Custom",
74
+ value: AI_CUSTOM_ENDPOINT_VALUE
75
+ }
76
+ ];
77
+ var KNOWN_AI_ENDPOINTS = new Set(
78
+ AI_ENDPOINT_OPTIONS.map((option) => option.value).filter(
79
+ (value) => value !== AI_CUSTOM_ENDPOINT_VALUE
80
+ )
81
+ );
82
+ var normalizeUrl = (value) => value.replace(/\/+$/, "");
83
+ var isIpv4Address = (value) => /^(?:\d{1,3}\.){3}\d{1,3}$/.test(value.trim());
84
+ var parseIpv4Address = (value) => value.trim().split(".").map((part) => Number(part));
85
+ var isPrivateIpv4Address = (value) => {
86
+ if (!isIpv4Address(value)) {
87
+ return false;
88
+ }
89
+ const [a, b, c, d] = parseIpv4Address(value);
90
+ if ([a, b, c, d].some(
91
+ (part) => !Number.isInteger(part) || part < 0 || part > 255
92
+ )) {
93
+ return false;
94
+ }
95
+ return a === 10 || a === 127 || a === 0 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
96
+ };
97
+ var isPrivateHostname = (hostname) => {
98
+ const normalized = hostname.trim().toLowerCase();
99
+ if (!normalized) {
100
+ return true;
101
+ }
102
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal") || normalized.endsWith(".lan") || normalized === "::1" || normalized === "[::1]" || isPrivateIpv4Address(normalized);
103
+ };
104
+ var isValidAiEndpointUrl = (value) => {
105
+ try {
106
+ const url = new URL(value);
107
+ return url.protocol === "https:" && !isPrivateHostname(url.hostname);
108
+ } catch {
109
+ return false;
110
+ }
111
+ };
112
+ var normalizeAiEndpoint = (value) => {
113
+ if (typeof value !== "string") {
114
+ return "";
115
+ }
116
+ const trimmed = normalizeUrl(value.trim());
117
+ if (!trimmed) {
118
+ return "";
119
+ }
120
+ if (KNOWN_AI_ENDPOINTS.has(trimmed)) {
121
+ return trimmed;
122
+ }
123
+ return isValidAiEndpointUrl(trimmed) ? trimmed : "";
124
+ };
125
+ var isKnownAiEndpoint = (value) => KNOWN_AI_ENDPOINTS.has(normalizeAiEndpoint(value));
126
+
127
+ export {
128
+ AI_CUSTOM_ENDPOINT_VALUE,
129
+ AI_ENDPOINT_OPTIONS,
130
+ normalizeAiEndpoint,
131
+ isKnownAiEndpoint
132
+ };
package/dist/ml.d.mts CHANGED
@@ -1,22 +1,4 @@
1
- declare const toFileToken: (value: string) => string;
2
- declare const getMlChunkFilePath: (strategyName: string, chunkId: string, outDir?: string) => string;
3
- declare const appendMlDatasetRow: (params: {
4
- strategyName: string;
5
- chunkId: string;
6
- row: Record<string, number | string | null>;
7
- outDir?: string;
8
- }) => Promise<string>;
9
- declare const flushMlDatasetWriter: (filePath: string) => Promise<void>;
10
- declare const closeMlDatasetWriter: (filePath: string) => Promise<void>;
11
- declare const closeAllMlDatasetWriters: () => Promise<void>;
12
- declare const listMlChunkFiles: (params: {
13
- strategyName: string;
14
- outDir?: string;
15
- }) => Promise<string[]>;
16
- declare const mergeJsonlFiles: (params: {
17
- filePaths: string[];
18
- outPath: string;
19
- }) => Promise<void>;
1
+ export { a as appendMlDatasetRow, c as closeAllMlDatasetWriters, b as closeMlDatasetWriter, f as flushMlDatasetWriter, g as getMlChunkFilePath, l as listMlChunkFiles, d as listMlChunkStrategies, m as mergeJsonlFiles, t as toFileToken } from './mlDatasetFile-sRGWgR_o.mjs';
20
2
 
21
3
  type MlPredictResponse = {
22
4
  probability: number;
@@ -134,4 +116,4 @@ type MlSeriesAnalysisSummary = Record<string, number>;
134
116
  declare const analyzeMlSeriesWindow: (input: MlSeriesAnalysisInput) => MlSeriesAnalysisSummary;
135
117
  declare const buildMlSeriesAlignment: (left: MlSeriesAnalysisSummary | undefined, right: MlSeriesAnalysisSummary | undefined) => MlSeriesAnalysisSummary;
136
118
 
137
- export { type LookaheadViolation, type MlPredictParams, type MlPredictResponse, type MlResultRecord, type MlSeriesAnalysisCandle, type MlSeriesAnalysisSummary, type MlSignalRecord, analyzeMlSeriesWindow, appendMlDatasetRow, buildMlFeatures, buildMlSeriesAlignment, buildMlTrainingRow, closeAllMlDatasetWriters, closeMlDatasetWriter, computeWindowBoundaries, fetchMlThreshold, findLookaheadViolations, flushMlDatasetWriter, getMlChunkFilePath, isDerivedDatasetFileName, isTimestampFeatureKey, listMlChunkFiles, mergeJsonlFiles, toFileToken, toIsoUtcOrNull, trimMlTrainingRowWindows };
119
+ export { type LookaheadViolation, type MlPredictParams, type MlPredictResponse, type MlResultRecord, type MlSeriesAnalysisCandle, type MlSeriesAnalysisSummary, type MlSignalRecord, analyzeMlSeriesWindow, buildMlFeatures, buildMlSeriesAlignment, buildMlTrainingRow, computeWindowBoundaries, fetchMlThreshold, findLookaheadViolations, isDerivedDatasetFileName, isTimestampFeatureKey, toIsoUtcOrNull, trimMlTrainingRowWindows };
package/dist/ml.d.ts CHANGED
@@ -1,22 +1,4 @@
1
- declare const toFileToken: (value: string) => string;
2
- declare const getMlChunkFilePath: (strategyName: string, chunkId: string, outDir?: string) => string;
3
- declare const appendMlDatasetRow: (params: {
4
- strategyName: string;
5
- chunkId: string;
6
- row: Record<string, number | string | null>;
7
- outDir?: string;
8
- }) => Promise<string>;
9
- declare const flushMlDatasetWriter: (filePath: string) => Promise<void>;
10
- declare const closeMlDatasetWriter: (filePath: string) => Promise<void>;
11
- declare const closeAllMlDatasetWriters: () => Promise<void>;
12
- declare const listMlChunkFiles: (params: {
13
- strategyName: string;
14
- outDir?: string;
15
- }) => Promise<string[]>;
16
- declare const mergeJsonlFiles: (params: {
17
- filePaths: string[];
18
- outPath: string;
19
- }) => Promise<void>;
1
+ export { a as appendMlDatasetRow, c as closeAllMlDatasetWriters, b as closeMlDatasetWriter, f as flushMlDatasetWriter, g as getMlChunkFilePath, l as listMlChunkFiles, d as listMlChunkStrategies, m as mergeJsonlFiles, t as toFileToken } from './mlDatasetFile-sRGWgR_o.js';
20
2
 
21
3
  type MlPredictResponse = {
22
4
  probability: number;
@@ -134,4 +116,4 @@ type MlSeriesAnalysisSummary = Record<string, number>;
134
116
  declare const analyzeMlSeriesWindow: (input: MlSeriesAnalysisInput) => MlSeriesAnalysisSummary;
135
117
  declare const buildMlSeriesAlignment: (left: MlSeriesAnalysisSummary | undefined, right: MlSeriesAnalysisSummary | undefined) => MlSeriesAnalysisSummary;
136
118
 
137
- export { type LookaheadViolation, type MlPredictParams, type MlPredictResponse, type MlResultRecord, type MlSeriesAnalysisCandle, type MlSeriesAnalysisSummary, type MlSignalRecord, analyzeMlSeriesWindow, appendMlDatasetRow, buildMlFeatures, buildMlSeriesAlignment, buildMlTrainingRow, closeAllMlDatasetWriters, closeMlDatasetWriter, computeWindowBoundaries, fetchMlThreshold, findLookaheadViolations, flushMlDatasetWriter, getMlChunkFilePath, isDerivedDatasetFileName, isTimestampFeatureKey, listMlChunkFiles, mergeJsonlFiles, toFileToken, toIsoUtcOrNull, trimMlTrainingRowWindows };
119
+ export { type LookaheadViolation, type MlPredictParams, type MlPredictResponse, type MlResultRecord, type MlSeriesAnalysisCandle, type MlSeriesAnalysisSummary, type MlSignalRecord, analyzeMlSeriesWindow, buildMlFeatures, buildMlSeriesAlignment, buildMlTrainingRow, computeWindowBoundaries, fetchMlThreshold, findLookaheadViolations, isDerivedDatasetFileName, isTimestampFeatureKey, toIsoUtcOrNull, trimMlTrainingRowWindows };
package/dist/ml.js CHANGED
@@ -45,6 +45,7 @@ __export(ml_exports, {
45
45
  isDerivedDatasetFileName: () => isDerivedDatasetFileName,
46
46
  isTimestampFeatureKey: () => isTimestampFeatureKey,
47
47
  listMlChunkFiles: () => listMlChunkFiles,
48
+ listMlChunkStrategies: () => listMlChunkStrategies,
48
49
  mergeJsonlFiles: () => mergeJsonlFiles,
49
50
  toFileToken: () => toFileToken,
50
51
  toIsoUtcOrNull: () => toIsoUtcOrNull,
@@ -59,6 +60,7 @@ var import_promises = __toESM(require("fs/promises"));
59
60
  var import_path = __toESM(require("path"));
60
61
  var DEFAULT_DIR = "data/ml/export";
61
62
  var ML_DATASET_WRITE_BATCH_SIZE = 200;
63
+ var ML_CHUNK_FILE_RE = /^ml-dataset-(.+)-chunk-[^.]+\.jsonl$/;
62
64
  var writerByPath = /* @__PURE__ */ new Map();
63
65
  var toFileToken = (value) => value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "any";
64
66
  var getMlChunkFilePath = (strategyName, chunkId, outDir = DEFAULT_DIR) => import_path.default.join(
@@ -148,6 +150,20 @@ var listMlChunkFiles = async (params) => {
148
150
  }
149
151
  return entries.filter((name) => name.startsWith(prefix) && name.endsWith(".jsonl")).map((name) => import_path.default.join(outDir, name)).sort();
150
152
  };
153
+ var listMlChunkStrategies = async (params) => {
154
+ const outDir = params?.outDir ?? DEFAULT_DIR;
155
+ let entries = [];
156
+ try {
157
+ entries = await import_promises.default.readdir(outDir);
158
+ } catch {
159
+ return [];
160
+ }
161
+ return [
162
+ ...new Set(
163
+ entries.map((name) => name.match(ML_CHUNK_FILE_RE)?.[1] || "").filter(Boolean)
164
+ )
165
+ ].sort();
166
+ };
151
167
  var mergeJsonlFiles = async (params) => {
152
168
  const { filePaths, outPath } = params;
153
169
  await import_promises.default.mkdir(import_path.default.dirname(outPath), { recursive: true });
@@ -621,6 +637,44 @@ var INDICATOR_TIMEFRAMES = [
621
637
  { label: "TF4H", suffix: "4h" },
622
638
  { label: "TF1D", suffix: "1d" }
623
639
  ];
640
+ var DERIVATIVES_INTERVALS = [
641
+ { label: "15M", key: "15m" },
642
+ { label: "1H", key: "1h" }
643
+ ];
644
+ var DERIVATIVES_PRESSURES = [
645
+ "neutral",
646
+ "crowded_long",
647
+ "crowded_short",
648
+ "long_flush",
649
+ "short_flush"
650
+ ];
651
+ var DERIVATIVES_PRESSURE_LABELS = {
652
+ neutral: "Neutral",
653
+ crowded_long: "CrowdedLong",
654
+ crowded_short: "CrowdedShort",
655
+ long_flush: "LongFlush",
656
+ short_flush: "ShortFlush"
657
+ };
658
+ var DERIVATIVES_RISK_FLAGS = [
659
+ "missing_derivatives",
660
+ "stale_derivatives",
661
+ "crowded_long",
662
+ "crowded_short",
663
+ "oi_falling",
664
+ "oi_not_confirming",
665
+ "long_liquidation_spike",
666
+ "short_liquidation_spike"
667
+ ];
668
+ var DERIVATIVES_RISK_FLAG_LABELS = {
669
+ missing_derivatives: "Missing",
670
+ stale_derivatives: "Stale",
671
+ crowded_long: "CrowdedLong",
672
+ crowded_short: "CrowdedShort",
673
+ oi_falling: "OiFalling",
674
+ oi_not_confirming: "OiNotConfirming",
675
+ long_liquidation_spike: "LongLiqSpike",
676
+ short_liquidation_spike: "ShortLiqSpike"
677
+ };
624
678
  var toNumber = (value, fallback = 0) => {
625
679
  if (typeof value === "number") {
626
680
  return Number.isFinite(value) ? value : fallback;
@@ -741,6 +795,7 @@ var sliceWindow = (values, endIndex, windowSize) => {
741
795
  return values.slice(start, endIndex + 1);
742
796
  };
743
797
  var asArray = (value) => Array.isArray(value) ? value : [];
798
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
744
799
  var dropLastFromIndicatorSeries = (indicators) => {
745
800
  if (!ML_WINDOW_POLICY.dropLastIndicatorElement) {
746
801
  return indicators;
@@ -1462,6 +1517,85 @@ var buildMlTrainingRow = (signalRecord, resultRecord) => {
1462
1517
  row.TrendLine_Slope = null;
1463
1518
  }
1464
1519
  };
1520
+ const applyDerivativesPhase = () => {
1521
+ const context2 = asRecord(signal?.additionalIndicators?.derivativesContext);
1522
+ const summary = asRecord(context2?.summary);
1523
+ const intervals = asRecord(context2?.intervals);
1524
+ const pressure = typeof summary?.pressure === "string" ? summary.pressure : "";
1525
+ const riskFlags = new Set(
1526
+ asArray(summary?.riskFlags).map((flag) => String(flag))
1527
+ );
1528
+ row.Deriv_HasContext = context2 ? 1 : 0;
1529
+ row.Deriv_Source_Coinalyze = context2?.source === "coinalyze" ? 1 : 0;
1530
+ row.Deriv_DirectionAligned = summary?.directionAligned === true ? 1 : summary?.directionAligned === false ? -1 : 0;
1531
+ for (const pressureName of DERIVATIVES_PRESSURES) {
1532
+ row[`Deriv_Pressure_${DERIVATIVES_PRESSURE_LABELS[pressureName]}`] = pressure === pressureName ? 1 : 0;
1533
+ }
1534
+ for (const riskFlag of DERIVATIVES_RISK_FLAGS) {
1535
+ row[`Deriv_Flag_${DERIVATIVES_RISK_FLAG_LABELS[riskFlag]}`] = riskFlags.has(riskFlag) ? 1 : 0;
1536
+ }
1537
+ for (const { label, key: intervalKey } of DERIVATIVES_INTERVALS) {
1538
+ const intervalContext = asRecord(intervals?.[intervalKey]);
1539
+ const prefix = `Deriv_${label}`;
1540
+ const asOfTs = toNumber(intervalContext?.asOfTs, entryTimestamp);
1541
+ const ageHours = intervalContext ? Math.max(0, safeDiv2(entryTimestamp - asOfTs, 60 * 60 * 1e3)) : 0;
1542
+ row[`${prefix}_Present`] = intervalContext ? 1 : 0;
1543
+ row[`${prefix}_Stale`] = intervalContext?.stale === true ? 1 : 0;
1544
+ row[`${prefix}_AgeHours_Log`] = safeLog1pPositive(ageHours);
1545
+ row[`${prefix}_Points_Log`] = safeLog1pPositive(
1546
+ toNumber(intervalContext?.points, 0)
1547
+ );
1548
+ row[`${prefix}_OpenInterest_Log`] = safeLog1pPositive(
1549
+ toNumber(intervalContext?.openInterest, 0)
1550
+ );
1551
+ row[`${prefix}_OiChange1h`] = clamp2(
1552
+ toNumber(intervalContext?.oiChangePct1h, 0) / 100,
1553
+ -5,
1554
+ 5
1555
+ );
1556
+ row[`${prefix}_OiChange4h`] = clamp2(
1557
+ toNumber(intervalContext?.oiChangePct4h, 0) / 100,
1558
+ -5,
1559
+ 5
1560
+ );
1561
+ row[`${prefix}_OiChange24h`] = clamp2(
1562
+ toNumber(intervalContext?.oiChangePct24h, 0) / 100,
1563
+ -5,
1564
+ 5
1565
+ );
1566
+ row[`${prefix}_FundingBps`] = clamp2(
1567
+ toNumber(intervalContext?.fundingRate, 0) * 1e4,
1568
+ -100,
1569
+ 100
1570
+ );
1571
+ row[`${prefix}_FundingZ`] = clamp2(
1572
+ toNumber(intervalContext?.fundingZScore, 0),
1573
+ -8,
1574
+ 8
1575
+ );
1576
+ row[`${prefix}_LiqLong_Log`] = safeLog1pPositive(
1577
+ toNumber(intervalContext?.liqLong, 0)
1578
+ );
1579
+ row[`${prefix}_LiqShort_Log`] = safeLog1pPositive(
1580
+ toNumber(intervalContext?.liqShort, 0)
1581
+ );
1582
+ row[`${prefix}_LiqTotal_Log`] = safeLog1pPositive(
1583
+ toNumber(intervalContext?.liqTotal, 0)
1584
+ );
1585
+ row[`${prefix}_LiqImbalance`] = clamp2(
1586
+ toNumber(intervalContext?.liqImbalance, 0),
1587
+ -1,
1588
+ 1
1589
+ );
1590
+ row[`${prefix}_LiqSpike_Log`] = safeLog1pPositive(
1591
+ toNumber(intervalContext?.liqSpikeRatio, 0)
1592
+ );
1593
+ row[`${prefix}_LiqSpike_Squashed`] = squash(
1594
+ toNumber(intervalContext?.liqSpikeRatio, 0),
1595
+ 3
1596
+ );
1597
+ }
1598
+ };
1465
1599
  const applyLabelPhase = () => {
1466
1600
  const profit = toNumber(resultRecord?.profit, NaN);
1467
1601
  row.label = Number.isFinite(profit) ? profit > 0 ? 1 : 0 : null;
@@ -1471,6 +1605,7 @@ var buildMlTrainingRow = (signalRecord, resultRecord) => {
1471
1605
  applyRegimePhase();
1472
1606
  applySeriesAnalysisPhase();
1473
1607
  applyTrendlinePhase();
1608
+ applyDerivativesPhase();
1474
1609
  applyLabelPhase();
1475
1610
  return row;
1476
1611
  };
@@ -1605,6 +1740,7 @@ var findLookaheadViolations = (row) => {
1605
1740
  isDerivedDatasetFileName,
1606
1741
  isTimestampFeatureKey,
1607
1742
  listMlChunkFiles,
1743
+ listMlChunkStrategies,
1608
1744
  mergeJsonlFiles,
1609
1745
  toFileToken,
1610
1746
  toIsoUtcOrNull,
package/dist/ml.mjs CHANGED
@@ -1,142 +1,37 @@
1
+ import {
2
+ appendMlDatasetRow,
3
+ closeAllMlDatasetWriters,
4
+ closeMlDatasetWriter,
5
+ flushMlDatasetWriter,
6
+ getMlChunkFilePath,
7
+ listMlChunkFiles,
8
+ listMlChunkStrategies,
9
+ mergeJsonlFiles,
10
+ toFileToken
11
+ } from "./chunk-KVEZORMS.mjs";
1
12
  import {
2
13
  logger
3
14
  } from "./chunk-LNFUOXDW.mjs";
4
15
 
5
- // src/mlDatasetFile.ts
6
- import { once } from "events";
7
- import { createReadStream, createWriteStream } from "fs";
8
- import fs from "fs/promises";
9
- import path from "path";
10
- var DEFAULT_DIR = "data/ml/export";
11
- var ML_DATASET_WRITE_BATCH_SIZE = 200;
12
- var writerByPath = /* @__PURE__ */ new Map();
13
- var toFileToken = (value) => value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "any";
14
- var getMlChunkFilePath = (strategyName, chunkId, outDir = DEFAULT_DIR) => path.join(
15
- outDir,
16
- `ml-dataset-${toFileToken(strategyName)}-chunk-${toFileToken(chunkId)}.jsonl`
17
- );
18
- var appendMlDatasetRow = async (params) => {
19
- const { strategyName, chunkId, row, outDir = DEFAULT_DIR } = params;
20
- const filePath = getMlChunkFilePath(strategyName, chunkId, outDir);
21
- let state = writerByPath.get(filePath);
22
- if (!state) {
23
- await fs.mkdir(outDir, { recursive: true });
24
- const stream = createWriteStream(filePath, {
25
- encoding: "utf8",
26
- flags: "a"
27
- });
28
- state = {
29
- filePath,
30
- stream,
31
- buffer: [],
32
- writeQueue: Promise.resolve(),
33
- closed: false
34
- };
35
- writerByPath.set(filePath, state);
36
- }
37
- if (state.closed) {
38
- throw new Error(`ML dataset writer is closed: ${filePath}`);
39
- }
40
- state.buffer.push(`${JSON.stringify(row)}
41
- `);
42
- if (state.buffer.length >= ML_DATASET_WRITE_BATCH_SIZE) {
43
- await flushMlDatasetWriter(filePath);
44
- }
45
- return filePath;
46
- };
47
- var flushState = async (state) => {
48
- if (state.closed || state.buffer.length === 0) {
49
- return;
50
- }
51
- const chunk = state.buffer.join("");
52
- state.buffer = [];
53
- if (!state.stream.write(chunk)) {
54
- await once(state.stream, "drain");
55
- }
56
- };
57
- var flushMlDatasetWriter = async (filePath) => {
58
- const state = writerByPath.get(filePath);
59
- if (!state || state.closed) {
60
- return;
61
- }
62
- state.writeQueue = state.writeQueue.then(() => flushState(state));
63
- await state.writeQueue;
64
- };
65
- var closeState = async (state) => {
66
- if (state.closed) {
67
- return;
68
- }
69
- await flushState(state);
70
- state.closed = true;
71
- state.stream.end();
72
- await Promise.all([
73
- once(state.stream, "finish"),
74
- once(state.stream, "close")
75
- ]);
76
- };
77
- var closeMlDatasetWriter = async (filePath) => {
78
- const state = writerByPath.get(filePath);
79
- if (!state) return;
80
- state.writeQueue = state.writeQueue.then(() => closeState(state));
81
- await state.writeQueue;
82
- writerByPath.delete(filePath);
83
- };
84
- var closeAllMlDatasetWriters = async () => {
85
- const filePaths = [...writerByPath.keys()];
86
- for (const filePath of filePaths) {
87
- await closeMlDatasetWriter(filePath);
88
- }
89
- };
90
- var listMlChunkFiles = async (params) => {
91
- const { strategyName, outDir = DEFAULT_DIR } = params;
92
- const prefix = `ml-dataset-${toFileToken(strategyName)}-chunk-`;
93
- let entries = [];
94
- try {
95
- entries = await fs.readdir(outDir);
96
- } catch (error) {
97
- return [];
98
- }
99
- return entries.filter((name) => name.startsWith(prefix) && name.endsWith(".jsonl")).map((name) => path.join(outDir, name)).sort();
100
- };
101
- var mergeJsonlFiles = async (params) => {
102
- const { filePaths, outPath } = params;
103
- await fs.mkdir(path.dirname(outPath), { recursive: true });
104
- const stream = createWriteStream(outPath, { encoding: "utf8" });
105
- const done = Promise.all([once(stream, "finish"), once(stream, "close")]);
106
- try {
107
- for (const filePath of filePaths) {
108
- const reader = createReadStream(filePath, { encoding: "utf8" });
109
- for await (const chunk of reader) {
110
- if (!stream.write(chunk)) {
111
- await once(stream, "drain");
112
- }
113
- }
114
- }
115
- } finally {
116
- stream.end();
117
- await done;
118
- }
119
- };
120
-
121
16
  // src/mlGrpc.ts
122
- import path2 from "path";
17
+ import path from "path";
123
18
  import * as grpc from "@grpc/grpc-js";
124
19
  import * as protoLoader from "@grpc/proto-loader";
125
- import fs2 from "fs";
20
+ import fs from "fs";
126
21
  var clientCache = /* @__PURE__ */ new Map();
127
22
  var resolveProtoPath = (projectRoot, protoPath) => {
128
- if (protoPath && fs2.existsSync(protoPath)) {
23
+ if (protoPath && fs.existsSync(protoPath)) {
129
24
  return protoPath;
130
25
  }
131
26
  const explicitRoot = String(projectRoot || "").trim();
132
- const root = explicitRoot ? path2.resolve(explicitRoot) : String(process.env.PROJECT_CWD || "").trim() ? path2.resolve(String(process.env.PROJECT_CWD || "").trim()) : process.cwd();
27
+ const root = explicitRoot ? path.resolve(explicitRoot) : String(process.env.PROJECT_CWD || "").trim() ? path.resolve(String(process.env.PROJECT_CWD || "").trim()) : process.cwd();
133
28
  const candidates = [
134
- path2.resolve(__dirname, "../proto/ml_infer.proto"),
135
- path2.resolve(__dirname, "../../proto/ml_infer.proto"),
136
- path2.resolve(root, "proto/ml_infer.proto")
29
+ path.resolve(__dirname, "../proto/ml_infer.proto"),
30
+ path.resolve(__dirname, "../../proto/ml_infer.proto"),
31
+ path.resolve(root, "proto/ml_infer.proto")
137
32
  ];
138
33
  for (const candidate of candidates) {
139
- if (fs2.existsSync(candidate)) {
34
+ if (fs.existsSync(candidate)) {
140
35
  return candidate;
141
36
  }
142
37
  }
@@ -530,6 +425,44 @@ var INDICATOR_TIMEFRAMES = [
530
425
  { label: "TF4H", suffix: "4h" },
531
426
  { label: "TF1D", suffix: "1d" }
532
427
  ];
428
+ var DERIVATIVES_INTERVALS = [
429
+ { label: "15M", key: "15m" },
430
+ { label: "1H", key: "1h" }
431
+ ];
432
+ var DERIVATIVES_PRESSURES = [
433
+ "neutral",
434
+ "crowded_long",
435
+ "crowded_short",
436
+ "long_flush",
437
+ "short_flush"
438
+ ];
439
+ var DERIVATIVES_PRESSURE_LABELS = {
440
+ neutral: "Neutral",
441
+ crowded_long: "CrowdedLong",
442
+ crowded_short: "CrowdedShort",
443
+ long_flush: "LongFlush",
444
+ short_flush: "ShortFlush"
445
+ };
446
+ var DERIVATIVES_RISK_FLAGS = [
447
+ "missing_derivatives",
448
+ "stale_derivatives",
449
+ "crowded_long",
450
+ "crowded_short",
451
+ "oi_falling",
452
+ "oi_not_confirming",
453
+ "long_liquidation_spike",
454
+ "short_liquidation_spike"
455
+ ];
456
+ var DERIVATIVES_RISK_FLAG_LABELS = {
457
+ missing_derivatives: "Missing",
458
+ stale_derivatives: "Stale",
459
+ crowded_long: "CrowdedLong",
460
+ crowded_short: "CrowdedShort",
461
+ oi_falling: "OiFalling",
462
+ oi_not_confirming: "OiNotConfirming",
463
+ long_liquidation_spike: "LongLiqSpike",
464
+ short_liquidation_spike: "ShortLiqSpike"
465
+ };
533
466
  var toNumber = (value, fallback = 0) => {
534
467
  if (typeof value === "number") {
535
468
  return Number.isFinite(value) ? value : fallback;
@@ -650,6 +583,7 @@ var sliceWindow = (values, endIndex, windowSize) => {
650
583
  return values.slice(start, endIndex + 1);
651
584
  };
652
585
  var asArray = (value) => Array.isArray(value) ? value : [];
586
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
653
587
  var dropLastFromIndicatorSeries = (indicators) => {
654
588
  if (!ML_WINDOW_POLICY.dropLastIndicatorElement) {
655
589
  return indicators;
@@ -1371,6 +1305,85 @@ var buildMlTrainingRow = (signalRecord, resultRecord) => {
1371
1305
  row.TrendLine_Slope = null;
1372
1306
  }
1373
1307
  };
1308
+ const applyDerivativesPhase = () => {
1309
+ const context2 = asRecord(signal?.additionalIndicators?.derivativesContext);
1310
+ const summary = asRecord(context2?.summary);
1311
+ const intervals = asRecord(context2?.intervals);
1312
+ const pressure = typeof summary?.pressure === "string" ? summary.pressure : "";
1313
+ const riskFlags = new Set(
1314
+ asArray(summary?.riskFlags).map((flag) => String(flag))
1315
+ );
1316
+ row.Deriv_HasContext = context2 ? 1 : 0;
1317
+ row.Deriv_Source_Coinalyze = context2?.source === "coinalyze" ? 1 : 0;
1318
+ row.Deriv_DirectionAligned = summary?.directionAligned === true ? 1 : summary?.directionAligned === false ? -1 : 0;
1319
+ for (const pressureName of DERIVATIVES_PRESSURES) {
1320
+ row[`Deriv_Pressure_${DERIVATIVES_PRESSURE_LABELS[pressureName]}`] = pressure === pressureName ? 1 : 0;
1321
+ }
1322
+ for (const riskFlag of DERIVATIVES_RISK_FLAGS) {
1323
+ row[`Deriv_Flag_${DERIVATIVES_RISK_FLAG_LABELS[riskFlag]}`] = riskFlags.has(riskFlag) ? 1 : 0;
1324
+ }
1325
+ for (const { label, key: intervalKey } of DERIVATIVES_INTERVALS) {
1326
+ const intervalContext = asRecord(intervals?.[intervalKey]);
1327
+ const prefix = `Deriv_${label}`;
1328
+ const asOfTs = toNumber(intervalContext?.asOfTs, entryTimestamp);
1329
+ const ageHours = intervalContext ? Math.max(0, safeDiv2(entryTimestamp - asOfTs, 60 * 60 * 1e3)) : 0;
1330
+ row[`${prefix}_Present`] = intervalContext ? 1 : 0;
1331
+ row[`${prefix}_Stale`] = intervalContext?.stale === true ? 1 : 0;
1332
+ row[`${prefix}_AgeHours_Log`] = safeLog1pPositive(ageHours);
1333
+ row[`${prefix}_Points_Log`] = safeLog1pPositive(
1334
+ toNumber(intervalContext?.points, 0)
1335
+ );
1336
+ row[`${prefix}_OpenInterest_Log`] = safeLog1pPositive(
1337
+ toNumber(intervalContext?.openInterest, 0)
1338
+ );
1339
+ row[`${prefix}_OiChange1h`] = clamp2(
1340
+ toNumber(intervalContext?.oiChangePct1h, 0) / 100,
1341
+ -5,
1342
+ 5
1343
+ );
1344
+ row[`${prefix}_OiChange4h`] = clamp2(
1345
+ toNumber(intervalContext?.oiChangePct4h, 0) / 100,
1346
+ -5,
1347
+ 5
1348
+ );
1349
+ row[`${prefix}_OiChange24h`] = clamp2(
1350
+ toNumber(intervalContext?.oiChangePct24h, 0) / 100,
1351
+ -5,
1352
+ 5
1353
+ );
1354
+ row[`${prefix}_FundingBps`] = clamp2(
1355
+ toNumber(intervalContext?.fundingRate, 0) * 1e4,
1356
+ -100,
1357
+ 100
1358
+ );
1359
+ row[`${prefix}_FundingZ`] = clamp2(
1360
+ toNumber(intervalContext?.fundingZScore, 0),
1361
+ -8,
1362
+ 8
1363
+ );
1364
+ row[`${prefix}_LiqLong_Log`] = safeLog1pPositive(
1365
+ toNumber(intervalContext?.liqLong, 0)
1366
+ );
1367
+ row[`${prefix}_LiqShort_Log`] = safeLog1pPositive(
1368
+ toNumber(intervalContext?.liqShort, 0)
1369
+ );
1370
+ row[`${prefix}_LiqTotal_Log`] = safeLog1pPositive(
1371
+ toNumber(intervalContext?.liqTotal, 0)
1372
+ );
1373
+ row[`${prefix}_LiqImbalance`] = clamp2(
1374
+ toNumber(intervalContext?.liqImbalance, 0),
1375
+ -1,
1376
+ 1
1377
+ );
1378
+ row[`${prefix}_LiqSpike_Log`] = safeLog1pPositive(
1379
+ toNumber(intervalContext?.liqSpikeRatio, 0)
1380
+ );
1381
+ row[`${prefix}_LiqSpike_Squashed`] = squash(
1382
+ toNumber(intervalContext?.liqSpikeRatio, 0),
1383
+ 3
1384
+ );
1385
+ }
1386
+ };
1374
1387
  const applyLabelPhase = () => {
1375
1388
  const profit = toNumber(resultRecord?.profit, NaN);
1376
1389
  row.label = Number.isFinite(profit) ? profit > 0 ? 1 : 0 : null;
@@ -1380,6 +1393,7 @@ var buildMlTrainingRow = (signalRecord, resultRecord) => {
1380
1393
  applyRegimePhase();
1381
1394
  applySeriesAnalysisPhase();
1382
1395
  applyTrendlinePhase();
1396
+ applyDerivativesPhase();
1383
1397
  applyLabelPhase();
1384
1398
  return row;
1385
1399
  };
@@ -1513,6 +1527,7 @@ export {
1513
1527
  isDerivedDatasetFileName,
1514
1528
  isTimestampFeatureKey,
1515
1529
  listMlChunkFiles,
1530
+ listMlChunkStrategies,
1516
1531
  mergeJsonlFiles,
1517
1532
  toFileToken,
1518
1533
  toIsoUtcOrNull,