Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
673b3798ca | ||
|
|
b6fb48e5b3 | ||
|
|
de1525a2d6 | ||
|
|
800c54cbe6 | ||
|
|
fb39d0c06c | ||
|
|
e4cc29f501 | ||
|
|
ea567789d7 | ||
|
|
6aea5a0912 | ||
|
|
0f3dfd8147 | ||
|
|
709d997e59 |
+12
-1
@@ -2,5 +2,16 @@
|
||||
"name": "@aiquant/api",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest run --"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aiquant/shared": "workspace:*",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^2.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createConfigFromEnv, createModelProfileRegistry } from "./model-profile-registry";
|
||||
|
||||
describe("model profile registry", () => {
|
||||
const env = {
|
||||
MODEL_PROFILES_JSON: JSON.stringify([
|
||||
{
|
||||
name: "signal.fast.v1",
|
||||
provider: "openai-compatible",
|
||||
apiBase: "https://example.com/v1",
|
||||
apiKeyRef: "OPENAI_API_KEY",
|
||||
model: "gpt-4.1-mini",
|
||||
temperature: 0.2,
|
||||
timeoutMs: 10_000,
|
||||
capabilities: {
|
||||
structuredOutputs: true,
|
||||
jsonMode: true
|
||||
},
|
||||
tags: ["fast"]
|
||||
},
|
||||
{
|
||||
name: "optimizer.reasoning.v1",
|
||||
provider: "openai-compatible",
|
||||
apiBase: "https://example.com/v1",
|
||||
apiKeyRef: "OPENAI_API_KEY",
|
||||
model: "o4-mini",
|
||||
temperature: 0.6,
|
||||
timeoutMs: 20_000,
|
||||
capabilities: {
|
||||
structuredOutputs: true,
|
||||
jsonMode: true
|
||||
},
|
||||
tags: ["reasoning"]
|
||||
}
|
||||
]),
|
||||
AGENT_MODEL_BINDINGS_JSON: JSON.stringify({
|
||||
signal: "signal.fast.v1",
|
||||
risk: "signal.fast.v1",
|
||||
evaluator: "optimizer.reasoning.v1",
|
||||
optimizer: "optimizer.reasoning.v1"
|
||||
})
|
||||
};
|
||||
|
||||
it("loads model profiles from environment-backed config", () => {
|
||||
const config = createConfigFromEnv(env);
|
||||
|
||||
expect(config.modelProfiles).toHaveLength(2);
|
||||
expect(config.agentBindings.optimizer).toBe("optimizer.reasoning.v1");
|
||||
});
|
||||
|
||||
it("resolves agent bindings to concrete profiles", () => {
|
||||
const config = createConfigFromEnv(env);
|
||||
const registry = createModelProfileRegistry(config);
|
||||
|
||||
expect(registry.get("signal.fast.v1").model).toBe("gpt-4.1-mini");
|
||||
expect(registry.resolveAgentBinding("optimizer").model).toBe("o4-mini");
|
||||
});
|
||||
|
||||
it("rejects bindings to unknown profiles", () => {
|
||||
expect(() =>
|
||||
createModelProfileRegistry({
|
||||
modelProfiles: createConfigFromEnv(env).modelProfiles,
|
||||
agentBindings: {
|
||||
signal: "signal.fast.v1",
|
||||
risk: "signal.fast.v1",
|
||||
evaluator: "signal.fast.v1",
|
||||
optimizer: "missing-profile"
|
||||
}
|
||||
})
|
||||
).toThrow(/Unknown model profile/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const jsonStringSchema = z.string().min(1);
|
||||
|
||||
export const agentBindingSchema = z.object({
|
||||
signal: z.string().min(1),
|
||||
risk: z.string().min(1),
|
||||
evaluator: z.string().min(1),
|
||||
optimizer: z.string().min(1)
|
||||
});
|
||||
|
||||
export const configEnvSchema = z.object({
|
||||
MODEL_PROFILES_JSON: jsonStringSchema,
|
||||
AGENT_MODEL_BINDINGS_JSON: jsonStringSchema
|
||||
});
|
||||
|
||||
export type AgentBindings = z.infer<typeof agentBindingSchema>;
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
"version": "0.0.0",
|
||||
"packageManager": "pnpm@10.18.3",
|
||||
"scripts": {
|
||||
"test": "pnpm --filter @aiquant/shared test"
|
||||
"test": "node scripts/workspace-test.mjs"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const AGENT_RUNTIME_PACKAGE = "agent-runtime";
|
||||
@@ -0,0 +1 @@
|
||||
export const AGENT_RUNTIME_PACKAGE = "agent-runtime";
|
||||
@@ -4,6 +4,9 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"default": "./index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run --"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aiquant/agent-runtime": "workspace:*"
|
||||
"@aiquant/agent-runtime": "workspace:*",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.2",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const experimentResultSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
strategyId: z.string().min(1),
|
||||
strategyVersion: z.number().int().positive(),
|
||||
sampleSize: z.number().int().nonnegative(),
|
||||
score: z.number(),
|
||||
netReturn: z.number(),
|
||||
maxDrawdown: z.number().nonnegative(),
|
||||
hitRate: z.number().min(0).max(1),
|
||||
evaluatedAt: z.string().datetime()
|
||||
});
|
||||
|
||||
export type ExperimentResult = z.infer<typeof experimentResultSchema>;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const marketSnapshotSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
symbol: z.string().min(1),
|
||||
contractId: z.string().min(1),
|
||||
timeframe: z.string().min(1),
|
||||
observedAt: z.string().datetime(),
|
||||
price: z.number().positive(),
|
||||
volume: z.number().nonnegative().optional()
|
||||
});
|
||||
|
||||
export type MarketSnapshot = z.infer<typeof marketSnapshotSchema>;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const modelCapabilitySchema = z.object({
|
||||
structuredOutputs: z.boolean().default(false),
|
||||
jsonMode: z.boolean().default(false)
|
||||
});
|
||||
|
||||
export const modelProfileSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
provider: z.string().min(1),
|
||||
apiBase: z.string().url(),
|
||||
apiKeyRef: z.string().min(1),
|
||||
model: z.string().min(1),
|
||||
temperature: z.number().min(0).max(2).optional(),
|
||||
maxTokens: z.number().int().positive().optional(),
|
||||
timeoutMs: z.number().int().positive().optional(),
|
||||
capabilities: modelCapabilitySchema,
|
||||
tags: z.array(z.string().min(1)).default([])
|
||||
});
|
||||
|
||||
export type ModelProfile = z.infer<typeof modelProfileSchema>;
|
||||
@@ -0,0 +1,103 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
experimentResultSchema,
|
||||
marketSnapshotSchema,
|
||||
modelProfileSchema,
|
||||
strategyDefinitionSchema,
|
||||
strategyStateSchema,
|
||||
tradeRecordSchema
|
||||
} from "../index";
|
||||
|
||||
describe("shared domain schemas", () => {
|
||||
it("accepts supported strategy lifecycle states", () => {
|
||||
expect(strategyStateSchema.parse("candidate")).toBe("candidate");
|
||||
expect(() => strategyStateSchema.parse("live")).toThrow();
|
||||
});
|
||||
|
||||
it("parses an OpenAI-compatible model profile", () => {
|
||||
const profile = modelProfileSchema.parse({
|
||||
name: "signal.fast.v1",
|
||||
provider: "openai-compatible",
|
||||
apiBase: "https://example.com/v1",
|
||||
apiKeyRef: "OPENAI_API_KEY",
|
||||
model: "gpt-4.1-mini",
|
||||
temperature: 0.2,
|
||||
maxTokens: 512,
|
||||
timeoutMs: 10_000,
|
||||
capabilities: {
|
||||
structuredOutputs: true,
|
||||
jsonMode: true
|
||||
},
|
||||
tags: ["fast", "production"]
|
||||
});
|
||||
|
||||
expect(profile.model).toBe("gpt-4.1-mini");
|
||||
expect(profile.capabilities.structuredOutputs).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects invalid dry-run trade confidence", () => {
|
||||
expect(() =>
|
||||
tradeRecordSchema.parse({
|
||||
id: "trade-1",
|
||||
strategyId: "strategy-1",
|
||||
strategyVersion: 1,
|
||||
mode: "dry-run",
|
||||
contractId: "btc-10m-updown",
|
||||
side: "up",
|
||||
confidence: 1.5,
|
||||
status: "proposed",
|
||||
createdAt: "2026-03-28T08:00:00.000Z"
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("parses market snapshots and experiment results", () => {
|
||||
const snapshot = marketSnapshotSchema.parse({
|
||||
id: "snapshot-1",
|
||||
symbol: "BTCUSDT",
|
||||
contractId: "btc-10m-updown",
|
||||
timeframe: "10m",
|
||||
observedAt: "2026-03-28T08:00:00.000Z",
|
||||
price: 87500.25,
|
||||
volume: 1200.5
|
||||
});
|
||||
|
||||
const strategy = strategyDefinitionSchema.parse({
|
||||
id: "strategy-1",
|
||||
version: 2,
|
||||
name: "momentum-10m",
|
||||
state: "dry-run",
|
||||
agentProfile: {
|
||||
signal: "signal.fast.v1",
|
||||
risk: "risk.default.v1",
|
||||
evaluator: "eval.default.v1",
|
||||
optimizer: "optimizer.reasoning.v1"
|
||||
},
|
||||
featureConfig: {
|
||||
windows: ["5m", "15m"]
|
||||
},
|
||||
riskConfig: {
|
||||
minConfidence: 0.65
|
||||
},
|
||||
scoringConfig: {
|
||||
objective: "risk-adjusted-return"
|
||||
},
|
||||
createdAt: "2026-03-28T08:00:00.000Z"
|
||||
});
|
||||
|
||||
const result = experimentResultSchema.parse({
|
||||
id: "experiment-1",
|
||||
strategyId: strategy.id,
|
||||
strategyVersion: strategy.version,
|
||||
sampleSize: 48,
|
||||
score: 1.27,
|
||||
netReturn: 0.13,
|
||||
maxDrawdown: 0.05,
|
||||
hitRate: 0.62,
|
||||
evaluatedAt: "2026-03-28T10:00:00.000Z"
|
||||
});
|
||||
|
||||
expect(snapshot.symbol).toBe("BTCUSDT");
|
||||
expect(result.score).toBeGreaterThan(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const strategyStateSchema = z.enum([
|
||||
"draft",
|
||||
"candidate",
|
||||
"dry-run",
|
||||
"approved-for-live",
|
||||
"archived"
|
||||
]);
|
||||
|
||||
export const strategyDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
version: z.number().int().positive(),
|
||||
name: z.string().min(1),
|
||||
state: strategyStateSchema,
|
||||
agentProfile: z.object({
|
||||
signal: z.string().min(1),
|
||||
risk: z.string().min(1),
|
||||
evaluator: z.string().min(1),
|
||||
optimizer: z.string().min(1)
|
||||
}),
|
||||
featureConfig: z.object({
|
||||
windows: z.array(z.string().min(1)).min(1)
|
||||
}),
|
||||
riskConfig: z.object({
|
||||
minConfidence: z.number().min(0).max(1)
|
||||
}),
|
||||
scoringConfig: z.object({
|
||||
objective: z.literal("risk-adjusted-return")
|
||||
}),
|
||||
createdAt: z.string().datetime()
|
||||
});
|
||||
|
||||
export type StrategyState = z.infer<typeof strategyStateSchema>;
|
||||
export type StrategyDefinition = z.infer<typeof strategyDefinitionSchema>;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const tradeModeSchema = z.enum(["dry-run", "live"]);
|
||||
export const tradeSideSchema = z.enum(["up", "down"]);
|
||||
export const tradeStatusSchema = z.enum([
|
||||
"proposed",
|
||||
"confirmed",
|
||||
"rejected",
|
||||
"settled"
|
||||
]);
|
||||
|
||||
export const tradeRecordSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
strategyId: z.string().min(1),
|
||||
strategyVersion: z.number().int().positive(),
|
||||
mode: tradeModeSchema,
|
||||
contractId: z.string().min(1),
|
||||
side: tradeSideSchema,
|
||||
confidence: z.number().min(0).max(1),
|
||||
status: tradeStatusSchema,
|
||||
createdAt: z.string().datetime(),
|
||||
settledAt: z.string().datetime().optional(),
|
||||
pnl: z.number().optional()
|
||||
});
|
||||
|
||||
export type TradeRecord = z.infer<typeof tradeRecordSchema>;
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./domain/experiment";
|
||||
export * from "./domain/market";
|
||||
export * from "./domain/model-profile";
|
||||
export * from "./domain/strategy";
|
||||
export * from "./domain/trade";
|
||||
@@ -1,8 +1,28 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AGENT_RUNTIME_PACKAGE } from "@aiquant/agent-runtime";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
describe("workspace smoke test", () => {
|
||||
it("resolves cross-workspace package exports", () => {
|
||||
expect(AGENT_RUNTIME_PACKAGE).toBe("agent-runtime");
|
||||
});
|
||||
|
||||
it("allows plain node to import the workspace package", async () => {
|
||||
const { stdout } = await execFileAsync(
|
||||
process.execPath,
|
||||
[
|
||||
"--input-type=module",
|
||||
"--eval",
|
||||
"import('@aiquant/agent-runtime').then((mod) => console.log(mod.AGENT_RUNTIME_PACKAGE));",
|
||||
],
|
||||
{
|
||||
cwd: new URL("..", import.meta.url),
|
||||
},
|
||||
);
|
||||
|
||||
expect(stdout.trim()).toBe("agent-runtime");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
let filterValue;
|
||||
const passthroughArgs = [];
|
||||
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
|
||||
if (arg === "--filter") {
|
||||
filterValue = args[index + 1];
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
passthroughArgs.push(arg);
|
||||
}
|
||||
|
||||
const commandArgs = ["-r", "--if-present"];
|
||||
|
||||
if (filterValue) {
|
||||
if (!filterValue.includes("/") && !filterValue.includes("*") && !filterValue.startsWith("@")) {
|
||||
filterValue = `@aiquant/${filterValue}`;
|
||||
}
|
||||
|
||||
commandArgs.push("--filter", filterValue);
|
||||
}
|
||||
|
||||
commandArgs.push("test", "--", ...passthroughArgs);
|
||||
|
||||
const result = spawnSync("pnpm", commandArgs, {
|
||||
cwd: process.cwd(),
|
||||
shell: true,
|
||||
stdio: "inherit"
|
||||
});
|
||||
|
||||
process.exit(result.status ?? 1);
|
||||
Reference in New Issue
Block a user