@coinrithm/mcp-trading 0.7.13 → 0.7.15
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 +692 -573
- package/README.md +400 -364
- package/dist/agent/act.d.ts +7 -2
- package/dist/agent/act.js +10 -3
- package/dist/agent/cli.d.ts +1 -0
- package/dist/agent/cli.js +25 -4
- package/dist/agent/client.d.ts +13 -0
- package/dist/agent/client.js +107 -4
- package/dist/agent/decisionReceipt.d.ts +5 -1
- package/dist/agent/decisionReceipt.js +95 -3
- package/dist/agent/decisionValidator.js +74 -0
- package/dist/agent/definitionSnapshot.d.ts +22 -0
- package/dist/agent/definitionSnapshot.js +28 -0
- package/dist/agent/engine.d.ts +1 -0
- package/dist/agent/engine.js +1 -0
- package/dist/agent/futuresEligibility.d.ts +36 -0
- package/dist/agent/futuresEligibility.js +90 -0
- package/dist/agent/mergeRules.d.ts +1 -1
- package/dist/agent/mergeRules.js +17 -2
- package/dist/agent/observe.d.ts +9 -1
- package/dist/agent/observe.js +507 -45
- package/dist/agent/pmContext.d.ts +10 -1
- package/dist/agent/pmContext.js +163 -1
- package/dist/agent/prompt.d.ts +14 -0
- package/dist/agent/prompt.js +138 -27
- package/dist/agent/providerCapabilities.d.ts +3 -0
- package/dist/agent/providerCapabilities.js +34 -4
- package/dist/agent/providers.d.ts +3 -0
- package/dist/agent/providers.js +12 -2
- package/dist/agent/resolve.js +8 -1
- package/dist/agent/runner.js +140 -18
- package/dist/agent/skill.js +9 -0
- package/dist/agent/skillValidator.js +11 -2
- package/dist/agent/strictLint.js +1 -0
- package/dist/agent/templates.js +30 -30
- package/dist/agent/types.d.ts +138 -1
- package/dist/agent/types.js +4 -0
- package/dist/client.d.ts +11 -2
- package/dist/client.js +22 -10
- package/dist/http.d.ts +1 -0
- package/dist/http.js +17 -2
- package/dist/httpCompletion.d.ts +1 -1
- package/dist/httpCompletion.js +1 -0
- package/dist/index.js +2 -1
- package/dist/instructions.d.ts +1 -0
- package/dist/instructions.js +16 -0
- package/dist/tools.d.ts +1 -0
- package/dist/tools.js +150 -66
- package/package.json +97 -97
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { INDICATOR_VERSION } from "./indicators.js";
|
|
2
|
+
import { COINRITHM_API, RESOLVER_VERSION } from "./version.js";
|
|
3
|
+
import { sha256, stableStringify } from "./util.js";
|
|
4
|
+
/**
|
|
5
|
+
* Binds the compiled definition and declared engine versions, not the original
|
|
6
|
+
* source folder alone. This is an integrity/comparison hash, not a signature,
|
|
7
|
+
* complete replay record, or attestation of a particular deployed binary.
|
|
8
|
+
*/
|
|
9
|
+
export function buildAgentDefinitionSnapshot(spec, mergedProse) {
|
|
10
|
+
const definition = {
|
|
11
|
+
schema: "coinrithm.agent-definition.v1",
|
|
12
|
+
engine: {
|
|
13
|
+
packageVersion: COINRITHM_API.mcpVersion,
|
|
14
|
+
resolverVersion: RESOLVER_VERSION,
|
|
15
|
+
indicatorVersion: INDICATOR_VERSION,
|
|
16
|
+
apiContractVersion: COINRITHM_API.openapiVersion,
|
|
17
|
+
},
|
|
18
|
+
spec,
|
|
19
|
+
mergedProse,
|
|
20
|
+
};
|
|
21
|
+
// Detach from caller-owned spec: later mutations must not invalidate a saved
|
|
22
|
+
// artifact while leaving its old hash attached to it.
|
|
23
|
+
const serialized = stableStringify(definition);
|
|
24
|
+
return {
|
|
25
|
+
...JSON.parse(serialized),
|
|
26
|
+
definitionHash: sha256(serialized),
|
|
27
|
+
};
|
|
28
|
+
}
|
package/dist/agent/engine.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { runCycle, type RunnerDeps } from "./runner.js";
|
|
2
|
+
export { buildAgentDefinitionSnapshot, type AgentDefinitionSnapshot, } from "./definitionSnapshot.js";
|
|
2
3
|
export { DECISION_INPUT_MAX_BYTES, sanitizeDecisionInputRecord, type DecisionInputRecord, } from "./decisionReceipt.js";
|
|
3
4
|
export { selectProvider, providerForRoute, classifyProviderFailure, type ProviderEnv, type Provider, type DecideInput, type DecideResult, type DecideRouteAttempt, type DecideRouteMeta, } from "./providers.js";
|
|
4
5
|
export { parseDecision } from "./decision.js";
|
package/dist/agent/engine.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// This barrel is the ONE import a host scheduler needs; it re-exports only the
|
|
7
7
|
// stable engine pieces, never the CLI.
|
|
8
8
|
export { runCycle } from "./runner.js";
|
|
9
|
+
export { buildAgentDefinitionSnapshot, } from "./definitionSnapshot.js";
|
|
9
10
|
export { DECISION_INPUT_MAX_BYTES, sanitizeDecisionInputRecord, } from "./decisionReceipt.js";
|
|
10
11
|
export { selectProvider, providerForRoute, classifyProviderFailure, } from "./providers.js";
|
|
11
12
|
export { parseDecision } from "./decision.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Observation, ProposedAction } from "./types.js";
|
|
2
|
+
export type FuturesEntryEligibilityStatus = "eligible" | "reference_stale" | "reference_unavailable";
|
|
3
|
+
export interface FuturesEntryEligibility {
|
|
4
|
+
status: FuturesEntryEligibilityStatus;
|
|
5
|
+
/** Whether the server gate currently requires a reference for NEW opens. */
|
|
6
|
+
referenceRequired: boolean;
|
|
7
|
+
venue: string | null;
|
|
8
|
+
symbol: string | null;
|
|
9
|
+
referenceFetchedAt: string | null;
|
|
10
|
+
maxReferenceAgeHours: number;
|
|
11
|
+
evaluatedAt: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Strict parse of the market context field against its contract (backend-v2
|
|
15
|
+
* #106). Anything missing or malformed (an older API, a partial response, an
|
|
16
|
+
* unknown status, a non-boolean referenceRequired, a missing or invalid
|
|
17
|
+
* evaluatedAt, a non-positive or non-finite maxReferenceAgeHours, or a present
|
|
18
|
+
* but invalid referenceFetchedAt) is `undefined` = UNKNOWN. Unknown never
|
|
19
|
+
* blocks: the runner keeps today's behaviour and the server gate decides.
|
|
20
|
+
*/
|
|
21
|
+
export declare function futuresEntryEligibilityOf(marketContext: unknown): FuturesEntryEligibility | undefined;
|
|
22
|
+
export interface FuturesEntryPreflightRejection {
|
|
23
|
+
code: "futures_reference_unavailable" | "futures_reference_stale";
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pre-quote check for a NEW futures open. Returns a rejection only when the
|
|
28
|
+
* observation EXPLICITLY reports that the server gate requires a reference and
|
|
29
|
+
* has none (or only a stale one) for this coin. Everything else passes:
|
|
30
|
+
* - non-futures_open actions (spot, PM, futures_close / futures_set_sltp);
|
|
31
|
+
* - any open futures position on the same coin (the open is an add or position
|
|
32
|
+
* management, which the server never refuses for the reference);
|
|
33
|
+
* - unknown eligibility (field absent on an older API);
|
|
34
|
+
* - referenceRequired false.
|
|
35
|
+
*/
|
|
36
|
+
export declare function futuresEntryPreflight(action: ProposedAction, observation: Observation): FuturesEntryPreflightRejection | null;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Futures entry eligibility (API >= backend-v2 #106): GET /api/agent/market/{coinId}
|
|
2
|
+
// carries `futuresEntryEligibility`, what the server entry gate's own
|
|
3
|
+
// perpetual-reference rule says about a NEW futures open on the coin. Before it,
|
|
4
|
+
// universe_scan surfaced coins with no supported perpetual reference (tokenized
|
|
5
|
+
// ETFs such as SLVON/NAKA) and the runner spent a futures quote only to be
|
|
6
|
+
// refused with perpetual_reference_unavailable (J-74). The server gate stays
|
|
7
|
+
// authoritative: eligibility can change between observation and execution.
|
|
8
|
+
import { asObj, asStr } from "./extract.js";
|
|
9
|
+
import { baseSymbol } from "./setups.js";
|
|
10
|
+
const STATUSES = [
|
|
11
|
+
"eligible",
|
|
12
|
+
"reference_stale",
|
|
13
|
+
"reference_unavailable",
|
|
14
|
+
];
|
|
15
|
+
// A zoned ISO 8601 date-time (the contract's `format: date-time`):
|
|
16
|
+
// YYYY-MM-DDTHH:MM[:SS[.fraction]] with Z or a +/-HH:MM offset. Date-only,
|
|
17
|
+
// zone-less and bare numbers ("0") are rejected even though Date.parse
|
|
18
|
+
// accepts them.
|
|
19
|
+
const ZONED_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
20
|
+
const isIsoDate = (v) => typeof v === "string" &&
|
|
21
|
+
ZONED_DATE_TIME.test(v) &&
|
|
22
|
+
Number.isFinite(Date.parse(v));
|
|
23
|
+
/**
|
|
24
|
+
* Strict parse of the market context field against its contract (backend-v2
|
|
25
|
+
* #106). Anything missing or malformed (an older API, a partial response, an
|
|
26
|
+
* unknown status, a non-boolean referenceRequired, a missing or invalid
|
|
27
|
+
* evaluatedAt, a non-positive or non-finite maxReferenceAgeHours, or a present
|
|
28
|
+
* but invalid referenceFetchedAt) is `undefined` = UNKNOWN. Unknown never
|
|
29
|
+
* blocks: the runner keeps today's behaviour and the server gate decides.
|
|
30
|
+
*/
|
|
31
|
+
export function futuresEntryEligibilityOf(marketContext) {
|
|
32
|
+
const raw = asObj(asObj(marketContext).futuresEntryEligibility);
|
|
33
|
+
const status = asStr(raw.status);
|
|
34
|
+
if (!status || !STATUSES.includes(status))
|
|
35
|
+
return undefined;
|
|
36
|
+
if (typeof raw.referenceRequired !== "boolean")
|
|
37
|
+
return undefined;
|
|
38
|
+
const hours = raw.maxReferenceAgeHours;
|
|
39
|
+
if (typeof hours !== "number" || !Number.isFinite(hours) || hours <= 0)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (!isIsoDate(raw.evaluatedAt))
|
|
42
|
+
return undefined;
|
|
43
|
+
const fetchedAt = raw.referenceFetchedAt;
|
|
44
|
+
if (fetchedAt != null && !isIsoDate(fetchedAt))
|
|
45
|
+
return undefined;
|
|
46
|
+
return {
|
|
47
|
+
status,
|
|
48
|
+
referenceRequired: raw.referenceRequired,
|
|
49
|
+
venue: asStr(raw.venue) ?? null,
|
|
50
|
+
symbol: asStr(raw.symbol) ?? null,
|
|
51
|
+
referenceFetchedAt: fetchedAt ?? null,
|
|
52
|
+
maxReferenceAgeHours: hours,
|
|
53
|
+
evaluatedAt: raw.evaluatedAt,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Pre-quote check for a NEW futures open. Returns a rejection only when the
|
|
58
|
+
* observation EXPLICITLY reports that the server gate requires a reference and
|
|
59
|
+
* has none (or only a stale one) for this coin. Everything else passes:
|
|
60
|
+
* - non-futures_open actions (spot, PM, futures_close / futures_set_sltp);
|
|
61
|
+
* - any open futures position on the same coin (the open is an add or position
|
|
62
|
+
* management, which the server never refuses for the reference);
|
|
63
|
+
* - unknown eligibility (field absent on an older API);
|
|
64
|
+
* - referenceRequired false.
|
|
65
|
+
*/
|
|
66
|
+
export function futuresEntryPreflight(action, observation) {
|
|
67
|
+
if (action.type !== "futures_open")
|
|
68
|
+
return null;
|
|
69
|
+
const base = baseSymbol(action.symbol);
|
|
70
|
+
const holdsCoin = (observation.openPositions ?? []).some((p) => p.venue === "futures" &&
|
|
71
|
+
(p.status ?? "open") === "open" &&
|
|
72
|
+
baseSymbol(p.symbol) === base);
|
|
73
|
+
if (holdsCoin)
|
|
74
|
+
return null;
|
|
75
|
+
const entry = observation.watch.find((w) => baseSymbol(w.symbol) === base);
|
|
76
|
+
const e = entry?.futuresEntryEligibility;
|
|
77
|
+
if (!e || !e.referenceRequired || e.status === "eligible")
|
|
78
|
+
return null;
|
|
79
|
+
const when = ` at ${e.evaluatedAt}`;
|
|
80
|
+
if (e.status === "reference_stale") {
|
|
81
|
+
return {
|
|
82
|
+
code: "futures_reference_stale",
|
|
83
|
+
reason: `${action.symbol}: the perpetual reference${e.venue ? ` (${e.venue})` : ""} is stale or unusable for a NEW futures open${when} (too old, or its refresh time is missing, invalid or in the future); the server would refuse it (perpetual_reference_stale). Trade another coin or wait for a fresh reference.`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
code: "futures_reference_unavailable",
|
|
88
|
+
reason: `${action.symbol}: CoinRithm has no supported perpetual reference for this coin${when}, so the server refuses a NEW futures open (perpetual_reference_unavailable). Use spot for it, or another coin for futures.`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ResolveIssue } from "./types.js";
|
|
2
|
-
export type CapDirection = "lower" | "higher" | "true";
|
|
2
|
+
export type CapDirection = "lower" | "lower_zero_off" | "higher" | "true";
|
|
3
3
|
export declare const RISK_CAPS: Record<string, CapDirection>;
|
|
4
4
|
export declare const LIMIT_CAPS: Record<string, CapDirection>;
|
|
5
5
|
export declare function isAtLeastAsRestrictive(dir: CapDirection, candidate: unknown, base: unknown): boolean;
|
package/dist/agent/mergeRules.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Normal (non-cap) config uses simple precedence (inline > $ref > defaults),
|
|
10
10
|
// handled in the resolver; this module owns only the cap arithmetic.
|
|
11
|
+
// 0 is "no cap" for lower_zero_off; every other value compares as itself.
|
|
12
|
+
const zeroOffLimit = (n) => (n === 0 ? Infinity : n);
|
|
13
|
+
const isZeroOffValue = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
11
14
|
// risk.* hard caps a tactic module may tighten.
|
|
12
15
|
export const RISK_CAPS = {
|
|
13
16
|
maxLeverage: "lower",
|
|
@@ -17,9 +20,9 @@ export const RISK_CAPS = {
|
|
|
17
20
|
};
|
|
18
21
|
// limits.* throughput/spend caps a tactic module may tighten.
|
|
19
22
|
export const LIMIT_CAPS = {
|
|
20
|
-
maxTradesPerDay: "
|
|
23
|
+
maxTradesPerDay: "lower_zero_off",
|
|
21
24
|
maxWritesPerCycle: "lower",
|
|
22
|
-
maxDailyLossMusd: "
|
|
25
|
+
maxDailyLossMusd: "lower_zero_off",
|
|
23
26
|
maxOpenMarginMusd: "lower",
|
|
24
27
|
};
|
|
25
28
|
// Is `candidate` at least as restrictive as `base` (i.e. a legal tightening)?
|
|
@@ -29,6 +32,11 @@ export function isAtLeastAsRestrictive(dir, candidate, base) {
|
|
|
29
32
|
// legal: base=false→candidate any; base=true→candidate must be true.
|
|
30
33
|
return base === true ? candidate === true : true;
|
|
31
34
|
}
|
|
35
|
+
if (dir === "lower_zero_off") {
|
|
36
|
+
if (!isZeroOffValue(candidate) || !isZeroOffValue(base))
|
|
37
|
+
return false;
|
|
38
|
+
return zeroOffLimit(candidate) <= zeroOffLimit(base);
|
|
39
|
+
}
|
|
32
40
|
if (typeof candidate !== "number" || typeof base !== "number") {
|
|
33
41
|
// a non-numeric cap candidate where a number is expected is not a legal
|
|
34
42
|
// tightening (caller will surface a type issue separately).
|
|
@@ -40,6 +48,13 @@ export function isAtLeastAsRestrictive(dir, candidate, base) {
|
|
|
40
48
|
export function mostRestrictive(dir, a, b) {
|
|
41
49
|
if (dir === "true")
|
|
42
50
|
return a === true || b === true ? true : false;
|
|
51
|
+
if (dir === "lower_zero_off") {
|
|
52
|
+
if (!isZeroOffValue(a))
|
|
53
|
+
return b;
|
|
54
|
+
if (!isZeroOffValue(b))
|
|
55
|
+
return a;
|
|
56
|
+
return zeroOffLimit(a) <= zeroOffLimit(b) ? a : b;
|
|
57
|
+
}
|
|
43
58
|
if (typeof a !== "number")
|
|
44
59
|
return b;
|
|
45
60
|
if (typeof b !== "number")
|
package/dist/agent/observe.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { CoinRithmClient } from "./client.js";
|
|
2
|
-
import { AgentSpec, RunState, Observation, AgentTrace } from "./types.js";
|
|
2
|
+
import { AgentSpec, RunState, Observation, AgentTrace, PmCalibration, WhaleContext } from "./types.js";
|
|
3
3
|
export interface ObserveOutput {
|
|
4
4
|
observation: Observation;
|
|
5
5
|
skip?: string;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Add bounded public whale context after the deterministic gate. This is
|
|
9
|
+
* deliberately separate from observe(): a skipped or mechanical cycle never
|
|
10
|
+
* pays for the public reads, and this context cannot widen candidate selection.
|
|
11
|
+
*/
|
|
12
|
+
export declare function enrichWhaleContext(client: CoinRithmClient, observation: Observation): Promise<WhaleContext>;
|
|
13
|
+
export declare function clearPmCalibrationCache(): void;
|
|
14
|
+
export declare function readPmCalibration(client: CoinRithmClient, trace?: AgentTrace, nowMs?: number): Promise<PmCalibration | undefined>;
|
|
7
15
|
export declare function isCalibrationChurnMarket(market: {
|
|
8
16
|
slug?: string;
|
|
9
17
|
title?: string;
|