feat: add debate arena and fix multiple issues

- Add AI debate arena for multi-AI trading decisions
- Fix debate consensus calculation and display
- Fix vote parsing to support both <decision> and <final_vote> tags
- Fix JSON field name compatibility (stop_loss/stop_loss_pct)
- Fix symbol validation to prevent AI hallucinating invalid symbols
- Fix Bybit position side display (was uppercase, now lowercase for consistency)
- Fix NOFX logo navigation to home page
- Add detailed logging for debugging trade execution
This commit is contained in:
tinkle-community
2025-12-12 11:24:32 +08:00
parent e5703ffab6
commit f5ae22d85c
14 changed files with 4133 additions and 32 deletions

View File

@@ -795,6 +795,29 @@ func (at *AutoTrader) executeDecisionWithRecord(decision *decision.Decision, act
}
}
// ExecuteDecision executes a trading decision from external sources (e.g., debate consensus)
// This is a public method that can be called by other modules
func (at *AutoTrader) ExecuteDecision(d *decision.Decision) error {
logger.Infof("[%s] Executing external decision: %s %s", at.name, d.Action, d.Symbol)
// Create a minimal action record for tracking
actionRecord := &store.DecisionAction{
Symbol: d.Symbol,
Action: d.Action,
Leverage: d.Leverage,
}
// Execute the decision
err := at.executeDecisionWithRecord(d, actionRecord)
if err != nil {
logger.Errorf("[%s] External decision execution failed: %v", at.name, err)
return err
}
logger.Infof("[%s] External decision executed successfully: %s %s", at.name, d.Action, d.Symbol)
return nil
}
// executeOpenLongWithRecord executes open long position and records detailed information
func (at *AutoTrader) executeOpenLongWithRecord(decision *decision.Decision, actionRecord *store.DecisionAction) error {
logger.Infof(" 📈 Open long: %s", decision.Symbol)

View File

@@ -233,16 +233,23 @@ func (t *BybitTrader) GetPositions() ([]map[string]interface{}, error) {
updatedTimeStr, _ := pos["updatedTime"].(string)
updatedTime, _ := strconv.ParseInt(updatedTimeStr, 10, 64)
positionSide, _ := pos["side"].(string) // Buy = LONG, Sell = SHORT
positionSide, _ := pos["side"].(string) // Buy = long, Sell = short
// Convert to unified format
side := "LONG"
// Log raw position data for debugging
logger.Infof("[Bybit] GetPositions raw: symbol=%v, side=%s, size=%v", pos["symbol"], positionSide, sizeStr)
// Convert to unified format (use lowercase for consistency with other exchanges)
// Bybit returns "Buy" for long, "Sell" for short
side := "long"
positionAmt := size
if positionSide == "Sell" {
side = "SHORT"
positionSideLower := strings.ToLower(positionSide)
if positionSideLower == "sell" {
side = "short"
positionAmt = -size
}
logger.Infof("[Bybit] GetPositions converted: symbol=%v, rawSide=%s -> side=%s", pos["symbol"], positionSide, side)
position := map[string]interface{}{
"symbol": pos["symbol"],
"side": side,
@@ -271,6 +278,8 @@ func (t *BybitTrader) GetPositions() ([]map[string]interface{}, error) {
// OpenLong opens a long position
func (t *BybitTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
logger.Infof("[Bybit] ===== OpenLong called: symbol=%s, qty=%.6f, leverage=%d =====", symbol, quantity, leverage)
// Set leverage first
if err := t.SetLeverage(symbol, leverage); err != nil {
logger.Infof("⚠️ [Bybit] Failed to set leverage: %v", err)
@@ -288,6 +297,8 @@ func (t *BybitTrader) OpenLong(symbol string, quantity float64, leverage int) (m
"positionIdx": 0, // One-way position mode
}
logger.Infof("[Bybit] OpenLong placing order: %+v", params)
result, err := t.client.NewUtaBybitServiceWithParams(params).PlaceOrder(context.Background())
if err != nil {
return nil, fmt.Errorf("Bybit open long failed: %w", err)
@@ -301,6 +312,8 @@ func (t *BybitTrader) OpenLong(symbol string, quantity float64, leverage int) (m
// OpenShort opens a short position
func (t *BybitTrader) OpenShort(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
logger.Infof("[Bybit] ===== OpenShort called: symbol=%s, qty=%.6f, leverage=%d =====", symbol, quantity, leverage)
// Set leverage first
if err := t.SetLeverage(symbol, leverage); err != nil {
logger.Infof("⚠️ [Bybit] Failed to set leverage: %v", err)
@@ -318,6 +331,8 @@ func (t *BybitTrader) OpenShort(symbol string, quantity float64, leverage int) (
"positionIdx": 0, // One-way position mode
}
logger.Infof("[Bybit] OpenShort placing order: %+v", params)
result, err := t.client.NewUtaBybitServiceWithParams(params).PlaceOrder(context.Background())
if err != nil {
return nil, fmt.Errorf("Bybit open short failed: %w", err)
@@ -338,7 +353,8 @@ func (t *BybitTrader) CloseLong(symbol string, quantity float64) (map[string]int
return nil, err
}
for _, pos := range positions {
if pos["symbol"] == symbol && pos["side"] == "LONG" {
side, _ := pos["side"].(string)
if pos["symbol"] == symbol && strings.ToLower(side) == "long" {
quantity = pos["positionAmt"].(float64)
break
}
@@ -382,7 +398,8 @@ func (t *BybitTrader) CloseShort(symbol string, quantity float64) (map[string]in
return nil, err
}
for _, pos := range positions {
if pos["symbol"] == symbol && pos["side"] == "SHORT" {
side, _ := pos["side"].(string)
if pos["symbol"] == symbol && strings.ToLower(side) == "short" {
quantity = -pos["positionAmt"].(float64) // Short position is negative
break
}