diff --git a/api/handler_user.go b/api/handler_user.go index bd4c57f8..029df453 100644 --- a/api/handler_user.go +++ b/api/handler_user.go @@ -265,9 +265,9 @@ func (s *Server) createDefaultStrategies(userID string, lang string) error { c.RiskControl.MaxPositions = 2 c.RiskControl.BTCETHMaxLeverage = 10 c.RiskControl.AltcoinMaxLeverage = 10 - c.RiskControl.BTCETHMaxPositionValueRatio = 1.0 - c.RiskControl.AltcoinMaxPositionValueRatio = 1.0 - c.RiskControl.MaxMarginUsage = 0.35 + c.RiskControl.BTCETHMaxPositionValueRatio = 10.0 + c.RiskControl.AltcoinMaxPositionValueRatio = 10.0 + c.RiskControl.MaxMarginUsage = 1.0 c.RiskControl.MinConfidence = 78 c.RiskControl.MinRiskRewardRatio = 3.0 c.Indicators.Klines.PrimaryTimeframe = "15m" diff --git a/api/handler_user_default_strategy_test.go b/api/handler_user_default_strategy_test.go index baeb0c80..fd2c9e04 100644 --- a/api/handler_user_default_strategy_test.go +++ b/api/handler_user_default_strategy_test.go @@ -54,12 +54,17 @@ func TestCreateDefaultStrategiesUsesOneReadyToRunClaw402Preset(t *testing.T) { if trendCfg.CoinSource.SourceType != "vergex_signal" || trendCfg.CoinSource.VergexLimit != 10 || trendCfg.CoinSource.VergexMarketType != "all" { t.Fatalf("default strategy should use the Claw402/Vergex all-market signal ranking, got %+v", trendCfg.CoinSource) } - if trendCfg.CoinSource.UseAI500 || trendCfg.RiskControl.MaxPositions > 2 || trendCfg.RiskControl.MaxMarginUsage > 0.45 { - t.Fatalf("default strategy should be low-risk Claw402/Vergex native, got coin=%+v risk=%+v", trendCfg.CoinSource, trendCfg.RiskControl) + if trendCfg.CoinSource.UseAI500 || trendCfg.RiskControl.MaxPositions > 2 { + t.Fatalf("default strategy should be Claw402/Vergex native with at most two positions, got coin=%+v risk=%+v", trendCfg.CoinSource, trendCfg.RiskControl) } if trendCfg.RiskControl.BTCETHMaxLeverage != 10 || trendCfg.RiskControl.AltcoinMaxLeverage != 10 { t.Fatalf("default strategy should use 10x leverage for all Claw402 opens, got risk=%+v", trendCfg.RiskControl) } + if trendCfg.RiskControl.BTCETHMaxPositionValueRatio != 10 || + trendCfg.RiskControl.AltcoinMaxPositionValueRatio != 10 || + trendCfg.RiskControl.MaxMarginUsage != 1.0 { + t.Fatalf("default strategy should use full-size 10x notional for Claw402 opens, got risk=%+v", trendCfg.RiskControl) + } } func TestCreateDefaultStrategiesMigratesLegacyPresetsWithoutOverridingActiveCustom(t *testing.T) { diff --git a/kernel/validate_test.go b/kernel/validate_test.go index 6cff077d..c6393c96 100644 --- a/kernel/validate_test.go +++ b/kernel/validate_test.go @@ -100,6 +100,20 @@ func TestLeverageFallback(t *testing.T) { } } +func TestClaw402XyzAllowsFullTenXNotional(t *testing.T) { + decision := Decision{ + Symbol: "xyz:SP500", + Action: "open_long", + Leverage: 10, + PositionSizeUSD: 306.8, + StopLoss: 95, + TakeProfit: 120, + } + + if err := validateDecision(&decision, 30.68, 10, 10, 10.0, 10.0); err != nil { + t.Fatalf("xyz TradeFi Claw402 full 10x notional should pass validation: %v", err) + } +} // contains checks if string contains substring (helper function) func contains(s, substr string) bool { diff --git a/store/strategy.go b/store/strategy.go index e273538a..ec48bfc5 100644 --- a/store/strategy.go +++ b/store/strategy.go @@ -1017,9 +1017,9 @@ func GetDefaultStrategyConfig(lang string) StrategyConfig { MaxPositions: 2, // Max 2 instruments simultaneously (CODE ENFORCED) BTCETHMaxLeverage: 10, // BTC/ETH exchange leverage (AI guided) AltcoinMaxLeverage: 10, // TradeFi exchange leverage (AI guided) - BTCETHMaxPositionValueRatio: 1.0, // Claw402 default: same cap across assets - AltcoinMaxPositionValueRatio: 1.0, // Claw402 default: same cap across assets - MaxMarginUsage: 0.35, // Max 35% margin usage (CODE ENFORCED) + BTCETHMaxPositionValueRatio: 10.0, // Claw402 full-size 10x notional: equity × 10 + AltcoinMaxPositionValueRatio: 10.0, // Claw402 full-size 10x notional: equity × 10 + MaxMarginUsage: 1.0, // Claw402 Autopilot intentionally uses full margin when opening MinPositionSize: 12, // Min 12 USDT per position (CODE ENFORCED) MinRiskRewardRatio: 3.0, // Min 3:1 profit/loss ratio (AI guided) MinConfidence: 78, // Min 78% confidence (AI guided) diff --git a/trader/auto_trader_full_size_test.go b/trader/auto_trader_full_size_test.go index 7ee67610..808abcd7 100644 --- a/trader/auto_trader_full_size_test.go +++ b/trader/auto_trader_full_size_test.go @@ -11,8 +11,8 @@ func TestApplyAutopilotFullSizeOpenForClaw402(t *testing.T) { cfg.CoinSource.SourceType = "vergex_signal" cfg.RiskControl.BTCETHMaxLeverage = 10 cfg.RiskControl.AltcoinMaxLeverage = 10 - cfg.RiskControl.BTCETHMaxPositionValueRatio = 1 - cfg.RiskControl.AltcoinMaxPositionValueRatio = 1 + cfg.RiskControl.BTCETHMaxPositionValueRatio = 10 + cfg.RiskControl.AltcoinMaxPositionValueRatio = 10 at := &AutoTrader{config: AutoTraderConfig{StrategyConfig: &cfg}} decision := &kernel.Decision{ @@ -27,8 +27,8 @@ func TestApplyAutopilotFullSizeOpenForClaw402(t *testing.T) { if decision.Leverage != 10 { t.Fatalf("expected leverage to be forced to 10x, got %dx", decision.Leverage) } - if decision.PositionSizeUSD != 29.8 { - t.Fatalf("expected position size to use full notional 29.8, got %.2f", decision.PositionSizeUSD) + if decision.PositionSizeUSD != 298 { + t.Fatalf("expected position size to use full 10x notional 298, got %.2f", decision.PositionSizeUSD) } } diff --git a/trader/hyperliquid/trader_orders.go b/trader/hyperliquid/trader_orders.go index 95884d22..55a94e6c 100644 --- a/trader/hyperliquid/trader_orders.go +++ b/trader/hyperliquid/trader_orders.go @@ -58,13 +58,14 @@ func (t *HyperliquidTrader) OpenLong(symbol string, quantity float64, leverage i // Check if this is an xyz dex asset isXyz := strings.HasPrefix(coin, "xyz:") - // Set leverage (skip for xyz dex as it may not support leverage adjustment) - if !isXyz { - if err := t.SetLeverage(symbol, leverage); err != nil { + // Set leverage before order placement. Hyperliquid supports leverage + // updates for HIP-3/XYZ perps as well; skipping this left reused accounts + // at whatever leverage they had previously selected (for example 20x). + if err := t.SetLeverage(symbol, leverage); err != nil { + if !isXyz { return nil, err } - } else { - logger.Infof(" ℹ xyz dex asset %s - using default leverage", coin) + logger.Warnf(" ⚠ Failed to set leverage for xyz dex asset %s: %v", coin, err) } // Get current price (for market order) @@ -130,13 +131,14 @@ func (t *HyperliquidTrader) OpenShort(symbol string, quantity float64, leverage // Check if this is an xyz dex asset isXyz := strings.HasPrefix(coin, "xyz:") - // Set leverage (skip for xyz dex) - if !isXyz { - if err := t.SetLeverage(symbol, leverage); err != nil { + // Set leverage before order placement. Hyperliquid supports leverage + // updates for HIP-3/XYZ perps as well; skipping this left reused accounts + // at whatever leverage they had previously selected (for example 20x). + if err := t.SetLeverage(symbol, leverage); err != nil { + if !isXyz { return nil, err } - } else { - logger.Infof(" ℹ xyz dex asset %s - using default leverage", coin) + logger.Warnf(" ⚠ Failed to set leverage for xyz dex asset %s: %v", coin, err) } // Get current price @@ -1029,11 +1031,15 @@ func (t *HyperliquidTrader) SetTakeProfit(symbol string, positionSide string, qu func (t *HyperliquidTrader) PlaceLimitOrder(req *types.LimitOrderRequest) (*types.LimitOrderResult, error) { coin := convertSymbolToHyperliquid(req.Symbol) - // Set leverage if specified and not xyz dex + // Set leverage if specified. isXyz := strings.HasPrefix(coin, "xyz:") - if req.Leverage > 0 && !isXyz { + if req.Leverage > 0 { if err := t.SetLeverage(req.Symbol, req.Leverage); err != nil { - logger.Warnf("[Hyperliquid] Failed to set leverage: %v", err) + if !isXyz { + logger.Warnf("[Hyperliquid] Failed to set leverage: %v", err) + } else { + logger.Warnf("[Hyperliquid] Failed to set xyz leverage for %s: %v", coin, err) + } } } diff --git a/web/src/components/common/HyperliquidWalletConnect.tsx b/web/src/components/common/HyperliquidWalletConnect.tsx index 487ed584..b6529e78 100644 --- a/web/src/components/common/HyperliquidWalletConnect.tsx +++ b/web/src/components/common/HyperliquidWalletConnect.tsx @@ -3,6 +3,7 @@ import { Check, ChevronDown, Copy, + Download, ExternalLink, Loader2, RefreshCw, @@ -237,6 +238,7 @@ export function HyperliquidWalletConnect({ const [balanceError, setBalanceError] = useState('') const [agentInfo, setAgentInfo] = useState(null) const [agentInfoLoading, setAgentInfoLoading] = useState(false) + const [hasWalletProvider, setHasWalletProvider] = useState(false) const text = useMemo( () => ({ title: language === 'zh' ? 'Hyperliquid 钱包' : 'Hyperliquid Wallet', @@ -278,10 +280,22 @@ export function HyperliquidWalletConnect({ language === 'zh' ? 'Hyperliquid 不允许重复使用同一个 Agent,续期会生成一个新的 Agent 并以 180 天有效期授权,然后自动更新 NOFX 保存的私钥(需登录)。' : 'Hyperliquid forbids reusing an agent, so renewal creates a new agent approved for 180 days, then updates the stored key in NOFX (sign-in required).', + noWalletTitle: + language === 'zh' ? '未检测到 EVM 钱包' : 'No EVM wallet detected', + noWalletDetail: + language === 'zh' + ? '先安装 Rabby 或 MetaMask,创建或导入钱包,然后回到这里连接 Hyperliquid。' + : 'Install Rabby or MetaMask, create or import a wallet, then return here to connect Hyperliquid.', + installRabby: language === 'zh' ? '安装 Rabby' : 'Install Rabby', + installMetaMask: language === 'zh' ? '安装 MetaMask' : 'Install MetaMask', }), [language] ) + useEffect(() => { + setHasWalletProvider(Boolean(getPreferredWalletProvider())) + }, []) + useEffect(() => { saveState(state) }, [state]) @@ -960,6 +974,37 @@ export function HyperliquidWalletConnect({ )} + {!state.mainWallet && !hasWalletProvider && ( +
+
+ {text.noWalletTitle} +
+

+ {text.noWalletDetail} +

+
+ + + {text.installRabby} + + + + {text.installMetaMask} + +
+
+ )} +
{state.mainWallet && (
diff --git a/web/src/components/trader/AutopilotLaunchPanel.tsx b/web/src/components/trader/AutopilotLaunchPanel.tsx index 6c15fcf8..6b67208c 100644 --- a/web/src/components/trader/AutopilotLaunchPanel.tsx +++ b/web/src/components/trader/AutopilotLaunchPanel.tsx @@ -3,9 +3,12 @@ import { useNavigate } from 'react-router-dom' import { AlertCircle, ArrowRight, + CircleDollarSign, + Download, CheckCircle2, Copy, ExternalLink, + KeyRound, Loader2, RefreshCw, ShieldCheck, @@ -67,6 +70,114 @@ async function copyText(value: string, label: string) { } } +function BeginnerHyperliquidGuide({ + hasInjectedWallet, +}: { + hasInjectedWallet: boolean +}) { + const steps = [ + { + title: 'Prepare an EVM wallet', + detail: hasInjectedWallet + ? 'Wallet extension detected. Unlock it, then connect below.' + : 'Install Rabby or MetaMask, create or import a wallet, then return here.', + icon: Wallet, + }, + { + title: 'Open Hyperliquid', + detail: + 'Use the same wallet on Hyperliquid. Deposit USDC there as trading collateral.', + icon: CircleDollarSign, + }, + { + title: 'Authorize NOFX', + detail: + 'Back in NOFX, approve the Agent and builder fee. NOFX stores the Agent key, not your main wallet key.', + icon: KeyRound, + }, + ] + + return ( +
+
+
+
+ New to Hyperliquid? +
+

+ Start here if you do not have a trading wallet or have never used + Hyperliquid before. +

+
+
+ {hasInjectedWallet ? 'Wallet detected' : 'Wallet needed'} +
+
+ +
+ {steps.map((step, index) => { + const Icon = step.icon + return ( +
+
+ +
+
+
+ {index + 1}. {step.title} +
+

+ {step.detail} +

+
+
+ ) + })} +
+ +
+ {!hasInjectedWallet ? ( + <> + + + Install Rabby + + + + MetaMask + + + ) : null} + + Open Hyperliquid + + +
+
+ ) +} + export function AutopilotLaunchPanel({ models, exchanges, @@ -83,8 +194,16 @@ export function AutopilotLaunchPanel({ const [walletLoading, setWalletLoading] = useState(false) const [launching, setLaunching] = useState(false) const [refreshing, setRefreshing] = useState(false) + const [hasInjectedWallet, setHasInjectedWallet] = useState(false) const isZh = language === 'zh' + useEffect(() => { + setHasInjectedWallet( + typeof window !== 'undefined' && + Boolean((window as Window & { ethereum?: unknown }).ethereum) + ) + }, []) + const claw402Model = useMemo( () => models.find( @@ -470,13 +589,16 @@ export function AutopilotLaunchPanel({

) : ( -
- +
+ +
+ +
)} diff --git a/web/src/components/trader/TraderLaunchGuestPage.tsx b/web/src/components/trader/TraderLaunchGuestPage.tsx index a185a669..e94b19d5 100644 --- a/web/src/components/trader/TraderLaunchGuestPage.tsx +++ b/web/src/components/trader/TraderLaunchGuestPage.tsx @@ -3,6 +3,8 @@ import { ArrowRight, CheckCircle2, CircleDollarSign, + Download, + ExternalLink, KeyRound, ShieldCheck, Wallet, @@ -106,6 +108,59 @@ export function TraderLaunchGuestPage() {
+
+
+
+ No trading wallet yet? +
+

+ NOFX does not need your main-wallet private key. Install or unlock + an EVM wallet, fund Hyperliquid with USDC, then authorize the NOFX + Agent after sign-in. +

+
+
+ + +
Install Rabby
+

+ Create or import an EVM wallet before connecting to Hyperliquid. +

+
+ + +
MetaMask
+

+ Already use MetaMask? Unlock it, then continue setup inside + NOFX. +

+
+ + +
Open Hyperliquid
+

+ Deposit USDC there. Trading funds stay in your Hyperliquid + account. +

+
+
+
+
diff --git a/web/src/pages/StrategyStudioPage.tsx b/web/src/pages/StrategyStudioPage.tsx index 59a2745f..1f164dc7 100644 --- a/web/src/pages/StrategyStudioPage.tsx +++ b/web/src/pages/StrategyStudioPage.tsx @@ -151,7 +151,7 @@ const profileOptions: Array<{ topN: 5, timeframe: '1h', bars: 30, - margin: 0.25, + margin: 1.0, promptZh: '稳健模式:只有 Claw402 榜单方向、Signal Lab、成本/清算热力图和原始 K 线同时支持时才开仓;信号冲突时等待。', promptEn: @@ -169,7 +169,7 @@ const profileOptions: Array<{ topN: 5, timeframe: '15m', bars: 30, - margin: 0.35, + margin: 1.0, promptZh: '均衡模式:优先交易 Claw402 排名靠前且 Signal Lab 与 K 线同向的标的;用清算热力图确定止损和止盈区域。', promptEn: @@ -187,7 +187,7 @@ const profileOptions: Array<{ topN: 8, timeframe: '5m', bars: 50, - margin: 0.5, + margin: 1.0, promptZh: '进取模式:可以更快跟随 Claw402 强信号,但必须用 Signal Lab 二次确认,用热力图区避开拥挤清算位,止损必须明确。', promptEn: @@ -287,10 +287,10 @@ function defaultRisk(risk?: Partial): RiskControlConfig { btc_eth_max_leverage: leverage, altcoin_max_leverage: leverage, btc_eth_max_position_value_ratio: - risk?.btc_eth_max_position_value_ratio || 1, + risk?.btc_eth_max_position_value_ratio || 10, altcoin_max_position_value_ratio: - risk?.altcoin_max_position_value_ratio || 1, - max_margin_usage: risk?.max_margin_usage || 0.35, + risk?.altcoin_max_position_value_ratio || 10, + max_margin_usage: risk?.max_margin_usage || 1.0, min_position_size: risk?.min_position_size || 12, min_risk_reward_ratio: risk?.min_risk_reward_ratio || 3, min_confidence: risk?.min_confidence || 78, @@ -1320,9 +1320,9 @@ export function StrategyStudioPage() { max_positions: 2, btc_eth_max_leverage: 10, altcoin_max_leverage: 10, - btc_eth_max_position_value_ratio: 1, - altcoin_max_position_value_ratio: 1, - max_margin_usage: 0.35, + btc_eth_max_position_value_ratio: 10, + altcoin_max_position_value_ratio: 10, + max_margin_usage: 1.0, min_confidence: 78, min_risk_reward_ratio: 3, }), @@ -1428,9 +1428,9 @@ export function StrategyStudioPage() { max_positions: 2, btc_eth_max_leverage: 10, altcoin_max_leverage: 10, - btc_eth_max_position_value_ratio: 1, - altcoin_max_position_value_ratio: 1, - max_margin_usage: 0.35, + btc_eth_max_position_value_ratio: 10, + altcoin_max_position_value_ratio: 10, + max_margin_usage: 1.0, min_confidence: 78, min_risk_reward_ratio: 3, }),