diff --git a/decision/engine.go b/decision/engine.go index 98cb4bb8..b46acc03 100644 --- a/decision/engine.go +++ b/decision/engine.go @@ -748,6 +748,16 @@ func (e *StrategyEngine) BuildSystemPrompt(accountEquity float64, variant string sb.WriteString(fmt.Sprintf("- Risk-Reward Ratio: ≥1:%.1f (take_profit / stop_loss)\n", riskControl.MinRiskRewardRatio)) sb.WriteString(fmt.Sprintf("- Min Confidence: ≥%d to open position\n\n", riskControl.MinConfidence)) + // Position sizing guidance + sb.WriteString("## Position Sizing Guidance\n") + sb.WriteString("Calculate `position_size_usd` based on your confidence and the Position Value Limits above:\n") + sb.WriteString("- High confidence (≥85): Use 80-100%% of max position value limit\n") + sb.WriteString("- Medium confidence (70-84): Use 50-80%% of max position value limit\n") + sb.WriteString("- Low confidence (60-69): Use 30-50%% of max position value limit\n") + sb.WriteString(fmt.Sprintf("- Example: With equity %.0f and BTC/ETH ratio %.1fx, max is %.0f USDT\n", + accountEquity, btcEthPosValueRatio, accountEquity*btcEthPosValueRatio)) + sb.WriteString("- **DO NOT** just use available_balance as position_size_usd. Use the Position Value Limits!\n\n") + // 4. Trading frequency (editable) if promptSections.TradingFrequency != "" { sb.WriteString(promptSections.TradingFrequency) @@ -795,8 +805,10 @@ func (e *StrategyEngine) BuildSystemPrompt(accountEquity float64, variant string sb.WriteString("\n") sb.WriteString("Step 2: JSON decision array\n\n") sb.WriteString("```json\n[\n") + // Use the actual configured position value ratio for BTC/ETH in the example + examplePositionSize := accountEquity * btcEthPosValueRatio sb.WriteString(fmt.Sprintf(" {\"symbol\": \"BTCUSDT\", \"action\": \"open_short\", \"leverage\": %d, \"position_size_usd\": %.0f, \"stop_loss\": 97000, \"take_profit\": 91000, \"confidence\": 85, \"risk_usd\": 300},\n", - riskControl.BTCETHMaxLeverage, accountEquity*5)) + riskControl.BTCETHMaxLeverage, examplePositionSize)) sb.WriteString(" {\"symbol\": \"ETHUSDT\", \"action\": \"close_long\"}\n") sb.WriteString("]\n```\n") sb.WriteString("\n\n") diff --git a/trader/lighter_trader_v2.go b/trader/lighter_trader_v2.go index ae3d59d3..2b6b8588 100644 --- a/trader/lighter_trader_v2.go +++ b/trader/lighter_trader_v2.go @@ -46,9 +46,12 @@ type LighterPositionInfo struct { } // AccountResponse LIGHTER account API response +// API may return accounts in "accounts" or "sub_accounts" field type AccountResponse struct { - Code int `json:"code"` - Accounts []AccountInfo `json:"accounts"` + Code int `json:"code"` + Message string `json:"message"` + Accounts []AccountInfo `json:"accounts"` + SubAccounts []AccountInfo `json:"sub_accounts"` // Sub-accounts field } // LighterTraderV2 New implementation using official lighter-go SDK @@ -175,6 +178,7 @@ func (t *LighterTraderV2) initializeAccount() error { } // getAccountByL1Address Get LIGHTER account info by L1 wallet address +// Supports both main accounts and sub-accounts func (t *LighterTraderV2) getAccountByL1Address() (*AccountInfo, error) { endpoint := fmt.Sprintf("%s/api/v1/account?by=l1_address&value=%s", t.baseURL, t.walletAddr) @@ -194,21 +198,40 @@ func (t *LighterTraderV2) getAccountByL1Address() (*AccountInfo, error) { return nil, err } + // Log raw response for debugging + logger.Infof("LIGHTER account API response: %s", string(body)) + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get account (status %d): %s", resp.StatusCode, string(body)) } - // Parse response - Lighter returns {"accounts": [...]} + // Parse response - Lighter may return accounts in "accounts" or "sub_accounts" var accountResp AccountResponse if err := json.Unmarshal(body, &accountResp); err != nil { return nil, fmt.Errorf("failed to parse account response: %w", err) } - if len(accountResp.Accounts) == 0 { - return nil, fmt.Errorf("no account found for wallet address: %s", t.walletAddr) + // Check for API error + if accountResp.Code != 0 && accountResp.Code != 200 { + return nil, fmt.Errorf("Lighter API error (code %d): %s", accountResp.Code, accountResp.Message) } - account := &accountResp.Accounts[0] + // Try accounts first, then sub_accounts + var allAccounts []AccountInfo + allAccounts = append(allAccounts, accountResp.Accounts...) + allAccounts = append(allAccounts, accountResp.SubAccounts...) + + if len(allAccounts) == 0 { + return nil, fmt.Errorf("no account found for wallet address: %s (try depositing funds first at app.lighter.xyz)", t.walletAddr) + } + + // Log all found accounts + logger.Infof("Found %d accounts (main: %d, sub: %d)", len(allAccounts), len(accountResp.Accounts), len(accountResp.SubAccounts)) + for i, acc := range allAccounts { + logger.Infof(" Account[%d]: index=%d, collateral=%s", i, acc.AccountIndex, acc.Collateral) + } + + account := &allAccounts[0] // Use index field if account_index is 0 if account.AccountIndex == 0 && account.Index != 0 { account.AccountIndex = account.Index