mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-12 15:26:55 +08:00
fix: position sizing guidance and Lighter sub-accounts support
- Fix hardcoded 5x position ratio in AI prompt example, now uses configured ratio - Add position sizing guidance section for AI to calculate proper position size - Add sub_accounts support for Lighter account API
This commit is contained in:
@@ -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("- 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))
|
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)
|
// 4. Trading frequency (editable)
|
||||||
if promptSections.TradingFrequency != "" {
|
if promptSections.TradingFrequency != "" {
|
||||||
sb.WriteString(promptSections.TradingFrequency)
|
sb.WriteString(promptSections.TradingFrequency)
|
||||||
@@ -795,8 +805,10 @@ func (e *StrategyEngine) BuildSystemPrompt(accountEquity float64, variant string
|
|||||||
sb.WriteString("<decision>\n")
|
sb.WriteString("<decision>\n")
|
||||||
sb.WriteString("Step 2: JSON decision array\n\n")
|
sb.WriteString("Step 2: JSON decision array\n\n")
|
||||||
sb.WriteString("```json\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",
|
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(" {\"symbol\": \"ETHUSDT\", \"action\": \"close_long\"}\n")
|
||||||
sb.WriteString("]\n```\n")
|
sb.WriteString("]\n```\n")
|
||||||
sb.WriteString("</decision>\n\n")
|
sb.WriteString("</decision>\n\n")
|
||||||
|
|||||||
@@ -46,9 +46,12 @@ type LighterPositionInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AccountResponse LIGHTER account API response
|
// AccountResponse LIGHTER account API response
|
||||||
|
// API may return accounts in "accounts" or "sub_accounts" field
|
||||||
type AccountResponse struct {
|
type AccountResponse struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Accounts []AccountInfo `json:"accounts"`
|
Message string `json:"message"`
|
||||||
|
Accounts []AccountInfo `json:"accounts"`
|
||||||
|
SubAccounts []AccountInfo `json:"sub_accounts"` // Sub-accounts field
|
||||||
}
|
}
|
||||||
|
|
||||||
// LighterTraderV2 New implementation using official lighter-go SDK
|
// 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
|
// getAccountByL1Address Get LIGHTER account info by L1 wallet address
|
||||||
|
// Supports both main accounts and sub-accounts
|
||||||
func (t *LighterTraderV2) getAccountByL1Address() (*AccountInfo, error) {
|
func (t *LighterTraderV2) getAccountByL1Address() (*AccountInfo, error) {
|
||||||
endpoint := fmt.Sprintf("%s/api/v1/account?by=l1_address&value=%s", t.baseURL, t.walletAddr)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log raw response for debugging
|
||||||
|
logger.Infof("LIGHTER account API response: %s", string(body))
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("failed to get account (status %d): %s", resp.StatusCode, string(body))
|
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
|
var accountResp AccountResponse
|
||||||
if err := json.Unmarshal(body, &accountResp); err != nil {
|
if err := json.Unmarshal(body, &accountResp); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse account response: %w", err)
|
return nil, fmt.Errorf("failed to parse account response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(accountResp.Accounts) == 0 {
|
// Check for API error
|
||||||
return nil, fmt.Errorf("no account found for wallet address: %s", t.walletAddr)
|
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
|
// Use index field if account_index is 0
|
||||||
if account.AccountIndex == 0 && account.Index != 0 {
|
if account.AccountIndex == 0 && account.Index != 0 {
|
||||||
account.AccountIndex = account.Index
|
account.AccountIndex = account.Index
|
||||||
|
|||||||
Reference in New Issue
Block a user