feat: 添加 Binance 模拟交易支持
在 BinanceFuturesClient 和 BinanceSpotClient 中添加 enable_demo_trading 参数 根据 enable_demo_trading 自动设置正确的 demo base_url Futures: https://demo-fapi.binance.com Spot: https://demo-api.binance.com 在 factory.py 中从 exchange_config 读取 enable_demo_trading 配置并传递给客户端
This commit is contained in:
@@ -19,7 +19,10 @@ from app.services.live_trading.symbols import to_binance_futures_symbol
|
|||||||
|
|
||||||
|
|
||||||
class BinanceFuturesClient(BaseRestClient):
|
class BinanceFuturesClient(BaseRestClient):
|
||||||
def __init__(self, *, api_key: str, secret_key: str, base_url: str = "https://fapi.binance.com", timeout_sec: float = 15.0):
|
def __init__(self, *, api_key: str, secret_key: str, base_url: str = None, enable_demo_trading: bool = False, timeout_sec: float = 15.0):
|
||||||
|
if not base_url:
|
||||||
|
base_url = "https://demo-fapi.binance.com" if enable_demo_trading else "https://fapi.binance.com"
|
||||||
|
|
||||||
super().__init__(base_url=base_url, timeout_sec=timeout_sec)
|
super().__init__(base_url=base_url, timeout_sec=timeout_sec)
|
||||||
self.api_key = (api_key or "").strip()
|
self.api_key = (api_key or "").strip()
|
||||||
self.secret_key = (secret_key or "").strip()
|
self.secret_key = (secret_key or "").strip()
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ from app.services.live_trading.symbols import to_binance_futures_symbol
|
|||||||
|
|
||||||
|
|
||||||
class BinanceSpotClient(BaseRestClient):
|
class BinanceSpotClient(BaseRestClient):
|
||||||
def __init__(self, *, api_key: str, secret_key: str, base_url: str = "https://api.binance.com", timeout_sec: float = 15.0):
|
def __init__(self, *, api_key: str, secret_key: str, base_url: str = None, enable_demo_trading: bool = False, timeout_sec: float = 15.0):
|
||||||
|
if not base_url:
|
||||||
|
base_url = "https://demo-api.binance.com" if enable_demo_trading else "https://api.binance.com"
|
||||||
|
|
||||||
super().__init__(base_url=base_url, timeout_sec=timeout_sec)
|
super().__init__(base_url=base_url, timeout_sec=timeout_sec)
|
||||||
self.api_key = (api_key or "").strip()
|
self.api_key = (api_key or "").strip()
|
||||||
self.secret_key = (secret_key or "").strip()
|
self.secret_key = (secret_key or "").strip()
|
||||||
|
|||||||
@@ -59,12 +59,18 @@ def create_client(exchange_config: Dict[str, Any], *, market_type: str = "swap")
|
|||||||
mt = "swap"
|
mt = "swap"
|
||||||
|
|
||||||
if exchange_id == "binance":
|
if exchange_id == "binance":
|
||||||
|
# 检查是否启用模拟交易,支持布尔值和字符串
|
||||||
|
enable_demo = exchange_config.get("enable_demo_trading") or exchange_config.get("enableDemoTrading")
|
||||||
|
is_demo = bool(enable_demo) if isinstance(enable_demo, bool) else str(enable_demo).lower() in ("true", "1", "yes")
|
||||||
|
|
||||||
if mt == "spot":
|
if mt == "spot":
|
||||||
base_url = _get(exchange_config, "base_url", "baseUrl") or "https://api.binance.com"
|
default_url = "https://demo-api.binance.com" if is_demo else "https://api.binance.com"
|
||||||
return BinanceSpotClient(api_key=api_key, secret_key=secret_key, base_url=base_url)
|
base_url = _get(exchange_config, "base_url", "baseUrl") or default_url
|
||||||
|
return BinanceSpotClient(api_key=api_key, secret_key=secret_key, base_url=base_url, enable_demo_trading=is_demo)
|
||||||
# Default to USDT-M futures
|
# Default to USDT-M futures
|
||||||
base_url = _get(exchange_config, "base_url", "baseUrl") or "https://fapi.binance.com"
|
default_url = "https://demo-fapi.binance.com" if is_demo else "https://fapi.binance.com"
|
||||||
return BinanceFuturesClient(api_key=api_key, secret_key=secret_key, base_url=base_url)
|
base_url = _get(exchange_config, "base_url", "baseUrl") or default_url
|
||||||
|
return BinanceFuturesClient(api_key=api_key, secret_key=secret_key, base_url=base_url, enable_demo_trading=is_demo)
|
||||||
if exchange_id == "okx":
|
if exchange_id == "okx":
|
||||||
base_url = _get(exchange_config, "base_url", "baseUrl") or "https://www.okx.com"
|
base_url = _get(exchange_config, "base_url", "baseUrl") or "https://www.okx.com"
|
||||||
return OkxClient(api_key=api_key, secret_key=secret_key, passphrase=passphrase, base_url=base_url)
|
return OkxClient(api_key=api_key, secret_key=secret_key, passphrase=passphrase, base_url=base_url)
|
||||||
|
|||||||
@@ -22,18 +22,15 @@
|
|||||||
<a-empty v-if="!loading && strategies.length === 0" :description="$t('trading-assistant.noStrategy')" />
|
<a-empty v-if="!loading && strategies.length === 0" :description="$t('trading-assistant.noStrategy')" />
|
||||||
<div v-else class="strategy-grouped-list">
|
<div v-else class="strategy-grouped-list">
|
||||||
<!-- 策略组列表 -->
|
<!-- 策略组列表 -->
|
||||||
<div
|
<div v-for="group in groupedStrategies.groups" :key="group.id" class="strategy-group">
|
||||||
v-for="group in groupedStrategies.groups"
|
|
||||||
:key="group.id"
|
|
||||||
class="strategy-group"
|
|
||||||
>
|
|
||||||
<!-- 策略组头部 -->
|
<!-- 策略组头部 -->
|
||||||
<div class="strategy-group-header" @click="toggleGroup(group.id)">
|
<div class="strategy-group-header" @click="toggleGroup(group.id)">
|
||||||
<div class="group-header-left">
|
<div class="group-header-left">
|
||||||
<a-icon :type="collapsedGroups[group.id] ? 'right' : 'down'" class="collapse-icon" />
|
<a-icon :type="collapsedGroups[group.id] ? 'right' : 'down'" class="collapse-icon" />
|
||||||
<a-icon type="folder" class="group-icon" />
|
<a-icon type="folder" class="group-icon" />
|
||||||
<span class="group-name">{{ group.baseName }}</span>
|
<span class="group-name">{{ group.baseName }}</span>
|
||||||
<a-tag size="small" color="blue">{{ group.strategies.length }} {{ $t('trading-assistant.symbolCount') }}</a-tag>
|
<a-tag size="small" color="blue">{{ group.strategies.length }} {{
|
||||||
|
$t('trading-assistant.symbolCount') }}</a-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-header-right" @click.stop>
|
<div class="group-header-right" @click.stop>
|
||||||
<span v-if="group.runningCount > 0" class="group-status running">
|
<span v-if="group.runningCount > 0" class="group-status running">
|
||||||
@@ -68,8 +65,7 @@
|
|||||||
v-for="item in group.strategies"
|
v-for="item in group.strategies"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:class="['strategy-list-item', { active: selectedStrategy && selectedStrategy.id === item.id }]"
|
:class="['strategy-list-item', { active: selectedStrategy && selectedStrategy.id === item.id }]"
|
||||||
@click="handleSelectStrategy(item)"
|
@click="handleSelectStrategy(item)">
|
||||||
>
|
|
||||||
<div class="strategy-item-content">
|
<div class="strategy-item-content">
|
||||||
<div class="strategy-item-header">
|
<div class="strategy-item-header">
|
||||||
<div class="strategy-name-wrapper">
|
<div class="strategy-name-wrapper">
|
||||||
@@ -82,8 +78,7 @@
|
|||||||
:class="[
|
:class="[
|
||||||
item.status ? `status-${item.status}` : '',
|
item.status ? `status-${item.status}` : '',
|
||||||
{ 'status-stopped': item.status === 'stopped' }
|
{ 'status-stopped': item.status === 'stopped' }
|
||||||
]"
|
]">
|
||||||
>
|
|
||||||
{{ getStatusText(item.status) }}
|
{{ getStatusText(item.status) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,8 +118,7 @@
|
|||||||
v-for="item in groupedStrategies.ungrouped"
|
v-for="item in groupedStrategies.ungrouped"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:class="['strategy-list-item', { active: selectedStrategy && selectedStrategy.id === item.id }]"
|
:class="['strategy-list-item', { active: selectedStrategy && selectedStrategy.id === item.id }]"
|
||||||
@click="handleSelectStrategy(item)"
|
@click="handleSelectStrategy(item)">
|
||||||
>
|
|
||||||
<div class="strategy-item-content">
|
<div class="strategy-item-content">
|
||||||
<div class="strategy-item-header">
|
<div class="strategy-item-header">
|
||||||
<div class="strategy-name-wrapper">
|
<div class="strategy-name-wrapper">
|
||||||
@@ -132,8 +126,7 @@
|
|||||||
v-if="item.exchange_config && item.exchange_config.exchange_id"
|
v-if="item.exchange_config && item.exchange_config.exchange_id"
|
||||||
:color="getExchangeTagColor(item.exchange_config.exchange_id)"
|
:color="getExchangeTagColor(item.exchange_config.exchange_id)"
|
||||||
size="small"
|
size="small"
|
||||||
class="exchange-tag"
|
class="exchange-tag">
|
||||||
>
|
|
||||||
<a-icon type="bank" style="margin-right: 4px;" />
|
<a-icon type="bank" style="margin-right: 4px;" />
|
||||||
{{ getExchangeDisplayName(item.exchange_config.exchange_id) }}
|
{{ getExchangeDisplayName(item.exchange_config.exchange_id) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
@@ -142,8 +135,7 @@
|
|||||||
v-if="item.strategy_type === 'PromptBasedStrategy'"
|
v-if="item.strategy_type === 'PromptBasedStrategy'"
|
||||||
color="purple"
|
color="purple"
|
||||||
size="small"
|
size="small"
|
||||||
class="strategy-type-tag"
|
class="strategy-type-tag">
|
||||||
>
|
|
||||||
<a-icon type="robot" style="margin-right: 2px;" />
|
<a-icon type="robot" style="margin-right: 2px;" />
|
||||||
AI
|
AI
|
||||||
</a-tag>
|
</a-tag>
|
||||||
@@ -159,8 +151,7 @@
|
|||||||
:class="[
|
:class="[
|
||||||
item.status ? `status-${item.status}` : '',
|
item.status ? `status-${item.status}` : '',
|
||||||
{ 'status-stopped': item.status === 'stopped' }
|
{ 'status-stopped': item.status === 'stopped' }
|
||||||
]"
|
]">
|
||||||
>
|
|
||||||
{{ getStatusText(item.status) }}
|
{{ getStatusText(item.status) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,13 +214,16 @@
|
|||||||
|
|
||||||
<!-- 关键数据卡片 -->
|
<!-- 关键数据卡片 -->
|
||||||
<div class="key-stats-grid">
|
<div class="key-stats-grid">
|
||||||
<div class="stat-card" v-if="selectedStrategy.initial_capital || (selectedStrategy.trading_config && selectedStrategy.trading_config.initial_capital)">
|
<div
|
||||||
|
class="stat-card"
|
||||||
|
v-if="selectedStrategy.initial_capital || (selectedStrategy.trading_config && selectedStrategy.trading_config.initial_capital)">
|
||||||
<div class="stat-icon investment">
|
<div class="stat-icon investment">
|
||||||
<a-icon type="wallet" />
|
<a-icon type="wallet" />
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-content">
|
<div class="stat-content">
|
||||||
<div class="stat-label">{{ $t('trading-assistant.detail.totalInvestment') }}</div>
|
<div class="stat-label">{{ $t('trading-assistant.detail.totalInvestment') }}</div>
|
||||||
<div class="stat-value">${{ ((selectedStrategy.initial_capital || selectedStrategy.trading_config?.initial_capital) || 0).toLocaleString() }}</div>
|
<div class="stat-value">${{ ((selectedStrategy.initial_capital ||
|
||||||
|
selectedStrategy.trading_config?.initial_capital) || 0).toLocaleString() }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card" v-if="currentEquity !== null">
|
<div class="stat-card" v-if="currentEquity !== null">
|
||||||
@@ -241,7 +235,10 @@
|
|||||||
<div class="stat-value" :class="getEquityColorClass">{{ formatCurrency(currentEquity) }}</div>
|
<div class="stat-value" :class="getEquityColorClass">{{ formatCurrency(currentEquity) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card pnl-card" v-if="totalPnl !== null" :class="{ 'profit': totalPnl > 0, 'loss': totalPnl < 0 }">
|
<div
|
||||||
|
class="stat-card pnl-card"
|
||||||
|
v-if="totalPnl !== null"
|
||||||
|
:class="{ 'profit': totalPnl > 0, 'loss': totalPnl < 0 }">
|
||||||
<div class="stat-icon pnl">
|
<div class="stat-icon pnl">
|
||||||
<a-icon :type="totalPnl >= 0 ? 'rise' : 'fall'" />
|
<a-icon :type="totalPnl >= 0 ? 'rise' : 'fall'" />
|
||||||
</div>
|
</div>
|
||||||
@@ -261,7 +258,9 @@
|
|||||||
<a-icon type="stock" />
|
<a-icon type="stock" />
|
||||||
<span>{{ selectedStrategy.trading_config.symbol }}</span>
|
<span>{{ selectedStrategy.trading_config.symbol }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-item" v-if="selectedStrategy.indicator_config && selectedStrategy.indicator_config.indicator_name">
|
<div
|
||||||
|
class="tag-item"
|
||||||
|
v-if="selectedStrategy.indicator_config && selectedStrategy.indicator_config.indicator_name">
|
||||||
<a-icon type="line-chart" />
|
<a-icon type="line-chart" />
|
||||||
<span>{{ selectedStrategy.indicator_config.indicator_name }}</span>
|
<span>{{ selectedStrategy.indicator_config.indicator_name }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -269,11 +268,15 @@
|
|||||||
<a-icon type="thunderbolt" />
|
<a-icon type="thunderbolt" />
|
||||||
<span>{{ selectedStrategy.trading_config.leverage || 1 }}x</span>
|
<span>{{ selectedStrategy.trading_config.leverage || 1 }}x</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-item" v-if="selectedStrategy.trading_config && selectedStrategy.trading_config.trade_direction">
|
<div
|
||||||
|
class="tag-item"
|
||||||
|
v-if="selectedStrategy.trading_config && selectedStrategy.trading_config.trade_direction">
|
||||||
<a-icon type="swap" />
|
<a-icon type="swap" />
|
||||||
<span>{{ getTradeDirectionText(selectedStrategy.trading_config.trade_direction) }}</span>
|
<span>{{ getTradeDirectionText(selectedStrategy.trading_config.trade_direction) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-item" v-if="selectedStrategy.trading_config && selectedStrategy.trading_config.timeframe">
|
<div
|
||||||
|
class="tag-item"
|
||||||
|
v-if="selectedStrategy.trading_config && selectedStrategy.trading_config.timeframe">
|
||||||
<a-icon type="clock-circle" />
|
<a-icon type="clock-circle" />
|
||||||
<span>{{ selectedStrategy.trading_config.timeframe }}</span>
|
<span>{{ selectedStrategy.trading_config.timeframe }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -285,8 +288,7 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
class="action-btn start-btn"
|
class="action-btn start-btn"
|
||||||
@click="handleStartStrategy(selectedStrategy.id)"
|
@click="handleStartStrategy(selectedStrategy.id)">
|
||||||
>
|
|
||||||
<a-icon type="play-circle" />
|
<a-icon type="play-circle" />
|
||||||
{{ $t('trading-assistant.startStrategy') }}
|
{{ $t('trading-assistant.startStrategy') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -295,8 +297,7 @@
|
|||||||
type="danger"
|
type="danger"
|
||||||
size="large"
|
size="large"
|
||||||
class="action-btn stop-btn"
|
class="action-btn stop-btn"
|
||||||
@click="handleStopStrategy(selectedStrategy.id)"
|
@click="handleStopStrategy(selectedStrategy.id)">
|
||||||
>
|
|
||||||
<a-icon type="pause-circle" />
|
<a-icon type="pause-circle" />
|
||||||
{{ $t('trading-assistant.stopStrategy') }}
|
{{ $t('trading-assistant.stopStrategy') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -312,14 +313,10 @@
|
|||||||
:strategy-id="selectedStrategy.id"
|
:strategy-id="selectedStrategy.id"
|
||||||
:market-type="(selectedStrategy.trading_config && selectedStrategy.trading_config.market_type) || 'swap'"
|
:market-type="(selectedStrategy.trading_config && selectedStrategy.trading_config.market_type) || 'swap'"
|
||||||
:leverage="(selectedStrategy.trading_config && selectedStrategy.trading_config.leverage) || 1"
|
:leverage="(selectedStrategy.trading_config && selectedStrategy.trading_config.leverage) || 1"
|
||||||
:loading="loadingRecords"
|
:loading="loadingRecords" />
|
||||||
/>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="trades" :tab="$t('trading-assistant.tabs.tradingRecords')">
|
<a-tab-pane key="trades" :tab="$t('trading-assistant.tabs.tradingRecords')">
|
||||||
<trading-records
|
<trading-records :strategy-id="selectedStrategy.id" :loading="loadingRecords" />
|
||||||
:strategy-id="selectedStrategy.id"
|
|
||||||
:loading="loadingRecords"
|
|
||||||
/>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-card>
|
</a-card>
|
||||||
@@ -337,8 +334,7 @@
|
|||||||
@cancel="handleCloseModal"
|
@cancel="handleCloseModal"
|
||||||
:maskClosable="false"
|
:maskClosable="false"
|
||||||
:wrapClassName="isMobile ? 'mobile-modal' : ''"
|
:wrapClassName="isMobile ? 'mobile-modal' : ''"
|
||||||
:bodyStyle="{ maxHeight: '70vh', overflowY: 'auto' }"
|
:bodyStyle="{ maxHeight: '70vh', overflowY: 'auto' }">
|
||||||
>
|
|
||||||
<a-spin :spinning="loadingIndicators">
|
<a-spin :spinning="loadingIndicators">
|
||||||
<a-steps :current="currentStep" class="steps-container">
|
<a-steps :current="currentStep" class="steps-container">
|
||||||
<a-step :title="$t('trading-assistant.form.step1')" />
|
<a-step :title="$t('trading-assistant.form.step1')" />
|
||||||
@@ -361,13 +357,11 @@
|
|||||||
@focus="handleIndicatorSelectFocus"
|
@focus="handleIndicatorSelectFocus"
|
||||||
@change="handleIndicatorChange"
|
@change="handleIndicatorChange"
|
||||||
:loading="loadingIndicators"
|
:loading="loadingIndicators"
|
||||||
:getPopupContainer="(triggerNode) => triggerNode.parentNode"
|
:getPopupContainer="(triggerNode) => triggerNode.parentNode">
|
||||||
>
|
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="indicator in availableIndicators"
|
v-for="indicator in availableIndicators"
|
||||||
:key="String(indicator.id)"
|
:key="String(indicator.id)"
|
||||||
:value="String(indicator.id)"
|
:value="String(indicator.id)">
|
||||||
>
|
|
||||||
<div class="indicator-option">
|
<div class="indicator-option">
|
||||||
<span class="indicator-name">{{ indicator.name }}</span>
|
<span class="indicator-name">{{ indicator.name }}</span>
|
||||||
<a-tag v-if="indicator.type" size="small" :color="getIndicatorTypeColor(indicator.type)">
|
<a-tag v-if="indicator.type" size="small" :color="getIndicatorTypeColor(indicator.type)">
|
||||||
@@ -392,11 +386,11 @@
|
|||||||
<a-form-item :label="$t('trading-assistant.form.strategyName')">
|
<a-form-item :label="$t('trading-assistant.form.strategyName')">
|
||||||
<a-input
|
<a-input
|
||||||
v-decorator="['strategy_name', { rules: [{ required: true, message: $t('trading-assistant.validation.strategyNameRequired') }] }]"
|
v-decorator="['strategy_name', { rules: [{ required: true, message: $t('trading-assistant.validation.strategyNameRequired') }] }]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.inputStrategyName')"
|
:placeholder="$t('trading-assistant.placeholders.inputStrategyName')" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="isEditMode ? $t('trading-assistant.form.symbol') : $t('trading-assistant.form.symbols')">
|
<a-form-item
|
||||||
|
:label="isEditMode ? $t('trading-assistant.form.symbol') : $t('trading-assistant.form.symbols')">
|
||||||
<!-- 编辑模式:单选 -->
|
<!-- 编辑模式:单选 -->
|
||||||
<a-select
|
<a-select
|
||||||
v-if="isEditMode"
|
v-if="isEditMode"
|
||||||
@@ -406,13 +400,11 @@
|
|||||||
:filter-option="filterWatchlistOption"
|
:filter-option="filterWatchlistOption"
|
||||||
:loading="loadingWatchlist"
|
:loading="loadingWatchlist"
|
||||||
@change="handleWatchlistSymbolChange"
|
@change="handleWatchlistSymbolChange"
|
||||||
:getPopupContainer="(triggerNode) => triggerNode.parentNode"
|
:getPopupContainer="(triggerNode) => triggerNode.parentNode">
|
||||||
>
|
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="item in watchlist"
|
v-for="item in watchlist"
|
||||||
:key="`${item.market}:${item.symbol}`"
|
:key="`${item.market}:${item.symbol}`"
|
||||||
:value="`${item.market}:${item.symbol}`"
|
:value="`${item.market}:${item.symbol}`">
|
||||||
>
|
|
||||||
<div class="symbol-option">
|
<div class="symbol-option">
|
||||||
<a-tag :color="getMarketColor(item.market)" style="margin-right: 8px; margin-bottom: 0;">
|
<a-tag :color="getMarketColor(item.market)" style="margin-right: 8px; margin-bottom: 0;">
|
||||||
{{ item.market }}
|
{{ item.market }}
|
||||||
@@ -433,13 +425,11 @@
|
|||||||
:loading="loadingWatchlist"
|
:loading="loadingWatchlist"
|
||||||
@change="handleMultiSymbolChange"
|
@change="handleMultiSymbolChange"
|
||||||
:getPopupContainer="(triggerNode) => triggerNode.parentNode"
|
:getPopupContainer="(triggerNode) => triggerNode.parentNode"
|
||||||
:maxTagCount="3"
|
:maxTagCount="3">
|
||||||
>
|
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="item in watchlist"
|
v-for="item in watchlist"
|
||||||
:key="`${item.market}:${item.symbol}`"
|
:key="`${item.market}:${item.symbol}`"
|
||||||
:value="`${item.market}:${item.symbol}`"
|
:value="`${item.market}:${item.symbol}`">
|
||||||
>
|
|
||||||
<div class="symbol-option">
|
<div class="symbol-option">
|
||||||
<a-tag :color="getMarketColor(item.market)" style="margin-right: 8px; margin-bottom: 0;">
|
<a-tag :color="getMarketColor(item.market)" style="margin-right: 8px; margin-bottom: 0;">
|
||||||
{{ item.market }}
|
{{ item.market }}
|
||||||
@@ -450,7 +440,8 @@
|
|||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<div class="form-item-hint">
|
<div class="form-item-hint">
|
||||||
{{ isEditMode ? $t('trading-assistant.form.symbolHintCrypto') : $t('trading-assistant.form.symbolsHint') }}
|
{{ isEditMode ? $t('trading-assistant.form.symbolHintCrypto') :
|
||||||
|
$t('trading-assistant.form.symbolsHint') }}
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
@@ -462,16 +453,14 @@
|
|||||||
:min="10"
|
:min="10"
|
||||||
:step="100"
|
:step="100"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
style="width: 100%"
|
style="width: 100%" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :md="12" :lg="12">
|
<a-col :xs="24" :sm="24" :md="12" :lg="12">
|
||||||
<a-form-item :label="$t('trading-assistant.form.marketType')">
|
<a-form-item :label="$t('trading-assistant.form.marketType')">
|
||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-decorator="['market_type', { initialValue: 'swap' }]"
|
v-decorator="['market_type', { initialValue: 'swap' }]"
|
||||||
@change="handleMarketTypeChange"
|
@change="handleMarketTypeChange">
|
||||||
>
|
|
||||||
<a-radio value="swap">{{ $t('trading-assistant.form.marketTypeFutures') }}</a-radio>
|
<a-radio value="swap">{{ $t('trading-assistant.form.marketTypeFutures') }}</a-radio>
|
||||||
<a-radio value="spot">{{ $t('trading-assistant.form.marketTypeSpot') }}</a-radio>
|
<a-radio value="spot">{{ $t('trading-assistant.form.marketTypeSpot') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
@@ -491,8 +480,7 @@
|
|||||||
:max="form.getFieldValue('market_type') === 'spot' ? 1 : 125"
|
:max="form.getFieldValue('market_type') === 'spot' ? 1 : 125"
|
||||||
:step="1"
|
:step="1"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:disabled="form.getFieldValue('market_type') === 'spot'"
|
:disabled="form.getFieldValue('market_type') === 'spot'" />
|
||||||
/>
|
|
||||||
<div class="form-item-hint">
|
<div class="form-item-hint">
|
||||||
<span v-if="form.getFieldValue('market_type') === 'spot'">
|
<span v-if="form.getFieldValue('market_type') === 'spot'">
|
||||||
{{ $t('trading-assistant.form.spotLeverageFixed') }}
|
{{ $t('trading-assistant.form.spotLeverageFixed') }}
|
||||||
@@ -507,8 +495,7 @@
|
|||||||
<a-form-item :label="$t('trading-assistant.form.tradeDirection')">
|
<a-form-item :label="$t('trading-assistant.form.tradeDirection')">
|
||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-decorator="['trade_direction', { initialValue: 'long' }]"
|
v-decorator="['trade_direction', { initialValue: 'long' }]"
|
||||||
:disabled="form.getFieldValue('market_type') === 'spot'"
|
:disabled="form.getFieldValue('market_type') === 'spot'">
|
||||||
>
|
|
||||||
<a-radio value="long">{{ $t('trading-assistant.form.tradeDirectionLong') }}</a-radio>
|
<a-radio value="long">{{ $t('trading-assistant.form.tradeDirectionLong') }}</a-radio>
|
||||||
<a-radio value="short" :disabled="form.getFieldValue('market_type') === 'spot'">
|
<a-radio value="short" :disabled="form.getFieldValue('market_type') === 'spot'">
|
||||||
{{ $t('trading-assistant.form.tradeDirectionShort') }}
|
{{ $t('trading-assistant.form.tradeDirectionShort') }}
|
||||||
@@ -517,7 +504,10 @@
|
|||||||
{{ $t('trading-assistant.form.tradeDirectionBoth') }}
|
{{ $t('trading-assistant.form.tradeDirectionBoth') }}
|
||||||
</a-radio>
|
</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<div v-if="form.getFieldValue('market_type') === 'spot'" class="form-item-hint" style="color: #ff9800;">
|
<div
|
||||||
|
v-if="form.getFieldValue('market_type') === 'spot'"
|
||||||
|
class="form-item-hint"
|
||||||
|
style="color: #ff9800;">
|
||||||
{{ $t('trading-assistant.form.spotOnlyLongHint') }}
|
{{ $t('trading-assistant.form.spotOnlyLongHint') }}
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -530,8 +520,7 @@
|
|||||||
<a-select
|
<a-select
|
||||||
v-decorator="['timeframe', { initialValue: '1H', rules: [{ required: true }] }]"
|
v-decorator="['timeframe', { initialValue: '1H', rules: [{ required: true }] }]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.selectKlinePeriod')"
|
:placeholder="$t('trading-assistant.placeholders.selectKlinePeriod')"
|
||||||
:getPopupContainer="(triggerNode) => triggerNode.parentNode"
|
:getPopupContainer="(triggerNode) => triggerNode.parentNode">
|
||||||
>
|
|
||||||
<a-select-option value="1m">{{ $t('trading-assistant.form.timeframe1m') }}</a-select-option>
|
<a-select-option value="1m">{{ $t('trading-assistant.form.timeframe1m') }}</a-select-option>
|
||||||
<a-select-option value="5m">{{ $t('trading-assistant.form.timeframe5m') }}</a-select-option>
|
<a-select-option value="5m">{{ $t('trading-assistant.form.timeframe5m') }}</a-select-option>
|
||||||
<a-select-option value="15m">{{ $t('trading-assistant.form.timeframe15m') }}</a-select-option>
|
<a-select-option value="15m">{{ $t('trading-assistant.form.timeframe15m') }}</a-select-option>
|
||||||
@@ -567,8 +556,7 @@
|
|||||||
:max="100"
|
:max="100"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
@@ -579,8 +567,7 @@
|
|||||||
:max="1000"
|
:max="1000"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -590,8 +577,7 @@
|
|||||||
<a-form-item :label="$t('dashboard.indicator.backtest.field.trailingEnabled')">
|
<a-form-item :label="$t('dashboard.indicator.backtest.field.trailingEnabled')">
|
||||||
<a-switch
|
<a-switch
|
||||||
v-decorator="['trailing_enabled', { valuePropName: 'checked', initialValue: false }]"
|
v-decorator="['trailing_enabled', { valuePropName: 'checked', initialValue: false }]"
|
||||||
@change="onTrailingToggle"
|
@change="onTrailingToggle" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12"></a-col>
|
<a-col :span="12"></a-col>
|
||||||
@@ -607,8 +593,7 @@
|
|||||||
:max="100"
|
:max="100"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
@@ -619,8 +604,7 @@
|
|||||||
:max="1000"
|
:max="1000"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -633,16 +617,14 @@
|
|||||||
<a-form-item :label="$t('dashboard.indicator.backtest.field.trendAddEnabled')">
|
<a-form-item :label="$t('dashboard.indicator.backtest.field.trendAddEnabled')">
|
||||||
<a-switch
|
<a-switch
|
||||||
v-decorator="['trend_add_enabled', { valuePropName: 'checked', initialValue: false }]"
|
v-decorator="['trend_add_enabled', { valuePropName: 'checked', initialValue: false }]"
|
||||||
@change="onTrendAddToggle"
|
@change="onTrendAddToggle" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item :label="$t('dashboard.indicator.backtest.field.dcaAddEnabled')">
|
<a-form-item :label="$t('dashboard.indicator.backtest.field.dcaAddEnabled')">
|
||||||
<a-switch
|
<a-switch
|
||||||
v-decorator="['dca_add_enabled', { valuePropName: 'checked', initialValue: false }]"
|
v-decorator="['dca_add_enabled', { valuePropName: 'checked', initialValue: false }]"
|
||||||
@change="onDcaAddToggle"
|
@change="onDcaAddToggle" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -656,8 +638,7 @@
|
|||||||
:step="0.01"
|
:step="0.01"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
@change="onScaleParamsChange"
|
@change="onScaleParamsChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
@@ -669,8 +650,7 @@
|
|||||||
:step="0.01"
|
:step="0.01"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
@change="onScaleParamsChange"
|
@change="onScaleParamsChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -684,8 +664,7 @@
|
|||||||
:step="0.1"
|
:step="0.1"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
@change="onScaleParamsChange"
|
@change="onScaleParamsChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
@@ -697,8 +676,7 @@
|
|||||||
:step="0.1"
|
:step="0.1"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
@change="onScaleParamsChange"
|
@change="onScaleParamsChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -712,8 +690,7 @@
|
|||||||
:step="1"
|
:step="1"
|
||||||
:precision="0"
|
:precision="0"
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
@change="onScaleParamsChange"
|
@change="onScaleParamsChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
@@ -725,8 +702,7 @@
|
|||||||
:step="1"
|
:step="1"
|
||||||
:precision="0"
|
:precision="0"
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
@change="onScaleParamsChange"
|
@change="onScaleParamsChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -736,12 +712,14 @@
|
|||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item :label="$t('dashboard.indicator.backtest.field.trendReduceEnabled')">
|
<a-form-item :label="$t('dashboard.indicator.backtest.field.trendReduceEnabled')">
|
||||||
<a-switch v-decorator="['trend_reduce_enabled', { valuePropName: 'checked', initialValue: false }]" />
|
<a-switch
|
||||||
|
v-decorator="['trend_reduce_enabled', { valuePropName: 'checked', initialValue: false }]" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item :label="$t('dashboard.indicator.backtest.field.adverseReduceEnabled')">
|
<a-form-item :label="$t('dashboard.indicator.backtest.field.adverseReduceEnabled')">
|
||||||
<a-switch v-decorator="['adverse_reduce_enabled', { valuePropName: 'checked', initialValue: false }]" />
|
<a-switch
|
||||||
|
v-decorator="['adverse_reduce_enabled', { valuePropName: 'checked', initialValue: false }]" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -754,8 +732,7 @@
|
|||||||
:max="1000"
|
:max="1000"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
@@ -766,8 +743,7 @@
|
|||||||
:max="1000"
|
:max="1000"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -780,8 +756,7 @@
|
|||||||
:max="100"
|
:max="100"
|
||||||
:step="0.1"
|
:step="0.1"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
@@ -792,8 +767,7 @@
|
|||||||
:max="100"
|
:max="100"
|
||||||
:step="0.1"
|
:step="0.1"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -806,8 +780,7 @@
|
|||||||
:max="50"
|
:max="50"
|
||||||
:step="1"
|
:step="1"
|
||||||
:precision="0"
|
:precision="0"
|
||||||
style="width: 100%"
|
style="width: 100%" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
@@ -818,8 +791,7 @@
|
|||||||
:max="50"
|
:max="50"
|
||||||
:step="1"
|
:step="1"
|
||||||
:precision="0"
|
:precision="0"
|
||||||
style="width: 100%"
|
style="width: 100%" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -830,8 +802,7 @@
|
|||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="$t('dashboard.indicator.backtest.field.entryPct')"
|
:label="$t('dashboard.indicator.backtest.field.entryPct')"
|
||||||
:help="$t('dashboard.indicator.backtest.hint.entryPctMax', { maxPct: Number(entryPctMaxUi || 0).toFixed(0) })"
|
:help="$t('dashboard.indicator.backtest.hint.entryPctMax', { maxPct: Number(entryPctMaxUi || 0).toFixed(0) })">
|
||||||
>
|
|
||||||
<a-input-number
|
<a-input-number
|
||||||
v-decorator="['entry_pct', { initialValue: 100 }]"
|
v-decorator="['entry_pct', { initialValue: 100 }]"
|
||||||
:min="0"
|
:min="0"
|
||||||
@@ -839,8 +810,7 @@
|
|||||||
:step="0.1"
|
:step="0.1"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
@change="onEntryPctChange"
|
@change="onEntryPctChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12"></a-col>
|
<a-col :span="12"></a-col>
|
||||||
@@ -855,10 +825,7 @@
|
|||||||
<a-icon type="robot" />
|
<a-icon type="robot" />
|
||||||
<span>{{ $t('trading-assistant.form.enableAiFilter') }}</span>
|
<span>{{ $t('trading-assistant.form.enableAiFilter') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<a-switch
|
<a-switch :checked="aiFilterEnabledUi" @change="onAiFilterToggle" />
|
||||||
:checked="aiFilterEnabledUi"
|
|
||||||
@change="onAiFilterToggle"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ai-filter-hint">{{ $t('trading-assistant.form.enableAiFilterHint') }}</div>
|
<div class="ai-filter-hint">{{ $t('trading-assistant.form.enableAiFilterHint') }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -875,10 +842,10 @@
|
|||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-decorator="['execution_mode', { initialValue: 'signal' }]"
|
v-decorator="['execution_mode', { initialValue: 'signal' }]"
|
||||||
:disabled="!canUseLiveTrading"
|
:disabled="!canUseLiveTrading"
|
||||||
@change="onExecutionModeChange"
|
@change="onExecutionModeChange">
|
||||||
>
|
|
||||||
<a-radio value="signal">{{ $t('trading-assistant.form.executionModeSignal') }}</a-radio>
|
<a-radio value="signal">{{ $t('trading-assistant.form.executionModeSignal') }}</a-radio>
|
||||||
<a-radio value="live" :disabled="!canUseLiveTrading">{{ $t('trading-assistant.form.executionModeLive') }}</a-radio>
|
<a-radio value="live" :disabled="!canUseLiveTrading">{{ $t('trading-assistant.form.executionModeLive')
|
||||||
|
}}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<div v-if="!canUseLiveTrading" class="form-item-hint" style="color: #ff9800;">
|
<div v-if="!canUseLiveTrading" class="form-item-hint" style="color: #ff9800;">
|
||||||
{{ $t('trading-assistant.form.liveTradingNotSupportedHint') }}
|
{{ $t('trading-assistant.form.liveTradingNotSupportedHint') }}
|
||||||
@@ -888,8 +855,7 @@
|
|||||||
<a-form-item :label="$t('trading-assistant.form.notifyChannels')">
|
<a-form-item :label="$t('trading-assistant.form.notifyChannels')">
|
||||||
<a-checkbox-group
|
<a-checkbox-group
|
||||||
v-decorator="['notify_channels', { initialValue: ['browser'] }]"
|
v-decorator="['notify_channels', { initialValue: ['browser'] }]"
|
||||||
@change="onNotifyChannelsChange"
|
@change="onNotifyChannelsChange">
|
||||||
>
|
|
||||||
<a-checkbox value="browser">{{ $t('trading-assistant.notify.browser') }}</a-checkbox>
|
<a-checkbox value="browser">{{ $t('trading-assistant.notify.browser') }}</a-checkbox>
|
||||||
<a-checkbox value="email">{{ $t('trading-assistant.notify.email') }}</a-checkbox>
|
<a-checkbox value="email">{{ $t('trading-assistant.notify.email') }}</a-checkbox>
|
||||||
<a-checkbox value="telegram">{{ $t('trading-assistant.notify.telegram') }}</a-checkbox>
|
<a-checkbox value="telegram">{{ $t('trading-assistant.notify.telegram') }}</a-checkbox>
|
||||||
@@ -903,8 +869,7 @@
|
|||||||
v-if="notifyChannelsUi.includes('telegram') || notifyChannelsUi.includes('email') || notifyChannelsUi.includes('discord')"
|
v-if="notifyChannelsUi.includes('telegram') || notifyChannelsUi.includes('email') || notifyChannelsUi.includes('discord')"
|
||||||
type="info"
|
type="info"
|
||||||
showIcon
|
showIcon
|
||||||
style="margin-bottom: 16px"
|
style="margin-bottom: 16px">
|
||||||
>
|
|
||||||
<template #message>
|
<template #message>
|
||||||
<span>
|
<span>
|
||||||
{{ $t('trading-assistant.form.notificationFromProfile') || '通知将发送到您在个人中心配置的地址' }}
|
{{ $t('trading-assistant.form.notificationFromProfile') || '通知将发送到您在个人中心配置的地址' }}
|
||||||
@@ -924,8 +889,7 @@
|
|||||||
show-icon
|
show-icon
|
||||||
style="margin-bottom: 12px;"
|
style="margin-bottom: 12px;"
|
||||||
:message="$t('trading-assistant.form.liveTradingConfigTitle')"
|
:message="$t('trading-assistant.form.liveTradingConfigTitle')"
|
||||||
:description="$t('trading-assistant.form.liveTradingConfigHint')"
|
:description="$t('trading-assistant.form.liveTradingConfigHint')" />
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- ========== Broker Configuration (US/HK Stocks) ========== -->
|
<!-- ========== Broker Configuration (US/HK Stocks) ========== -->
|
||||||
<template v-if="isIBKRMarket">
|
<template v-if="isIBKRMarket">
|
||||||
@@ -937,13 +901,8 @@
|
|||||||
}]"
|
}]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.selectBroker')"
|
:placeholder="$t('trading-assistant.placeholders.selectBroker')"
|
||||||
:getPopupContainer="getModalPopupContainer"
|
:getPopupContainer="getModalPopupContainer"
|
||||||
@change="handleBrokerSelectChange"
|
@change="handleBrokerSelectChange">
|
||||||
>
|
<a-select-option v-for="broker in brokerOptions" :key="broker.value" :value="broker.value">
|
||||||
<a-select-option
|
|
||||||
v-for="broker in brokerOptions"
|
|
||||||
:key="broker.value"
|
|
||||||
:value="broker.value"
|
|
||||||
>
|
|
||||||
{{ broker.displayName }}
|
{{ broker.displayName }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -956,15 +915,13 @@
|
|||||||
show-icon
|
show-icon
|
||||||
style="margin-bottom: 16px;"
|
style="margin-bottom: 16px;"
|
||||||
:message="$t('trading-assistant.form.ibkrConnectionTitle')"
|
:message="$t('trading-assistant.form.ibkrConnectionTitle')"
|
||||||
:description="$t('trading-assistant.form.ibkrConnectionHint')"
|
:description="$t('trading-assistant.form.ibkrConnectionHint')" />
|
||||||
/>
|
|
||||||
|
|
||||||
<a-form-item :label="$t('trading-assistant.form.ibkrHost')">
|
<a-form-item :label="$t('trading-assistant.form.ibkrHost')">
|
||||||
<a-input
|
<a-input
|
||||||
v-decorator="['ibkr_host', { initialValue: '127.0.0.1' }]"
|
v-decorator="['ibkr_host', { initialValue: '127.0.0.1' }]"
|
||||||
placeholder="127.0.0.1"
|
placeholder="127.0.0.1"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="$t('trading-assistant.form.ibkrPort')">
|
<a-form-item :label="$t('trading-assistant.form.ibkrPort')">
|
||||||
@@ -974,8 +931,7 @@
|
|||||||
:min="1"
|
:min="1"
|
||||||
:max="65535"
|
:max="65535"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
<div class="form-item-hint">{{ $t('trading-assistant.form.ibkrPortHint') }}</div>
|
<div class="form-item-hint">{{ $t('trading-assistant.form.ibkrPortHint') }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
@@ -986,16 +942,14 @@
|
|||||||
:min="1"
|
:min="1"
|
||||||
:max="999"
|
:max="999"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="$t('trading-assistant.form.ibkrAccount')">
|
<a-form-item :label="$t('trading-assistant.form.ibkrAccount')">
|
||||||
<a-input
|
<a-input
|
||||||
v-decorator="['ibkr_account', { initialValue: '' }]"
|
v-decorator="['ibkr_account', { initialValue: '' }]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.ibkrAccount')"
|
:placeholder="$t('trading-assistant.placeholders.ibkrAccount')"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
<div class="form-item-hint">{{ $t('trading-assistant.form.ibkrAccountHint') }}</div>
|
<div class="form-item-hint">{{ $t('trading-assistant.form.ibkrAccountHint') }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -1014,13 +968,8 @@
|
|||||||
}]"
|
}]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.selectBroker')"
|
:placeholder="$t('trading-assistant.placeholders.selectBroker')"
|
||||||
:getPopupContainer="getModalPopupContainer"
|
:getPopupContainer="getModalPopupContainer"
|
||||||
@change="handleForexBrokerSelectChange"
|
@change="handleForexBrokerSelectChange">
|
||||||
>
|
<a-select-option v-for="broker in forexBrokerOptions" :key="broker.value" :value="broker.value">
|
||||||
<a-select-option
|
|
||||||
v-for="broker in forexBrokerOptions"
|
|
||||||
:key="broker.value"
|
|
||||||
:value="broker.value"
|
|
||||||
>
|
|
||||||
{{ broker.displayName }}
|
{{ broker.displayName }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -1033,8 +982,7 @@
|
|||||||
show-icon
|
show-icon
|
||||||
style="margin-bottom: 16px;"
|
style="margin-bottom: 16px;"
|
||||||
:message="$t('trading-assistant.form.mt5ConnectionTitle')"
|
:message="$t('trading-assistant.form.mt5ConnectionTitle')"
|
||||||
:description="$t('trading-assistant.form.mt5ConnectionHint')"
|
:description="$t('trading-assistant.form.mt5ConnectionHint')" />
|
||||||
/>
|
|
||||||
|
|
||||||
<a-form-item :label="$t('trading-assistant.form.mt5Server')">
|
<a-form-item :label="$t('trading-assistant.form.mt5Server')">
|
||||||
<a-input
|
<a-input
|
||||||
@@ -1042,8 +990,7 @@
|
|||||||
rules: [{ required: true, message: $t('trading-assistant.validation.mt5ServerRequired') }]
|
rules: [{ required: true, message: $t('trading-assistant.validation.mt5ServerRequired') }]
|
||||||
}]"
|
}]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.mt5Server')"
|
:placeholder="$t('trading-assistant.placeholders.mt5Server')"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
<div class="form-item-hint">{{ $t('trading-assistant.form.mt5ServerHint') }}</div>
|
<div class="form-item-hint">{{ $t('trading-assistant.form.mt5ServerHint') }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
@@ -1055,8 +1002,7 @@
|
|||||||
:placeholder="$t('trading-assistant.placeholders.mt5Login')"
|
:placeholder="$t('trading-assistant.placeholders.mt5Login')"
|
||||||
:min="1"
|
:min="1"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="$t('trading-assistant.form.mt5Password')">
|
<a-form-item :label="$t('trading-assistant.form.mt5Password')">
|
||||||
@@ -1065,16 +1011,14 @@
|
|||||||
rules: [{ required: true, message: $t('trading-assistant.validation.mt5PasswordRequired') }]
|
rules: [{ required: true, message: $t('trading-assistant.validation.mt5PasswordRequired') }]
|
||||||
}]"
|
}]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.mt5Password')"
|
:placeholder="$t('trading-assistant.placeholders.mt5Password')"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="$t('trading-assistant.form.mt5TerminalPath')">
|
<a-form-item :label="$t('trading-assistant.form.mt5TerminalPath')">
|
||||||
<a-input
|
<a-input
|
||||||
v-decorator="['mt5_terminal_path']"
|
v-decorator="['mt5_terminal_path']"
|
||||||
:placeholder="$t('trading-assistant.placeholders.mt5TerminalPath')"
|
:placeholder="$t('trading-assistant.placeholders.mt5TerminalPath')"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
<div class="form-item-hint">{{ $t('trading-assistant.form.mt5TerminalPathHint') }}</div>
|
<div class="form-item-hint">{{ $t('trading-assistant.form.mt5TerminalPathHint') }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -1093,13 +1037,8 @@
|
|||||||
show-search
|
show-search
|
||||||
option-filter-prop="children"
|
option-filter-prop="children"
|
||||||
:loading="loadingExchangeCredentials"
|
:loading="loadingExchangeCredentials"
|
||||||
@change="handleCredentialSelectChange"
|
@change="handleCredentialSelectChange">
|
||||||
>
|
<a-select-option v-for="cred in exchangeCredentials" :key="cred.id" :value="cred.id">
|
||||||
<a-select-option
|
|
||||||
v-for="cred in exchangeCredentials"
|
|
||||||
:key="cred.id"
|
|
||||||
:value="cred.id"
|
|
||||||
>
|
|
||||||
{{ formatCredentialLabel(cred) }}
|
{{ formatCredentialLabel(cred) }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -1116,13 +1055,11 @@
|
|||||||
allow-clear
|
allow-clear
|
||||||
show-search
|
show-search
|
||||||
option-filter-prop="children"
|
option-filter-prop="children"
|
||||||
@change="handleExchangeSelectChange"
|
@change="handleExchangeSelectChange">
|
||||||
>
|
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="exchange in cryptoExchangeOptions"
|
v-for="exchange in cryptoExchangeOptions"
|
||||||
:key="exchange.value"
|
:key="exchange.value"
|
||||||
:value="exchange.value"
|
:value="exchange.value">
|
||||||
>
|
|
||||||
{{ exchange.displayName }}
|
{{ exchange.displayName }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -1133,8 +1070,7 @@
|
|||||||
v-decorator="['api_key', { rules: [{ required: true, message: $t('trading-assistant.validation.apiKeyRequired') }] }]"
|
v-decorator="['api_key', { rules: [{ required: true, message: $t('trading-assistant.validation.apiKeyRequired') }] }]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.inputApiKey')"
|
:placeholder="$t('trading-assistant.placeholders.inputApiKey')"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="$t('trading-assistant.form.secretKey')">
|
<a-form-item :label="$t('trading-assistant.form.secretKey')">
|
||||||
@@ -1142,50 +1078,45 @@
|
|||||||
v-decorator="['secret_key', { rules: [{ required: true, message: $t('trading-assistant.validation.secretKeyRequired') }] }]"
|
v-decorator="['secret_key', { rules: [{ required: true, message: $t('trading-assistant.validation.secretKeyRequired') }] }]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.inputSecretKey')"
|
:placeholder="$t('trading-assistant.placeholders.inputSecretKey')"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item
|
<a-form-item v-if="needsPassphrase" :label="$t('trading-assistant.form.passphrase')">
|
||||||
v-if="needsPassphrase"
|
|
||||||
:label="$t('trading-assistant.form.passphrase')"
|
|
||||||
>
|
|
||||||
<a-input-password
|
<a-input-password
|
||||||
v-decorator="['passphrase', { rules: [{ required: true, message: $t('trading-assistant.validation.passphraseRequired') }] }]"
|
v-decorator="['passphrase', { rules: [{ required: true, message: $t('trading-assistant.validation.passphraseRequired') }] }]"
|
||||||
:placeholder="$t('trading-assistant.placeholders.inputPassphrase')"
|
:placeholder="$t('trading-assistant.placeholders.inputPassphrase')"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
@change="handleApiConfigChange"
|
@change="handleApiConfigChange" />
|
||||||
/>
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
v-if="showDemoTradingSwitch"
|
||||||
|
:label="`${getExchangeDisplayName(currentExchangeId)} Demo Trading`"
|
||||||
|
key="demo-trading-switch">
|
||||||
|
<a-switch v-decorator="['enable_demo_trading', { valuePropName: 'checked', initialValue: false }]">
|
||||||
|
<a-icon slot="checkedChildren" type="check" />
|
||||||
|
<a-icon slot="unCheckedChildren" type="close" />
|
||||||
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-checkbox
|
<a-checkbox
|
||||||
v-decorator="['save_credential', { valuePropName: 'checked', initialValue: false }]"
|
v-decorator="['save_credential', { valuePropName: 'checked', initialValue: false }]"
|
||||||
@change="onSaveCredentialChange"
|
@change="onSaveCredentialChange">
|
||||||
>
|
|
||||||
{{ $t('trading-assistant.form.saveCredential') }}
|
{{ $t('trading-assistant.form.saveCredential') }}
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item
|
<a-form-item v-if="saveCredentialUi" :label="$t('trading-assistant.form.credentialName')">
|
||||||
v-if="saveCredentialUi"
|
|
||||||
:label="$t('trading-assistant.form.credentialName')"
|
|
||||||
>
|
|
||||||
<a-input
|
<a-input
|
||||||
v-decorator="['credential_name']"
|
v-decorator="['credential_name']"
|
||||||
:placeholder="$t('trading-assistant.placeholders.inputCredentialName')"
|
:placeholder="$t('trading-assistant.placeholders.inputCredentialName')" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Test Connection Button (shared by both IBKR and Crypto) -->
|
<!-- Test Connection Button (shared by both IBKR and Crypto) -->
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button
|
<a-button type="default" :loading="testing" @click="handleTestConnection" block>
|
||||||
type="default"
|
|
||||||
:loading="testing"
|
|
||||||
@click="handleTestConnection"
|
|
||||||
block
|
|
||||||
>
|
|
||||||
<a-icon type="wallet" />
|
<a-icon type="wallet" />
|
||||||
{{ $t('trading-assistant.form.testConnection') }}
|
{{ $t('trading-assistant.form.testConnection') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -1201,26 +1132,13 @@
|
|||||||
|
|
||||||
<template slot="footer">
|
<template slot="footer">
|
||||||
<a-button @click="handleCloseModal">{{ $t('trading-assistant.form.cancel') }}</a-button>
|
<a-button @click="handleCloseModal">{{ $t('trading-assistant.form.cancel') }}</a-button>
|
||||||
<a-button
|
<a-button v-show="currentStep > 0" @click="handlePrev">
|
||||||
v-show="currentStep > 0"
|
|
||||||
@click="handlePrev"
|
|
||||||
>
|
|
||||||
{{ $t('trading-assistant.form.prev') }}
|
{{ $t('trading-assistant.form.prev') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button v-show="currentStep < 2" type="primary" @click="handleNext" :loading="saving">
|
||||||
v-show="currentStep < 2"
|
|
||||||
type="primary"
|
|
||||||
@click="handleNext"
|
|
||||||
:loading="saving"
|
|
||||||
>
|
|
||||||
{{ $t('trading-assistant.form.next') }}
|
{{ $t('trading-assistant.form.next') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button v-show="currentStep === 2" type="primary" @click="handleSubmit" :loading="saving">
|
||||||
v-show="currentStep === 2"
|
|
||||||
type="primary"
|
|
||||||
@click="handleSubmit"
|
|
||||||
:loading="saving"
|
|
||||||
>
|
|
||||||
{{ editingStrategy ? $t('trading-assistant.form.confirmEdit') : $t('trading-assistant.form.confirmCreate') }}
|
{{ editingStrategy ? $t('trading-assistant.form.confirmEdit') : $t('trading-assistant.form.confirmCreate') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -1391,6 +1309,11 @@ export default {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
// 是否显示模拟交易开关
|
||||||
|
showDemoTradingSwitch () {
|
||||||
|
// 目前仅支持 Binance 的 Demo Trading
|
||||||
|
return this.currentExchangeId && this.currentExchangeId.toLowerCase() === 'binance'
|
||||||
|
},
|
||||||
// Broker options for US/HK stocks (with i18n support)
|
// Broker options for US/HK stocks (with i18n support)
|
||||||
brokerOptions () {
|
brokerOptions () {
|
||||||
return BROKER_OPTIONS.map(broker => {
|
return BROKER_OPTIONS.map(broker => {
|
||||||
@@ -2642,11 +2565,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear API fields when exchange changes, as we rely on "Saved credential"
|
// Clear API fields when exchange changes, as we rely on "Saved credential"
|
||||||
|
// to auto-fill api_key/secret_key. User must re-enter if changing exchange.
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const fieldsToClear = {
|
const fieldsToClear = {
|
||||||
api_key: undefined,
|
api_key: undefined,
|
||||||
secret_key: undefined,
|
secret_key: undefined,
|
||||||
passphrase: undefined
|
passphrase: undefined,
|
||||||
|
enable_demo_trading: false // Reset demo switch too
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.form.setFieldsValue(fieldsToClear)
|
this.form.setFieldsValue(fieldsToClear)
|
||||||
@@ -2836,7 +2761,8 @@ export default {
|
|||||||
exchange_id: values.exchange_id,
|
exchange_id: values.exchange_id,
|
||||||
api_key: values.api_key,
|
api_key: values.api_key,
|
||||||
secret_key: values.secret_key,
|
secret_key: values.secret_key,
|
||||||
market_type: String(marketType || 'swap')
|
market_type: String(marketType || 'swap'),
|
||||||
|
enableDemoTrading: !!this.form.getFieldValue('enable_demo_trading')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.needsPassphrase && values.passphrase) {
|
if (this.needsPassphrase && values.passphrase) {
|
||||||
@@ -3041,6 +2967,7 @@ export default {
|
|||||||
credential_id: values.credential_id,
|
credential_id: values.credential_id,
|
||||||
api_key: values.api_key,
|
api_key: values.api_key,
|
||||||
secret_key: values.secret_key,
|
secret_key: values.secret_key,
|
||||||
|
enableDemoTrading: !!values.enable_demo_trading,
|
||||||
passphrase: this.needsPassphrase ? values.passphrase : undefined
|
passphrase: this.needsPassphrase ? values.passphrase : undefined
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
trading_config: {
|
trading_config: {
|
||||||
@@ -3835,9 +3762,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes statusPulse {
|
@keyframes statusPulse {
|
||||||
0%, 100% {
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
@@ -4149,10 +4079,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% {
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
@@ -4201,6 +4134,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-tabpane {
|
.ant-tabs-tabpane {
|
||||||
|
|
||||||
.trading-records,
|
.trading-records,
|
||||||
.position-records {
|
.position-records {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -4676,6 +4610,7 @@ export default {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(10px);
|
transform: translateY(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
@@ -4721,5 +4656,4 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user