Supports MT5 and forex trading.
Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
This commit is contained in:
@@ -1334,6 +1334,8 @@ const locale = {
|
||||
'trading-assistant.placeholders.selectBroker': 'Select broker',
|
||||
'trading-assistant.brokerNames': {
|
||||
'ibkr': 'Interactive Brokers (IBKR)',
|
||||
'mt5': 'MetaTrader 5 (MT5)',
|
||||
'mt4': 'MetaTrader 4 (MT4)',
|
||||
'futu': 'Futu Securities',
|
||||
'tiger': 'Tiger Brokers',
|
||||
'td': 'TD Ameritrade',
|
||||
@@ -1348,6 +1350,25 @@ const locale = {
|
||||
'trading-assistant.placeholders.ibkrAccount': 'Optional, e.g. U1234567',
|
||||
'trading-assistant.exchange.ibkrConnectionSuccess': 'IBKR connected successfully',
|
||||
'trading-assistant.exchange.ibkrConnectionFailed': 'IBKR connection failed. Please check if TWS/Gateway is running.',
|
||||
// MT5/Forex configuration
|
||||
'trading-assistant.form.forexBroker': 'Forex Broker',
|
||||
'trading-assistant.form.mt5ConnectionTitle': 'MetaTrader 5 Connection',
|
||||
'trading-assistant.form.mt5ConnectionHint': 'Make sure MT5 terminal is running and logged in (Windows only)',
|
||||
'trading-assistant.form.mt5Server': 'Server',
|
||||
'trading-assistant.form.mt5ServerHint': 'Broker server name (e.g., ICMarkets-Demo)',
|
||||
'trading-assistant.form.mt5Login': 'Account Number',
|
||||
'trading-assistant.form.mt5Password': 'Password',
|
||||
'trading-assistant.form.mt5TerminalPath': 'MT5 Terminal Path (Optional)',
|
||||
'trading-assistant.form.mt5TerminalPathHint': 'If MT5 terminal is not installed in the default location, specify the full path to terminal64.exe (e.g., C:\\Program Files\\MetaTrader 5\\terminal64.exe)',
|
||||
'trading-assistant.placeholders.mt5Server': 'e.g., ICMarkets-Demo',
|
||||
'trading-assistant.placeholders.mt5Login': 'e.g., 12345678',
|
||||
'trading-assistant.placeholders.mt5Password': 'Your MT5 password',
|
||||
'trading-assistant.placeholders.mt5TerminalPath': 'e.g., C:\\Program Files\\MetaTrader 5\\terminal64.exe',
|
||||
'trading-assistant.validation.mt5ServerRequired': 'Please enter MT5 server',
|
||||
'trading-assistant.validation.mt5LoginRequired': 'Please enter MT5 account number',
|
||||
'trading-assistant.validation.mt5PasswordRequired': 'Please enter MT5 password',
|
||||
'trading-assistant.exchange.mt5ConnectionSuccess': 'MT5 connected successfully',
|
||||
'trading-assistant.exchange.mt5ConnectionFailed': 'MT5 connection failed. Please check if terminal is running.',
|
||||
'trading-assistant.form.notifyChannels': 'Notification Channels',
|
||||
'trading-assistant.form.notifyChannelsHint': 'Choose how you want to receive buy/sell and risk-management signals.',
|
||||
'trading-assistant.notify.browser': 'Browser',
|
||||
|
||||
@@ -1212,6 +1212,8 @@ const locale = {
|
||||
'trading-assistant.placeholders.selectBroker': '选择券商',
|
||||
'trading-assistant.brokerNames': {
|
||||
'ibkr': '盈透证券 (Interactive Brokers)',
|
||||
'mt5': 'MetaTrader 5 (MT5)',
|
||||
'mt4': 'MetaTrader 4 (MT4)',
|
||||
'futu': '富途证券 (Futu)',
|
||||
'tiger': '老虎证券 (Tiger Brokers)',
|
||||
'td': 'TD Ameritrade',
|
||||
@@ -1226,6 +1228,25 @@ const locale = {
|
||||
'trading-assistant.placeholders.ibkrAccount': '可选,如 U1234567',
|
||||
'trading-assistant.exchange.ibkrConnectionSuccess': '盈透证券连接成功',
|
||||
'trading-assistant.exchange.ibkrConnectionFailed': '盈透证券连接失败,请检查 TWS/Gateway 是否运行',
|
||||
// MT5/Forex 配置
|
||||
'trading-assistant.form.forexBroker': '外汇券商',
|
||||
'trading-assistant.form.mt5ConnectionTitle': 'MetaTrader 5 连接配置',
|
||||
'trading-assistant.form.mt5ConnectionHint': '请确保 MT5 终端已启动并登录(仅支持 Windows)',
|
||||
'trading-assistant.form.mt5Server': '服务器',
|
||||
'trading-assistant.form.mt5ServerHint': '券商服务器名称(如:ICMarkets-Demo)',
|
||||
'trading-assistant.form.mt5Login': '账户号',
|
||||
'trading-assistant.form.mt5Password': '密码',
|
||||
'trading-assistant.form.mt5TerminalPath': 'MT5 终端路径(可选)',
|
||||
'trading-assistant.form.mt5TerminalPathHint': '如果 MT5 终端未安装在默认位置,请指定 terminal64.exe 的完整路径(例如:C:\\Program Files\\MetaTrader 5\\terminal64.exe)',
|
||||
'trading-assistant.placeholders.mt5Server': '例如:ICMarkets-Demo',
|
||||
'trading-assistant.placeholders.mt5Login': '例如:12345678',
|
||||
'trading-assistant.placeholders.mt5Password': '您的 MT5 密码',
|
||||
'trading-assistant.placeholders.mt5TerminalPath': '例如:C:\\Program Files\\MetaTrader 5\\terminal64.exe',
|
||||
'trading-assistant.validation.mt5ServerRequired': '请输入 MT5 服务器',
|
||||
'trading-assistant.validation.mt5LoginRequired': '请输入 MT5 账户号',
|
||||
'trading-assistant.validation.mt5PasswordRequired': '请输入 MT5 密码',
|
||||
'trading-assistant.exchange.mt5ConnectionSuccess': 'MT5 连接成功',
|
||||
'trading-assistant.exchange.mt5ConnectionFailed': 'MT5 连接失败,请检查终端是否运行',
|
||||
'trading-assistant.form.notifyChannels': '通知渠道',
|
||||
'trading-assistant.form.notifyChannelsHint': '选择信号触发时的通知方式',
|
||||
'trading-assistant.form.notifyEmail': '邮箱地址',
|
||||
|
||||
@@ -1039,6 +1039,85 @@
|
||||
<!-- <template v-else-if="currentBrokerId === 'futu'">...</template> -->
|
||||
</template>
|
||||
|
||||
<!-- ========== MT5/Forex Broker Configuration ========== -->
|
||||
<template v-else-if="isMT5Market">
|
||||
<a-form-item :label="$t('trading-assistant.form.forexBroker')">
|
||||
<a-select
|
||||
v-decorator="['forex_broker_id', {
|
||||
initialValue: 'mt5',
|
||||
rules: [{ required: true, message: $t('trading-assistant.validation.brokerRequired') }]
|
||||
}]"
|
||||
:placeholder="$t('trading-assistant.placeholders.selectBroker')"
|
||||
:getPopupContainer="getModalPopupContainer"
|
||||
@change="handleForexBrokerSelectChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="broker in forexBrokerOptions"
|
||||
:key="broker.value"
|
||||
:value="broker.value"
|
||||
>
|
||||
{{ broker.displayName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- MT5 specific configuration -->
|
||||
<template v-if="currentBrokerId === 'mt5'">
|
||||
<a-alert
|
||||
type="info"
|
||||
show-icon
|
||||
style="margin-bottom: 16px;"
|
||||
:message="$t('trading-assistant.form.mt5ConnectionTitle')"
|
||||
:description="$t('trading-assistant.form.mt5ConnectionHint')"
|
||||
/>
|
||||
|
||||
<a-form-item :label="$t('trading-assistant.form.mt5Server')">
|
||||
<a-input
|
||||
v-decorator="['mt5_server', {
|
||||
rules: [{ required: true, message: $t('trading-assistant.validation.mt5ServerRequired') }]
|
||||
}]"
|
||||
:placeholder="$t('trading-assistant.placeholders.mt5Server')"
|
||||
@change="handleApiConfigChange"
|
||||
/>
|
||||
<div class="form-item-hint">{{ $t('trading-assistant.form.mt5ServerHint') }}</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('trading-assistant.form.mt5Login')">
|
||||
<a-input-number
|
||||
v-decorator="['mt5_login', {
|
||||
rules: [{ required: true, message: $t('trading-assistant.validation.mt5LoginRequired') }]
|
||||
}]"
|
||||
:placeholder="$t('trading-assistant.placeholders.mt5Login')"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
@change="handleApiConfigChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('trading-assistant.form.mt5Password')">
|
||||
<a-input-password
|
||||
v-decorator="['mt5_password', {
|
||||
rules: [{ required: true, message: $t('trading-assistant.validation.mt5PasswordRequired') }]
|
||||
}]"
|
||||
:placeholder="$t('trading-assistant.placeholders.mt5Password')"
|
||||
@change="handleApiConfigChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('trading-assistant.form.mt5TerminalPath')">
|
||||
<a-input
|
||||
v-decorator="['mt5_terminal_path']"
|
||||
:placeholder="$t('trading-assistant.placeholders.mt5TerminalPath')"
|
||||
@change="handleApiConfigChange"
|
||||
/>
|
||||
<div class="form-item-hint">{{ $t('trading-assistant.form.mt5TerminalPathHint') }}</div>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- Future forex broker configurations can be added here -->
|
||||
<!-- <template v-else-if="currentBrokerId === 'mt4'">...</template> -->
|
||||
</template>
|
||||
|
||||
<!-- ========== Crypto Exchange Configuration ========== -->
|
||||
<template v-else>
|
||||
<a-form-item :label="$t('trading-assistant.form.savedCredential')">
|
||||
@@ -1223,6 +1302,14 @@ const BROKER_OPTIONS = [
|
||||
// { value: 'tiger', labelKey: 'tiger', name: 'Tiger Brokers (老虎证券)' },
|
||||
]
|
||||
|
||||
// Forex broker options
|
||||
const FOREX_BROKER_OPTIONS = [
|
||||
{ value: 'mt5', labelKey: 'mt5', name: 'MetaTrader 5' }
|
||||
// Future forex brokers can be added here:
|
||||
// { value: 'mt4', labelKey: 'mt4', name: 'MetaTrader 4' },
|
||||
// { value: 'ctrader', labelKey: 'ctrader', name: 'cTrader' },
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'TradingAssistant',
|
||||
mixins: [baseMixin],
|
||||
@@ -1242,6 +1329,14 @@ export default {
|
||||
isIBKRMarket () {
|
||||
return ['USStock', 'HShare'].includes(this.selectedMarketCategory)
|
||||
},
|
||||
// Check if current market uses MT5 (Forex)
|
||||
isMT5Market () {
|
||||
return this.selectedMarketCategory === 'Forex'
|
||||
},
|
||||
// Check if current market uses any broker (not crypto exchange)
|
||||
isBrokerMarket () {
|
||||
return this.isIBKRMarket || this.isMT5Market
|
||||
},
|
||||
// 预处理交易所列表,包含显示名称,提升性能
|
||||
formattedExchangeOptions () {
|
||||
return EXCHANGE_OPTIONS.map(exchange => {
|
||||
@@ -1294,7 +1389,7 @@ export default {
|
||||
const cat = this.selectedMarketCategory || 'Crypto'
|
||||
return String(cat).toLowerCase() === 'crypto'
|
||||
},
|
||||
// Check if selected market supports live trading (Crypto or USStock/HShare with IBKR)
|
||||
// Check if selected market supports live trading (Crypto, USStock/HShare with IBKR, or Forex with MT5)
|
||||
canUseLiveTrading () {
|
||||
const cat = this.selectedMarketCategory || 'Crypto'
|
||||
// Crypto always supports live trading via crypto exchanges
|
||||
@@ -1305,6 +1400,10 @@ export default {
|
||||
if (['USStock', 'HShare'].includes(cat)) {
|
||||
return true
|
||||
}
|
||||
// Forex can use MT5 for live trading
|
||||
if (cat === 'Forex') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
// Check if current market + exchange combination supports live trading
|
||||
@@ -1317,7 +1416,11 @@ export default {
|
||||
}
|
||||
// USStock/HShare use IBKR
|
||||
if (['USStock', 'HShare'].includes(cat)) {
|
||||
return exchangeId === 'ibkr'
|
||||
return this.currentBrokerId === 'ibkr'
|
||||
}
|
||||
// Forex uses MT5
|
||||
if (cat === 'Forex') {
|
||||
return this.currentBrokerId === 'mt5'
|
||||
}
|
||||
return false
|
||||
},
|
||||
@@ -1341,6 +1444,26 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
// Forex broker options (with i18n support)
|
||||
forexBrokerOptions () {
|
||||
return FOREX_BROKER_OPTIONS.map(broker => {
|
||||
let label = ''
|
||||
try {
|
||||
const translationKey = `trading-assistant.brokerNames.${broker.labelKey}`
|
||||
const translated = this.$t(translationKey)
|
||||
if (translated !== translationKey) {
|
||||
label = translated
|
||||
}
|
||||
} catch (e) {}
|
||||
if (!label) {
|
||||
label = broker.name || broker.value.toUpperCase()
|
||||
}
|
||||
return {
|
||||
...broker,
|
||||
displayName: label
|
||||
}
|
||||
})
|
||||
},
|
||||
// Crypto exchange options only
|
||||
cryptoExchangeOptions () {
|
||||
return EXCHANGE_OPTIONS.map(exchange => {
|
||||
@@ -1502,9 +1625,22 @@ export default {
|
||||
// Keep selection reactive for Step 3 execution gating
|
||||
this.selectedMarketCategory = market || 'Crypto'
|
||||
|
||||
// Auto-set broker ID based on market category
|
||||
if (this.selectedMarketCategory === 'Forex') {
|
||||
this.currentBrokerId = 'mt5'
|
||||
try {
|
||||
this.form && this.form.setFieldsValue && this.form.setFieldsValue({ forex_broker_id: 'mt5' })
|
||||
} catch (e) {}
|
||||
} else if (['USStock', 'HShare'].includes(this.selectedMarketCategory)) {
|
||||
this.currentBrokerId = 'ibkr'
|
||||
try {
|
||||
this.form && this.form.setFieldsValue && this.form.setFieldsValue({ broker_id: 'ibkr' })
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Markets without live trading support: force back to signal mode
|
||||
// Crypto, USStock, HShare support live trading; others do not
|
||||
const supportsLiveTrading = ['Crypto', 'USStock', 'HShare'].includes(this.selectedMarketCategory)
|
||||
// Crypto, USStock, HShare, Forex support live trading; others do not
|
||||
const supportsLiveTrading = ['Crypto', 'USStock', 'HShare', 'Forex'].includes(this.selectedMarketCategory)
|
||||
if (!supportsLiveTrading) {
|
||||
this.executionModeUi = 'signal'
|
||||
try {
|
||||
@@ -1532,8 +1668,21 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-set broker ID based on market category
|
||||
if (this.selectedMarketCategory === 'Forex') {
|
||||
this.currentBrokerId = 'mt5'
|
||||
try {
|
||||
this.form && this.form.setFieldsValue && this.form.setFieldsValue({ forex_broker_id: 'mt5' })
|
||||
} catch (e) {}
|
||||
} else if (['USStock', 'HShare'].includes(this.selectedMarketCategory)) {
|
||||
this.currentBrokerId = 'ibkr'
|
||||
try {
|
||||
this.form && this.form.setFieldsValue && this.form.setFieldsValue({ broker_id: 'ibkr' })
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Markets without live trading support: force back to signal mode
|
||||
const supportsLiveTrading = ['Crypto', 'USStock', 'HShare'].includes(this.selectedMarketCategory)
|
||||
const supportsLiveTrading = ['Crypto', 'USStock', 'HShare', 'Forex'].includes(this.selectedMarketCategory)
|
||||
if (!supportsLiveTrading) {
|
||||
this.executionModeUi = 'signal'
|
||||
try {
|
||||
@@ -1868,8 +2017,9 @@ export default {
|
||||
if (strategy.exchange_config) {
|
||||
const exchangeId = strategy.exchange_config.exchange_id || ''
|
||||
const isLive = this.executionModeUi === 'live'
|
||||
const supportsLiveTrading = ['Crypto', 'USStock', 'HShare'].includes(this.selectedMarketCategory)
|
||||
const supportsLiveTrading = ['Crypto', 'USStock', 'HShare', 'Forex'].includes(this.selectedMarketCategory)
|
||||
const isBrokerMarket = ['USStock', 'HShare'].includes(this.selectedMarketCategory)
|
||||
const isForexMarket = this.selectedMarketCategory === 'Forex'
|
||||
|
||||
if (isLive && supportsLiveTrading) {
|
||||
if (isBrokerMarket) {
|
||||
@@ -1882,6 +2032,16 @@ export default {
|
||||
ibkr_client_id: strategy.exchange_config.ibkr_client_id || 1,
|
||||
ibkr_account: strategy.exchange_config.ibkr_account || ''
|
||||
})
|
||||
} else if (isForexMarket) {
|
||||
// MT5 configuration (Forex)
|
||||
this.currentBrokerId = exchangeId || 'mt5'
|
||||
this.form.setFieldsValue({
|
||||
forex_broker_id: exchangeId || 'mt5',
|
||||
mt5_server: strategy.exchange_config.mt5_server || '',
|
||||
mt5_login: strategy.exchange_config.mt5_login || '',
|
||||
mt5_password: strategy.exchange_config.mt5_password || '',
|
||||
mt5_terminal_path: strategy.exchange_config.mt5_terminal_path || ''
|
||||
})
|
||||
} else {
|
||||
// Crypto exchange configuration
|
||||
this.currentExchangeId = exchangeId
|
||||
@@ -1903,8 +2063,8 @@ export default {
|
||||
}
|
||||
|
||||
// Update UI state
|
||||
if (isBrokerMarket) {
|
||||
this.currentBrokerId = exchangeId || 'ibkr'
|
||||
if (isBrokerMarket || isForexMarket) {
|
||||
this.currentBrokerId = exchangeId || (isForexMarket ? 'mt5' : 'ibkr')
|
||||
} else {
|
||||
this.currentExchangeId = exchangeId
|
||||
}
|
||||
@@ -2465,6 +2625,11 @@ export default {
|
||||
this.testResult = null
|
||||
this.connectionTestResult = null
|
||||
},
|
||||
handleForexBrokerSelectChange (value) {
|
||||
this.currentBrokerId = value || 'mt5'
|
||||
this.testResult = null
|
||||
this.connectionTestResult = null
|
||||
},
|
||||
handleExchangeSelectChange (value) {
|
||||
this.currentExchangeId = value || ''
|
||||
this.testResult = null
|
||||
@@ -2601,6 +2766,58 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// MT5 uses different connection test (server/login/password)
|
||||
if (this.isMT5Market) {
|
||||
const values = this.form.getFieldsValue(['mt5_server', 'mt5_login', 'mt5_password', 'mt5_terminal_path'])
|
||||
const server = values.mt5_server || ''
|
||||
const login = values.mt5_login || ''
|
||||
const password = values.mt5_password || ''
|
||||
const terminal_path = values.mt5_terminal_path || ''
|
||||
|
||||
if (!server || !login || !password) {
|
||||
this.testResult = {
|
||||
success: false,
|
||||
message: this.$t('trading-assistant.exchange.fillComplete')
|
||||
}
|
||||
this.$message.error(this.testResult.message)
|
||||
this.testing = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Call MT5 connect API
|
||||
const res = await this.$http.post('/api/mt5/connect', {
|
||||
server: server,
|
||||
login: parseInt(login),
|
||||
password: password,
|
||||
terminal_path: terminal_path
|
||||
})
|
||||
|
||||
if (res.data && res.data.success) {
|
||||
this.testResult = {
|
||||
success: true,
|
||||
message: this.$t('trading-assistant.exchange.mt5ConnectionSuccess')
|
||||
}
|
||||
this.$message.success(this.$t('trading-assistant.exchange.mt5ConnectionSuccess'))
|
||||
} else {
|
||||
this.testResult = {
|
||||
success: false,
|
||||
message: res.data?.error || this.$t('trading-assistant.exchange.mt5ConnectionFailed')
|
||||
}
|
||||
this.$message.error(this.testResult.message)
|
||||
}
|
||||
} catch (error) {
|
||||
this.testResult = {
|
||||
success: false,
|
||||
message: error.response?.data?.error || error.message || this.$t('trading-assistant.exchange.mt5ConnectionFailed')
|
||||
}
|
||||
this.$message.error(this.testResult.message)
|
||||
} finally {
|
||||
this.testing = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Crypto exchanges: validate api_key/secret_key fields
|
||||
const fieldsToValidate = ['exchange_id', 'api_key', 'secret_key']
|
||||
if (this.needsPassphrase) {
|
||||
@@ -2808,6 +3025,14 @@ export default {
|
||||
ibkr_port: values.ibkr_port || 7497,
|
||||
ibkr_client_id: values.ibkr_client_id || 1,
|
||||
ibkr_account: values.ibkr_account || ''
|
||||
} : this.isMT5Market ? {
|
||||
// MT5/Forex broker configuration
|
||||
exchange_id: values.forex_broker_id || this.currentBrokerId || 'mt5',
|
||||
// MT5 specific fields
|
||||
mt5_server: values.mt5_server || '',
|
||||
mt5_login: values.mt5_login || '',
|
||||
mt5_password: values.mt5_password || '',
|
||||
mt5_terminal_path: values.mt5_terminal_path || ''
|
||||
} : {
|
||||
// Crypto exchange configuration
|
||||
exchange_id: values.exchange_id,
|
||||
@@ -2882,8 +3107,8 @@ export default {
|
||||
const totalCreated = res.data?.total_created || this.selectedSymbols.length
|
||||
this.$message.success(this.$t('trading-assistant.messages.batchCreateSuccess', { count: totalCreated }))
|
||||
}
|
||||
// Save credential to vault (crypto exchanges only, IBKR doesn't need this)
|
||||
if (isLive && values.save_credential && !this.isIBKRMarket) {
|
||||
// Save credential to vault (crypto exchanges only, IBKR/MT5 don't need this)
|
||||
if (isLive && values.save_credential && !this.isIBKRMarket && !this.isMT5Market) {
|
||||
try {
|
||||
await createExchangeCredential({
|
||||
user_id: 1,
|
||||
|
||||
Reference in New Issue
Block a user