From 4369d9ef8ccd0e65c6ff0b8817d74b0dea69faf9 Mon Sep 17 00:00:00 2001 From: hazeone <709547807@qq.com> Date: Sun, 30 Nov 2025 15:43:45 +0800 Subject: [PATCH 1/6] add test connection api --- frontend/src/api/strategy.ts | 7 +++ .../strategy-items/forms/exchange-form.tsx | 50 +++++++++++++++++++ .../common/trading/execution/ccxt_trading.py | 10 ++++ .../common/trading/execution/interfaces.py | 9 ++++ .../server/api/routers/strategy_agent.py | 48 ++++++++++++++++++ 5 files changed, 124 insertions(+) diff --git a/frontend/src/api/strategy.ts b/frontend/src/api/strategy.ts index 74cce1314..ecd9b5c8b 100644 --- a/frontend/src/api/strategy.ts +++ b/frontend/src/api/strategy.ts @@ -97,6 +97,13 @@ export const useCreateStrategy = () => { }); }; +export const useTestConnection = () => { + return useMutation({ + mutationFn: (data: CreateStrategyRequest["exchange_config"]) => + apiClient.post>("/strategies/test-connection", data), + }); +}; + export const useStopStrategy = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx b/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx index fbd488ba1..ed818dcb2 100644 --- a/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx +++ b/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx @@ -1,10 +1,15 @@ +import { Button } from "@/components/ui/button"; import { FieldGroup } from "@/components/ui/field"; import { Label } from "@/components/ui/label"; import { RadioGroupItem } from "@/components/ui/radio-group"; import { SelectItem } from "@/components/ui/select"; +import { Spinner } from "@/components/ui/spinner"; import PngIcon from "@/components/valuecell/icon/png-icon"; import { EXCHANGE_ICONS } from "@/constants/icons"; import { withForm } from "@/hooks/use-form"; +import { Wallet } from "lucide-react"; +import { useState } from "react"; +import { useTestConnection } from "@/api/strategy"; const EXCHANGE_OPTIONS = [ { @@ -48,6 +53,25 @@ export const ExchangeForm = withForm({ private_key: "", }, render({ form }) { + const { mutateAsync: testConnection, isPending } = useTestConnection(); + const [testStatus, setTestStatus] = useState<{ + success: boolean; + message: string; + } | null>(null); + + const handleTestConnection = async () => { + setTestStatus(null); + try { + await testConnection(form.state.values); + setTestStatus({ success: true, message: "Success!" }); + } catch (error) { + setTestStatus({ + success: false, + message: "Failed, please check your API key", + }); + } + }; + return ( + +
+ + {testStatus && ( +

+ {testStatus.message} +

+ )} +
) ); diff --git a/python/valuecell/agents/common/trading/execution/ccxt_trading.py b/python/valuecell/agents/common/trading/execution/ccxt_trading.py index 0e904f80b..c18bbabfe 100644 --- a/python/valuecell/agents/common/trading/execution/ccxt_trading.py +++ b/python/valuecell/agents/common/trading/execution/ccxt_trading.py @@ -1280,6 +1280,16 @@ async def _exec_noop(self, inst: TradeInstruction) -> TxResult: meta=inst.meta, ) + async def test_connection(self) -> bool: + """Test connectivity and authentication with the exchange.""" + try: + # Attempt to fetch balance which requires authentication + await self.fetch_balance() + return True + except Exception as e: + logger.warning(f"⚠️ Connection test failed for {self.exchange_id}: {e}") + return False + async def close(self) -> None: """Close the exchange connection and cleanup resources.""" if self._exchange is not None: diff --git a/python/valuecell/agents/common/trading/execution/interfaces.py b/python/valuecell/agents/common/trading/execution/interfaces.py index 72021c3b7..e269f58c3 100644 --- a/python/valuecell/agents/common/trading/execution/interfaces.py +++ b/python/valuecell/agents/common/trading/execution/interfaces.py @@ -32,6 +32,15 @@ async def execute( raise NotImplementedError + @abstractmethod + async def test_connection(self) -> bool: + """Test the connection to the exchange/broker. + + Returns: + True if connection is successful and credentials are valid, False otherwise. + """ + raise NotImplementedError + @abstractmethod async def close(self) -> None: """Close the gateway and release any held resources. diff --git a/python/valuecell/server/api/routers/strategy_agent.py b/python/valuecell/server/api/routers/strategy_agent.py index 48142b8c4..9d6a3f30a 100644 --- a/python/valuecell/server/api/routers/strategy_agent.py +++ b/python/valuecell/server/api/routers/strategy_agent.py @@ -14,6 +14,7 @@ StrategyStatusContent, StrategyType, UserRequest, + ExchangeConfig, ) from valuecell.config.loader import get_config_loader from valuecell.core.coordinate.orchestrator import AgentOrchestrator @@ -315,6 +316,53 @@ async def create_strategy_agent( strategy_id=fallback_strategy_id, status=StrategyStatus.ERROR ) + @router.post("/test-connection") + async def test_exchange_connection(request: ExchangeConfig): + """Test connection to the exchange with provided credentials.""" + try: + # If virtual trading, just return success immediately + if getattr(request, "trading_mode", None) == "virtual": + return SuccessResponse.create(msg="Success!") + + from valuecell.agents.common.trading.execution.ccxt_trading import ( + create_ccxt_gateway, + ) + + # Map ExchangeConfig fields to gateway args + # Note: ExchangeConfig might differ slightly from create_ccxt_gateway args + gateway = await create_ccxt_gateway( + exchange_id=request.exchange_id, + api_key=request.api_key or "", + secret_key=request.secret_key or "", + passphrase=request.passphrase, + wallet_address=request.wallet_address, + private_key=request.private_key, + # Ensure we pass a safe default for required args if missing in config + market_type="swap", # Default to swap/perpetual for testing + ) + + try: + is_connected = await gateway.test_connection() + if is_connected: + return SuccessResponse.create(msg="Success!") + else: + # Return 200 with error message or 400? User asked for "Failed..." return + # We'll throw 400 for UI to catch, or return success=False in body + # But SuccessResponse implies 200. + # If I raise HTTPException it shows as error. + raise HTTPException( + status_code=400, detail="Failed, please check your API key" + ) + finally: + await gateway.close() + + except Exception as e: + # If create_ccxt_gateway fails or other error + logger.warning(f"Connection test failed: {e}") + raise HTTPException( + status_code=400, detail=f"Failed, please check your API key: {str(e)}" + ) + @router.delete("/delete") async def delete_strategy_agent( id: str = Query(..., description="Strategy ID"), From 92a2149da433db5cd9ce89907b4295dfdaff34a0 Mon Sep 17 00:00:00 2001 From: hazeone <709547807@qq.com> Date: Sun, 30 Nov 2025 15:44:24 +0800 Subject: [PATCH 2/6] lint --- python/valuecell/server/api/routers/strategy_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/valuecell/server/api/routers/strategy_agent.py b/python/valuecell/server/api/routers/strategy_agent.py index 9d6a3f30a..17f622c34 100644 --- a/python/valuecell/server/api/routers/strategy_agent.py +++ b/python/valuecell/server/api/routers/strategy_agent.py @@ -10,11 +10,11 @@ from sqlalchemy.orm import Session from valuecell.agents.common.trading.models import ( + ExchangeConfig, StrategyStatus, StrategyStatusContent, StrategyType, UserRequest, - ExchangeConfig, ) from valuecell.config.loader import get_config_loader from valuecell.core.coordinate.orchestrator import AgentOrchestrator From 2d9a9ffc98f56223f25c1e5fd60145aa834670fa Mon Sep 17 00:00:00 2001 From: hazeone <709547807@qq.com> Date: Sun, 30 Nov 2025 15:52:44 +0800 Subject: [PATCH 3/6] adjust style --- .../strategy-items/forms/exchange-form.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx b/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx index ed818dcb2..e7fe825f6 100644 --- a/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx +++ b/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx @@ -73,7 +73,7 @@ export const ExchangeForm = withForm({ }; return ( - + { @@ -192,10 +192,19 @@ export const ExchangeForm = withForm({ }} -
+
+ {testStatus && ( +

+ {testStatus.message} +

+ )}
) From f059baaad5a2e7655359d78fabd89f0f5c8f526f Mon Sep 17 00:00:00 2001 From: hazeone <709547807@qq.com> Date: Sun, 30 Nov 2025 15:53:32 +0800 Subject: [PATCH 4/6] lint --- .../agent/components/strategy-items/forms/exchange-form.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx b/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx index e7fe825f6..f7799a099 100644 --- a/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx +++ b/frontend/src/app/agent/components/strategy-items/forms/exchange-form.tsx @@ -192,10 +192,10 @@ export const ExchangeForm = withForm({ }} -
+
{testStatus && (

@@ -204,7 +204,7 @@ export const ExchangeForm = withForm({ )}