mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-09 22:10:57 +08:00
Feature/custom strategy (#1173)
* feat: add Strategy Studio with multi-timeframe support - Add Strategy Studio page with three-column layout for strategy management - Support multi-timeframe K-line data selection (5m, 15m, 1h, 4h, etc.) - Add GetWithTimeframes() function in market package for fetching multiple timeframes - Add TimeframeSeriesData struct for storing per-timeframe technical indicators - Update formatMarketData() to display all selected timeframes in AI prompt - Add strategy API endpoints for CRUD operations and test run - Integrate real AI test runs with configured AI models - Support custom AI500 and OI Top API URLs from strategy config * docs: add Strategy Studio screenshot to README files * feat: add quant data integration and fix position click navigation - Integrate quant data API (netflow, OI, price changes) into Strategy Studio - Add enable_quant_data toggle in indicator editor - Fix position symbol click to navigate to chart (sync defaultSymbol prop) - Fix TradingView chart fullscreen flickering
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { EquityChart } from './EquityChart'
|
||||
import { TradingViewChart } from './TradingViewChart'
|
||||
import { useLanguage } from '../contexts/LanguageContext'
|
||||
@@ -7,13 +7,25 @@ import { BarChart3, CandlestickChart } from 'lucide-react'
|
||||
|
||||
interface ChartTabsProps {
|
||||
traderId: string
|
||||
selectedSymbol?: string // 从外部选择的币种
|
||||
updateKey?: number // 强制更新的 key
|
||||
}
|
||||
|
||||
type ChartTab = 'equity' | 'kline'
|
||||
|
||||
export function ChartTabs({ traderId }: ChartTabsProps) {
|
||||
export function ChartTabs({ traderId, selectedSymbol, updateKey }: ChartTabsProps) {
|
||||
const { language } = useLanguage()
|
||||
const [activeTab, setActiveTab] = useState<ChartTab>('equity')
|
||||
const [chartSymbol, setChartSymbol] = useState<string>('BTCUSDT')
|
||||
|
||||
// 当从外部选择币种时,自动切换到K线图
|
||||
useEffect(() => {
|
||||
if (selectedSymbol) {
|
||||
console.log('[ChartTabs] 收到币种选择:', selectedSymbol, 'updateKey:', updateKey)
|
||||
setChartSymbol(selectedSymbol)
|
||||
setActiveTab('kline')
|
||||
}
|
||||
}, [selectedSymbol, updateKey])
|
||||
|
||||
console.log('[ChartTabs] rendering, activeTab:', activeTab)
|
||||
|
||||
@@ -81,7 +93,12 @@ export function ChartTabs({ traderId }: ChartTabsProps) {
|
||||
{activeTab === 'equity' ? (
|
||||
<EquityChart traderId={traderId} embedded />
|
||||
) : (
|
||||
<TradingViewChart height={400} embedded />
|
||||
<TradingViewChart
|
||||
height={400}
|
||||
embedded
|
||||
defaultSymbol={chartSymbol}
|
||||
key={chartSymbol} // 强制重新渲染当币种变化时
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,6 +66,14 @@ function TradingViewChartComponent({
|
||||
const [showSymbolDropdown, setShowSymbolDropdown] = useState(false)
|
||||
const [isFullscreen, setIsFullscreen] = useState(false)
|
||||
|
||||
// 当外部传入的 defaultSymbol 变化时,更新内部 symbol
|
||||
useEffect(() => {
|
||||
if (defaultSymbol && defaultSymbol !== symbol) {
|
||||
console.log('[TradingViewChart] 更新币种:', defaultSymbol)
|
||||
setSymbol(defaultSymbol)
|
||||
}
|
||||
}, [defaultSymbol])
|
||||
|
||||
// 获取完整的交易对符号 (合约格式: BINANCE:BTCUSDT.P)
|
||||
const getFullSymbol = () => {
|
||||
const exchangeInfo = EXCHANGES.find((e) => e.id === exchange)
|
||||
@@ -102,7 +110,8 @@ function TradingViewChartComponent({
|
||||
script.type = 'text/javascript'
|
||||
script.async = true
|
||||
script.innerHTML = JSON.stringify({
|
||||
autosize: true,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
symbol: getFullSymbol(),
|
||||
interval: timeInterval,
|
||||
timezone: 'Etc/UTC',
|
||||
@@ -147,9 +156,10 @@ function TradingViewChartComponent({
|
||||
<div
|
||||
className={`${embedded ? '' : 'binance-card'} overflow-hidden ${embedded ? '' : 'animate-fade-in'} ${
|
||||
isFullscreen
|
||||
? 'fixed inset-0 z-50 rounded-none'
|
||||
? 'fixed inset-0 z-50 rounded-none flex flex-col'
|
||||
: ''
|
||||
}`}
|
||||
style={isFullscreen ? { background: '#0B0E11' } : undefined}
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
@@ -354,8 +364,9 @@ function TradingViewChartComponent({
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
height: isFullscreen ? 'calc(100vh - 60px)' : height,
|
||||
height: isFullscreen ? 'calc(100vh - 65px)' : height,
|
||||
background: '#0B0E11',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Clock, Activity } from 'lucide-react'
|
||||
import { Clock, Activity, Database } from 'lucide-react'
|
||||
import type { IndicatorConfig } from '../../types'
|
||||
|
||||
interface IndicatorEditorProps {
|
||||
@@ -51,6 +51,9 @@ export function IndicatorEditor({
|
||||
intraday: { zh: '日内', en: 'Intraday' },
|
||||
swing: { zh: '波段', en: 'Swing' },
|
||||
position: { zh: '趋势', en: 'Position' },
|
||||
quantData: { zh: '量化数据', en: 'Quant Data' },
|
||||
quantDataDesc: { zh: '资金流向、持仓变化、价格变化(按币种查询)', en: 'Netflow, OI delta, price change (per coin)' },
|
||||
quantDataUrl: { zh: '量化数据 API', en: 'Quant Data API' },
|
||||
}
|
||||
return translations[key]?.[language] || key
|
||||
}
|
||||
@@ -257,6 +260,57 @@ export function IndicatorEditor({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quant Data Source */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Database className="w-4 h-4" style={{ color: '#22c55e' }} />
|
||||
<span className="text-sm font-medium" style={{ color: '#EAECEF' }}>{t('quantData')}</span>
|
||||
</div>
|
||||
<p className="text-xs mb-3" style={{ color: '#848E9C' }}>{t('quantDataDesc')}</p>
|
||||
|
||||
<div
|
||||
className="p-3 rounded-lg space-y-3"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
||||
>
|
||||
{/* Enable Toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full" style={{ background: '#22c55e' }} />
|
||||
<span className="text-xs" style={{ color: '#EAECEF' }}>{t('quantData')}</span>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.enable_quant_data || false}
|
||||
onChange={(e) =>
|
||||
!disabled && onChange({ ...config, enable_quant_data: e.target.checked })
|
||||
}
|
||||
disabled={disabled}
|
||||
className="w-4 h-4 rounded accent-green-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* API URL */}
|
||||
{config.enable_quant_data && (
|
||||
<div>
|
||||
<label className="text-[10px] mb-1 block" style={{ color: '#848E9C' }}>
|
||||
{t('quantDataUrl')} <span style={{ color: '#5E6673' }}>({'{symbol}'} = 币种)</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={config.quant_data_api_url || ''}
|
||||
onChange={(e) =>
|
||||
!disabled && onChange({ ...config, quant_data_api_url: e.target.value })
|
||||
}
|
||||
disabled={disabled}
|
||||
placeholder="http://example.com/api/coin/{symbol}?include=netflow,oi,price"
|
||||
className="w-full px-2 py-1.5 rounded text-xs font-mono"
|
||||
style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user