Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
b7b346d
feat: add planner passthrough support for agents and enhance planning…
vcfgv Nov 5, 2025
d2b0ad9
feat: implement StrategyAgent streaming and decision cycle with enhan…
vcfgv Nov 5, 2025
0463fe9
feat:create strategy agent router
su8su Nov 5, 2025
cff38d6
feat: enhance StrategyAgent streaming with portfolio updates and refi…
vcfgv Nov 5, 2025
087d432
feature: add ccxt as dependency
vcfgv Nov 5, 2025
80cc857
add
su8su Nov 5, 2025
75b6e63
feat: refactor StrategyAgent and DecisionCoordinator for async suppor…
vcfgv Nov 5, 2025
18aa0ee
feat: enhance feature computation with technical indicators using pan…
vcfgv Nov 5, 2025
328622a
feat: update PortfolioService to expose apply_trades method for state…
vcfgv Nov 5, 2025
f7dce68
feat: refactor Composer interface and implement LlmComposer
vcfgv Nov 6, 2025
5a57996
feat: enhance logging in LlmComposer for better traceability and debu…
vcfgv Nov 6, 2025
9eedbbe
feat: enhance InMemoryPortfolioService to compute derived metrics and…
vcfgv Nov 6, 2025
bf2923a
make format
vcfgv Nov 6, 2025
784ca85
feat: add aggressive and insane trading strategy templates
vcfgv Nov 6, 2025
5293a0a
feat: enhance trading execution and portfolio management with constra…
vcfgv Nov 6, 2025
2f032c0
add
su8su Nov 6, 2025
9d56f34
feat: implement sub-step handling for trade instructions to enforce s…
vcfgv Nov 6, 2025
901d68a
feat: update PortfolioView to use buying_power instead of available_c…
vcfgv Nov 6, 2025
7a91b14
fix: correct logging format in StrategyAgent and simplify target quan…
vcfgv Nov 6, 2025
3e2e9ba
Merge remote-tracking branch 'origin/feature/strategy-agent' into fea…
su8su Nov 6, 2025
16fb542
feat: enhance decision coordination with authoritative unrealized PnL…
vcfgv Nov 6, 2025
38a93ff
feat: add conservative slippage handling in LlmComposer to improve un…
vcfgv Nov 6, 2025
c14b848
feat: enhance trade history pairing by detecting closes and annotatin…
vcfgv Nov 6, 2025
5833287
feat: make cap_factor configurable in LlmComposer and add to TradingC…
vcfgv Nov 6, 2025
60c9682
refactor: streamline trade application by removing redundant method a…
vcfgv Nov 6, 2025
e7cf3cd
refactor: simplify DefaultDecisionCoordinator constructor by removing…
vcfgv Nov 6, 2025
d12e965
feat: enhance trade note handling by preserving LLM rationale and app…
vcfgv Nov 6, 2025
a4d2e8f
feat: enhance trade history entry creation by implementing full close…
vcfgv Nov 6, 2025
c0bde55
add strategy router
su8su Nov 6, 2025
0ed4054
feat: add fee_cost to TradeHistoryEntry and adjust cash flow calculat…
vcfgv Nov 6, 2025
69735b3
refactor: move components out of runtime.py
vcfgv Nov 6, 2025
0c28a15
fix: update template_id in test_agent to use aggressive strategy
vcfgv Nov 7, 2025
ca6f79b
Merge branch 'feature/strategy-agent' into feature/dev
vcfgv Nov 7, 2025
ec6b900
refactor: chat conversation adapter different layout
DigHuang Nov 5, 2025
51b88db
feat: trading strategy modal ui init
DigHuang Nov 5, 2025
ba6334e
feat: optimize schema support validator
DigHuang Nov 5, 2025
f367079
chore: schema mainly key
DigHuang Nov 6, 2025
6327a87
chore: use DialogTitle to fix warning in browser
DigHuang Nov 6, 2025
523953c
feat: add multi-select support trading symbols
DigHuang Nov 6, 2025
992ce59
feat: adapter string[] to parse in strategy modal
DigHuang Nov 6, 2025
adeeca8
fix: DialogContent aria-describedby warning
DigHuang Nov 6, 2025
9de09fe
feat: add min-h to DialogContent
DigHuang Nov 6, 2025
5a0a10a
feat: add strategy-card & chore strategy key
DigHuang Nov 6, 2025
584d35f
feat: strategy card renderer & chore border-gradient
DigHuang Nov 6, 2025
bd877df
feat: default select the first strategy on mount
DigHuang Nov 6, 2025
488bca5
refactor: group trade-strategy
DigHuang Nov 6, 2025
0670ae1
feat: trade history component renderer init
DigHuang Nov 6, 2025
8155547
feat: mock data & fix trade type badge color
DigHuang Nov 6, 2025
adedf85
feat: portfolio value & positions component renderer
DigHuang Nov 6, 2025
220a393
feat: Intl.NumberFormat instead of fixed
DigHuang Nov 7, 2025
c7b62c7
add
su8su Nov 7, 2025
a962f46
Merge branch 'feat/dev_strategy_agent' into feature/dev
su8su Nov 7, 2025
179a315
Merge branch 'feat/strategy_agent_support' into feature/dev
DigHuang Nov 7, 2025
5e00a2e
fix: holding curve
su8su Nov 7, 2025
c7df332
feat: model provider map in trade strategy
DigHuang Nov 7, 2025
66c90a6
chore: rm create_time
DigHuang Nov 7, 2025
5614025
feat: model provider map in trade strategy
DigHuang Nov 7, 2025
b9cccbf
chore: rm create_time
DigHuang Nov 7, 2025
1d62ba3
feat: enhance logging and error handling in StrategyAgent and market …
vcfgv Nov 7, 2025
c2d4c7b
feat: implement trade history persistence and portfolio view handling
vcfgv Nov 7, 2025
b0ad920
feat: fetch interval
DigHuang Nov 7, 2025
5b235ce
fix: create strategy api & trading symbols
DigHuang Nov 7, 2025
7c096f6
feat: fetch interval
DigHuang Nov 7, 2025
479b2ed
fix: create strategy api & trading symbols
DigHuang Nov 7, 2025
fa5e0c5
fix: priceCurve data struct
DigHuang Nov 7, 2025
7dca909
fix: rm mock data
DigHuang Nov 7, 2025
7f9b749
feat: enhance strategy persistence with improved trade history and su…
vcfgv Nov 7, 2025
ca4cb80
feat: add comprehensive strategy template
vcfgv Nov 7, 2025
a59bbdb
fix: create strategy request key
DigHuang Nov 7, 2025
2ff22e8
feature: add strategy agent launch script
vcfgv Nov 7, 2025
67faa6d
feat: use StrategyAgent instead of Autotrading Agent
DigHuang Nov 7, 2025
75fb982
fix: priceCurve data struct
DigHuang Nov 7, 2025
3f0f645
fix: rm mock data
DigHuang Nov 7, 2025
157ff8d
fix: create strategy request key
DigHuang Nov 7, 2025
e02118e
feat: use StrategyAgent instead of Autotrading Agent
DigHuang Nov 7, 2025
47b0657
fix data
su8su Nov 7, 2025
f5ec3e2
Merge remote-tracking branch 'origin/feature/dev' into feature/dev
su8su Nov 7, 2025
eca1ffd
feat: enhance strategy agent with improved runtime checks and logging
vcfgv Nov 7, 2025
9f58214
feat: api request for apikey and reuse in create strategy
DigHuang Nov 7, 2025
f2880b0
add stop
su8su Nov 7, 2025
3c8ec96
feat: add stop strategy api
DigHuang Nov 7, 2025
1cb74af
feat: api request for apikey and reuse in create strategy
DigHuang Nov 7, 2025
e981fae
feat: add stop strategy api
DigHuang Nov 7, 2025
e71a5ae
remove model_id
su8su Nov 7, 2025
27aeba8
Merge remote-tracking branch 'origin/feature/dev' into feature/dev
su8su Nov 7, 2025
e4db4f7
add
su8su Nov 7, 2025
5452417
chore: TimeUtils time format & interval strategy
DigHuang Nov 7, 2025
03f3f40
chore: TimeUtils time format & interval strategy
DigHuang Nov 7, 2025
3d42be0
feat: stop strategy with second confirm
DigHuang Nov 7, 2025
38a8706
feat: show ai trade reasoning with cursor hover
DigHuang Nov 7, 2025
5a5ac74
style: optimize active when hover Reasoning Detail
DigHuang Nov 7, 2025
5bdd5e3
feat: stop strategy with second confirm
DigHuang Nov 7, 2025
2a98e36
feat: show ai trade reasoning with cursor hover
DigHuang Nov 7, 2025
2d2fde7
style: optimize active when hover Reasoning Detail
DigHuang Nov 7, 2025
eefa721
feat: add strategy portfolio view model and persistence functionality
vcfgv Nov 7, 2025
5d828e1
fix: PortfolioPriceCurve not use anymore
DigHuang Nov 7, 2025
79b8c43
Merge branch 'feat/strategy_agent_support' into feature/dev
DigHuang Nov 7, 2025
d5075d3
fix tests
vcfgv Nov 7, 2025
2521773
make format
vcfgv Nov 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.1/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.3/schema.json",
"files": {
"includes": [
"src/**/*.{ts,tsx,js,jsx}",
Expand Down
94 changes: 75 additions & 19 deletions frontend/bun.lock

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,34 @@
"tauri": "tauri"
},
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@react-router/node": "^7.9.4",
"@tanstack/react-form": "^1.23.8",
"@tanstack/react-query": "^5.90.5",
"@tauri-apps/api": "^2.9.0",
"@tauri-apps/plugin-opener": "^2.5.1",
"best-effort-json-parser": "^1.2.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"dayjs": "^1.11.18",
"echarts": "^6.0.0",
"isbot": "5.1.31",
"lucide-react": "^0.548.0",
"lucide-react": "^0.552.0",
"mutative": "^1.3.0",
"next-themes": "^0.4.6",
"overlayscrollbars": "^2.12.0",
Expand All @@ -51,10 +56,11 @@
"remark-gfm": "^4.0.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"zod": "^4.1.12",
"zustand": "^5.0.8"
},
"devDependencies": {
"@biomejs/biome": "^2.3.1",
"@biomejs/biome": "^2.3.3",
"@react-router/dev": "^7.9.4",
"@react-router/serve": "^7.9.4",
"@tailwindcss/typography": "^0.5.19",
Expand Down
108 changes: 108 additions & 0 deletions frontend/src/api/strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { API_QUERY_KEYS } from "@/constants/api";
import { type ApiResponse, apiClient } from "@/lib/api-client";
import type {
CreateStrategyRequest,
LlmConfig,
Position,
Strategy,
Trade,
} from "@/types/strategy";

export const useGetStrategyList = () => {
return useQuery({
queryKey: API_QUERY_KEYS.STRATEGY.strategyList,
queryFn: () =>
apiClient.get<
ApiResponse<{
strategies: Strategy[];
}>
>("/strategies"),
select: (data) => data.data.strategies,
refetchInterval: 15 * 1000,
});
};

export const useGetStrategyTrades = (strategyId?: string) => {
return useQuery({
queryKey: API_QUERY_KEYS.STRATEGY.strategyTrades([strategyId ?? ""]),
queryFn: () =>
apiClient.get<ApiResponse<Trade[]>>(
`/strategies/detail?id=${strategyId}`,
),
select: (data) => data.data,
refetchInterval: 15 * 1000,
enabled: !!strategyId,
});
};

export const useGetStrategyHoldings = (strategyId?: string) => {
return useQuery({
queryKey: API_QUERY_KEYS.STRATEGY.strategyHoldings([strategyId ?? ""]),
queryFn: () =>
apiClient.get<ApiResponse<Position[]>>(
`/strategies/holding?id=${strategyId}`,
),
select: (data) => data.data,
refetchInterval: 15 * 1000,
enabled: !!strategyId,
});
};

export const useGetStrategyPriceCurve = (strategyId?: string) => {
return useQuery({
queryKey: API_QUERY_KEYS.STRATEGY.strategyPriceCurve([strategyId ?? ""]),
queryFn: () =>
apiClient.get<ApiResponse<Array<Array<string | number>>>>(
`/strategies/holding_price_curve?id=${strategyId}`,
),
select: (data) => data.data,
refetchInterval: 15 * 1000,
enabled: !!strategyId,
});
};

export const useCreateStrategy = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (data: CreateStrategyRequest) =>
apiClient.post<ApiResponse<{ strategy_id: string }>>(
"/strategies/create",
data,
),
onSuccess: () => {
// Invalidate strategy list to refetch
queryClient.invalidateQueries({
queryKey: API_QUERY_KEYS.STRATEGY.strategyList,
});
},
});
};

export const useGetStrategyApiKey = () => {
return useQuery({
queryKey: API_QUERY_KEYS.STRATEGY.strategyApiKey,
queryFn: () =>
apiClient.get<ApiResponse<LlmConfig[]>>("/models/llm/config"),
select: (data) => data.data,
staleTime: 0,
});
};

export const useStopStrategy = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (strategyId: string) =>
apiClient.post<ApiResponse<{ message: string }>>(
`/strategies/stop?id=${strategyId}`,
),
onSuccess: () => {
// Invalidate strategy list to refetch
queryClient.invalidateQueries({
queryKey: API_QUERY_KEYS.STRATEGY.strategyList,
});
},
});
};
175 changes: 12 additions & 163 deletions frontend/src/app/agent/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,174 +1,23 @@
import { useQueryClient } from "@tanstack/react-query";
import { useCallback, useEffect } from "react";
import {
Navigate,
useLocation,
useNavigate,
useParams,
useSearchParams,
} from "react-router";
import { toast } from "sonner";
import { useGetAgentInfo } from "@/api/agent";
import { useGetConversationHistory, usePollTaskList } from "@/api/conversation";
import { API_QUERY_KEYS } from "@/constants/api";
import { useSSE } from "@/hooks/use-sse";
import { getServerUrl } from "@/lib/api-client";
import {
useAgentStoreActions,
useCurrentConversation,
} from "@/store/agent-store";
import type { AgentStreamRequest, SSEData } from "@/types/agent";
import { Navigate, useParams } from "react-router";
import type { Route } from "./+types/chat";
import ChatConversationArea from "./components/chat-conversation/chat-conversation-area";
import CommonAgentArea from "./components/agent-view/common-agent-area";
import StrategyAgentArea from "./components/agent-view/strategy-agent-area";

export default function AgentChat() {
const { agentName } = useParams<Route.LoaderArgs["params"]>();
const conversationId = useSearchParams()[0].get("id") ?? "";
const navigate = useNavigate();
const inputValue = useLocation().state?.inputValue;

// Use optimized hooks with built-in shallow comparison
const { curConversation, curConversationId } = useCurrentConversation();
const {
dispatchAgentStore,
setCurConversationId,
dispatchAgentStoreHistory,
} = useAgentStoreActions();

const queryClient = useQueryClient();
const { data: agent, isLoading: isLoadingAgent } = useGetAgentInfo({
agentName: agentName ?? "",
});
const { data: conversationHistory } =
useGetConversationHistory(conversationId);
const { data: taskList } = usePollTaskList(conversationId);

// Load conversation history (only once when conversation changes)
useEffect(() => {
if (
!conversationId ||
!conversationHistory ||
conversationHistory.length === 0
)
return;

dispatchAgentStoreHistory(conversationId, conversationHistory, true);
}, [conversationId, conversationHistory, dispatchAgentStoreHistory]);

// Update task list (polls every 30s)
useEffect(() => {
if (!conversationId || !taskList || taskList.length === 0) return;

dispatchAgentStoreHistory(conversationId, taskList);
}, [conversationId, taskList, dispatchAgentStoreHistory]);

// Initialize SSE connection using the useSSE hook
const { connect, close, isStreaming } = useSSE({
url: getServerUrl("/agents/stream"),
handlers: {
onData: (sseData: SSEData) => {
// Update agent store using the reducer
dispatchAgentStore(sseData);

// Handle specific UI state updates
const { event, data } = sseData;
switch (event) {
case "conversation_started":
navigate(`/agent/${agentName}?id=${data.conversation_id}`, {
replace: true,
});
queryClient.invalidateQueries({
queryKey: API_QUERY_KEYS.CONVERSATION.conversationList,
});
break;

case "component_generator":
if (data.payload.component_type === "subagent_conversation") {
queryClient.invalidateQueries({
queryKey: API_QUERY_KEYS.CONVERSATION.conversationList,
});
}
break;

case "system_failed":
// Handle system errors in UI layer
toast.error(data.payload.content, {
closeButton: true,
duration: 30 * 1000,
});
break;

case "done":
close();
break;

// All message-related events are handled by the store
default:
break;
}
},
onOpen: () => {
console.log("✅ SSE connection opened");
},
onError: (error: Error) => {
console.error("❌ SSE connection error:", error);
},
onClose: () => {
console.log("🔌 SSE connection closed");
},
},
});

// Send message to agent
// biome-ignore lint/correctness/useExhaustiveDependencies: connect is no need to be in dependencies
const sendMessage = useCallback(
async (message: string) => {
try {
const request: AgentStreamRequest = {
query: message,
agent_name: agentName ?? "",
conversation_id: conversationId,
};

// Connect SSE client with request body to receive streaming response
await connect(JSON.stringify(request));
} catch (error) {
console.error("Failed to send message:", error);
}
},
[agentName, conversationId],
);

useEffect(() => {
if (curConversationId !== conversationId) {
setCurConversationId(conversationId);
}

if (inputValue) {
sendMessage(inputValue);
// Clear the state after using it once to prevent re-triggering on page refresh
navigate(".", { replace: true, state: {} });
}
}, [
conversationId,
inputValue,
sendMessage,
setCurConversationId,
curConversationId,
navigate,
]);

if (isLoadingAgent) return null;
if (!agent) return <Navigate to="/" replace />;
if (!agentName) return <Navigate to="/" replace />;

return (
<main className="relative flex flex-1 flex-col overflow-hidden">
<ChatConversationArea
agent={agent}
currentConversation={curConversation}
isStreaming={isStreaming}
sendMessage={sendMessage}
/>
{(() => {
switch (agentName) {
case "StrategyAgent":
return <StrategyAgentArea agentName={agentName} />;
default:
return <CommonAgentArea agentName={agentName} />;
}
})()}
</main>
);
}
Loading