From 0f3dfd81470e0b21fbdfab2fe8ef981a59814712 Mon Sep 17 00:00:00 2001 From: Caihaohan Date: Sat, 28 Mar 2026 16:47:41 +0800 Subject: [PATCH] feat: add shared domain schemas --- packages/shared/package.json | 3 +- packages/shared/src/domain/experiment.ts | 15 +++ packages/shared/src/domain/market.ts | 13 +++ packages/shared/src/domain/model-profile.ts | 21 ++++ packages/shared/src/domain/strategy.test.ts | 103 ++++++++++++++++++++ packages/shared/src/domain/strategy.ts | 35 +++++++ packages/shared/src/domain/trade.ts | 26 +++++ packages/shared/src/index.ts | 5 + 8 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 packages/shared/src/domain/experiment.ts create mode 100644 packages/shared/src/domain/market.ts create mode 100644 packages/shared/src/domain/model-profile.ts create mode 100644 packages/shared/src/domain/strategy.test.ts create mode 100644 packages/shared/src/domain/strategy.ts create mode 100644 packages/shared/src/domain/trade.ts create mode 100644 packages/shared/src/index.ts diff --git a/packages/shared/package.json b/packages/shared/package.json index 818736d..2bdc995 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -7,7 +7,8 @@ "test": "vitest run --" }, "dependencies": { - "@aiquant/agent-runtime": "workspace:*" + "@aiquant/agent-runtime": "workspace:*", + "zod": "^3.24.1" }, "devDependencies": { "typescript": "^5.9.2", diff --git a/packages/shared/src/domain/experiment.ts b/packages/shared/src/domain/experiment.ts new file mode 100644 index 0000000..55ba632 --- /dev/null +++ b/packages/shared/src/domain/experiment.ts @@ -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; diff --git a/packages/shared/src/domain/market.ts b/packages/shared/src/domain/market.ts new file mode 100644 index 0000000..9927c37 --- /dev/null +++ b/packages/shared/src/domain/market.ts @@ -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; diff --git a/packages/shared/src/domain/model-profile.ts b/packages/shared/src/domain/model-profile.ts new file mode 100644 index 0000000..944e2ab --- /dev/null +++ b/packages/shared/src/domain/model-profile.ts @@ -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; diff --git a/packages/shared/src/domain/strategy.test.ts b/packages/shared/src/domain/strategy.test.ts new file mode 100644 index 0000000..b181c23 --- /dev/null +++ b/packages/shared/src/domain/strategy.test.ts @@ -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); + }); +}); diff --git a/packages/shared/src/domain/strategy.ts b/packages/shared/src/domain/strategy.ts new file mode 100644 index 0000000..37a4d67 --- /dev/null +++ b/packages/shared/src/domain/strategy.ts @@ -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; +export type StrategyDefinition = z.infer; diff --git a/packages/shared/src/domain/trade.ts b/packages/shared/src/domain/trade.ts new file mode 100644 index 0000000..961baa2 --- /dev/null +++ b/packages/shared/src/domain/trade.ts @@ -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; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..fcc7589 --- /dev/null +++ b/packages/shared/src/index.ts @@ -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";