mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-11 06:46:59 +08:00
feat: improve AI decision input format and chart UX
Backend changes: - Add symbol headers to market data sections for clarity - Add symbol labels to quantitative data output - Skip position coins in candidate list to avoid duplicate data Frontend changes: - Add B/S toggle button to show/hide order markers - Remove bottom legend (B BUY S SELL) - Fix chart position reset on data refresh - Separate marker toggle logic from data loading
This commit is contained in:
@@ -983,10 +983,20 @@ func (e *StrategyEngine) BuildUserPrompt(ctx *Context) string {
|
|||||||
sb.WriteString("Current Positions: None\n\n")
|
sb.WriteString("Current Positions: None\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Candidate coins
|
// Candidate coins (exclude coins already in positions to avoid duplicate data)
|
||||||
|
positionSymbols := make(map[string]bool)
|
||||||
|
for _, pos := range ctx.Positions {
|
||||||
|
positionSymbols[pos.Symbol] = true
|
||||||
|
}
|
||||||
|
|
||||||
sb.WriteString(fmt.Sprintf("## Candidate Coins (%d coins)\n\n", len(ctx.MarketDataMap)))
|
sb.WriteString(fmt.Sprintf("## Candidate Coins (%d coins)\n\n", len(ctx.MarketDataMap)))
|
||||||
displayedCount := 0
|
displayedCount := 0
|
||||||
for _, coin := range ctx.CandidateCoins {
|
for _, coin := range ctx.CandidateCoins {
|
||||||
|
// Skip if this coin is already a position (data already shown in positions section)
|
||||||
|
if positionSymbols[coin.Symbol] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
marketData, hasData := ctx.MarketDataMap[coin.Symbol]
|
marketData, hasData := ctx.MarketDataMap[coin.Symbol]
|
||||||
if !hasData {
|
if !hasData {
|
||||||
continue
|
continue
|
||||||
@@ -1081,6 +1091,8 @@ func (e *StrategyEngine) formatMarketData(data *market.Data) string {
|
|||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
indicators := e.config.Indicators
|
indicators := e.config.Indicators
|
||||||
|
|
||||||
|
// 明确标注币种
|
||||||
|
sb.WriteString(fmt.Sprintf("=== %s Market Data ===\n\n", data.Symbol))
|
||||||
sb.WriteString(fmt.Sprintf("current_price = %.4f", data.CurrentPrice))
|
sb.WriteString(fmt.Sprintf("current_price = %.4f", data.CurrentPrice))
|
||||||
|
|
||||||
if indicators.EnableEMA {
|
if indicators.EnableEMA {
|
||||||
@@ -1252,7 +1264,7 @@ func (e *StrategyEngine) formatQuantData(data *QuantData) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString("📊 Quantitative Data:\n")
|
sb.WriteString(fmt.Sprintf("📊 %s Quantitative Data:\n", data.Symbol))
|
||||||
|
|
||||||
if len(data.PriceChange) > 0 {
|
if len(data.PriceChange) > 0 {
|
||||||
sb.WriteString("Price Change: ")
|
sb.WriteString("Price Change: ")
|
||||||
|
|||||||
@@ -76,10 +76,13 @@ export function AdvancedChart({
|
|||||||
const volumeSeriesRef = useRef<ISeriesApi<'Histogram'> | null>(null)
|
const volumeSeriesRef = useRef<ISeriesApi<'Histogram'> | null>(null)
|
||||||
const indicatorSeriesRef = useRef<Map<string, ISeriesApi<any>>>(new Map())
|
const indicatorSeriesRef = useRef<Map<string, ISeriesApi<any>>>(new Map())
|
||||||
const seriesMarkersRef = useRef<any>(null) // Markers primitive for v5
|
const seriesMarkersRef = useRef<any>(null) // Markers primitive for v5
|
||||||
|
const currentMarkersDataRef = useRef<any[]>([]) // 存储当前的标记数据
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [showIndicatorPanel, setShowIndicatorPanel] = useState(false)
|
const [showIndicatorPanel, setShowIndicatorPanel] = useState(false)
|
||||||
|
const [showOrderMarkers, setShowOrderMarkers] = useState(true) // 订单标记显示开关,默认显示
|
||||||
|
const isInitialLoadRef = useRef(true) // 跟踪是否为初始加载
|
||||||
|
|
||||||
// 指标配置
|
// 指标配置
|
||||||
const [indicators, setIndicators] = useState<IndicatorConfig[]>([
|
const [indicators, setIndicators] = useState<IndicatorConfig[]>([
|
||||||
@@ -360,6 +363,9 @@ export function AdvancedChart({
|
|||||||
|
|
||||||
// 加载数据和指标
|
// 加载数据和指标
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 当 symbol 或 interval 改变时,重置初始加载标志(以便自动适配新数据)
|
||||||
|
isInitialLoadRef.current = true
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
if (!candlestickSeriesRef.current) return
|
if (!candlestickSeriesRef.current) return
|
||||||
|
|
||||||
@@ -436,15 +442,20 @@ export function AdvancedChart({
|
|||||||
console.log('[AdvancedChart] Markers data:', JSON.stringify(markers, null, 2))
|
console.log('[AdvancedChart] Markers data:', JSON.stringify(markers, null, 2))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 存储标记数据供后续切换使用
|
||||||
|
currentMarkersDataRef.current = markers
|
||||||
|
|
||||||
// 使用 v5 API: createSeriesMarkers
|
// 使用 v5 API: createSeriesMarkers
|
||||||
|
const markersToShow = showOrderMarkers ? markers : []
|
||||||
|
|
||||||
if (seriesMarkersRef.current) {
|
if (seriesMarkersRef.current) {
|
||||||
// 如果已经存在,更新标记
|
// 如果已经存在,更新标记
|
||||||
seriesMarkersRef.current.setMarkers(markers)
|
seriesMarkersRef.current.setMarkers(markersToShow)
|
||||||
} else {
|
} else {
|
||||||
// 首次创建标记
|
// 首次创建标记
|
||||||
seriesMarkersRef.current = createSeriesMarkers(candlestickSeriesRef.current, markers)
|
seriesMarkersRef.current = createSeriesMarkers(candlestickSeriesRef.current, markersToShow)
|
||||||
}
|
}
|
||||||
console.log('[AdvancedChart] ✅ Markers set successfully!')
|
console.log('[AdvancedChart] ✅ Markers updated! Count:', markersToShow.length, 'Visible:', showOrderMarkers)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AdvancedChart] ❌ Failed to set markers:', err)
|
console.error('[AdvancedChart] ❌ Failed to set markers:', err)
|
||||||
}
|
}
|
||||||
@@ -465,8 +476,11 @@ export function AdvancedChart({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自动适配视图
|
// 只在初始加载时自动适配视图,避免刷新时抖动
|
||||||
chartRef.current?.timeScale().fitContent()
|
if (isInitialLoadRef.current) {
|
||||||
|
chartRef.current?.timeScale().fitContent()
|
||||||
|
isInitialLoadRef.current = false
|
||||||
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('[AdvancedChart] Error loading data:', err)
|
console.error('[AdvancedChart] Error loading data:', err)
|
||||||
@@ -482,6 +496,19 @@ export function AdvancedChart({
|
|||||||
return () => clearInterval(refreshInterval)
|
return () => clearInterval(refreshInterval)
|
||||||
}, [symbol, interval, traderID, indicators])
|
}, [symbol, interval, traderID, indicators])
|
||||||
|
|
||||||
|
// 单独处理订单标记的显示/隐藏,避免重新加载数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (!seriesMarkersRef.current) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const markersToShow = showOrderMarkers ? currentMarkersDataRef.current : []
|
||||||
|
seriesMarkersRef.current.setMarkers(markersToShow)
|
||||||
|
console.log('[AdvancedChart] 🔄 Toggled markers visibility:', showOrderMarkers, 'Count:', markersToShow.length)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[AdvancedChart] ❌ Failed to toggle markers:', err)
|
||||||
|
}
|
||||||
|
}, [showOrderMarkers])
|
||||||
|
|
||||||
// 更新指标
|
// 更新指标
|
||||||
const updateIndicators = (klineData: Kline[]) => {
|
const updateIndicators = (klineData: Kline[]) => {
|
||||||
if (!chartRef.current) return
|
if (!chartRef.current) return
|
||||||
@@ -599,6 +626,20 @@ export function AdvancedChart({
|
|||||||
<Settings className="w-3.5 h-3.5" />
|
<Settings className="w-3.5 h-3.5" />
|
||||||
<span>{language === 'zh' ? '指标' : 'Indicators'}</span>
|
<span>{language === 'zh' ? '指标' : 'Indicators'}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* 订单标记开关 */}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowOrderMarkers(!showOrderMarkers)}
|
||||||
|
className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all"
|
||||||
|
style={{
|
||||||
|
background: showOrderMarkers ? 'rgba(240, 185, 11, 0.15)' : 'rgba(255, 255, 255, 0.05)',
|
||||||
|
color: showOrderMarkers ? '#F0B90B' : '#848E9C',
|
||||||
|
border: `1px solid ${showOrderMarkers ? 'rgba(240, 185, 11, 0.3)' : '#2B3139'}`,
|
||||||
|
}}
|
||||||
|
title={language === 'zh' ? '切换订单标记显示' : 'Toggle Order Markers'}
|
||||||
|
>
|
||||||
|
<span className="font-bold text-[11px]">B/S</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -704,9 +745,8 @@ export function AdvancedChart({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '50%',
|
bottom: '20%',
|
||||||
left: '50%',
|
right: '5%',
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
@@ -714,13 +754,12 @@ export function AdvancedChart({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
fontSize: '120px',
|
fontSize: '56px',
|
||||||
fontWeight: '900',
|
fontWeight: '700',
|
||||||
color: 'rgba(240, 185, 11, 0.15)',
|
color: 'rgba(240, 185, 11, 0.12)',
|
||||||
letterSpacing: '12px',
|
letterSpacing: '4px',
|
||||||
fontFamily: 'Arial Black, sans-serif',
|
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
|
||||||
textShadow: '0 0 80px rgba(240, 185, 11, 0.25)',
|
textShadow: '0 2px 30px rgba(240, 185, 11, 0.2)',
|
||||||
opacity: 0.6,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
NOFX
|
NOFX
|
||||||
@@ -741,24 +780,6 @@ export function AdvancedChart({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 图例说明 - 简化版 */}
|
|
||||||
<div
|
|
||||||
className="flex items-center gap-4 px-4 py-2.5 text-xs"
|
|
||||||
style={{ borderTop: '1px solid #2B3139', background: '#0F1215' }}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold" style={{ background: '#0ECB81', color: '#0B0E11' }}>
|
|
||||||
B
|
|
||||||
</div>
|
|
||||||
<span style={{ color: '#EAECEF' }}>{language === 'zh' ? '买入 (BUY)' : 'BUY'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold" style={{ background: '#F6465D', color: '#0B0E11' }}>
|
|
||||||
S
|
|
||||||
</div>
|
|
||||||
<span style={{ color: '#EAECEF' }}>{language === 'zh' ? '卖出 (SELL)' : 'SELL'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user