Skip to content

Commit d062126

Browse files
committed
Add MarginMode configration
1 parent 24a2a83 commit d062126

File tree

10 files changed

+189
-52
lines changed

10 files changed

+189
-52
lines changed

api/server.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ type CreateTraderRequest struct {
170170
InitialBalance float64 `json:"initial_balance"`
171171
CustomPrompt string `json:"custom_prompt"`
172172
OverrideBasePrompt bool `json:"override_base_prompt"`
173+
IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型,nil表示使用默认值true
173174
}
174175

175176
type ModelConfig struct {
@@ -222,6 +223,12 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
222223
// 生成交易员ID
223224
traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix())
224225

226+
// 设置默认值
227+
isCrossMargin := true // 默认为全仓模式
228+
if req.IsCrossMargin != nil {
229+
isCrossMargin = *req.IsCrossMargin
230+
}
231+
225232
// 创建交易员配置(数据库实体)
226233
trader := &config.TraderRecord{
227234
ID: traderID,
@@ -232,6 +239,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
232239
InitialBalance: req.InitialBalance,
233240
CustomPrompt: req.CustomPrompt,
234241
OverrideBasePrompt: req.OverrideBasePrompt,
242+
IsCrossMargin: isCrossMargin,
235243
ScanIntervalMinutes: 3, // 默认3分钟
236244
IsRunning: false,
237245
}

config/database.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ func (d *Database) createTables() error {
153153
`ALTER TABLE exchanges ADD COLUMN aster_private_key TEXT DEFAULT ''`,
154154
`ALTER TABLE traders ADD COLUMN custom_prompt TEXT DEFAULT ''`,
155155
`ALTER TABLE traders ADD COLUMN override_base_prompt BOOLEAN DEFAULT 0`,
156+
`ALTER TABLE traders ADD COLUMN is_cross_margin BOOLEAN DEFAULT 1`, // 默认为全仓模式
156157
}
157158

158159
for _, query := range alterQueries {
@@ -369,6 +370,7 @@ type TraderRecord struct {
369370
IsRunning bool `json:"is_running"`
370371
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
371372
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
373+
IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式(true=全仓,false=逐仓)
372374
CreatedAt time.Time `json:"created_at"`
373375
UpdatedAt time.Time `json:"updated_at"`
374376
}
@@ -656,17 +658,18 @@ func (d *Database) CreateExchange(userID, id, name, typ string, enabled bool, ap
656658
// CreateTrader 创建交易员
657659
func (d *Database) CreateTrader(trader *TraderRecord) error {
658660
_, err := d.db.Exec(`
659-
INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, custom_prompt, override_base_prompt)
660-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
661-
`, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.CustomPrompt, trader.OverrideBasePrompt)
661+
INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, custom_prompt, override_base_prompt, is_cross_margin)
662+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
663+
`, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.CustomPrompt, trader.OverrideBasePrompt, trader.IsCrossMargin)
662664
return err
663665
}
664666

665667
// GetTraders 获取用户的交易员
666668
func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
667669
rows, err := d.db.Query(`
668670
SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running,
669-
COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt, created_at, updated_at
671+
COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt,
672+
COALESCE(is_cross_margin, 1) as is_cross_margin, created_at, updated_at
670673
FROM traders WHERE user_id = ? ORDER BY created_at DESC
671674
`, userID)
672675
if err != nil {
@@ -680,7 +683,8 @@ func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
680683
err := rows.Scan(
681684
&trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
682685
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
683-
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.CreatedAt, &trader.UpdatedAt,
686+
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.IsCrossMargin,
687+
&trader.CreatedAt, &trader.UpdatedAt,
684688
)
685689
if err != nil {
686690
return nil, err

manager/trader_manager.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
154154
MaxDailyLoss: maxDailyLoss,
155155
MaxDrawdown: maxDrawdown,
156156
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
157+
IsCrossMargin: traderCfg.IsCrossMargin,
157158
}
158159

159160
// 根据交易所类型设置API密钥
@@ -228,6 +229,7 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModel
228229
MaxDailyLoss: maxDailyLoss,
229230
MaxDrawdown: maxDrawdown,
230231
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
232+
IsCrossMargin: traderCfg.IsCrossMargin,
231233
}
232234

233235
// 根据交易所类型设置API密钥
@@ -560,6 +562,7 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode
560562
MaxDailyLoss: maxDailyLoss,
561563
MaxDrawdown: maxDrawdown,
562564
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
565+
IsCrossMargin: traderCfg.IsCrossMargin,
563566
}
564567

565568
// 根据交易所类型设置API密钥

trader/aster_trader.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,38 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in
819819
return result, nil
820820
}
821821

822+
// SetMarginMode 设置仓位模式
823+
func (t *AsterTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
824+
// Aster支持仓位模式设置
825+
// API格式与币安相似:CROSSED(全仓) / ISOLATED(逐仓)
826+
marginType := "CROSSED"
827+
if !isCrossMargin {
828+
marginType = "ISOLATED"
829+
}
830+
831+
params := map[string]interface{}{
832+
"symbol": symbol,
833+
"marginType": marginType,
834+
}
835+
836+
// 使用request方法调用API
837+
_, err := t.request("POST", "/fapi/v3/marginType", params)
838+
if err != nil {
839+
// 如果错误表示无需更改,忽略错误
840+
if strings.Contains(err.Error(), "No need to change") ||
841+
strings.Contains(err.Error(), "Margin type cannot be changed") {
842+
log.Printf(" ✓ %s 仓位模式已是 %s 或有持仓无法更改", symbol, marginType)
843+
return nil
844+
}
845+
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
846+
// 不返回错误,让交易继续
847+
return nil
848+
}
849+
850+
log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginType)
851+
return nil
852+
}
853+
822854
// SetLeverage 设置杠杆倍数
823855
func (t *AsterTrader) SetLeverage(symbol string, leverage int) error {
824856
params := map[string]interface{}{

trader/auto_trader.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ type AutoTraderConfig struct {
6363
MaxDailyLoss float64 // 最大日亏损百分比(提示)
6464
MaxDrawdown float64 // 最大回撤百分比(提示)
6565
StopTradingTime time.Duration // 触发风控后暂停时长
66+
67+
// 仓位模式
68+
IsCrossMargin bool // true=全仓模式, false=逐仓模式
6669
}
6770

6871
// AutoTrader 自动交易器
@@ -135,6 +138,13 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
135138
var trader Trader
136139
var err error
137140

141+
// 记录仓位模式(通用)
142+
marginModeStr := "全仓"
143+
if !config.IsCrossMargin {
144+
marginModeStr = "逐仓"
145+
}
146+
log.Printf("📊 [%s] 仓位模式: %s", config.Name, marginModeStr)
147+
138148
switch config.Exchange {
139149
case "binance":
140150
log.Printf("🏦 [%s] 使用币安合约交易", config.Name)
@@ -589,6 +599,12 @@ func (at *AutoTrader) executeOpenLongWithRecord(decision *decision.Decision, act
589599
actionRecord.Quantity = quantity
590600
actionRecord.Price = marketData.CurrentPrice
591601

602+
// 设置仓位模式
603+
if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil {
604+
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
605+
// 继续执行,不影响交易
606+
}
607+
592608
// 开仓
593609
order, err := at.trader.OpenLong(decision.Symbol, quantity, decision.Leverage)
594610
if err != nil {
@@ -642,6 +658,12 @@ func (at *AutoTrader) executeOpenShortWithRecord(decision *decision.Decision, ac
642658
actionRecord.Quantity = quantity
643659
actionRecord.Price = marketData.CurrentPrice
644660

661+
// 设置仓位模式
662+
if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil {
663+
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
664+
// 继续执行,不影响交易
665+
}
666+
645667
// 开仓
646668
order, err := at.trader.OpenShort(decision.Symbol, quantity, decision.Leverage)
647669
if err != nil {
@@ -885,7 +907,7 @@ func (at *AutoTrader) GetPositions() ([]map[string]interface{}, error) {
885907

886908
// 计算占用保证金
887909
marginUsed := (quantity * markPrice) / float64(leverage)
888-
910+
889911
// 计算盈亏百分比(基于保证金)
890912
// 收益率 = 未实现盈亏 / 保证金 × 100%
891913
pnlPct := 0.0

trader/binance_futures.go

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,46 @@ func (t *FuturesTrader) GetPositions() ([]map[string]interface{}, error) {
131131
return result, nil
132132
}
133133

134+
// SetMarginMode 设置仓位模式
135+
func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
136+
var marginType futures.MarginType
137+
if isCrossMargin {
138+
marginType = futures.MarginTypeCrossed
139+
} else {
140+
marginType = futures.MarginTypeIsolated
141+
}
142+
143+
// 尝试设置仓位模式
144+
err := t.client.NewChangeMarginTypeService().
145+
Symbol(symbol).
146+
MarginType(marginType).
147+
Do(context.Background())
148+
149+
marginModeStr := "全仓"
150+
if !isCrossMargin {
151+
marginModeStr = "逐仓"
152+
}
153+
154+
if err != nil {
155+
// 如果错误信息包含"No need to change",说明仓位模式已经是目标值
156+
if contains(err.Error(), "No need to change margin type") {
157+
log.Printf(" ✓ %s 仓位模式已是 %s", symbol, marginModeStr)
158+
return nil
159+
}
160+
// 如果有持仓,无法更改仓位模式,但不影响交易
161+
if contains(err.Error(), "Margin type cannot be changed if there exists position") {
162+
log.Printf(" ⚠️ %s 有持仓,无法更改仓位模式,继续使用当前模式", symbol)
163+
return nil
164+
}
165+
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
166+
// 不返回错误,让交易继续
167+
return nil
168+
}
169+
170+
log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginModeStr)
171+
return nil
172+
}
173+
134174
// SetLeverage 设置杠杆(智能判断+冷却期)
135175
func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
136176
// 先尝试获取当前杠杆(从持仓信息)
@@ -177,31 +217,6 @@ func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
177217
return nil
178218
}
179219

180-
// SetMarginType 设置保证金模式
181-
func (t *FuturesTrader) SetMarginType(symbol string, marginType futures.MarginType) error {
182-
err := t.client.NewChangeMarginTypeService().
183-
Symbol(symbol).
184-
MarginType(marginType).
185-
Do(context.Background())
186-
187-
if err != nil {
188-
// 如果已经是该模式,不算错误
189-
if contains(err.Error(), "No need to change") {
190-
log.Printf(" ✓ %s 保证金模式已是 %s", symbol, marginType)
191-
return nil
192-
}
193-
return fmt.Errorf("设置保证金模式失败: %w", err)
194-
}
195-
196-
log.Printf(" ✓ %s 保证金模式已切换为 %s", symbol, marginType)
197-
198-
// 切换保证金模式后等待3秒(避免冷却期错误)
199-
log.Printf(" ⏱ 等待3秒冷却期...")
200-
time.Sleep(3 * time.Second)
201-
202-
return nil
203-
}
204-
205220
// OpenLong 开多仓
206221
func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
207222
// 先取消该币种的所有委托单(清理旧的止损止盈单)
@@ -214,10 +229,7 @@ func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int)
214229
return nil, err
215230
}
216231

217-
// 设置逐仓模式
218-
if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil {
219-
return nil, err
220-
}
232+
// 注意:仓位模式应该由调用方(AutoTrader)在开仓前通过 SetMarginMode 设置
221233

222234
// 格式化数量到正确精度
223235
quantityStr, err := t.FormatQuantity(symbol, quantity)
@@ -260,10 +272,7 @@ func (t *FuturesTrader) OpenShort(symbol string, quantity float64, leverage int)
260272
return nil, err
261273
}
262274

263-
// 设置逐仓模式
264-
if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil {
265-
return nil, err
266-
}
275+
// 注意:仓位模式应该由调用方(AutoTrader)在开仓前通过 SetMarginMode 设置
267276

268277
// 格式化数量到正确精度
269278
quantityStr, err := t.FormatQuantity(symbol, quantity)

trader/hyperliquid_trader.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import (
1313

1414
// HyperliquidTrader Hyperliquid交易器
1515
type HyperliquidTrader struct {
16-
exchange *hyperliquid.Exchange
17-
ctx context.Context
18-
walletAddr string
19-
meta *hyperliquid.Meta // 缓存meta信息(包含精度等)
16+
exchange *hyperliquid.Exchange
17+
ctx context.Context
18+
walletAddr string
19+
meta *hyperliquid.Meta // 缓存meta信息(包含精度等)
20+
isCrossMargin bool // 是否为全仓模式
2021
}
2122

2223
// NewHyperliquidTrader 创建Hyperliquid交易器
@@ -63,10 +64,11 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool)
6364
}
6465

6566
return &HyperliquidTrader{
66-
exchange: exchange,
67-
ctx: ctx,
68-
walletAddr: walletAddr,
69-
meta: meta,
67+
exchange: exchange,
68+
ctx: ctx,
69+
walletAddr: walletAddr,
70+
meta: meta,
71+
isCrossMargin: true, // 默认使用全仓模式
7072
}, nil
7173
}
7274

@@ -187,13 +189,26 @@ func (t *HyperliquidTrader) GetPositions() ([]map[string]interface{}, error) {
187189
return result, nil
188190
}
189191

192+
// SetMarginMode 设置仓位模式 (在SetLeverage时一并设置)
193+
func (t *HyperliquidTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
194+
// Hyperliquid的仓位模式在SetLeverage时设置,这里只记录
195+
t.isCrossMargin = isCrossMargin
196+
marginModeStr := "全仓"
197+
if !isCrossMargin {
198+
marginModeStr = "逐仓"
199+
}
200+
log.Printf(" ✓ %s 将使用 %s 模式", symbol, marginModeStr)
201+
return nil
202+
}
203+
190204
// SetLeverage 设置杠杆
191205
func (t *HyperliquidTrader) SetLeverage(symbol string, leverage int) error {
192206
// Hyperliquid symbol格式(去掉USDT后缀)
193207
coin := convertSymbolToHyperliquid(symbol)
194208

195209
// 调用UpdateLeverage (leverage int, name string, isCross bool)
196-
_, err := t.exchange.UpdateLeverage(t.ctx, leverage, coin, false) // false = 逐仓模式
210+
// 第三个参数: true=全仓模式, false=逐仓模式
211+
_, err := t.exchange.UpdateLeverage(t.ctx, leverage, coin, t.isCrossMargin)
197212
if err != nil {
198213
return fmt.Errorf("设置杠杆失败: %w", err)
199214
}

trader/interface.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ type Trader interface {
2424
// SetLeverage 设置杠杆
2525
SetLeverage(symbol string, leverage int) error
2626

27+
// SetMarginMode 设置仓位模式 (true=全仓, false=逐仓)
28+
SetMarginMode(symbol string, isCrossMargin bool) error
29+
2730
// GetMarketPrice 获取市场价格
2831
GetMarketPrice(symbol string) (float64, error)
2932

0 commit comments

Comments
 (0)