Files
auto-trade/features/autotrade/stores/use-autotrade-engine-store.ts

213 lines
4.9 KiB
TypeScript
Raw Normal View History

2026-03-12 09:26:27 +09:00
"use client";
import { create } from "zustand";
import type {
AutotradeCompiledStrategy,
AutotradeEngineState,
AutotradeRuntimeLog,
AutotradeSessionInfo,
AutotradeSetupFormValues,
AutotradeSignalCandidate,
AutotradeValidationResult,
} from "@/features/autotrade/types/autotrade.types";
import { resolveSetupDefaults } from "@/lib/autotrade/strategy";
interface AutotradeEngineStoreState {
panelOpen: boolean;
setupForm: AutotradeSetupFormValues;
engineState: AutotradeEngineState;
isWorking: boolean;
activeSession: AutotradeSessionInfo | null;
compiledStrategy: AutotradeCompiledStrategy | null;
validation: AutotradeValidationResult | null;
lastSignal: AutotradeSignalCandidate | null;
orderCountToday: number;
cumulativeLossAmount: number;
consecutiveFailures: number;
lastOrderAtBySymbol: Record<string, number>;
logs: AutotradeRuntimeLog[];
}
interface AutotradeEngineStoreActions {
setPanelOpen: (open: boolean) => void;
patchSetupForm: (patch: Partial<AutotradeSetupFormValues>) => void;
setEngineState: (state: AutotradeEngineState) => void;
setWorking: (working: boolean) => void;
setActiveSession: (session: AutotradeSessionInfo | null) => void;
setCompiledStrategy: (strategy: AutotradeCompiledStrategy | null) => void;
setValidation: (validation: AutotradeValidationResult | null) => void;
setLastSignal: (signal: AutotradeSignalCandidate | null) => void;
increaseOrderCount: (count?: number) => void;
addLossAmount: (lossAmount: number) => void;
setLastOrderAt: (symbol: string, timestampMs: number) => void;
increaseFailure: () => void;
resetFailure: () => void;
appendLog: (
level: AutotradeRuntimeLog["level"],
message: string,
options?: {
stage?: AutotradeRuntimeLog["stage"];
detail?: string | Record<string, unknown>;
},
) => void;
clearRuntime: () => void;
}
const INITIAL_FORM = resolveSetupDefaults();
const INITIAL_STATE: AutotradeEngineStoreState = {
panelOpen: false,
setupForm: INITIAL_FORM,
engineState: "IDLE",
isWorking: false,
activeSession: null,
compiledStrategy: null,
validation: null,
lastSignal: null,
orderCountToday: 0,
cumulativeLossAmount: 0,
consecutiveFailures: 0,
lastOrderAtBySymbol: {},
logs: [],
};
export const useAutotradeEngineStore = create<
AutotradeEngineStoreState & AutotradeEngineStoreActions
>((set) => ({
...INITIAL_STATE,
setPanelOpen: (open) => {
set({ panelOpen: open });
},
patchSetupForm: (patch) => {
set((state) => ({
setupForm: {
...state.setupForm,
...patch,
},
}));
},
setEngineState: (engineState) => {
set({ engineState });
},
setWorking: (isWorking) => {
set({ isWorking });
},
setActiveSession: (activeSession) => {
set({ activeSession });
},
setCompiledStrategy: (compiledStrategy) => {
set({ compiledStrategy });
},
setValidation: (validation) => {
set({ validation });
},
setLastSignal: (lastSignal) => {
set({ lastSignal });
},
increaseOrderCount: (count = 1) => {
set((state) => ({
orderCountToday: state.orderCountToday + Math.max(1, count),
}));
},
addLossAmount: (lossAmount) => {
set((state) => ({
cumulativeLossAmount:
state.cumulativeLossAmount + Math.max(0, Math.floor(lossAmount)),
}));
},
setLastOrderAt: (symbol, timestampMs) => {
set((state) => ({
lastOrderAtBySymbol: {
...state.lastOrderAtBySymbol,
[symbol]: timestampMs,
},
}));
},
increaseFailure: () => {
set((state) => ({
consecutiveFailures: state.consecutiveFailures + 1,
}));
},
resetFailure: () => {
set({ consecutiveFailures: 0 });
},
appendLog: (level, message, options) => {
const entry: AutotradeRuntimeLog = {
id: safeLogId(),
level,
stage: options?.stage,
message,
detail: normalizeLogDetail(options?.detail),
createdAt: new Date().toISOString(),
};
set((state) => ({
logs: [entry, ...state.logs].slice(0, 80),
}));
},
clearRuntime: () => {
set((state) => ({
...state,
engineState: "IDLE",
isWorking: false,
activeSession: null,
compiledStrategy: null,
validation: null,
lastSignal: null,
orderCountToday: 0,
cumulativeLossAmount: 0,
consecutiveFailures: 0,
lastOrderAtBySymbol: {},
}));
},
}));
function safeLogId() {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
return `autotrade-log-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}
function normalizeLogDetail(detail: string | Record<string, unknown> | undefined) {
if (!detail) {
return undefined;
}
if (typeof detail === "string") {
const cleaned = detail.trim();
return cleaned.length > 0 ? cleaned : undefined;
}
try {
return JSON.stringify(detail, null, 2);
} catch {
return undefined;
}
}