mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-04 03:21:04 +08:00
Add MarginMode configration
This commit is contained in:
@@ -170,6 +170,7 @@ type CreateTraderRequest struct {
|
|||||||
InitialBalance float64 `json:"initial_balance"`
|
InitialBalance float64 `json:"initial_balance"`
|
||||||
CustomPrompt string `json:"custom_prompt"`
|
CustomPrompt string `json:"custom_prompt"`
|
||||||
OverrideBasePrompt bool `json:"override_base_prompt"`
|
OverrideBasePrompt bool `json:"override_base_prompt"`
|
||||||
|
IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型,nil表示使用默认值true
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModelConfig struct {
|
type ModelConfig struct {
|
||||||
@@ -222,6 +223,12 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
|||||||
// 生成交易员ID
|
// 生成交易员ID
|
||||||
traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix())
|
traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix())
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
|
isCrossMargin := true // 默认为全仓模式
|
||||||
|
if req.IsCrossMargin != nil {
|
||||||
|
isCrossMargin = *req.IsCrossMargin
|
||||||
|
}
|
||||||
|
|
||||||
// 创建交易员配置(数据库实体)
|
// 创建交易员配置(数据库实体)
|
||||||
trader := &config.TraderRecord{
|
trader := &config.TraderRecord{
|
||||||
ID: traderID,
|
ID: traderID,
|
||||||
@@ -232,6 +239,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
|||||||
InitialBalance: req.InitialBalance,
|
InitialBalance: req.InitialBalance,
|
||||||
CustomPrompt: req.CustomPrompt,
|
CustomPrompt: req.CustomPrompt,
|
||||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||||
|
IsCrossMargin: isCrossMargin,
|
||||||
ScanIntervalMinutes: 3, // 默认3分钟
|
ScanIntervalMinutes: 3, // 默认3分钟
|
||||||
IsRunning: false,
|
IsRunning: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ func (d *Database) createTables() error {
|
|||||||
`ALTER TABLE exchanges ADD COLUMN aster_private_key TEXT DEFAULT ''`,
|
`ALTER TABLE exchanges ADD COLUMN aster_private_key TEXT DEFAULT ''`,
|
||||||
`ALTER TABLE traders ADD COLUMN custom_prompt TEXT DEFAULT ''`,
|
`ALTER TABLE traders ADD COLUMN custom_prompt TEXT DEFAULT ''`,
|
||||||
`ALTER TABLE traders ADD COLUMN override_base_prompt BOOLEAN DEFAULT 0`,
|
`ALTER TABLE traders ADD COLUMN override_base_prompt BOOLEAN DEFAULT 0`,
|
||||||
|
`ALTER TABLE traders ADD COLUMN is_cross_margin BOOLEAN DEFAULT 1`, // 默认为全仓模式
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, query := range alterQueries {
|
for _, query := range alterQueries {
|
||||||
@@ -369,6 +370,7 @@ type TraderRecord struct {
|
|||||||
IsRunning bool `json:"is_running"`
|
IsRunning bool `json:"is_running"`
|
||||||
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
|
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
|
||||||
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
|
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
|
||||||
|
IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式(true=全仓,false=逐仓)
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
@@ -656,9 +658,9 @@ func (d *Database) CreateExchange(userID, id, name, typ string, enabled bool, ap
|
|||||||
// CreateTrader 创建交易员
|
// CreateTrader 创建交易员
|
||||||
func (d *Database) CreateTrader(trader *TraderRecord) error {
|
func (d *Database) CreateTrader(trader *TraderRecord) error {
|
||||||
_, err := d.db.Exec(`
|
_, err := d.db.Exec(`
|
||||||
INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, custom_prompt, override_base_prompt)
|
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)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.CustomPrompt, trader.OverrideBasePrompt)
|
`, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.CustomPrompt, trader.OverrideBasePrompt, trader.IsCrossMargin)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,7 +668,8 @@ func (d *Database) CreateTrader(trader *TraderRecord) error {
|
|||||||
func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
|
func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
|
||||||
rows, err := d.db.Query(`
|
rows, err := d.db.Query(`
|
||||||
SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running,
|
SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running,
|
||||||
COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt, created_at, updated_at
|
COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt,
|
||||||
|
COALESCE(is_cross_margin, 1) as is_cross_margin, created_at, updated_at
|
||||||
FROM traders WHERE user_id = ? ORDER BY created_at DESC
|
FROM traders WHERE user_id = ? ORDER BY created_at DESC
|
||||||
`, userID)
|
`, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -680,7 +683,8 @@ func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
|
|||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
|
&trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
|
||||||
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
||||||
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.CreatedAt, &trader.UpdatedAt,
|
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.IsCrossMargin,
|
||||||
|
&trader.CreatedAt, &trader.UpdatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
|
|||||||
MaxDailyLoss: maxDailyLoss,
|
MaxDailyLoss: maxDailyLoss,
|
||||||
MaxDrawdown: maxDrawdown,
|
MaxDrawdown: maxDrawdown,
|
||||||
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
||||||
|
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据交易所类型设置API密钥
|
// 根据交易所类型设置API密钥
|
||||||
@@ -228,6 +229,7 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModel
|
|||||||
MaxDailyLoss: maxDailyLoss,
|
MaxDailyLoss: maxDailyLoss,
|
||||||
MaxDrawdown: maxDrawdown,
|
MaxDrawdown: maxDrawdown,
|
||||||
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
||||||
|
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据交易所类型设置API密钥
|
// 根据交易所类型设置API密钥
|
||||||
@@ -560,6 +562,7 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode
|
|||||||
MaxDailyLoss: maxDailyLoss,
|
MaxDailyLoss: maxDailyLoss,
|
||||||
MaxDrawdown: maxDrawdown,
|
MaxDrawdown: maxDrawdown,
|
||||||
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
||||||
|
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据交易所类型设置API密钥
|
// 根据交易所类型设置API密钥
|
||||||
|
|||||||
@@ -819,6 +819,38 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMarginMode 设置仓位模式
|
||||||
|
func (t *AsterTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
|
||||||
|
// Aster支持仓位模式设置
|
||||||
|
// API格式与币安相似:CROSSED(全仓) / ISOLATED(逐仓)
|
||||||
|
marginType := "CROSSED"
|
||||||
|
if !isCrossMargin {
|
||||||
|
marginType = "ISOLATED"
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"symbol": symbol,
|
||||||
|
"marginType": marginType,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用request方法调用API
|
||||||
|
_, err := t.request("POST", "/fapi/v3/marginType", params)
|
||||||
|
if err != nil {
|
||||||
|
// 如果错误表示无需更改,忽略错误
|
||||||
|
if strings.Contains(err.Error(), "No need to change") ||
|
||||||
|
strings.Contains(err.Error(), "Margin type cannot be changed") {
|
||||||
|
log.Printf(" ✓ %s 仓位模式已是 %s 或有持仓无法更改", symbol, marginType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
|
||||||
|
// 不返回错误,让交易继续
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetLeverage 设置杠杆倍数
|
// SetLeverage 设置杠杆倍数
|
||||||
func (t *AsterTrader) SetLeverage(symbol string, leverage int) error {
|
func (t *AsterTrader) SetLeverage(symbol string, leverage int) error {
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ type AutoTraderConfig struct {
|
|||||||
MaxDailyLoss float64 // 最大日亏损百分比(提示)
|
MaxDailyLoss float64 // 最大日亏损百分比(提示)
|
||||||
MaxDrawdown float64 // 最大回撤百分比(提示)
|
MaxDrawdown float64 // 最大回撤百分比(提示)
|
||||||
StopTradingTime time.Duration // 触发风控后暂停时长
|
StopTradingTime time.Duration // 触发风控后暂停时长
|
||||||
|
|
||||||
|
// 仓位模式
|
||||||
|
IsCrossMargin bool // true=全仓模式, false=逐仓模式
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoTrader 自动交易器
|
// AutoTrader 自动交易器
|
||||||
@@ -135,6 +138,13 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
|||||||
var trader Trader
|
var trader Trader
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// 记录仓位模式(通用)
|
||||||
|
marginModeStr := "全仓"
|
||||||
|
if !config.IsCrossMargin {
|
||||||
|
marginModeStr = "逐仓"
|
||||||
|
}
|
||||||
|
log.Printf("📊 [%s] 仓位模式: %s", config.Name, marginModeStr)
|
||||||
|
|
||||||
switch config.Exchange {
|
switch config.Exchange {
|
||||||
case "binance":
|
case "binance":
|
||||||
log.Printf("🏦 [%s] 使用币安合约交易", config.Name)
|
log.Printf("🏦 [%s] 使用币安合约交易", config.Name)
|
||||||
@@ -589,6 +599,12 @@ func (at *AutoTrader) executeOpenLongWithRecord(decision *decision.Decision, act
|
|||||||
actionRecord.Quantity = quantity
|
actionRecord.Quantity = quantity
|
||||||
actionRecord.Price = marketData.CurrentPrice
|
actionRecord.Price = marketData.CurrentPrice
|
||||||
|
|
||||||
|
// 设置仓位模式
|
||||||
|
if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil {
|
||||||
|
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
|
||||||
|
// 继续执行,不影响交易
|
||||||
|
}
|
||||||
|
|
||||||
// 开仓
|
// 开仓
|
||||||
order, err := at.trader.OpenLong(decision.Symbol, quantity, decision.Leverage)
|
order, err := at.trader.OpenLong(decision.Symbol, quantity, decision.Leverage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -642,6 +658,12 @@ func (at *AutoTrader) executeOpenShortWithRecord(decision *decision.Decision, ac
|
|||||||
actionRecord.Quantity = quantity
|
actionRecord.Quantity = quantity
|
||||||
actionRecord.Price = marketData.CurrentPrice
|
actionRecord.Price = marketData.CurrentPrice
|
||||||
|
|
||||||
|
// 设置仓位模式
|
||||||
|
if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil {
|
||||||
|
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
|
||||||
|
// 继续执行,不影响交易
|
||||||
|
}
|
||||||
|
|
||||||
// 开仓
|
// 开仓
|
||||||
order, err := at.trader.OpenShort(decision.Symbol, quantity, decision.Leverage)
|
order, err := at.trader.OpenShort(decision.Symbol, quantity, decision.Leverage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -885,7 +907,7 @@ func (at *AutoTrader) GetPositions() ([]map[string]interface{}, error) {
|
|||||||
|
|
||||||
// 计算占用保证金
|
// 计算占用保证金
|
||||||
marginUsed := (quantity * markPrice) / float64(leverage)
|
marginUsed := (quantity * markPrice) / float64(leverage)
|
||||||
|
|
||||||
// 计算盈亏百分比(基于保证金)
|
// 计算盈亏百分比(基于保证金)
|
||||||
// 收益率 = 未实现盈亏 / 保证金 × 100%
|
// 收益率 = 未实现盈亏 / 保证金 × 100%
|
||||||
pnlPct := 0.0
|
pnlPct := 0.0
|
||||||
|
|||||||
@@ -131,6 +131,46 @@ func (t *FuturesTrader) GetPositions() ([]map[string]interface{}, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMarginMode 设置仓位模式
|
||||||
|
func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
|
||||||
|
var marginType futures.MarginType
|
||||||
|
if isCrossMargin {
|
||||||
|
marginType = futures.MarginTypeCrossed
|
||||||
|
} else {
|
||||||
|
marginType = futures.MarginTypeIsolated
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试设置仓位模式
|
||||||
|
err := t.client.NewChangeMarginTypeService().
|
||||||
|
Symbol(symbol).
|
||||||
|
MarginType(marginType).
|
||||||
|
Do(context.Background())
|
||||||
|
|
||||||
|
marginModeStr := "全仓"
|
||||||
|
if !isCrossMargin {
|
||||||
|
marginModeStr = "逐仓"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// 如果错误信息包含"No need to change",说明仓位模式已经是目标值
|
||||||
|
if contains(err.Error(), "No need to change margin type") {
|
||||||
|
log.Printf(" ✓ %s 仓位模式已是 %s", symbol, marginModeStr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 如果有持仓,无法更改仓位模式,但不影响交易
|
||||||
|
if contains(err.Error(), "Margin type cannot be changed if there exists position") {
|
||||||
|
log.Printf(" ⚠️ %s 有持仓,无法更改仓位模式,继续使用当前模式", symbol)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Printf(" ⚠️ 设置仓位模式失败: %v", err)
|
||||||
|
// 不返回错误,让交易继续
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginModeStr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetLeverage 设置杠杆(智能判断+冷却期)
|
// SetLeverage 设置杠杆(智能判断+冷却期)
|
||||||
func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
|
func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
|
||||||
// 先尝试获取当前杠杆(从持仓信息)
|
// 先尝试获取当前杠杆(从持仓信息)
|
||||||
@@ -177,31 +217,6 @@ func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMarginType 设置保证金模式
|
|
||||||
func (t *FuturesTrader) SetMarginType(symbol string, marginType futures.MarginType) error {
|
|
||||||
err := t.client.NewChangeMarginTypeService().
|
|
||||||
Symbol(symbol).
|
|
||||||
MarginType(marginType).
|
|
||||||
Do(context.Background())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// 如果已经是该模式,不算错误
|
|
||||||
if contains(err.Error(), "No need to change") {
|
|
||||||
log.Printf(" ✓ %s 保证金模式已是 %s", symbol, marginType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("设置保证金模式失败: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf(" ✓ %s 保证金模式已切换为 %s", symbol, marginType)
|
|
||||||
|
|
||||||
// 切换保证金模式后等待3秒(避免冷却期错误)
|
|
||||||
log.Printf(" ⏱ 等待3秒冷却期...")
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenLong 开多仓
|
// OpenLong 开多仓
|
||||||
func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
|
func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
|
||||||
// 先取消该币种的所有委托单(清理旧的止损止盈单)
|
// 先取消该币种的所有委托单(清理旧的止损止盈单)
|
||||||
@@ -214,10 +229,7 @@ func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置逐仓模式
|
// 注意:仓位模式应该由调用方(AutoTrader)在开仓前通过 SetMarginMode 设置
|
||||||
if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化数量到正确精度
|
// 格式化数量到正确精度
|
||||||
quantityStr, err := t.FormatQuantity(symbol, quantity)
|
quantityStr, err := t.FormatQuantity(symbol, quantity)
|
||||||
@@ -260,10 +272,7 @@ func (t *FuturesTrader) OpenShort(symbol string, quantity float64, leverage int)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置逐仓模式
|
// 注意:仓位模式应该由调用方(AutoTrader)在开仓前通过 SetMarginMode 设置
|
||||||
if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化数量到正确精度
|
// 格式化数量到正确精度
|
||||||
quantityStr, err := t.FormatQuantity(symbol, quantity)
|
quantityStr, err := t.FormatQuantity(symbol, quantity)
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ import (
|
|||||||
|
|
||||||
// HyperliquidTrader Hyperliquid交易器
|
// HyperliquidTrader Hyperliquid交易器
|
||||||
type HyperliquidTrader struct {
|
type HyperliquidTrader struct {
|
||||||
exchange *hyperliquid.Exchange
|
exchange *hyperliquid.Exchange
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
walletAddr string
|
walletAddr string
|
||||||
meta *hyperliquid.Meta // 缓存meta信息(包含精度等)
|
meta *hyperliquid.Meta // 缓存meta信息(包含精度等)
|
||||||
|
isCrossMargin bool // 是否为全仓模式
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHyperliquidTrader 创建Hyperliquid交易器
|
// NewHyperliquidTrader 创建Hyperliquid交易器
|
||||||
@@ -63,10 +64,11 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &HyperliquidTrader{
|
return &HyperliquidTrader{
|
||||||
exchange: exchange,
|
exchange: exchange,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
walletAddr: walletAddr,
|
walletAddr: walletAddr,
|
||||||
meta: meta,
|
meta: meta,
|
||||||
|
isCrossMargin: true, // 默认使用全仓模式
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,13 +189,26 @@ func (t *HyperliquidTrader) GetPositions() ([]map[string]interface{}, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMarginMode 设置仓位模式 (在SetLeverage时一并设置)
|
||||||
|
func (t *HyperliquidTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
|
||||||
|
// Hyperliquid的仓位模式在SetLeverage时设置,这里只记录
|
||||||
|
t.isCrossMargin = isCrossMargin
|
||||||
|
marginModeStr := "全仓"
|
||||||
|
if !isCrossMargin {
|
||||||
|
marginModeStr = "逐仓"
|
||||||
|
}
|
||||||
|
log.Printf(" ✓ %s 将使用 %s 模式", symbol, marginModeStr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetLeverage 设置杠杆
|
// SetLeverage 设置杠杆
|
||||||
func (t *HyperliquidTrader) SetLeverage(symbol string, leverage int) error {
|
func (t *HyperliquidTrader) SetLeverage(symbol string, leverage int) error {
|
||||||
// Hyperliquid symbol格式(去掉USDT后缀)
|
// Hyperliquid symbol格式(去掉USDT后缀)
|
||||||
coin := convertSymbolToHyperliquid(symbol)
|
coin := convertSymbolToHyperliquid(symbol)
|
||||||
|
|
||||||
// 调用UpdateLeverage (leverage int, name string, isCross bool)
|
// 调用UpdateLeverage (leverage int, name string, isCross bool)
|
||||||
_, err := t.exchange.UpdateLeverage(t.ctx, leverage, coin, false) // false = 逐仓模式
|
// 第三个参数: true=全仓模式, false=逐仓模式
|
||||||
|
_, err := t.exchange.UpdateLeverage(t.ctx, leverage, coin, t.isCrossMargin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("设置杠杆失败: %w", err)
|
return fmt.Errorf("设置杠杆失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ type Trader interface {
|
|||||||
// SetLeverage 设置杠杆
|
// SetLeverage 设置杠杆
|
||||||
SetLeverage(symbol string, leverage int) error
|
SetLeverage(symbol string, leverage int) error
|
||||||
|
|
||||||
|
// SetMarginMode 设置仓位模式 (true=全仓, false=逐仓)
|
||||||
|
SetMarginMode(symbol string, isCrossMargin bool) error
|
||||||
|
|
||||||
// GetMarketPrice 获取市场价格
|
// GetMarketPrice 获取市场价格
|
||||||
GetMarketPrice(symbol string) (float64, error)
|
GetMarketPrice(symbol string) (float64, error)
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
return traders?.some(t => t.exchange_id === exchangeId && t.is_running) || false;
|
return traders?.some(t => t.exchange_id === exchangeId && t.is_running) || false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateTrader = async (modelId: string, exchangeId: string, name: string, initialBalance: number, customPrompt?: string, overrideBase?: boolean) => {
|
const handleCreateTrader = async (modelId: string, exchangeId: string, name: string, initialBalance: number, customPrompt?: string, overrideBase?: boolean, isCrossMargin?: boolean) => {
|
||||||
try {
|
try {
|
||||||
const model = allModels?.find(m => m.id === modelId);
|
const model = allModels?.find(m => m.id === modelId);
|
||||||
const exchange = allExchanges?.find(e => e.id === exchangeId);
|
const exchange = allExchanges?.find(e => e.id === exchangeId);
|
||||||
@@ -120,7 +120,8 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
exchange_id: exchangeId,
|
exchange_id: exchangeId,
|
||||||
initial_balance: initialBalance,
|
initial_balance: initialBalance,
|
||||||
custom_prompt: customPrompt,
|
custom_prompt: customPrompt,
|
||||||
override_base_prompt: overrideBase
|
override_base_prompt: overrideBase,
|
||||||
|
is_cross_margin: isCrossMargin
|
||||||
};
|
};
|
||||||
|
|
||||||
await api.createTrader(request);
|
await api.createTrader(request);
|
||||||
@@ -663,7 +664,7 @@ function CreateTraderModal({
|
|||||||
}: {
|
}: {
|
||||||
enabledModels: AIModel[];
|
enabledModels: AIModel[];
|
||||||
enabledExchanges: Exchange[];
|
enabledExchanges: Exchange[];
|
||||||
onCreate: (modelId: string, exchangeId: string, name: string, initialBalance: number, customPrompt?: string, overrideBase?: boolean) => void;
|
onCreate: (modelId: string, exchangeId: string, name: string, initialBalance: number, customPrompt?: string, overrideBase?: boolean, isCrossMargin?: boolean) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
language: any;
|
language: any;
|
||||||
}) {
|
}) {
|
||||||
@@ -679,12 +680,13 @@ function CreateTraderModal({
|
|||||||
const [customPrompt, setCustomPrompt] = useState('');
|
const [customPrompt, setCustomPrompt] = useState('');
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||||
const [overrideBase, setOverrideBase] = useState(false);
|
const [overrideBase, setOverrideBase] = useState(false);
|
||||||
|
const [isCrossMargin, setIsCrossMargin] = useState(true); // 默认为全仓模式
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!selectedModel || !selectedExchange || !traderName.trim()) return;
|
if (!selectedModel || !selectedExchange || !traderName.trim()) return;
|
||||||
|
|
||||||
onCreate(selectedModel, selectedExchange, traderName.trim(), initialBalance, customPrompt.trim() || undefined, overrideBase);
|
onCreate(selectedModel, selectedExchange, traderName.trim(), initialBalance, customPrompt.trim() || undefined, overrideBase, isCrossMargin);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -763,6 +765,44 @@ function CreateTraderModal({
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Margin Mode Selection */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
|
||||||
|
仓位模式
|
||||||
|
</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsCrossMargin(true)}
|
||||||
|
className={`px-3 py-2 rounded text-sm font-semibold transition-all ${
|
||||||
|
isCrossMargin
|
||||||
|
? 'bg-yellow-500 text-black'
|
||||||
|
: 'bg-gray-700 text-gray-400 hover:bg-gray-600'
|
||||||
|
}`}
|
||||||
|
style={isCrossMargin ? { background: '#F0B90B', color: '#000' } : { background: '#2B3139', color: '#848E9C' }}
|
||||||
|
>
|
||||||
|
全仓模式
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsCrossMargin(false)}
|
||||||
|
className={`px-3 py-2 rounded text-sm font-semibold transition-all ${
|
||||||
|
!isCrossMargin
|
||||||
|
? 'bg-yellow-500 text-black'
|
||||||
|
: 'bg-gray-700 text-gray-400 hover:bg-gray-600'
|
||||||
|
}`}
|
||||||
|
style={!isCrossMargin ? { background: '#F0B90B', color: '#000' } : { background: '#2B3139', color: '#848E9C' }}
|
||||||
|
>
|
||||||
|
逐仓模式
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
|
||||||
|
{isCrossMargin
|
||||||
|
? '全仓模式:所有仓位共享账户余额作为保证金'
|
||||||
|
: '逐仓模式:每个仓位独立管理保证金,风险隔离'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Advanced Settings Toggle */}
|
{/* Advanced Settings Toggle */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export interface CreateTraderRequest {
|
|||||||
initial_balance: number;
|
initial_balance: number;
|
||||||
custom_prompt?: string;
|
custom_prompt?: string;
|
||||||
override_base_prompt?: boolean;
|
override_base_prompt?: boolean;
|
||||||
|
is_cross_margin?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateModelConfigRequest {
|
export interface UpdateModelConfigRequest {
|
||||||
|
|||||||
Reference in New Issue
Block a user