From 6a19552da324e0c89b8da18646a3d89bd6ecfecc Mon Sep 17 00:00:00 2001 From: paisley Date: Fri, 14 Nov 2025 19:03:47 +0800 Subject: [PATCH 1/3] delete strategy --- .../server/api/routers/strategy_agent.py | 39 ++++++++++++++++- .../db/repositories/strategy_repository.py | 42 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/python/valuecell/server/api/routers/strategy_agent.py b/python/valuecell/server/api/routers/strategy_agent.py index 480cf9ae8..94724eb8e 100644 --- a/python/valuecell/server/api/routers/strategy_agent.py +++ b/python/valuecell/server/api/routers/strategy_agent.py @@ -4,7 +4,8 @@ import os -from fastapi import APIRouter, Depends +# New imports for delete endpoint +from fastapi import APIRouter, Depends, HTTPException, Query from loguru import logger from sqlalchemy.orm import Session @@ -16,6 +17,7 @@ from valuecell.config.loader import get_config_loader from valuecell.core.coordinate.orchestrator import AgentOrchestrator from valuecell.core.types import CommonResponseEvent, UserInput, UserInputMetadata +from valuecell.server.api.schemas.base import SuccessResponse from valuecell.server.db.connection import get_db from valuecell.server.db.repositories import get_strategy_repository from valuecell.utils.uuid import generate_conversation_id, generate_uuid @@ -278,4 +280,39 @@ async def create_strategy_agent( strategy_id=fallback_strategy_id, status=StrategyStatus.ERROR ) + @router.delete("/delete") + async def delete_strategy_agent( + id: str = Query(..., description="Strategy ID"), + cascade: bool = Query( + True, description="Delete related records (holdings/details/portfolio)" + ), + db: Session = Depends(get_db), + ): + """Delete a strategy created by StrategyAgent. + + - Validates the strategy exists. + - Optionally cascades deletion to holdings, portfolio snapshots, and details. + - Returns a success response when completed. + """ + try: + repo = get_strategy_repository(db_session=db) + strategy = repo.get_strategy_by_strategy_id(id) + if not strategy: + raise HTTPException(status_code=404, detail="Strategy not found") + + ok = repo.delete_strategy(id, cascade=cascade) + if not ok: + raise HTTPException(status_code=500, detail="Failed to delete strategy") + + return SuccessResponse.create( + data={"strategy_id": id}, + msg=f"Strategy '{id}' deleted successfully", + ) + except HTTPException: + raise + except Exception as e: + raise HTTPException( + status_code=500, detail=f"Error deleting strategy: {str(e)}" + ) + return router diff --git a/python/valuecell/server/db/repositories/strategy_repository.py b/python/valuecell/server/db/repositories/strategy_repository.py index 07c5d0143..5407ad44b 100644 --- a/python/valuecell/server/db/repositories/strategy_repository.py +++ b/python/valuecell/server/db/repositories/strategy_repository.py @@ -370,6 +370,48 @@ def get_prompt_by_id(self, prompt_id: str) -> Optional[StrategyPrompt]: if not self.db_session: session.close() + def delete_strategy(self, strategy_id: str, cascade: bool = True) -> bool: + """Delete a strategy by strategy_id. + + If cascade=True, remove associated holdings, portfolio snapshots, + and detail records for the strategy before deleting the strategy row. + Returns True on success, False if the strategy does not exist or on error. + """ + session = self._get_session() + try: + # Ensure the strategy exists + strategy = ( + session.query(Strategy) + .filter(Strategy.strategy_id == strategy_id) + .first() + ) + if not strategy: + return False + + if cascade: + session.query(StrategyHolding).filter( + StrategyHolding.strategy_id == strategy_id + ).delete(synchronize_session=False) + session.query(StrategyPortfolioView).filter( + StrategyPortfolioView.strategy_id == strategy_id + ).delete(synchronize_session=False) + session.query(StrategyDetail).filter( + StrategyDetail.strategy_id == strategy_id + ).delete(synchronize_session=False) + + session.query(Strategy).filter(Strategy.strategy_id == strategy_id).delete( + synchronize_session=False + ) + + session.commit() + return True + except Exception: + session.rollback() + return False + finally: + if not self.db_session: + session.close() + # Global repository instance _strategy_repository: Optional[StrategyRepository] = None From ff3d64f397727caf893db0fb9464619273115718 Mon Sep 17 00:00:00 2001 From: DigHuang <114602213+DigHuang@users.noreply.github.com> Date: Fri, 14 Nov 2025 19:12:51 +0800 Subject: [PATCH 2/3] feat: support delete strategy --- frontend/.gitignore | 2 + frontend/src/api/strategy.ts | 15 +++ .../agent-view/strategy-agent-area.tsx | 5 + .../strategy-items/trade-strategy-group.tsx | 102 ++++++++++++------ frontend/src/assets/svg/delete-strategy.svg | 17 +++ frontend/src/assets/svg/index.ts | 1 + 6 files changed, 108 insertions(+), 34 deletions(-) create mode 100644 frontend/src/assets/svg/delete-strategy.svg diff --git a/frontend/.gitignore b/frontend/.gitignore index 054200b12..30d31eae4 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -22,3 +22,5 @@ dist-ssr !lib .react-router .vite + +.claude \ No newline at end of file diff --git a/frontend/src/api/strategy.ts b/frontend/src/api/strategy.ts index c6d5cfc4e..3aa6482b0 100644 --- a/frontend/src/api/strategy.ts +++ b/frontend/src/api/strategy.ts @@ -108,6 +108,21 @@ export const useStopStrategy = () => { }); }; +export const useDeleteStrategy = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (strategyId: string) => + apiClient.delete>( + `/strategies/delete?id=${strategyId}`, + ), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: API_QUERY_KEYS.STRATEGY.strategyList, + }); + }, + }); +}; + export const useGetStrategyPrompts = () => { return useQuery({ queryKey: API_QUERY_KEYS.STRATEGY.strategyPrompts, diff --git a/frontend/src/app/agent/components/agent-view/strategy-agent-area.tsx b/frontend/src/app/agent/components/agent-view/strategy-agent-area.tsx index 1f381ea8f..13ed761d9 100644 --- a/frontend/src/app/agent/components/agent-view/strategy-agent-area.tsx +++ b/frontend/src/app/agent/components/agent-view/strategy-agent-area.tsx @@ -1,6 +1,7 @@ import { Plus } from "lucide-react"; import { type FC, useEffect, useState } from "react"; import { + useDeleteStrategy, useGetStrategyHoldings, useGetStrategyList, useGetStrategyPriceCurve, @@ -51,6 +52,7 @@ const StrategyAgentArea: FC = () => { ); const { mutateAsync: stopStrategy } = useStopStrategy(); + const { mutateAsync: deleteStrategy } = useDeleteStrategy(); useEffect(() => { if (strategies.length === 0 || selectedStrategy) return; @@ -73,6 +75,9 @@ const StrategyAgentArea: FC = () => { onStrategyStop={async (strategyId) => await stopStrategy(strategyId) } + onStrategyDelete={async (strategyId) => + await deleteStrategy(strategyId) + } /> ) : (
diff --git a/frontend/src/app/agent/components/strategy-items/trade-strategy-group.tsx b/frontend/src/app/agent/components/strategy-items/trade-strategy-group.tsx index 55183699a..8d6917411 100644 --- a/frontend/src/app/agent/components/strategy-items/trade-strategy-group.tsx +++ b/frontend/src/app/agent/components/strategy-items/trade-strategy-group.tsx @@ -1,6 +1,6 @@ import { Plus, TrendingUp } from "lucide-react"; import { type FC, memo } from "react"; -import { StrategyStatus } from "@/assets/svg"; +import { DeleteStrategy, StrategyStatus } from "@/assets/svg"; import { AlertDialog, AlertDialogAction, @@ -26,6 +26,7 @@ interface TradeStrategyCardProps { isSelected?: boolean; onClick?: () => void; onStop?: () => void; + onDelete?: () => void; } interface TradeStrategyGroupProps { @@ -33,6 +34,7 @@ interface TradeStrategyGroupProps { selectedStrategy?: Strategy | null; onStrategySelect?: (strategy: Strategy) => void; onStrategyStop?: (strategyId: string) => void; + onStrategyDelete?: (strategyId: string) => void; } const TradeStrategyCard: FC = ({ @@ -40,6 +42,7 @@ const TradeStrategyCard: FC = ({ isSelected = false, onClick, onStop, + onDelete, }) => { const stockColors = useStockColors(); const changeType = getChangeType(strategy.unrealized_pnl_pct); @@ -88,39 +91,68 @@ const TradeStrategyCard: FC = ({
{/* Status Badge */} - - - - - - - Stop Trading Strategy? - - Are you sure you want to stop the strategy " - {strategy.strategy_name}"?
This action will halt all - trading activities for this strategy. -
-
- - Cancel - - Confirm Stop - - -
-
+
+ + + + + + + Stop Trading Strategy? + + Are you sure you want to stop the strategy " + {strategy.strategy_name}"?
This action will halt all + trading activities for this strategy. +
+
+ + Cancel + + Confirm Stop + + +
+
+ + + + + + + + Delete Strategy? + + Are you sure you want to delete the strategy " + {strategy.strategy_name}"? + + + + Cancel + + Confirm Delete + + + + +
); @@ -131,6 +163,7 @@ const TradeStrategyGroup: FC = ({ selectedStrategy, onStrategySelect, onStrategyStop, + onStrategyDelete, }) => { const hasStrategies = strategies.length > 0; @@ -148,6 +181,7 @@ const TradeStrategyGroup: FC = ({ } onClick={() => onStrategySelect?.(strategy)} onStop={() => onStrategyStop?.(strategy.strategy_id)} + onDelete={() => onStrategyDelete?.(strategy.strategy_id)} /> ))} diff --git a/frontend/src/assets/svg/delete-strategy.svg b/frontend/src/assets/svg/delete-strategy.svg new file mode 100644 index 000000000..83b104964 --- /dev/null +++ b/frontend/src/assets/svg/delete-strategy.svg @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/svg/index.ts b/frontend/src/assets/svg/index.ts index 431b107af..bc813eeca 100644 --- a/frontend/src/assets/svg/index.ts +++ b/frontend/src/assets/svg/index.ts @@ -8,3 +8,4 @@ export { default as NewsPush } from "./news-push.svg"; export { default as ResearchReport } from "./research-report.svg"; export { default as Setting } from "./setting.svg"; export { default as StrategyStatus } from "./strategy-status.svg"; +export { default as DeleteStrategy } from "./delete-strategy.svg"; \ No newline at end of file From 5997874a21b1f6f925c05e0c02792765a02ea791 Mon Sep 17 00:00:00 2001 From: DigHuang <114602213+DigHuang@users.noreply.github.com> Date: Fri, 14 Nov 2025 19:20:01 +0800 Subject: [PATCH 3/3] fix: biome check --- frontend/src/assets/svg/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/assets/svg/index.ts b/frontend/src/assets/svg/index.ts index bc813eeca..e72fd0bda 100644 --- a/frontend/src/assets/svg/index.ts +++ b/frontend/src/assets/svg/index.ts @@ -2,10 +2,10 @@ export { default as AutoTrade } from "./auto-trade.svg"; export { default as BookOpen } from "./book-open.svg"; export { default as ChartBarVertical } from "./char-bar-vertical.svg"; export { default as Conversation } from "./conversation.svg"; +export { default as DeleteStrategy } from "./delete-strategy.svg"; export { default as House } from "./house.svg"; export { default as Logo } from "./logo.svg"; export { default as NewsPush } from "./news-push.svg"; export { default as ResearchReport } from "./research-report.svg"; export { default as Setting } from "./setting.svg"; export { default as StrategyStatus } from "./strategy-status.svg"; -export { default as DeleteStrategy } from "./delete-strategy.svg"; \ No newline at end of file