Feature/custom strategy (#1172)

* 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
* fix: correct strategy-studio.png filename case in README
* refactor: remove legacy signal source config and simplify trader creation
- Remove signal source configuration from traders page (now handled by strategy)
- Remove advanced options (legacy config) from TraderConfigModal
- Rename default strategy to "默认山寨策略" with AI500 coin pool URL
- Delete SignalSourceModal and SignalSourceWarning components
- Clean up related stores, hooks, and page components
This commit is contained in:
tinkle-community
2025-12-06 07:20:11 +08:00
committed by GitHub
parent afb2d158ac
commit 5cff32e4f2
37 changed files with 4965 additions and 1051 deletions

View File

@@ -1,138 +0,0 @@
import { useState } from 'react'
import { t, type Language } from '../../i18n/translations'
interface SignalSourceModalProps {
coinPoolUrl: string
oiTopUrl: string
onSave: (coinPoolUrl: string, oiTopUrl: string) => void
onClose: () => void
language: Language
}
export function SignalSourceModal({
coinPoolUrl,
oiTopUrl,
onSave,
onClose,
language,
}: SignalSourceModalProps) {
const [coinPool, setCoinPool] = useState(coinPoolUrl || '')
const [oiTop, setOiTop] = useState(oiTopUrl || '')
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
onSave(coinPool.trim(), oiTop.trim())
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4 overflow-y-auto">
<div
className="bg-gray-800 rounded-lg w-full max-w-lg relative my-8"
style={{
background: '#1E2329',
maxHeight: 'calc(100vh - 4rem)',
}}
>
<h3 className="text-xl font-bold mb-4" style={{ color: '#EAECEF' }}>
{t('signalSourceConfig', language)}
</h3>
<form onSubmit={handleSubmit} className="px-6 pb-6">
<div
className="space-y-4 overflow-y-auto"
style={{ maxHeight: 'calc(100vh - 16rem)' }}
>
<div>
<label
className="block text-sm font-semibold mb-2"
style={{ color: '#EAECEF' }}
>
COIN POOL URL
</label>
<input
type="url"
value={coinPool}
onChange={(e) => setCoinPool(e.target.value)}
placeholder="https://api.example.com/coinpool"
className="w-full px-3 py-2 rounded"
style={{
background: '#0B0E11',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
/>
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
{t('coinPoolDescription', language)}
</div>
</div>
<div>
<label
className="block text-sm font-semibold mb-2"
style={{ color: '#EAECEF' }}
>
OI TOP URL
</label>
<input
type="url"
value={oiTop}
onChange={(e) => setOiTop(e.target.value)}
placeholder="https://api.example.com/oitop"
className="w-full px-3 py-2 rounded"
style={{
background: '#0B0E11',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
/>
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
{t('oiTopDescription', language)}
</div>
</div>
<div
className="p-4 rounded"
style={{
background: 'rgba(240, 185, 11, 0.1)',
border: '1px solid rgba(240, 185, 11, 0.2)',
}}
>
<div
className="text-sm font-semibold mb-2"
style={{ color: '#F0B90B' }}
>
{t('information', language)}
</div>
<div className="text-xs space-y-1" style={{ color: '#848E9C' }}>
<div>{t('signalSourceInfo1', language)}</div>
<div>{t('signalSourceInfo2', language)}</div>
<div>{t('signalSourceInfo3', language)}</div>
</div>
</div>
</div>
<div
className="flex gap-3 mt-6 pt-4 sticky bottom-0"
style={{ background: '#1E2329' }}
>
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 rounded text-sm font-semibold"
style={{ background: '#2B3139', color: '#848E9C' }}
>
{t('cancel', language)}
</button>
<button
type="submit"
className="flex-1 px-4 py-2 rounded text-sm font-semibold"
style={{ background: '#F0B90B', color: '#000' }}
>
{t('save', language)}
</button>
</div>
</form>
</div>
</div>
)
}

View File

@@ -1,5 +1,4 @@
export { Tooltip } from './Tooltip'
export { SignalSourceModal } from './SignalSourceModal'
export { ModelConfigModal } from './ModelConfigModal'
export { ExchangeConfigModal } from './ExchangeConfigModal'
export { getModelDisplayName, getShortName } from './utils'

View File

@@ -1,4 +1,4 @@
import { Bot, Plus, Radio } from 'lucide-react'
import { Bot, Plus } from 'lucide-react'
import { t, type Language } from '../../../i18n/translations'
interface PageHeaderProps {
@@ -8,7 +8,6 @@ interface PageHeaderProps {
configuredExchangesCount: number
onAddModel: () => void
onAddExchange: () => void
onConfigureSignalSource: () => void
onCreateTrader: () => void
}
@@ -19,7 +18,6 @@ export function PageHeader({
configuredExchangesCount,
onAddModel,
onAddExchange,
onConfigureSignalSource,
onCreateTrader,
}: PageHeaderProps) {
const canCreateTrader =
@@ -86,19 +84,6 @@ export function PageHeader({
{t('exchanges', language)}
</button>
<button
onClick={onConfigureSignalSource}
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1 md:gap-2 whitespace-nowrap"
style={{
background: '#2B3139',
color: '#EAECEF',
border: '1px solid #474D57',
}}
>
<Radio className="w-3 h-3 md:w-4 md:h-4" />
{t('signalSource', language)}
</button>
<button
onClick={onCreateTrader}
disabled={!canCreateTrader}

View File

@@ -1,54 +0,0 @@
import { AlertTriangle } from 'lucide-react'
import { t, type Language } from '../../../i18n/translations'
interface SignalSourceWarningProps {
language: Language
onConfigure: () => void
}
export function SignalSourceWarning({
language,
onConfigure,
}: SignalSourceWarningProps) {
return (
<div
className="rounded-lg px-4 py-3 flex items-start gap-3 animate-slide-in"
style={{
background: 'rgba(246, 70, 93, 0.1)',
border: '1px solid rgba(246, 70, 93, 0.3)',
}}
>
<AlertTriangle
size={20}
className="flex-shrink-0 mt-0.5"
style={{ color: '#F6465D' }}
/>
<div className="flex-1">
<div className="font-semibold mb-1" style={{ color: '#F6465D' }}>
{t('signalSourceNotConfigured', language)}
</div>
<div className="text-sm" style={{ color: '#848E9C' }}>
<p className="mb-2">{t('signalSourceWarningMessage', language)}</p>
<p>
<strong>{t('solutions', language)}</strong>
</p>
<ul className="list-disc list-inside space-y-1 ml-2 mt-1">
<li>"{t('signalSource', language)}"API地址</li>
<li>"使用币种池""使用OI Top"</li>
<li></li>
</ul>
</div>
<button
onClick={onConfigure}
className="mt-3 px-3 py-1.5 rounded text-sm font-semibold transition-all hover:scale-105"
style={{
background: '#F0B90B',
color: '#000',
}}
>
{t('configureSignalSourceNow', language)}
</button>
</div>
</div>
)
}