@coinrithm/mcp-trading 0.7.15 → 0.7.16
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 +127 -8
- package/README.md +18 -3
- package/dist/agent/bench/bench.d.ts +59 -0
- package/dist/agent/bench/bench.js +319 -0
- package/dist/agent/bench/cassette.d.ts +97 -0
- package/dist/agent/bench/cassette.js +181 -0
- package/dist/agent/bench/costs.d.ts +28 -0
- package/dist/agent/bench/costs.js +60 -0
- package/dist/agent/bench/labelBuilder.d.ts +123 -0
- package/dist/agent/bench/labelBuilder.js +282 -0
- package/dist/agent/bench/labels.d.ts +75 -0
- package/dist/agent/bench/labels.js +259 -0
- package/dist/agent/bench/recordingClient.d.ts +51 -0
- package/dist/agent/bench/recordingClient.js +193 -0
- package/dist/agent/bench/replayClient.d.ts +41 -0
- package/dist/agent/bench/replayClient.js +262 -0
- package/dist/agent/bench/report.d.ts +115 -0
- package/dist/agent/bench/report.js +435 -0
- package/dist/agent/cli.d.ts +25 -0
- package/dist/agent/cli.js +300 -2
- package/dist/agent/client.d.ts +1 -0
- package/dist/agent/client.js +6 -0
- package/dist/agent/configurationWarnings.js +11 -0
- package/dist/agent/decision.d.ts +3 -0
- package/dist/agent/decision.js +85 -3
- package/dist/agent/decisionValidator.d.ts +30 -0
- package/dist/agent/decisionValidator.js +147 -2
- package/dist/agent/engine.d.ts +3 -3
- package/dist/agent/engine.js +3 -3
- package/dist/agent/observe.d.ts +27 -2
- package/dist/agent/observe.js +529 -89
- package/dist/agent/prompt.d.ts +4 -1
- package/dist/agent/prompt.js +98 -7
- package/dist/agent/promptTables.d.ts +1 -0
- package/dist/agent/promptTables.js +33 -0
- package/dist/agent/providerCapabilities.d.ts +6 -0
- package/dist/agent/providerCapabilities.js +23 -0
- package/dist/agent/providers.d.ts +25 -7
- package/dist/agent/providers.js +188 -48
- package/dist/agent/resolve.js +3 -0
- package/dist/agent/runner.d.ts +8 -0
- package/dist/agent/runner.js +59 -15
- package/dist/agent/setups.d.ts +2 -1
- package/dist/agent/setups.js +17 -16
- package/dist/agent/signals.d.ts +33 -0
- package/dist/agent/signals.js +89 -0
- package/dist/agent/skill.js +37 -0
- package/dist/agent/skillValidator.js +33 -0
- package/dist/agent/strictLint.js +29 -0
- package/dist/agent/types.d.ts +149 -12
- package/dist/agent/universe.d.ts +78 -0
- package/dist/agent/universe.js +215 -0
- package/package.json +3 -2
package/dist/agent/setups.js
CHANGED
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Pure + stateless: no I/O, no model. Strategy-neutral — it reports the structure
|
|
12
12
|
// and the trend-following bias; a contrarian agent fades the same facts.
|
|
13
|
+
import { DEFAULT_SIGNAL_THRESHOLDS } from "./signals.js";
|
|
13
14
|
// Thresholds tuned to FIRE readily on a normal moving market (the failure mode we
|
|
14
15
|
// are fixing is under-firing). A genuinely flat tape still yields an empty list,
|
|
15
16
|
// which is the correct "nothing to do" signal.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
// Defaults live in signals.ts (DEFAULT_SIGNAL_THRESHOLDS): |24h %| 2.0 for a
|
|
18
|
+
// strong push, 0.8 for a move that still confirms an EMA-stack trend, RSI 35 /
|
|
19
|
+
// 68, and 0.5 as the strength under which nothing is flagged. An agent may
|
|
20
|
+
// declare its own (spec.signals), which changes both this shortlist and the
|
|
21
|
+
// gate that decides whether the model is called.
|
|
21
22
|
function pct(n) {
|
|
22
23
|
return `${n >= 0 ? "+" : ""}${n.toFixed(1)}%`;
|
|
23
24
|
}
|
|
@@ -34,7 +35,7 @@ export function baseSymbol(s) {
|
|
|
34
35
|
// signal — the same structure is a momentum trade to a trend-follower and a
|
|
35
36
|
// mean-reversion trade to a contrarian, so we surface both and let each agent pick
|
|
36
37
|
// the one matching its style (fixes contrarians skipping "no setup fits me").
|
|
37
|
-
function classify(w, openPositions) {
|
|
38
|
+
function classify(w, openPositions, t) {
|
|
38
39
|
const ind = w.indicators;
|
|
39
40
|
if (!ind)
|
|
40
41
|
return [];
|
|
@@ -42,8 +43,8 @@ function classify(w, openPositions) {
|
|
|
42
43
|
const rsi = ind.rsi14;
|
|
43
44
|
const up = ind.ema20AboveEma50 === true && ind.aboveEma20 === true;
|
|
44
45
|
const down = ind.ema20AboveEma50 === false && ind.aboveEma20 === false;
|
|
45
|
-
const oversold = rsi != null && rsi <=
|
|
46
|
-
const overbought = rsi != null && rsi >=
|
|
46
|
+
const oversold = rsi != null && rsi <= t.rsiOversold;
|
|
47
|
+
const overbought = rsi != null && rsi >= t.rsiOverbought;
|
|
47
48
|
// Compact, factual note the model reads (no interpretation — just the structure).
|
|
48
49
|
const facts = [`${pct(ch)} 24h`];
|
|
49
50
|
if (up)
|
|
@@ -79,21 +80,21 @@ function classify(w, openPositions) {
|
|
|
79
80
|
note,
|
|
80
81
|
});
|
|
81
82
|
}
|
|
82
|
-
else if (up && ch >=
|
|
83
|
+
else if (up && ch >= t.leanMovePct) {
|
|
83
84
|
out.push({
|
|
84
85
|
symbol: w.symbol,
|
|
85
86
|
kind: "uptrend",
|
|
86
87
|
bias: "long",
|
|
87
|
-
strength: ch >=
|
|
88
|
+
strength: ch >= t.strongMovePct ? 0.75 : 0.6,
|
|
88
89
|
note,
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
|
-
else if (down && ch <= -
|
|
92
|
+
else if (down && ch <= -t.leanMovePct) {
|
|
92
93
|
out.push({
|
|
93
94
|
symbol: w.symbol,
|
|
94
95
|
kind: "downtrend",
|
|
95
96
|
bias: "short",
|
|
96
|
-
strength: ch <= -
|
|
97
|
+
strength: ch <= -t.strongMovePct ? 0.75 : 0.6,
|
|
97
98
|
note,
|
|
98
99
|
});
|
|
99
100
|
}
|
|
@@ -115,7 +116,7 @@ function classify(w, openPositions) {
|
|
|
115
116
|
note,
|
|
116
117
|
});
|
|
117
118
|
}
|
|
118
|
-
else if (Math.abs(ch) >=
|
|
119
|
+
else if (Math.abs(ch) >= t.strongMovePct) {
|
|
119
120
|
// A strong move with no clean EMA stack — still tradeable momentum.
|
|
120
121
|
out.push({
|
|
121
122
|
symbol: w.symbol,
|
|
@@ -170,11 +171,11 @@ function classify(w, openPositions) {
|
|
|
170
171
|
// Scan the whole watchlist, return the flagged setups strongest-first. An empty
|
|
171
172
|
// list = a flat tape = a legitimate reason to skip new entries this cycle. Setups
|
|
172
173
|
// on a symbol we already hold are tagged `held` (manage, don't re-open).
|
|
173
|
-
export function scanSetups(watch, openPositions = []) {
|
|
174
|
+
export function scanSetups(watch, openPositions = [], thresholds = DEFAULT_SIGNAL_THRESHOLDS) {
|
|
174
175
|
const out = [];
|
|
175
176
|
for (const w of watch) {
|
|
176
|
-
for (const s of classify(w, openPositions))
|
|
177
|
-
if (s.strength >=
|
|
177
|
+
for (const s of classify(w, openPositions, thresholds))
|
|
178
|
+
if (s.strength >= thresholds.minStrength)
|
|
178
179
|
out.push(s);
|
|
179
180
|
}
|
|
180
181
|
return out.sort((a, b) => b.strength - a.strength);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AgentSpec } from "./types.js";
|
|
2
|
+
export interface SignalThresholds {
|
|
3
|
+
rsiOversold: number;
|
|
4
|
+
rsiOverbought: number;
|
|
5
|
+
strongMovePct: number;
|
|
6
|
+
leanMovePct: number;
|
|
7
|
+
minStrength: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const DEFAULT_SIGNAL_THRESHOLDS: SignalThresholds;
|
|
10
|
+
/** Candle range -> bar interval, as GET /api/agent/market/:id/candles serves. */
|
|
11
|
+
export declare const INDICATOR_RANGES: {
|
|
12
|
+
readonly "1D": 300;
|
|
13
|
+
readonly "1W": 900;
|
|
14
|
+
readonly "1M": 3600;
|
|
15
|
+
readonly "3M": 14400;
|
|
16
|
+
};
|
|
17
|
+
export type IndicatorRange = keyof typeof INDICATOR_RANGES;
|
|
18
|
+
export declare const DEFAULT_INDICATOR_RANGE: IndicatorRange;
|
|
19
|
+
/** Effective thresholds: declared values over the defaults. */
|
|
20
|
+
export declare function signalThresholdsOf(spec: {
|
|
21
|
+
signals?: Partial<SignalThresholds>;
|
|
22
|
+
}): SignalThresholds;
|
|
23
|
+
export declare function indicatorRangeOf(spec: {
|
|
24
|
+
data?: {
|
|
25
|
+
indicatorRange?: IndicatorRange;
|
|
26
|
+
};
|
|
27
|
+
}): IndicatorRange;
|
|
28
|
+
/** "5-minute", "15-minute", "1-hour", "4-hour". */
|
|
29
|
+
export declare function barLabel(range: IndicatorRange): string;
|
|
30
|
+
/** Validation issues for raw `signals` / `data` blocks ([] when valid). */
|
|
31
|
+
export declare function signalIssues(rawSignals: unknown, rawData: unknown): Array<[string, string]>;
|
|
32
|
+
/** Type guard used where only the spec shape is known. */
|
|
33
|
+
export type SignalSpec = Pick<AgentSpec, "signals" | "data">;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// What counts as a signal, per agent (owner 2026-10-07, Telegram 56624: show
|
|
2
|
+
// that personality changes trade handling; audit: setup thresholds and the
|
|
3
|
+
// indicator window were hard-coded for every agent).
|
|
4
|
+
//
|
|
5
|
+
// signals: # optional; absent = the platform defaults below
|
|
6
|
+
// rsiOversold: 30 # RSI at or under this flags an oversold fade
|
|
7
|
+
// rsiOverbought: 72 # RSI at or over this flags an overbought fade
|
|
8
|
+
// strongMovePct: 3 # |24h %| that counts as a strong push
|
|
9
|
+
// leanMovePct: 1 # smaller move that still confirms a trend stack
|
|
10
|
+
// minStrength: 0.6 # flags under this are not raised at all
|
|
11
|
+
// data:
|
|
12
|
+
// indicatorRange: 1W # 1D = 5m bars, 1W = 15m, 1M = 1h, 3M = 4h
|
|
13
|
+
//
|
|
14
|
+
// The flags feed both the prompt's setup shortlist AND the deterministic gate
|
|
15
|
+
// that decides whether a cycle calls the model at all, so these numbers change
|
|
16
|
+
// what the agent notices and when it thinks, not just what it is told.
|
|
17
|
+
export const DEFAULT_SIGNAL_THRESHOLDS = {
|
|
18
|
+
rsiOversold: 35,
|
|
19
|
+
rsiOverbought: 68,
|
|
20
|
+
strongMovePct: 2.0,
|
|
21
|
+
leanMovePct: 0.8,
|
|
22
|
+
minStrength: 0.5,
|
|
23
|
+
};
|
|
24
|
+
/** Candle range -> bar interval, as GET /api/agent/market/:id/candles serves. */
|
|
25
|
+
export const INDICATOR_RANGES = {
|
|
26
|
+
"1D": 300,
|
|
27
|
+
"1W": 900,
|
|
28
|
+
"1M": 3_600,
|
|
29
|
+
"3M": 14_400,
|
|
30
|
+
};
|
|
31
|
+
export const DEFAULT_INDICATOR_RANGE = "1D";
|
|
32
|
+
/** Effective thresholds: declared values over the defaults. */
|
|
33
|
+
export function signalThresholdsOf(spec) {
|
|
34
|
+
return { ...DEFAULT_SIGNAL_THRESHOLDS, ...(spec.signals ?? {}) };
|
|
35
|
+
}
|
|
36
|
+
export function indicatorRangeOf(spec) {
|
|
37
|
+
return spec.data?.indicatorRange ?? DEFAULT_INDICATOR_RANGE;
|
|
38
|
+
}
|
|
39
|
+
/** "5-minute", "15-minute", "1-hour", "4-hour". */
|
|
40
|
+
export function barLabel(range) {
|
|
41
|
+
const s = INDICATOR_RANGES[range];
|
|
42
|
+
return s < 3_600 ? `${s / 60}-minute` : `${s / 3_600}-hour`;
|
|
43
|
+
}
|
|
44
|
+
const BOUNDS = {
|
|
45
|
+
rsiOversold: [5, 50],
|
|
46
|
+
rsiOverbought: [50, 95],
|
|
47
|
+
strongMovePct: [0.1, 50],
|
|
48
|
+
leanMovePct: [0.05, 20],
|
|
49
|
+
minStrength: [0, 1],
|
|
50
|
+
};
|
|
51
|
+
/** Validation issues for raw `signals` / `data` blocks ([] when valid). */
|
|
52
|
+
export function signalIssues(rawSignals, rawData) {
|
|
53
|
+
const issues = [];
|
|
54
|
+
const add = (code, reason) => issues.push([code, reason]);
|
|
55
|
+
const isMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
56
|
+
if (rawSignals !== undefined) {
|
|
57
|
+
if (!isMap(rawSignals)) {
|
|
58
|
+
add("skill_signals", "signals must be a mapping");
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
for (const [key, [lo, hi]] of Object.entries(BOUNDS)) {
|
|
62
|
+
const v = rawSignals[key];
|
|
63
|
+
if (v === undefined)
|
|
64
|
+
continue;
|
|
65
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < lo || v > hi)
|
|
66
|
+
add("skill_signals_range", `signals.${key} must be a number between ${lo} and ${hi}`);
|
|
67
|
+
}
|
|
68
|
+
const t = signalThresholdsOf({
|
|
69
|
+
signals: rawSignals,
|
|
70
|
+
});
|
|
71
|
+
if (t.rsiOversold >= t.rsiOverbought)
|
|
72
|
+
add("skill_signals_rsi", "signals.rsiOversold must be below signals.rsiOverbought");
|
|
73
|
+
if (t.leanMovePct > t.strongMovePct)
|
|
74
|
+
add("skill_signals_moves", "signals.leanMovePct must not exceed signals.strongMovePct");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (rawData !== undefined) {
|
|
78
|
+
if (!isMap(rawData)) {
|
|
79
|
+
add("skill_data", "data must be a mapping");
|
|
80
|
+
}
|
|
81
|
+
else if (rawData.indicatorRange !== undefined &&
|
|
82
|
+
// A real string only: String(['1W']) is "1W" and must not pass.
|
|
83
|
+
(typeof rawData.indicatorRange !== "string" ||
|
|
84
|
+
!Object.hasOwn(INDICATOR_RANGES, rawData.indicatorRange))) {
|
|
85
|
+
add("skill_data_range", `data.indicatorRange must be one of ${Object.keys(INDICATOR_RANGES).join(", ")}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return issues;
|
|
89
|
+
}
|
package/dist/agent/skill.js
CHANGED
|
@@ -118,6 +118,32 @@ export function buildSpec(raw) {
|
|
|
118
118
|
pmMinEntryProbabilityPct: risk.pmMinEntryProbabilityPct,
|
|
119
119
|
}
|
|
120
120
|
: {}),
|
|
121
|
+
// The three PM policy dials follow the same as-written rule.
|
|
122
|
+
...(risk.pmMinEdgeGapPct !== undefined
|
|
123
|
+
? {
|
|
124
|
+
pmMinEdgeGapPct: risk.pmMinEdgeGapPct,
|
|
125
|
+
}
|
|
126
|
+
: {}),
|
|
127
|
+
...(risk.pmMaxEdgePoints !== undefined
|
|
128
|
+
? {
|
|
129
|
+
pmMaxEdgePoints: risk.pmMaxEdgePoints,
|
|
130
|
+
}
|
|
131
|
+
: {}),
|
|
132
|
+
...(risk.pmMaxOpenPerEvent !== undefined
|
|
133
|
+
? {
|
|
134
|
+
pmMaxOpenPerEvent: risk.pmMaxOpenPerEvent,
|
|
135
|
+
}
|
|
136
|
+
: {}),
|
|
137
|
+
...(risk.pmMaxOpenPerCoinHorizon !== undefined
|
|
138
|
+
? {
|
|
139
|
+
pmMaxOpenPerCoinHorizon: risk.pmMaxOpenPerCoinHorizon,
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
142
|
+
...(risk.pmMinMinutesToClose !== undefined
|
|
143
|
+
? {
|
|
144
|
+
pmMinMinutesToClose: risk.pmMinMinutesToClose,
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
121
147
|
maxLeverage: num(risk.maxLeverage, 1),
|
|
122
148
|
perTradeMarginMusd: num(risk.perTradeMarginMusd, 0),
|
|
123
149
|
maxConcurrentPositions: num(risk.maxConcurrentPositions, 0),
|
|
@@ -155,6 +181,17 @@ export function buildSpec(raw) {
|
|
|
155
181
|
},
|
|
156
182
|
objective: buildObjective(raw.objective),
|
|
157
183
|
capabilities: strArr(raw.capabilities).filter((c) => ALLOWED_CAPABILITIES.includes(c)),
|
|
184
|
+
// Signal thresholds and data diet: carried AS WRITTEN so skillValidator
|
|
185
|
+
// fails closed on a malformed block instead of a typo meaning "defaults".
|
|
186
|
+
...(raw.signals !== undefined
|
|
187
|
+
? { signals: raw.signals }
|
|
188
|
+
: {}),
|
|
189
|
+
...(raw.data !== undefined ? { data: raw.data } : {}),
|
|
190
|
+
// Market boundaries: carried AS WRITTEN (an object) so skillValidator can
|
|
191
|
+
// fail closed on a malformed block instead of a typo meaning "no limits".
|
|
192
|
+
...(raw.universe !== undefined
|
|
193
|
+
? { universe: raw.universe }
|
|
194
|
+
: {}),
|
|
158
195
|
// OKF v2 (load-bearing): the gate reads this; omitted -> DEFAULT_TRIGGER_POLICY.
|
|
159
196
|
// This is the agent's INTENT — the platform deployment overlay may tighten it
|
|
160
197
|
// server-side, and it can never widen a hard cap (caps live in the runner).
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { fail, VENUES, PROVIDERS, SPEC_VERSION, OBJECTIVE_PRIMARIES, ALLOWED_CAPABILITIES, } from "./types.js";
|
|
2
|
+
import { universeIssues } from "./universe.js";
|
|
2
3
|
import { parseCadenceMs, scanForSecrets } from "./util.js";
|
|
4
|
+
import { signalIssues } from "./signals.js";
|
|
3
5
|
import { entryPredicateIssues } from "./entryPredicates.js";
|
|
4
6
|
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5
7
|
const isPosNum = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
@@ -88,6 +90,12 @@ export function validateSkill(parsed, mode = "self-host") {
|
|
|
88
90
|
add("skill_venue_unknown", `unknown venue "${String(v)}" (allowed: ${VENUES.join(", ")})`);
|
|
89
91
|
}
|
|
90
92
|
}
|
|
93
|
+
// Signal thresholds and data diet (optional).
|
|
94
|
+
for (const [code, reason] of signalIssues(raw.signals, raw.data))
|
|
95
|
+
add(code, reason);
|
|
96
|
+
// Market boundaries (optional).
|
|
97
|
+
for (const [code, reason] of universeIssues(raw.universe))
|
|
98
|
+
add(code, reason);
|
|
91
99
|
// Risk — always required
|
|
92
100
|
if (!isObj(raw.risk)) {
|
|
93
101
|
add("skill_risk", "risk block is required (the caps the agent runs under)");
|
|
@@ -115,6 +123,31 @@ export function validateSkill(parsed, mode = "self-host") {
|
|
|
115
123
|
if (typeof f !== "number" || !Number.isFinite(f) || f < 0 || f > 100)
|
|
116
124
|
add("skill_risk_pm_entry_floor", "risk.pmMinEntryProbabilityPct must be a number between 0 and 100 (points; omit for no floor)");
|
|
117
125
|
}
|
|
126
|
+
if (r.pmMinEdgeGapPct !== undefined) {
|
|
127
|
+
const g = r.pmMinEdgeGapPct;
|
|
128
|
+
if (typeof g !== "number" || !Number.isFinite(g) || g < 0 || g > 100)
|
|
129
|
+
add("skill_risk_pm_edge_gap", "risk.pmMinEdgeGapPct must be a number between 0 and 100 (% of the gap to 100; omit for the global minimum edge only)");
|
|
130
|
+
}
|
|
131
|
+
if (r.pmMaxEdgePoints !== undefined) {
|
|
132
|
+
const m = r.pmMaxEdgePoints;
|
|
133
|
+
if (typeof m !== "number" || !Number.isFinite(m) || m < 0 || m > 100)
|
|
134
|
+
add("skill_risk_pm_max_edge", "risk.pmMaxEdgePoints must be a number between 0 and 100 (points over cost; omit for no cap)");
|
|
135
|
+
}
|
|
136
|
+
if (r.pmMaxOpenPerEvent !== undefined) {
|
|
137
|
+
const n = r.pmMaxOpenPerEvent;
|
|
138
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 50)
|
|
139
|
+
add("skill_risk_pm_per_event", "risk.pmMaxOpenPerEvent must be a whole number between 1 and 50 (omit for no per-event cap)");
|
|
140
|
+
}
|
|
141
|
+
if (r.pmMaxOpenPerCoinHorizon !== undefined) {
|
|
142
|
+
const n = r.pmMaxOpenPerCoinHorizon;
|
|
143
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 50)
|
|
144
|
+
add("skill_risk_pm_coin_horizon", "risk.pmMaxOpenPerCoinHorizon must be a whole number between 1 and 50 (omit for no per-coin-horizon cap)");
|
|
145
|
+
}
|
|
146
|
+
if (r.pmMinMinutesToClose !== undefined) {
|
|
147
|
+
const m = r.pmMinMinutesToClose;
|
|
148
|
+
if (typeof m !== "number" || !Number.isFinite(m) || m < 0 || m > 10_080)
|
|
149
|
+
add("skill_risk_pm_close_cutoff", "risk.pmMinMinutesToClose must be a number of minutes between 0 and 10080 (omit for no cutoff)");
|
|
150
|
+
}
|
|
118
151
|
// Fail-closed on the side restriction: a typo ("shorts_only") must never
|
|
119
152
|
// silently mean "unrestricted" — that is exactly how a prose-only
|
|
120
153
|
// constraint failed live on 2026-08-24.
|
package/dist/agent/strictLint.js
CHANGED
|
@@ -30,8 +30,29 @@ const ALLOWED_KEYS = {
|
|
|
30
30
|
// it) but was missing here, so any bundle actually SETTING it got an
|
|
31
31
|
// unknown_key lint — the knob existed and was unreachable (audit rank 10).
|
|
32
32
|
"triggerPolicy",
|
|
33
|
+
"signals",
|
|
34
|
+
"data",
|
|
35
|
+
"universe",
|
|
33
36
|
],
|
|
34
37
|
trigger: ["cadence", "timezone", "events"],
|
|
38
|
+
signals: [
|
|
39
|
+
"rsiOversold",
|
|
40
|
+
"rsiOverbought",
|
|
41
|
+
"strongMovePct",
|
|
42
|
+
"leanMovePct",
|
|
43
|
+
"minStrength",
|
|
44
|
+
],
|
|
45
|
+
data: ["indicatorRange"],
|
|
46
|
+
universe: [
|
|
47
|
+
"rank",
|
|
48
|
+
"minVolume24hUsd",
|
|
49
|
+
"excludeStablecoins",
|
|
50
|
+
"includeSectors",
|
|
51
|
+
"excludeSectors",
|
|
52
|
+
"sort",
|
|
53
|
+
"resolveTop",
|
|
54
|
+
"scanLimit",
|
|
55
|
+
],
|
|
35
56
|
triggerPolicy: [
|
|
36
57
|
"mode",
|
|
37
58
|
"skipLlmWhenNoTrigger",
|
|
@@ -51,6 +72,11 @@ const ALLOWED_KEYS = {
|
|
|
51
72
|
"direction",
|
|
52
73
|
"entryPredicates",
|
|
53
74
|
"pmMinEntryProbabilityPct",
|
|
75
|
+
"pmMinEdgeGapPct",
|
|
76
|
+
"pmMaxEdgePoints",
|
|
77
|
+
"pmMaxOpenPerEvent",
|
|
78
|
+
"pmMaxOpenPerCoinHorizon",
|
|
79
|
+
"pmMinMinutesToClose",
|
|
54
80
|
],
|
|
55
81
|
sizing: null,
|
|
56
82
|
capitalSizing: [
|
|
@@ -141,6 +167,9 @@ export function strictLint(raw) {
|
|
|
141
167
|
"sync",
|
|
142
168
|
"killSwitch",
|
|
143
169
|
"objective",
|
|
170
|
+
"signals",
|
|
171
|
+
"data",
|
|
172
|
+
"universe",
|
|
144
173
|
]) {
|
|
145
174
|
if (isObj(raw[block]))
|
|
146
175
|
lintKeys(block, raw[block], issues);
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FuturesEntryEligibility } from "./futuresEligibility.js";
|
|
2
2
|
import { IndicatorSet } from "./indicators.js";
|
|
3
3
|
import type { DecisionInputRecord } from "./decisionReceipt.js";
|
|
4
|
+
import type { DecideRouteAttempt } from "./providers.js";
|
|
4
5
|
export declare const SPEC_VERSION = "coinrithm.agent.v1";
|
|
5
6
|
export type Venue = "spot" | "futures" | "pm";
|
|
6
7
|
export declare const VENUES: readonly Venue[];
|
|
@@ -36,6 +37,11 @@ export interface RiskConfig {
|
|
|
36
37
|
blocklist?: string[];
|
|
37
38
|
direction?: "long_only" | "short_only";
|
|
38
39
|
pmMinEntryProbabilityPct?: number;
|
|
40
|
+
pmMinEdgeGapPct?: number;
|
|
41
|
+
pmMaxEdgePoints?: number;
|
|
42
|
+
pmMaxOpenPerEvent?: number;
|
|
43
|
+
pmMaxOpenPerCoinHorizon?: number;
|
|
44
|
+
pmMinMinutesToClose?: number;
|
|
39
45
|
}
|
|
40
46
|
export interface LimitsConfig {
|
|
41
47
|
maxTradesPerDay: number;
|
|
@@ -116,6 +122,25 @@ export interface AgentSpec {
|
|
|
116
122
|
objective?: ObjectiveConfig;
|
|
117
123
|
capabilities: Capability[];
|
|
118
124
|
triggerPolicy?: TriggerPolicy;
|
|
125
|
+
signals?: Partial<import("./signals.js").SignalThresholds>;
|
|
126
|
+
data?: {
|
|
127
|
+
indicatorRange?: import("./signals.js").IndicatorRange;
|
|
128
|
+
};
|
|
129
|
+
universe?: UniverseConfig;
|
|
130
|
+
}
|
|
131
|
+
/** Declared market boundaries; see universe.ts for semantics and defaults. */
|
|
132
|
+
export interface UniverseConfig {
|
|
133
|
+
rank?: {
|
|
134
|
+
min?: number;
|
|
135
|
+
max?: number;
|
|
136
|
+
};
|
|
137
|
+
minVolume24hUsd?: number;
|
|
138
|
+
excludeStablecoins?: boolean;
|
|
139
|
+
includeSectors?: string[];
|
|
140
|
+
excludeSectors?: string[];
|
|
141
|
+
sort?: "gainers_24h" | "losers_24h" | "abs_change_24h" | "abs_change_1h" | "volume_24h" | "rank";
|
|
142
|
+
resolveTop?: number;
|
|
143
|
+
scanLimit?: number;
|
|
119
144
|
}
|
|
120
145
|
export interface ParsedSkill {
|
|
121
146
|
spec: AgentSpec;
|
|
@@ -136,8 +161,8 @@ export interface Freshness {
|
|
|
136
161
|
basis?: string;
|
|
137
162
|
}
|
|
138
163
|
export interface IndicatorContext {
|
|
139
|
-
range: "
|
|
140
|
-
nominalIntervalSeconds:
|
|
164
|
+
range: import("./signals.js").IndicatorRange;
|
|
165
|
+
nominalIntervalSeconds: number;
|
|
141
166
|
barCount: number;
|
|
142
167
|
timestampedBarCount: number;
|
|
143
168
|
checkedIntervalCount: number;
|
|
@@ -150,6 +175,105 @@ export interface IndicatorContext {
|
|
|
150
175
|
intervalStatus: "regular" | "irregular" | "unknown";
|
|
151
176
|
};
|
|
152
177
|
}
|
|
178
|
+
export interface OpenInterestContext {
|
|
179
|
+
totalUsd: number;
|
|
180
|
+
venues: string[];
|
|
181
|
+
change1hPct: number | null;
|
|
182
|
+
change1hVenues: string[];
|
|
183
|
+
change24hPct: number | null;
|
|
184
|
+
change24hVenues: string[];
|
|
185
|
+
asOf: string;
|
|
186
|
+
stale: boolean;
|
|
187
|
+
}
|
|
188
|
+
export interface DatedValue {
|
|
189
|
+
value: number;
|
|
190
|
+
asOf: string;
|
|
191
|
+
stale: boolean;
|
|
192
|
+
}
|
|
193
|
+
export interface PositioningContext {
|
|
194
|
+
venue: string;
|
|
195
|
+
symbol: string;
|
|
196
|
+
longShortAccountRatio?: DatedValue;
|
|
197
|
+
longAccountPct?: DatedValue;
|
|
198
|
+
topTraderPositionRatio?: DatedValue;
|
|
199
|
+
takerBuySellRatio?: DatedValue;
|
|
200
|
+
}
|
|
201
|
+
export interface LiquidationWindow {
|
|
202
|
+
longLiquidatedUsdt: number;
|
|
203
|
+
shortLiquidatedUsdt: number;
|
|
204
|
+
events: number;
|
|
205
|
+
capturedPct: number;
|
|
206
|
+
}
|
|
207
|
+
export interface LiquidationContext {
|
|
208
|
+
venue: string;
|
|
209
|
+
instId: string;
|
|
210
|
+
last1h: LiquidationWindow;
|
|
211
|
+
last24h: LiquidationWindow;
|
|
212
|
+
lastEventAt: string | null;
|
|
213
|
+
}
|
|
214
|
+
export interface MacroQuote {
|
|
215
|
+
symbol: string;
|
|
216
|
+
label: string;
|
|
217
|
+
kind: string;
|
|
218
|
+
price: number;
|
|
219
|
+
change24hPct: number | null;
|
|
220
|
+
asOf: string;
|
|
221
|
+
stale: boolean;
|
|
222
|
+
}
|
|
223
|
+
export interface MacroContext {
|
|
224
|
+
note: string;
|
|
225
|
+
quotes: MacroQuote[];
|
|
226
|
+
}
|
|
227
|
+
export interface ChainTvlContext {
|
|
228
|
+
chain: string;
|
|
229
|
+
tvlUsd: number;
|
|
230
|
+
publishedAt: string | null;
|
|
231
|
+
fetchedAt: string | null;
|
|
232
|
+
sourceObservedAt: null;
|
|
233
|
+
stale: boolean;
|
|
234
|
+
dayAt: string | null;
|
|
235
|
+
change1dPct: number | null;
|
|
236
|
+
change7dPct: number | null;
|
|
237
|
+
}
|
|
238
|
+
export interface StablecoinSupplyContext {
|
|
239
|
+
totalUsd: number;
|
|
240
|
+
dayAt: string;
|
|
241
|
+
change1dPct: number | null;
|
|
242
|
+
change7dPct: number | null;
|
|
243
|
+
stale: boolean;
|
|
244
|
+
}
|
|
245
|
+
export interface DepthSideContext {
|
|
246
|
+
usd: number;
|
|
247
|
+
reachPct: number;
|
|
248
|
+
levels: number;
|
|
249
|
+
complete: boolean;
|
|
250
|
+
}
|
|
251
|
+
export interface DepthContext {
|
|
252
|
+
venue: string;
|
|
253
|
+
bid: DepthSideContext | null;
|
|
254
|
+
ask: DepthSideContext | null;
|
|
255
|
+
asOf: string;
|
|
256
|
+
stale: boolean;
|
|
257
|
+
}
|
|
258
|
+
export interface FundingVenueRate {
|
|
259
|
+
role: "settlement_reference" | "context_only";
|
|
260
|
+
venue: string;
|
|
261
|
+
symbol: string;
|
|
262
|
+
source: "paper_futures_reference" | "hyperliquid_predicted_fundings";
|
|
263
|
+
rateFraction: number;
|
|
264
|
+
intervalHours: number | null;
|
|
265
|
+
hourlyEquivalentFraction: number | null;
|
|
266
|
+
nextFundingTime: string | null;
|
|
267
|
+
fetchedAt: string;
|
|
268
|
+
sourceAt: null;
|
|
269
|
+
ageSeconds: number;
|
|
270
|
+
stale: boolean;
|
|
271
|
+
freshnessBasis: "collection_time";
|
|
272
|
+
}
|
|
273
|
+
export interface FundingByVenueContext {
|
|
274
|
+
rates: FundingVenueRate[];
|
|
275
|
+
sameTime: false;
|
|
276
|
+
}
|
|
153
277
|
export interface WatchEntry {
|
|
154
278
|
symbol: string;
|
|
155
279
|
coinId: string | null;
|
|
@@ -166,8 +290,15 @@ export interface WatchEntry {
|
|
|
166
290
|
indicators?: IndicatorSet;
|
|
167
291
|
indicatorContext?: IndicatorContext;
|
|
168
292
|
discovered?: boolean;
|
|
293
|
+
withinBoundaries?: boolean;
|
|
169
294
|
slug?: string;
|
|
170
295
|
fundamentals?: CoinFundamentals;
|
|
296
|
+
openInterest?: OpenInterestContext;
|
|
297
|
+
positioning?: PositioningContext;
|
|
298
|
+
liquidations?: LiquidationContext;
|
|
299
|
+
chainTvl?: ChainTvlContext;
|
|
300
|
+
depth?: DepthContext;
|
|
301
|
+
fundingByVenue?: FundingByVenueContext;
|
|
171
302
|
futuresEntryEligibility?: FuturesEntryEligibility;
|
|
172
303
|
}
|
|
173
304
|
export interface CoinFundamentals {
|
|
@@ -227,6 +358,8 @@ export interface PmPosition {
|
|
|
227
358
|
entryProbability?: number;
|
|
228
359
|
currentProbability?: number;
|
|
229
360
|
openedAt?: string;
|
|
361
|
+
coins?: string[];
|
|
362
|
+
endDate?: string;
|
|
230
363
|
thesis?: ThesisView;
|
|
231
364
|
}
|
|
232
365
|
export interface PmResolution {
|
|
@@ -250,6 +383,7 @@ export interface PmMarket {
|
|
|
250
383
|
volumeUsd?: number;
|
|
251
384
|
endDate?: string;
|
|
252
385
|
liquidityUsd?: number;
|
|
386
|
+
coins?: string[];
|
|
253
387
|
quality?: PmQuality;
|
|
254
388
|
decisionSupport?: PmDecisionSupport;
|
|
255
389
|
consensus?: PmConsensus | null;
|
|
@@ -322,6 +456,8 @@ export interface NewsItem {
|
|
|
322
456
|
ageHours?: number;
|
|
323
457
|
publishedAt?: string;
|
|
324
458
|
coins?: string[];
|
|
459
|
+
url?: string;
|
|
460
|
+
category?: string;
|
|
325
461
|
}
|
|
326
462
|
export type WhaleContextStatus = "available" | "partial" | "unavailable";
|
|
327
463
|
export type WhaleContextFailure = "timeout" | "network_error" | "http_error" | "invalid_payload";
|
|
@@ -414,6 +550,8 @@ export interface Observation {
|
|
|
414
550
|
label: string;
|
|
415
551
|
fetchedAt?: string;
|
|
416
552
|
};
|
|
553
|
+
macro?: MacroContext;
|
|
554
|
+
stablecoinSupply?: StablecoinSupplyContext;
|
|
417
555
|
syncCursor: string | null;
|
|
418
556
|
newClosedTrades: Array<Record<string, unknown>>;
|
|
419
557
|
polledBeforeWrite: boolean;
|
|
@@ -638,19 +776,18 @@ export interface CycleResult {
|
|
|
638
776
|
tokensIn?: number;
|
|
639
777
|
tokensOut?: number;
|
|
640
778
|
estimatedCostUsd?: number;
|
|
779
|
+
providerUsage?: {
|
|
780
|
+
promptTokens: number;
|
|
781
|
+
completionTokens: number;
|
|
782
|
+
cacheReadTokens?: number;
|
|
783
|
+
cacheWriteTokens?: number;
|
|
784
|
+
cacheWrite5mTokens?: number;
|
|
785
|
+
cacheWrite1hTokens?: number;
|
|
786
|
+
};
|
|
641
787
|
effectiveProvider?: string;
|
|
642
788
|
effectiveModel?: string;
|
|
643
789
|
routeReason?: string;
|
|
644
|
-
routeAttempts?:
|
|
645
|
-
provider: string;
|
|
646
|
-
model: string;
|
|
647
|
-
outcome: "success" | "failed" | "deferred";
|
|
648
|
-
failureClass?: "capacity" | "permanent" | "transient" | "malformed";
|
|
649
|
-
status?: number;
|
|
650
|
-
retryAfterMs?: number;
|
|
651
|
-
latencyMs: number;
|
|
652
|
-
error?: string;
|
|
653
|
-
}>;
|
|
790
|
+
routeAttempts?: DecideRouteAttempt[];
|
|
654
791
|
decisionType?: "act" | "skip" | "gate_skip" | "model_error";
|
|
655
792
|
writeAttempted?: number;
|
|
656
793
|
writeAccepted?: number;
|