mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
feat: add oi_low coin source for short opportunities
- Add GetOILowPositions/GetOILowSymbols in oi.go - Add UseOILow/OILowLimit config fields - Add oi_low case in GetCandidateCoins - Support oi_low in mixed mode - Update source tag formatting
This commit is contained in:
@@ -470,6 +470,26 @@ func (e *StrategyEngine) GetCandidateCoins() ([]CandidateCoin, error) {
|
|||||||
// 空列表是正常情况,直接返回
|
// 空列表是正常情况,直接返回
|
||||||
return e.filterExcludedCoins(coins), nil
|
return e.filterExcludedCoins(coins), nil
|
||||||
|
|
||||||
|
case "oi_low":
|
||||||
|
// 持仓减少榜,适合做空
|
||||||
|
if !coinSource.UseOILow {
|
||||||
|
logger.Infof("⚠️ source_type is 'oi_low' but use_oi_low is false, falling back to static coins")
|
||||||
|
for _, symbol := range coinSource.StaticCoins {
|
||||||
|
symbol = market.Normalize(symbol)
|
||||||
|
candidates = append(candidates, CandidateCoin{
|
||||||
|
Symbol: symbol,
|
||||||
|
Sources: []string{"static"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return e.filterExcludedCoins(candidates), nil
|
||||||
|
}
|
||||||
|
coins, err := e.getOILowCoins(coinSource.OILowLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 空列表是正常情况,直接返回
|
||||||
|
return e.filterExcludedCoins(coins), nil
|
||||||
|
|
||||||
case "mixed":
|
case "mixed":
|
||||||
if coinSource.UseAI500 {
|
if coinSource.UseAI500 {
|
||||||
poolCoins, err := e.getAI500Coins(coinSource.AI500Limit)
|
poolCoins, err := e.getAI500Coins(coinSource.AI500Limit)
|
||||||
@@ -493,6 +513,17 @@ func (e *StrategyEngine) GetCandidateCoins() ([]CandidateCoin, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if coinSource.UseOILow {
|
||||||
|
oiLowCoins, err := e.getOILowCoins(coinSource.OILowLimit)
|
||||||
|
if err != nil {
|
||||||
|
logger.Infof("⚠️ Failed to get OI Low: %v", err)
|
||||||
|
} else {
|
||||||
|
for _, coin := range oiLowCoins {
|
||||||
|
symbolSources[coin.Symbol] = append(symbolSources[coin.Symbol], "oi_low")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, symbol := range coinSource.StaticCoins {
|
for _, symbol := range coinSource.StaticCoins {
|
||||||
symbol = market.Normalize(symbol)
|
symbol = market.Normalize(symbol)
|
||||||
if _, exists := symbolSources[symbol]; !exists {
|
if _, exists := symbolSources[symbol]; !exists {
|
||||||
@@ -585,6 +616,30 @@ func (e *StrategyEngine) getOITopCoins(limit int) ([]CandidateCoin, error) {
|
|||||||
return candidates, nil
|
return candidates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *StrategyEngine) getOILowCoins(limit int) ([]CandidateCoin, error) {
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
positions, err := e.nofxosClient.GetOILowPositions()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidates []CandidateCoin
|
||||||
|
for i, pos := range positions {
|
||||||
|
if i >= limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
symbol := market.Normalize(pos.Symbol)
|
||||||
|
candidates = append(candidates, CandidateCoin{
|
||||||
|
Symbol: symbol,
|
||||||
|
Sources: []string{"oi_low"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return candidates, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// External & Quant Data
|
// External & Quant Data
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1291,13 +1346,38 @@ func (e *StrategyEngine) formatPositionInfo(index int, pos PositionInfo, ctx *Co
|
|||||||
|
|
||||||
func (e *StrategyEngine) formatCoinSourceTag(sources []string) string {
|
func (e *StrategyEngine) formatCoinSourceTag(sources []string) string {
|
||||||
if len(sources) > 1 {
|
if len(sources) > 1 {
|
||||||
return " (AI500+OI_Top dual signal)"
|
// 多信号源组合
|
||||||
|
hasAI500 := false
|
||||||
|
hasOITop := false
|
||||||
|
hasOILow := false
|
||||||
|
for _, s := range sources {
|
||||||
|
switch s {
|
||||||
|
case "ai500":
|
||||||
|
hasAI500 = true
|
||||||
|
case "oi_top":
|
||||||
|
hasOITop = true
|
||||||
|
case "oi_low":
|
||||||
|
hasOILow = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasAI500 && hasOITop {
|
||||||
|
return " (AI500+OI_Top dual signal)"
|
||||||
|
}
|
||||||
|
if hasAI500 && hasOILow {
|
||||||
|
return " (AI500+OI_Low dual signal)"
|
||||||
|
}
|
||||||
|
if hasOITop && hasOILow {
|
||||||
|
return " (OI_Top+OI_Low)"
|
||||||
|
}
|
||||||
|
return " (Multiple sources)"
|
||||||
} else if len(sources) == 1 {
|
} else if len(sources) == 1 {
|
||||||
switch sources[0] {
|
switch sources[0] {
|
||||||
case "ai500":
|
case "ai500":
|
||||||
return " (AI500)"
|
return " (AI500)"
|
||||||
case "oi_top":
|
case "oi_top":
|
||||||
return " (OI_Top position growth)"
|
return " (OI_Top 持仓增加)"
|
||||||
|
case "oi_low":
|
||||||
|
return " (OI_Low 持仓减少)"
|
||||||
case "static":
|
case "static":
|
||||||
return " (Manual selection)"
|
return " (Manual selection)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,6 +129,31 @@ func (c *Client) GetOITopSymbols() ([]string, error) {
|
|||||||
return symbols, nil
|
return symbols, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOILowPositions retrieves OI decrease positions (for short opportunities)
|
||||||
|
func (c *Client) GetOILowPositions() ([]OIPosition, error) {
|
||||||
|
data, err := c.GetOIRanking("1h", 20)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data.LowPositions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOILowSymbols retrieves OI low coin symbol list
|
||||||
|
func (c *Client) GetOILowSymbols() ([]string, error) {
|
||||||
|
positions, err := c.GetOILowPositions()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var symbols []string
|
||||||
|
for _, pos := range positions {
|
||||||
|
symbol := NormalizeSymbol(pos.Symbol)
|
||||||
|
symbols = append(symbols, symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
return symbols, nil
|
||||||
|
}
|
||||||
|
|
||||||
// FormatOIRankingForAI formats OI ranking data for AI consumption
|
// FormatOIRankingForAI formats OI ranking data for AI consumption
|
||||||
func FormatOIRankingForAI(data *OIRankingData, lang Language) string {
|
func FormatOIRankingForAI(data *OIRankingData, lang Language) string {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ type PromptSectionsConfig struct {
|
|||||||
|
|
||||||
// CoinSourceConfig coin source configuration
|
// CoinSourceConfig coin source configuration
|
||||||
type CoinSourceConfig struct {
|
type CoinSourceConfig struct {
|
||||||
// source type: "static" | "ai500" | "oi_top" | "mixed"
|
// source type: "static" | "ai500" | "oi_top" | "oi_low" | "mixed"
|
||||||
SourceType string `json:"source_type"`
|
SourceType string `json:"source_type"`
|
||||||
// static coin list (used when source_type = "static")
|
// static coin list (used when source_type = "static")
|
||||||
StaticCoins []string `json:"static_coins,omitempty"`
|
StaticCoins []string `json:"static_coins,omitempty"`
|
||||||
@@ -107,10 +107,14 @@ type CoinSourceConfig struct {
|
|||||||
UseAI500 bool `json:"use_ai500"`
|
UseAI500 bool `json:"use_ai500"`
|
||||||
// AI500 coin pool maximum count
|
// AI500 coin pool maximum count
|
||||||
AI500Limit int `json:"ai500_limit,omitempty"`
|
AI500Limit int `json:"ai500_limit,omitempty"`
|
||||||
// whether to use OI Top
|
// whether to use OI Top (持仓增加榜,适合做多)
|
||||||
UseOITop bool `json:"use_oi_top"`
|
UseOITop bool `json:"use_oi_top"`
|
||||||
// OI Top maximum count
|
// OI Top maximum count
|
||||||
OITopLimit int `json:"oi_top_limit,omitempty"`
|
OITopLimit int `json:"oi_top_limit,omitempty"`
|
||||||
|
// whether to use OI Low (持仓减少榜,适合做空)
|
||||||
|
UseOILow bool `json:"use_oi_low"`
|
||||||
|
// OI Low maximum count
|
||||||
|
OILowLimit int `json:"oi_low_limit,omitempty"`
|
||||||
// Note: API URLs are now built automatically using NofxOSAPIKey from IndicatorConfig
|
// Note: API URLs are now built automatically using NofxOSAPIKey from IndicatorConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user