@coinrithm/mcp-trading 0.7.15 → 0.7.17
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 +885 -692
- package/README.md +486 -400
- 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/capitalSizing.d.ts +10 -1
- package/dist/agent/capitalSizing.js +28 -4
- package/dist/agent/cli.d.ts +25 -0
- package/dist/agent/cli.js +300 -2
- package/dist/agent/client.d.ts +14 -0
- package/dist/agent/client.js +23 -6
- package/dist/agent/configurationWarnings.js +11 -0
- package/dist/agent/decision.d.ts +24 -16
- package/dist/agent/decision.js +92 -3
- package/dist/agent/decisionValidator.d.ts +31 -0
- package/dist/agent/decisionValidator.js +148 -3
- package/dist/agent/engine.d.ts +5 -3
- package/dist/agent/engine.js +5 -3
- package/dist/agent/futuresCloseOutcome.d.ts +19 -0
- package/dist/agent/futuresCloseOutcome.js +54 -0
- package/dist/agent/futuresEligibility.d.ts +13 -0
- package/dist/agent/futuresEligibility.js +43 -5
- package/dist/agent/observe.d.ts +28 -2
- package/dist/agent/observe.js +530 -90
- package/dist/agent/pmPaperCandidates.d.ts +17 -0
- package/dist/agent/pmPaperCandidates.js +125 -0
- package/dist/agent/pmPaperCycle.d.ts +16 -0
- package/dist/agent/pmPaperCycle.js +372 -0
- package/dist/agent/pmPaperDecision.d.ts +187 -0
- package/dist/agent/pmPaperDecision.js +101 -0
- package/dist/agent/pmPaperPolicy.d.ts +71 -0
- package/dist/agent/pmPaperPolicy.js +46 -0
- package/dist/agent/prompt.d.ts +15 -1
- package/dist/agent/prompt.js +187 -9
- package/dist/agent/promptTables.d.ts +1 -0
- package/dist/agent/promptTables.js +33 -0
- package/dist/agent/providerCapabilities.d.ts +7 -0
- package/dist/agent/providerCapabilities.js +27 -1
- package/dist/agent/providers.d.ts +26 -7
- package/dist/agent/providers.js +193 -48
- package/dist/agent/resolve.js +3 -0
- package/dist/agent/runner.d.ts +17 -1
- package/dist/agent/runner.js +210 -38
- 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 +46 -0
- package/dist/agent/skillValidator.js +56 -0
- package/dist/agent/strictLint.js +30 -0
- package/dist/agent/templates.js +30 -30
- package/dist/agent/types.d.ts +180 -12
- package/dist/agent/universe.d.ts +78 -0
- package/dist/agent/universe.js +215 -0
- package/dist/agent/version.d.ts +1 -1
- package/dist/agent/version.js +1 -1
- package/dist/client.d.ts +14 -0
- package/dist/client.js +31 -1
- package/dist/http.js +1 -1
- package/dist/pmPaperV2.d.ts +81 -0
- package/dist/pmPaperV2.js +53 -0
- package/dist/tools.js +84 -6
- package/package.json +100 -97
|
@@ -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
|
@@ -75,6 +75,15 @@ function buildCapitalSizing(raw) {
|
|
|
75
75
|
totalCapitalPct: num(policy.totalCapitalPct, Number.NaN),
|
|
76
76
|
cashReservePct: num(policy.cashReservePct, Number.NaN),
|
|
77
77
|
minRewardRisk: num(policy.minRewardRisk, Number.NaN),
|
|
78
|
+
...(policy.highConviction !== undefined
|
|
79
|
+
? {
|
|
80
|
+
highConviction: {
|
|
81
|
+
minConfidence: num(obj(policy.highConviction).minConfidence, Number.NaN),
|
|
82
|
+
perTicketCapitalPct: num(obj(policy.highConviction).perTicketCapitalPct, Number.NaN),
|
|
83
|
+
futuresRiskPct: num(obj(policy.highConviction).futuresRiskPct, Number.NaN),
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
: {}),
|
|
78
87
|
};
|
|
79
88
|
}
|
|
80
89
|
// Coerce raw frontmatter into a best-effort AgentSpec. This NEVER throws on bad
|
|
@@ -118,6 +127,32 @@ export function buildSpec(raw) {
|
|
|
118
127
|
pmMinEntryProbabilityPct: risk.pmMinEntryProbabilityPct,
|
|
119
128
|
}
|
|
120
129
|
: {}),
|
|
130
|
+
// The three PM policy dials follow the same as-written rule.
|
|
131
|
+
...(risk.pmMinEdgeGapPct !== undefined
|
|
132
|
+
? {
|
|
133
|
+
pmMinEdgeGapPct: risk.pmMinEdgeGapPct,
|
|
134
|
+
}
|
|
135
|
+
: {}),
|
|
136
|
+
...(risk.pmMaxEdgePoints !== undefined
|
|
137
|
+
? {
|
|
138
|
+
pmMaxEdgePoints: risk.pmMaxEdgePoints,
|
|
139
|
+
}
|
|
140
|
+
: {}),
|
|
141
|
+
...(risk.pmMaxOpenPerEvent !== undefined
|
|
142
|
+
? {
|
|
143
|
+
pmMaxOpenPerEvent: risk.pmMaxOpenPerEvent,
|
|
144
|
+
}
|
|
145
|
+
: {}),
|
|
146
|
+
...(risk.pmMaxOpenPerCoinHorizon !== undefined
|
|
147
|
+
? {
|
|
148
|
+
pmMaxOpenPerCoinHorizon: risk.pmMaxOpenPerCoinHorizon,
|
|
149
|
+
}
|
|
150
|
+
: {}),
|
|
151
|
+
...(risk.pmMinMinutesToClose !== undefined
|
|
152
|
+
? {
|
|
153
|
+
pmMinMinutesToClose: risk.pmMinMinutesToClose,
|
|
154
|
+
}
|
|
155
|
+
: {}),
|
|
121
156
|
maxLeverage: num(risk.maxLeverage, 1),
|
|
122
157
|
perTradeMarginMusd: num(risk.perTradeMarginMusd, 0),
|
|
123
158
|
maxConcurrentPositions: num(risk.maxConcurrentPositions, 0),
|
|
@@ -155,6 +190,17 @@ export function buildSpec(raw) {
|
|
|
155
190
|
},
|
|
156
191
|
objective: buildObjective(raw.objective),
|
|
157
192
|
capabilities: strArr(raw.capabilities).filter((c) => ALLOWED_CAPABILITIES.includes(c)),
|
|
193
|
+
// Signal thresholds and data diet: carried AS WRITTEN so skillValidator
|
|
194
|
+
// fails closed on a malformed block instead of a typo meaning "defaults".
|
|
195
|
+
...(raw.signals !== undefined
|
|
196
|
+
? { signals: raw.signals }
|
|
197
|
+
: {}),
|
|
198
|
+
...(raw.data !== undefined ? { data: raw.data } : {}),
|
|
199
|
+
// Market boundaries: carried AS WRITTEN (an object) so skillValidator can
|
|
200
|
+
// fail closed on a malformed block instead of a typo meaning "no limits".
|
|
201
|
+
...(raw.universe !== undefined
|
|
202
|
+
? { universe: raw.universe }
|
|
203
|
+
: {}),
|
|
158
204
|
// OKF v2 (load-bearing): the gate reads this; omitted -> DEFAULT_TRIGGER_POLICY.
|
|
159
205
|
// This is the agent's INTENT — the platform deployment overlay may tighten it
|
|
160
206
|
// 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;
|
|
@@ -26,6 +28,7 @@ export function validateCapitalSizingPolicy(value) {
|
|
|
26
28
|
...percentKeys,
|
|
27
29
|
"cashReservePct",
|
|
28
30
|
"minRewardRisk",
|
|
31
|
+
"highConviction",
|
|
29
32
|
]);
|
|
30
33
|
for (const key of Object.keys(p)) {
|
|
31
34
|
if (!allowedKeys.has(key))
|
|
@@ -55,6 +58,28 @@ export function validateCapitalSizingPolicy(value) {
|
|
|
55
58
|
p.totalCapitalPct + p.cashReservePct > 100) {
|
|
56
59
|
add("skill_capital_sizing_total_reserve", "capitalSizing.totalCapitalPct + cashReservePct cannot exceed 100");
|
|
57
60
|
}
|
|
61
|
+
if (p.highConviction !== undefined) {
|
|
62
|
+
const hc = p.highConviction;
|
|
63
|
+
if (!isObj(hc)) {
|
|
64
|
+
add("skill_capital_sizing_high_conviction", "capitalSizing.highConviction must be an object");
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
for (const key of Object.keys(hc)) {
|
|
68
|
+
if (!["minConfidence", "perTicketCapitalPct", "futuresRiskPct"].includes(key))
|
|
69
|
+
add("skill_capital_sizing_unknown_key", `unknown capitalSizing.highConviction key "${key}"`);
|
|
70
|
+
}
|
|
71
|
+
if (!isPosNum(hc.minConfidence) || hc.minConfidence > 1)
|
|
72
|
+
add("skill_capital_sizing_high_conviction", "capitalSizing.highConviction.minConfidence must be a finite number in (0, 1]");
|
|
73
|
+
for (const key of ["perTicketCapitalPct", "futuresRiskPct"]) {
|
|
74
|
+
if (!isPosNum(hc[key]) || hc[key] > 100)
|
|
75
|
+
add("skill_capital_sizing_percent", `capitalSizing.highConviction.${key} must be a finite number in (0, 100]`);
|
|
76
|
+
}
|
|
77
|
+
if (isPosNum(hc.perTicketCapitalPct) &&
|
|
78
|
+
isPosNum(p.totalCapitalPct) &&
|
|
79
|
+
hc.perTicketCapitalPct > p.totalCapitalPct)
|
|
80
|
+
add("skill_capital_sizing_ticket_cap", "capitalSizing.highConviction.perTicketCapitalPct cannot exceed totalCapitalPct");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
58
83
|
return issues;
|
|
59
84
|
}
|
|
60
85
|
export function validateSkill(parsed, mode = "self-host") {
|
|
@@ -88,6 +113,12 @@ export function validateSkill(parsed, mode = "self-host") {
|
|
|
88
113
|
add("skill_venue_unknown", `unknown venue "${String(v)}" (allowed: ${VENUES.join(", ")})`);
|
|
89
114
|
}
|
|
90
115
|
}
|
|
116
|
+
// Signal thresholds and data diet (optional).
|
|
117
|
+
for (const [code, reason] of signalIssues(raw.signals, raw.data))
|
|
118
|
+
add(code, reason);
|
|
119
|
+
// Market boundaries (optional).
|
|
120
|
+
for (const [code, reason] of universeIssues(raw.universe))
|
|
121
|
+
add(code, reason);
|
|
91
122
|
// Risk — always required
|
|
92
123
|
if (!isObj(raw.risk)) {
|
|
93
124
|
add("skill_risk", "risk block is required (the caps the agent runs under)");
|
|
@@ -115,6 +146,31 @@ export function validateSkill(parsed, mode = "self-host") {
|
|
|
115
146
|
if (typeof f !== "number" || !Number.isFinite(f) || f < 0 || f > 100)
|
|
116
147
|
add("skill_risk_pm_entry_floor", "risk.pmMinEntryProbabilityPct must be a number between 0 and 100 (points; omit for no floor)");
|
|
117
148
|
}
|
|
149
|
+
if (r.pmMinEdgeGapPct !== undefined) {
|
|
150
|
+
const g = r.pmMinEdgeGapPct;
|
|
151
|
+
if (typeof g !== "number" || !Number.isFinite(g) || g < 0 || g > 100)
|
|
152
|
+
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)");
|
|
153
|
+
}
|
|
154
|
+
if (r.pmMaxEdgePoints !== undefined) {
|
|
155
|
+
const m = r.pmMaxEdgePoints;
|
|
156
|
+
if (typeof m !== "number" || !Number.isFinite(m) || m < 0 || m > 100)
|
|
157
|
+
add("skill_risk_pm_max_edge", "risk.pmMaxEdgePoints must be a number between 0 and 100 (points over cost; omit for no cap)");
|
|
158
|
+
}
|
|
159
|
+
if (r.pmMaxOpenPerEvent !== undefined) {
|
|
160
|
+
const n = r.pmMaxOpenPerEvent;
|
|
161
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 50)
|
|
162
|
+
add("skill_risk_pm_per_event", "risk.pmMaxOpenPerEvent must be a whole number between 1 and 50 (omit for no per-event cap)");
|
|
163
|
+
}
|
|
164
|
+
if (r.pmMaxOpenPerCoinHorizon !== undefined) {
|
|
165
|
+
const n = r.pmMaxOpenPerCoinHorizon;
|
|
166
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 50)
|
|
167
|
+
add("skill_risk_pm_coin_horizon", "risk.pmMaxOpenPerCoinHorizon must be a whole number between 1 and 50 (omit for no per-coin-horizon cap)");
|
|
168
|
+
}
|
|
169
|
+
if (r.pmMinMinutesToClose !== undefined) {
|
|
170
|
+
const m = r.pmMinMinutesToClose;
|
|
171
|
+
if (typeof m !== "number" || !Number.isFinite(m) || m < 0 || m > 10_080)
|
|
172
|
+
add("skill_risk_pm_close_cutoff", "risk.pmMinMinutesToClose must be a number of minutes between 0 and 10080 (omit for no cutoff)");
|
|
173
|
+
}
|
|
118
174
|
// Fail-closed on the side restriction: a typo ("shorts_only") must never
|
|
119
175
|
// silently mean "unrestricted" — that is exactly how a prose-only
|
|
120
176
|
// 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: [
|
|
@@ -61,6 +87,7 @@ const ALLOWED_KEYS = {
|
|
|
61
87
|
"totalCapitalPct",
|
|
62
88
|
"cashReservePct",
|
|
63
89
|
"minRewardRisk",
|
|
90
|
+
"highConviction",
|
|
64
91
|
],
|
|
65
92
|
limits: [
|
|
66
93
|
"maxTradesPerDay",
|
|
@@ -141,6 +168,9 @@ export function strictLint(raw) {
|
|
|
141
168
|
"sync",
|
|
142
169
|
"killSwitch",
|
|
143
170
|
"objective",
|
|
171
|
+
"signals",
|
|
172
|
+
"data",
|
|
173
|
+
"universe",
|
|
144
174
|
]) {
|
|
145
175
|
if (isObj(raw[block]))
|
|
146
176
|
lintKeys(block, raw[block], issues);
|
package/dist/agent/templates.js
CHANGED
|
@@ -50,37 +50,37 @@ const PRESETS = {
|
|
|
50
50
|
modelFail: 5,
|
|
51
51
|
},
|
|
52
52
|
};
|
|
53
|
-
const THESIS_BODY = `# Momentum Futures — strategy
|
|
54
|
-
|
|
55
|
-
You operate a CoinRithm **paper-trading** futures account (50,000 virtual mUSD).
|
|
56
|
-
Everything here is simulated; it is not financial advice and never touches real
|
|
57
|
-
money. Edit this prose freely (any language) — it is your agent's borders.
|
|
58
|
-
|
|
59
|
-
## Each cycle
|
|
60
|
-
|
|
61
|
-
1. Ground yourself: read your portfolio and open positions first. Never assume
|
|
62
|
-
balances or what is already open.
|
|
63
|
-
2. Scan the watchlist. A candidate is a coin whose short and medium momentum
|
|
64
|
-
agree (both up, or both down) and is not already an open position.
|
|
65
|
-
3. Pick the strongest candidate and commit when the read is clear — even a
|
|
66
|
-
moderate-confidence one — sized small with a stop. Skip only when the signals
|
|
67
|
-
contradict or the data is stale; a quiet tape where your edge is still real
|
|
68
|
-
is an act, not a skip.
|
|
69
|
-
4. Quote before you open. Read the liquidation price and confirm it is sane. If
|
|
70
|
-
the quote is not eligible, relay the reason and stop.
|
|
71
|
-
5. Open small and protected: enter in the trend direction and set a stop-loss at
|
|
72
|
-
open. Place the take-profit a touch wider than the stop.
|
|
73
|
-
6. Stay in sync: poll your trades for any stop / take-profit / liquidation that
|
|
74
|
-
fired while you were not looking, and react to what actually happened.
|
|
75
|
-
|
|
76
|
-
The hard caps (leverage, margin, watchlist) live in the config blocks above and
|
|
53
|
+
const THESIS_BODY = `# Momentum Futures — strategy
|
|
54
|
+
|
|
55
|
+
You operate a CoinRithm **paper-trading** futures account (50,000 virtual mUSD).
|
|
56
|
+
Everything here is simulated; it is not financial advice and never touches real
|
|
57
|
+
money. Edit this prose freely (any language) — it is your agent's borders.
|
|
58
|
+
|
|
59
|
+
## Each cycle
|
|
60
|
+
|
|
61
|
+
1. Ground yourself: read your portfolio and open positions first. Never assume
|
|
62
|
+
balances or what is already open.
|
|
63
|
+
2. Scan the watchlist. A candidate is a coin whose short and medium momentum
|
|
64
|
+
agree (both up, or both down) and is not already an open position.
|
|
65
|
+
3. Pick the strongest candidate and commit when the read is clear — even a
|
|
66
|
+
moderate-confidence one — sized small with a stop. Skip only when the signals
|
|
67
|
+
contradict or the data is stale; a quiet tape where your edge is still real
|
|
68
|
+
is an act, not a skip.
|
|
69
|
+
4. Quote before you open. Read the liquidation price and confirm it is sane. If
|
|
70
|
+
the quote is not eligible, relay the reason and stop.
|
|
71
|
+
5. Open small and protected: enter in the trend direction and set a stop-loss at
|
|
72
|
+
open. Place the take-profit a touch wider than the stop.
|
|
73
|
+
6. Stay in sync: poll your trades for any stop / take-profit / liquidation that
|
|
74
|
+
fired while you were not looking, and react to what actually happened.
|
|
75
|
+
|
|
76
|
+
The hard caps (leverage, margin, watchlist) live in the config blocks above and
|
|
77
77
|
are enforced by the runner — change them there, not in this prose.`;
|
|
78
|
-
const PERSONA_STUB = `# Persona
|
|
79
|
-
|
|
80
|
-
Decisive and in character. Acts on a clear read — even a moderate-confidence one
|
|
81
|
-
— sized small and protected with a stop, and skips only when the read is
|
|
82
|
-
contradictory or the data is stale. States its reasoning plainly in its own
|
|
83
|
-
voice, and never frames paper results as real-money advice.
|
|
78
|
+
const PERSONA_STUB = `# Persona
|
|
79
|
+
|
|
80
|
+
Decisive and in character. Acts on a clear read — even a moderate-confidence one
|
|
81
|
+
— sized small and protected with a stop, and skips only when the read is
|
|
82
|
+
contradictory or the data is stale. States its reasoning plainly in its own
|
|
83
|
+
voice, and never frames paper results as real-money advice.
|
|
84
84
|
`;
|
|
85
85
|
export function buildAgentObject(name, preset) {
|
|
86
86
|
const p = PRESETS[preset];
|
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;
|
|
@@ -52,6 +58,15 @@ export interface CapitalSizingPolicy {
|
|
|
52
58
|
totalCapitalPct: number;
|
|
53
59
|
cashReservePct: number;
|
|
54
60
|
minRewardRisk: number;
|
|
61
|
+
/** Opt-in (owner 2026-10-08): a futures_open the model rates at or above
|
|
62
|
+
* minConfidence uses these larger ticket and stop-risk limits instead of
|
|
63
|
+
* the base ones. Every other cap (total, reserve, open margin, R:R) holds. */
|
|
64
|
+
highConviction?: CapitalHighConviction;
|
|
65
|
+
}
|
|
66
|
+
export interface CapitalHighConviction {
|
|
67
|
+
minConfidence: number;
|
|
68
|
+
perTicketCapitalPct: number;
|
|
69
|
+
futuresRiskPct: number;
|
|
55
70
|
}
|
|
56
71
|
export type CapitalBook = {
|
|
57
72
|
status: "unavailable";
|
|
@@ -116,6 +131,25 @@ export interface AgentSpec {
|
|
|
116
131
|
objective?: ObjectiveConfig;
|
|
117
132
|
capabilities: Capability[];
|
|
118
133
|
triggerPolicy?: TriggerPolicy;
|
|
134
|
+
signals?: Partial<import("./signals.js").SignalThresholds>;
|
|
135
|
+
data?: {
|
|
136
|
+
indicatorRange?: import("./signals.js").IndicatorRange;
|
|
137
|
+
};
|
|
138
|
+
universe?: UniverseConfig;
|
|
139
|
+
}
|
|
140
|
+
/** Declared market boundaries; see universe.ts for semantics and defaults. */
|
|
141
|
+
export interface UniverseConfig {
|
|
142
|
+
rank?: {
|
|
143
|
+
min?: number;
|
|
144
|
+
max?: number;
|
|
145
|
+
};
|
|
146
|
+
minVolume24hUsd?: number;
|
|
147
|
+
excludeStablecoins?: boolean;
|
|
148
|
+
includeSectors?: string[];
|
|
149
|
+
excludeSectors?: string[];
|
|
150
|
+
sort?: "gainers_24h" | "losers_24h" | "abs_change_24h" | "abs_change_1h" | "volume_24h" | "rank";
|
|
151
|
+
resolveTop?: number;
|
|
152
|
+
scanLimit?: number;
|
|
119
153
|
}
|
|
120
154
|
export interface ParsedSkill {
|
|
121
155
|
spec: AgentSpec;
|
|
@@ -136,8 +170,8 @@ export interface Freshness {
|
|
|
136
170
|
basis?: string;
|
|
137
171
|
}
|
|
138
172
|
export interface IndicatorContext {
|
|
139
|
-
range: "
|
|
140
|
-
nominalIntervalSeconds:
|
|
173
|
+
range: import("./signals.js").IndicatorRange;
|
|
174
|
+
nominalIntervalSeconds: number;
|
|
141
175
|
barCount: number;
|
|
142
176
|
timestampedBarCount: number;
|
|
143
177
|
checkedIntervalCount: number;
|
|
@@ -150,6 +184,105 @@ export interface IndicatorContext {
|
|
|
150
184
|
intervalStatus: "regular" | "irregular" | "unknown";
|
|
151
185
|
};
|
|
152
186
|
}
|
|
187
|
+
export interface OpenInterestContext {
|
|
188
|
+
totalUsd: number;
|
|
189
|
+
venues: string[];
|
|
190
|
+
change1hPct: number | null;
|
|
191
|
+
change1hVenues: string[];
|
|
192
|
+
change24hPct: number | null;
|
|
193
|
+
change24hVenues: string[];
|
|
194
|
+
asOf: string;
|
|
195
|
+
stale: boolean;
|
|
196
|
+
}
|
|
197
|
+
export interface DatedValue {
|
|
198
|
+
value: number;
|
|
199
|
+
asOf: string;
|
|
200
|
+
stale: boolean;
|
|
201
|
+
}
|
|
202
|
+
export interface PositioningContext {
|
|
203
|
+
venue: string;
|
|
204
|
+
symbol: string;
|
|
205
|
+
longShortAccountRatio?: DatedValue;
|
|
206
|
+
longAccountPct?: DatedValue;
|
|
207
|
+
topTraderPositionRatio?: DatedValue;
|
|
208
|
+
takerBuySellRatio?: DatedValue;
|
|
209
|
+
}
|
|
210
|
+
export interface LiquidationWindow {
|
|
211
|
+
longLiquidatedUsdt: number;
|
|
212
|
+
shortLiquidatedUsdt: number;
|
|
213
|
+
events: number;
|
|
214
|
+
capturedPct: number;
|
|
215
|
+
}
|
|
216
|
+
export interface LiquidationContext {
|
|
217
|
+
venue: string;
|
|
218
|
+
instId: string;
|
|
219
|
+
last1h: LiquidationWindow;
|
|
220
|
+
last24h: LiquidationWindow;
|
|
221
|
+
lastEventAt: string | null;
|
|
222
|
+
}
|
|
223
|
+
export interface MacroQuote {
|
|
224
|
+
symbol: string;
|
|
225
|
+
label: string;
|
|
226
|
+
kind: string;
|
|
227
|
+
price: number;
|
|
228
|
+
change24hPct: number | null;
|
|
229
|
+
asOf: string;
|
|
230
|
+
stale: boolean;
|
|
231
|
+
}
|
|
232
|
+
export interface MacroContext {
|
|
233
|
+
note: string;
|
|
234
|
+
quotes: MacroQuote[];
|
|
235
|
+
}
|
|
236
|
+
export interface ChainTvlContext {
|
|
237
|
+
chain: string;
|
|
238
|
+
tvlUsd: number;
|
|
239
|
+
publishedAt: string | null;
|
|
240
|
+
fetchedAt: string | null;
|
|
241
|
+
sourceObservedAt: null;
|
|
242
|
+
stale: boolean;
|
|
243
|
+
dayAt: string | null;
|
|
244
|
+
change1dPct: number | null;
|
|
245
|
+
change7dPct: number | null;
|
|
246
|
+
}
|
|
247
|
+
export interface StablecoinSupplyContext {
|
|
248
|
+
totalUsd: number;
|
|
249
|
+
dayAt: string;
|
|
250
|
+
change1dPct: number | null;
|
|
251
|
+
change7dPct: number | null;
|
|
252
|
+
stale: boolean;
|
|
253
|
+
}
|
|
254
|
+
export interface DepthSideContext {
|
|
255
|
+
usd: number;
|
|
256
|
+
reachPct: number;
|
|
257
|
+
levels: number;
|
|
258
|
+
complete: boolean;
|
|
259
|
+
}
|
|
260
|
+
export interface DepthContext {
|
|
261
|
+
venue: string;
|
|
262
|
+
bid: DepthSideContext | null;
|
|
263
|
+
ask: DepthSideContext | null;
|
|
264
|
+
asOf: string;
|
|
265
|
+
stale: boolean;
|
|
266
|
+
}
|
|
267
|
+
export interface FundingVenueRate {
|
|
268
|
+
role: "settlement_reference" | "context_only";
|
|
269
|
+
venue: string;
|
|
270
|
+
symbol: string;
|
|
271
|
+
source: "paper_futures_reference" | "hyperliquid_predicted_fundings";
|
|
272
|
+
rateFraction: number;
|
|
273
|
+
intervalHours: number | null;
|
|
274
|
+
hourlyEquivalentFraction: number | null;
|
|
275
|
+
nextFundingTime: string | null;
|
|
276
|
+
fetchedAt: string;
|
|
277
|
+
sourceAt: null;
|
|
278
|
+
ageSeconds: number;
|
|
279
|
+
stale: boolean;
|
|
280
|
+
freshnessBasis: "collection_time";
|
|
281
|
+
}
|
|
282
|
+
export interface FundingByVenueContext {
|
|
283
|
+
rates: FundingVenueRate[];
|
|
284
|
+
sameTime: false;
|
|
285
|
+
}
|
|
153
286
|
export interface WatchEntry {
|
|
154
287
|
symbol: string;
|
|
155
288
|
coinId: string | null;
|
|
@@ -166,8 +299,15 @@ export interface WatchEntry {
|
|
|
166
299
|
indicators?: IndicatorSet;
|
|
167
300
|
indicatorContext?: IndicatorContext;
|
|
168
301
|
discovered?: boolean;
|
|
302
|
+
withinBoundaries?: boolean;
|
|
169
303
|
slug?: string;
|
|
170
304
|
fundamentals?: CoinFundamentals;
|
|
305
|
+
openInterest?: OpenInterestContext;
|
|
306
|
+
positioning?: PositioningContext;
|
|
307
|
+
liquidations?: LiquidationContext;
|
|
308
|
+
chainTvl?: ChainTvlContext;
|
|
309
|
+
depth?: DepthContext;
|
|
310
|
+
fundingByVenue?: FundingByVenueContext;
|
|
171
311
|
futuresEntryEligibility?: FuturesEntryEligibility;
|
|
172
312
|
}
|
|
173
313
|
export interface CoinFundamentals {
|
|
@@ -227,6 +367,8 @@ export interface PmPosition {
|
|
|
227
367
|
entryProbability?: number;
|
|
228
368
|
currentProbability?: number;
|
|
229
369
|
openedAt?: string;
|
|
370
|
+
coins?: string[];
|
|
371
|
+
endDate?: string;
|
|
230
372
|
thesis?: ThesisView;
|
|
231
373
|
}
|
|
232
374
|
export interface PmResolution {
|
|
@@ -250,6 +392,7 @@ export interface PmMarket {
|
|
|
250
392
|
volumeUsd?: number;
|
|
251
393
|
endDate?: string;
|
|
252
394
|
liquidityUsd?: number;
|
|
395
|
+
coins?: string[];
|
|
253
396
|
quality?: PmQuality;
|
|
254
397
|
decisionSupport?: PmDecisionSupport;
|
|
255
398
|
consensus?: PmConsensus | null;
|
|
@@ -322,6 +465,8 @@ export interface NewsItem {
|
|
|
322
465
|
ageHours?: number;
|
|
323
466
|
publishedAt?: string;
|
|
324
467
|
coins?: string[];
|
|
468
|
+
url?: string;
|
|
469
|
+
category?: string;
|
|
325
470
|
}
|
|
326
471
|
export type WhaleContextStatus = "available" | "partial" | "unavailable";
|
|
327
472
|
export type WhaleContextFailure = "timeout" | "network_error" | "http_error" | "invalid_payload";
|
|
@@ -414,6 +559,8 @@ export interface Observation {
|
|
|
414
559
|
label: string;
|
|
415
560
|
fetchedAt?: string;
|
|
416
561
|
};
|
|
562
|
+
macro?: MacroContext;
|
|
563
|
+
stablecoinSupply?: StablecoinSupplyContext;
|
|
417
564
|
syncCursor: string | null;
|
|
418
565
|
newClosedTrades: Array<Record<string, unknown>>;
|
|
419
566
|
polledBeforeWrite: boolean;
|
|
@@ -549,6 +696,8 @@ export interface FuturesFundingQuoteEvidence {
|
|
|
549
696
|
annualizedRate?: number;
|
|
550
697
|
}
|
|
551
698
|
export interface RunState {
|
|
699
|
+
/** Exact checkpointed v2 request; validated before replay, never model-controlled. */
|
|
700
|
+
pmPaperV2Pending?: unknown;
|
|
552
701
|
runId: string;
|
|
553
702
|
cyclesRun: number;
|
|
554
703
|
writesToday: number;
|
|
@@ -580,6 +729,13 @@ export interface RunState {
|
|
|
580
729
|
did: string;
|
|
581
730
|
}>;
|
|
582
731
|
theses?: Record<string, PositionThesis>;
|
|
732
|
+
lastRejections?: LastRejection[];
|
|
733
|
+
}
|
|
734
|
+
export interface LastRejection {
|
|
735
|
+
at: string;
|
|
736
|
+
action: string;
|
|
737
|
+
code: string;
|
|
738
|
+
reason?: string;
|
|
583
739
|
}
|
|
584
740
|
export interface AgentTrace {
|
|
585
741
|
runId?: string;
|
|
@@ -605,8 +761,21 @@ export interface PlannedAction {
|
|
|
605
761
|
reason?: string;
|
|
606
762
|
quote?: QuoteEvidence;
|
|
607
763
|
executed?: boolean;
|
|
764
|
+
/** V2 close accepted but exposure remains open; accounting is separate. */
|
|
765
|
+
executionPending?: boolean;
|
|
766
|
+
executionReplayed?: boolean;
|
|
767
|
+
/** A newly accepted v2 intent/fill consumes write budget; replay does not. */
|
|
768
|
+
writeRecorded?: boolean;
|
|
608
769
|
result?: unknown;
|
|
609
770
|
capitalSizing?: CapitalSizingAdjustment;
|
|
771
|
+
/** Set when the runner replaced the model's futures take-profit; persisted
|
|
772
|
+
* with the cycle so the requested target is never lost. */
|
|
773
|
+
takeProfitRepair?: TakeProfitRepair;
|
|
774
|
+
}
|
|
775
|
+
export interface TakeProfitRepair {
|
|
776
|
+
requestedTakeProfitPrice: number | null;
|
|
777
|
+
appliedTakeProfitPrice: number;
|
|
778
|
+
reason: "missing" | "wrong_side" | "below_reward_risk_floor";
|
|
610
779
|
}
|
|
611
780
|
export interface CycleResult {
|
|
612
781
|
/** Private non-enumerable evidence. Never forward through AgentTrace/public APIs. */
|
|
@@ -638,19 +807,18 @@ export interface CycleResult {
|
|
|
638
807
|
tokensIn?: number;
|
|
639
808
|
tokensOut?: number;
|
|
640
809
|
estimatedCostUsd?: number;
|
|
810
|
+
providerUsage?: {
|
|
811
|
+
promptTokens: number;
|
|
812
|
+
completionTokens: number;
|
|
813
|
+
cacheReadTokens?: number;
|
|
814
|
+
cacheWriteTokens?: number;
|
|
815
|
+
cacheWrite5mTokens?: number;
|
|
816
|
+
cacheWrite1hTokens?: number;
|
|
817
|
+
};
|
|
641
818
|
effectiveProvider?: string;
|
|
642
819
|
effectiveModel?: string;
|
|
643
820
|
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
|
-
}>;
|
|
821
|
+
routeAttempts?: DecideRouteAttempt[];
|
|
654
822
|
decisionType?: "act" | "skip" | "gate_skip" | "model_error";
|
|
655
823
|
writeAttempted?: number;
|
|
656
824
|
writeAccepted?: number;
|