mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-17 01:14:40 +08:00
feat: use OHLCV table format for kline data in AI prompts
- Add KlineBar struct with full OHLCV data and timestamp - Store complete kline data in TimeframeSeriesData.Klines - Format klines as readable table with Time, Open, High, Low, Close, Volume - Mark current (latest) bar for clarity - Use kline count from strategy config instead of hardcoded 10 - Keep MidPrices/Volume for backward compatibility - Update both market/data.go and decision/strategy_engine.go formatters
This commit is contained in:
@@ -702,39 +702,56 @@ func (e *StrategyEngine) formatMarketData(data *market.Data) string {
|
|||||||
|
|
||||||
// formatTimeframeSeriesData formats series data for a single timeframe
|
// formatTimeframeSeriesData formats series data for a single timeframe
|
||||||
func (e *StrategyEngine) formatTimeframeSeriesData(sb *strings.Builder, data *market.TimeframeSeriesData, indicators store.IndicatorConfig) {
|
func (e *StrategyEngine) formatTimeframeSeriesData(sb *strings.Builder, data *market.TimeframeSeriesData, indicators store.IndicatorConfig) {
|
||||||
if len(data.MidPrices) > 0 {
|
// Use OHLCV table format if kline data is available
|
||||||
|
if len(data.Klines) > 0 {
|
||||||
|
sb.WriteString("Time(UTC) Open High Low Close Volume\n")
|
||||||
|
for i, k := range data.Klines {
|
||||||
|
t := time.Unix(k.Time/1000, 0).UTC()
|
||||||
|
timeStr := t.Format("01-02 15:04")
|
||||||
|
marker := ""
|
||||||
|
if i == len(data.Klines)-1 {
|
||||||
|
marker = " <- current"
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("%-14s %-9.4f %-9.4f %-9.4f %-9.4f %-12.2f%s\n",
|
||||||
|
timeStr, k.Open, k.High, k.Low, k.Close, k.Volume, marker))
|
||||||
|
}
|
||||||
|
sb.WriteString("\n")
|
||||||
|
} else if len(data.MidPrices) > 0 {
|
||||||
|
// Fallback to old format for backward compatibility
|
||||||
sb.WriteString(fmt.Sprintf("Mid prices: %s\n\n", formatFloatSlice(data.MidPrices)))
|
sb.WriteString(fmt.Sprintf("Mid prices: %s\n\n", formatFloatSlice(data.MidPrices)))
|
||||||
|
if indicators.EnableVolume && len(data.Volume) > 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("Volume: %s\n\n", formatFloatSlice(data.Volume)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Technical indicators (only show if enabled and data available)
|
||||||
if indicators.EnableEMA {
|
if indicators.EnableEMA {
|
||||||
if len(data.EMA20Values) > 0 {
|
if len(data.EMA20Values) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("EMA indicators (20-period): %s\n\n", formatFloatSlice(data.EMA20Values)))
|
sb.WriteString(fmt.Sprintf("EMA20: %s\n", formatFloatSlice(data.EMA20Values)))
|
||||||
}
|
}
|
||||||
if len(data.EMA50Values) > 0 {
|
if len(data.EMA50Values) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("EMA indicators (50-period): %s\n\n", formatFloatSlice(data.EMA50Values)))
|
sb.WriteString(fmt.Sprintf("EMA50: %s\n", formatFloatSlice(data.EMA50Values)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if indicators.EnableMACD && len(data.MACDValues) > 0 {
|
if indicators.EnableMACD && len(data.MACDValues) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("MACD indicators: %s\n\n", formatFloatSlice(data.MACDValues)))
|
sb.WriteString(fmt.Sprintf("MACD: %s\n", formatFloatSlice(data.MACDValues)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if indicators.EnableRSI {
|
if indicators.EnableRSI {
|
||||||
if len(data.RSI7Values) > 0 {
|
if len(data.RSI7Values) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("RSI indicators (7-Period): %s\n\n", formatFloatSlice(data.RSI7Values)))
|
sb.WriteString(fmt.Sprintf("RSI7: %s\n", formatFloatSlice(data.RSI7Values)))
|
||||||
}
|
}
|
||||||
if len(data.RSI14Values) > 0 {
|
if len(data.RSI14Values) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("RSI indicators (14-Period): %s\n\n", formatFloatSlice(data.RSI14Values)))
|
sb.WriteString(fmt.Sprintf("RSI14: %s\n", formatFloatSlice(data.RSI14Values)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if indicators.EnableVolume && len(data.Volume) > 0 {
|
if indicators.EnableATR && data.ATR14 > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("Volume: %s\n\n", formatFloatSlice(data.Volume)))
|
sb.WriteString(fmt.Sprintf("ATR14: %.4f\n", data.ATR14))
|
||||||
}
|
}
|
||||||
|
|
||||||
if indicators.EnableATR {
|
sb.WriteString("\n")
|
||||||
sb.WriteString(fmt.Sprintf("ATR (14-period): %.3f\n\n", data.ATR14))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatFloatSlice formats float slice
|
// formatFloatSlice formats float slice
|
||||||
|
|||||||
@@ -162,8 +162,8 @@ func GetWithTimeframes(symbol string, timeframes []string, primaryTimeframe stri
|
|||||||
primaryKlines = klines
|
primaryKlines = klines
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate series data for this timeframe
|
// Calculate series data for this timeframe (use count from config)
|
||||||
seriesData := calculateTimeframeSeries(klines, tf)
|
seriesData := calculateTimeframeSeries(klines, tf, count)
|
||||||
timeframeData[tf] = seriesData
|
timeframeData[tf] = seriesData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,25 +212,41 @@ func GetWithTimeframes(symbol string, timeframes []string, primaryTimeframe stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculateTimeframeSeries calculates series data for a single timeframe
|
// calculateTimeframeSeries calculates series data for a single timeframe
|
||||||
func calculateTimeframeSeries(klines []Kline, timeframe string) *TimeframeSeriesData {
|
func calculateTimeframeSeries(klines []Kline, timeframe string, count int) *TimeframeSeriesData {
|
||||||
data := &TimeframeSeriesData{
|
if count <= 0 {
|
||||||
Timeframe: timeframe,
|
count = 10 // default
|
||||||
MidPrices: make([]float64, 0, 10),
|
|
||||||
EMA20Values: make([]float64, 0, 10),
|
|
||||||
EMA50Values: make([]float64, 0, 10),
|
|
||||||
MACDValues: make([]float64, 0, 10),
|
|
||||||
RSI7Values: make([]float64, 0, 10),
|
|
||||||
RSI14Values: make([]float64, 0, 10),
|
|
||||||
Volume: make([]float64, 0, 10),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get latest 10 data points
|
data := &TimeframeSeriesData{
|
||||||
start := len(klines) - 10
|
Timeframe: timeframe,
|
||||||
|
Klines: make([]KlineBar, 0, count),
|
||||||
|
MidPrices: make([]float64, 0, count),
|
||||||
|
EMA20Values: make([]float64, 0, count),
|
||||||
|
EMA50Values: make([]float64, 0, count),
|
||||||
|
MACDValues: make([]float64, 0, count),
|
||||||
|
RSI7Values: make([]float64, 0, count),
|
||||||
|
RSI14Values: make([]float64, 0, count),
|
||||||
|
Volume: make([]float64, 0, count),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get latest N data points based on count from config
|
||||||
|
start := len(klines) - count
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := start; i < len(klines); i++ {
|
for i := start; i < len(klines); i++ {
|
||||||
|
// Store full OHLCV kline data
|
||||||
|
data.Klines = append(data.Klines, KlineBar{
|
||||||
|
Time: klines[i].OpenTime,
|
||||||
|
Open: klines[i].Open,
|
||||||
|
High: klines[i].High,
|
||||||
|
Low: klines[i].Low,
|
||||||
|
Close: klines[i].Close,
|
||||||
|
Volume: klines[i].Volume,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Keep MidPrices and Volume for backward compatibility
|
||||||
data.MidPrices = append(data.MidPrices, klines[i].Close)
|
data.MidPrices = append(data.MidPrices, klines[i].Close)
|
||||||
data.Volume = append(data.Volume, klines[i].Volume)
|
data.Volume = append(data.Volume, klines[i].Volume)
|
||||||
|
|
||||||
@@ -722,35 +738,54 @@ func Format(data *Data) string {
|
|||||||
|
|
||||||
// formatTimeframeData formats data for a single timeframe
|
// formatTimeframeData formats data for a single timeframe
|
||||||
func formatTimeframeData(sb *strings.Builder, data *TimeframeSeriesData) {
|
func formatTimeframeData(sb *strings.Builder, data *TimeframeSeriesData) {
|
||||||
if len(data.MidPrices) > 0 {
|
// Use OHLCV table format if kline data is available
|
||||||
|
if len(data.Klines) > 0 {
|
||||||
|
sb.WriteString("Time(UTC) Open High Low Close Volume\n")
|
||||||
|
for i, k := range data.Klines {
|
||||||
|
t := time.Unix(k.Time/1000, 0).UTC()
|
||||||
|
timeStr := t.Format("01-02 15:04")
|
||||||
|
marker := ""
|
||||||
|
if i == len(data.Klines)-1 {
|
||||||
|
marker = " <- current"
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("%-14s %-9.4f %-9.4f %-9.4f %-9.4f %-12.2f%s\n",
|
||||||
|
timeStr, k.Open, k.High, k.Low, k.Close, k.Volume, marker))
|
||||||
|
}
|
||||||
|
sb.WriteString("\n")
|
||||||
|
} else if len(data.MidPrices) > 0 {
|
||||||
|
// Fallback to old format for backward compatibility
|
||||||
sb.WriteString(fmt.Sprintf("Mid prices: %s\n\n", formatFloatSlice(data.MidPrices)))
|
sb.WriteString(fmt.Sprintf("Mid prices: %s\n\n", formatFloatSlice(data.MidPrices)))
|
||||||
|
if len(data.Volume) > 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("Volume: %s\n\n", formatFloatSlice(data.Volume)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Technical indicators
|
||||||
if len(data.EMA20Values) > 0 {
|
if len(data.EMA20Values) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("EMA indicators (20‑period): %s\n\n", formatFloatSlice(data.EMA20Values)))
|
sb.WriteString(fmt.Sprintf("EMA20: %s\n", formatFloatSlice(data.EMA20Values)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.EMA50Values) > 0 {
|
if len(data.EMA50Values) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("EMA indicators (50‑period): %s\n\n", formatFloatSlice(data.EMA50Values)))
|
sb.WriteString(fmt.Sprintf("EMA50: %s\n", formatFloatSlice(data.EMA50Values)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.MACDValues) > 0 {
|
if len(data.MACDValues) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("MACD indicators: %s\n\n", formatFloatSlice(data.MACDValues)))
|
sb.WriteString(fmt.Sprintf("MACD: %s\n", formatFloatSlice(data.MACDValues)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.RSI7Values) > 0 {
|
if len(data.RSI7Values) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("RSI indicators (7‑Period): %s\n\n", formatFloatSlice(data.RSI7Values)))
|
sb.WriteString(fmt.Sprintf("RSI7: %s\n", formatFloatSlice(data.RSI7Values)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.RSI14Values) > 0 {
|
if len(data.RSI14Values) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("RSI indicators (14‑Period): %s\n\n", formatFloatSlice(data.RSI14Values)))
|
sb.WriteString(fmt.Sprintf("RSI14: %s\n", formatFloatSlice(data.RSI14Values)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.Volume) > 0 {
|
if data.ATR14 > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("Volume: %s\n\n", formatFloatSlice(data.Volume)))
|
sb.WriteString(fmt.Sprintf("ATR14: %.4f\n", data.ATR14))
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString(fmt.Sprintf("ATR (14‑period): %.3f\n\n", data.ATR14))
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatPriceWithDynamicPrecision dynamically selects precision based on price range
|
// formatPriceWithDynamicPrecision dynamically selects precision based on price range
|
||||||
|
|||||||
@@ -19,17 +19,28 @@ type Data struct {
|
|||||||
TimeframeData map[string]*TimeframeSeriesData `json:"timeframe_data,omitempty"`
|
TimeframeData map[string]*TimeframeSeriesData `json:"timeframe_data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KlineBar single kline bar with OHLCV data
|
||||||
|
type KlineBar struct {
|
||||||
|
Time int64 `json:"time"` // Unix timestamp in milliseconds
|
||||||
|
Open float64 `json:"open"` // Open price
|
||||||
|
High float64 `json:"high"` // High price
|
||||||
|
Low float64 `json:"low"` // Low price
|
||||||
|
Close float64 `json:"close"` // Close price
|
||||||
|
Volume float64 `json:"volume"` // Volume
|
||||||
|
}
|
||||||
|
|
||||||
// TimeframeSeriesData series data for a single timeframe
|
// TimeframeSeriesData series data for a single timeframe
|
||||||
type TimeframeSeriesData struct {
|
type TimeframeSeriesData struct {
|
||||||
Timeframe string `json:"timeframe"` // Timeframe identifier, e.g. "5m", "15m", "1h"
|
Timeframe string `json:"timeframe"` // Timeframe identifier, e.g. "5m", "15m", "1h"
|
||||||
MidPrices []float64 `json:"mid_prices"` // Price series
|
Klines []KlineBar `json:"klines"` // Full OHLCV kline data
|
||||||
EMA20Values []float64 `json:"ema20_values"` // EMA20 series
|
MidPrices []float64 `json:"mid_prices"` // Price series (deprecated, kept for compatibility)
|
||||||
EMA50Values []float64 `json:"ema50_values"` // EMA50 series
|
EMA20Values []float64 `json:"ema20_values"` // EMA20 series
|
||||||
MACDValues []float64 `json:"macd_values"` // MACD series
|
EMA50Values []float64 `json:"ema50_values"` // EMA50 series
|
||||||
RSI7Values []float64 `json:"rsi7_values"` // RSI7 series
|
MACDValues []float64 `json:"macd_values"` // MACD series
|
||||||
RSI14Values []float64 `json:"rsi14_values"` // RSI14 series
|
RSI7Values []float64 `json:"rsi7_values"` // RSI7 series
|
||||||
Volume []float64 `json:"volume"` // Volume series
|
RSI14Values []float64 `json:"rsi14_values"` // RSI14 series
|
||||||
ATR14 float64 `json:"atr14"` // ATR14
|
Volume []float64 `json:"volume"` // Volume series (deprecated, use Klines)
|
||||||
|
ATR14 float64 `json:"atr14"` // ATR14
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIData Open Interest data
|
// OIData Open Interest data
|
||||||
|
|||||||
Reference in New Issue
Block a user