| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685 |
- import { Config } from '../../config/simpleEnv.js'
- import { httpClient } from '../../utils/httpClient.js'
- import { logger } from '../../utils/logger.js'
- import { PacificaProxyClient } from '../../exchanges/pacifica/PacificaProxyClient.js'
- /**
- * 同一平台对冲管理器
- * 支持在同一交易所内使用不同账户进行对冲操作,通过代理实现网络隔离
- */
- export class SamePlatformHedgingManager {
- constructor(exchange, riskLimits) {
- this.exchange = exchange
- this.accounts = new Map()
- this.hedgePairs = new Map()
- this.clients = new Map()
- // 初始化风险限制
- this.riskLimits = {
- maxPositionSize: riskLimits?.maxPositionSize || 1.0,
- maxTotalExposure: riskLimits?.maxTotalExposure || 5.0,
- maxAccountBalance: riskLimits?.maxAccountBalance || 10000,
- minAccountBalance: riskLimits?.minAccountBalance || 100,
- maxDailyTrades: riskLimits?.maxDailyTrades || 100,
- maxSlippage: riskLimits?.maxSlippage || 0.02,
- emergencyStopLoss: riskLimits?.emergencyStopLoss || 0.05,
- enabled: riskLimits?.enabled ?? true,
- }
- }
- /**
- * 添加对冲账户
- */
- addAccount(accountId, config) {
- const account = {
- id: accountId,
- exchange: this.exchange,
- config,
- positions: new Map(),
- lastUpdate: 0,
- proxyConfig: this.getProxyForAccount(accountId),
- }
- this.accounts.set(accountId, account)
- // 创建对应的交易客户端
- this.createTradingClient(accountId, config)
- logger.info(`添加${this.exchange}对冲账户`, {
- accountId,
- hasProxy: !!account.proxyConfig,
- exchange: this.exchange,
- })
- }
- /**
- * 创建对冲对
- */
- createHedgePair(pairId, longAccountId, shortAccountId, symbol, targetRatio = 1.0) {
- if (!this.accounts.has(longAccountId) || !this.accounts.has(shortAccountId)) {
- throw new Error(`账户不存在: ${longAccountId} 或 ${shortAccountId}`)
- }
- const pair = {
- id: pairId,
- longAccountId,
- shortAccountId,
- symbol,
- targetRatio,
- currentLongPosition: 0,
- currentShortPosition: 0,
- netExposure: 0,
- lastRebalance: 0,
- isActive: true,
- }
- this.hedgePairs.set(pairId, pair)
- logger.info(`创建${this.exchange}对冲对`, {
- pairId,
- longAccount: longAccountId,
- shortAccount: shortAccountId,
- symbol,
- targetRatio,
- })
- }
- /**
- * 创建交易客户端
- */
- createTradingClient(accountId, config) {
- if (this.exchange === 'pacifica') {
- const client = new PacificaProxyClient({
- account: config.account,
- privateKey: config.privateKey,
- agentWallet: config.agentWallet,
- agentPrivateKey: config.agentPrivateKey,
- })
- this.clients.set(accountId, client)
- } else if (this.exchange === 'aster') {
- // TODO: 实现Aster客户端
- logger.warn('Aster交易客户端暂未实现', { accountId })
- } else if (this.exchange === 'binance') {
- // TODO: 实现Binance客户端
- logger.warn('Binance交易客户端暂未实现', { accountId })
- }
- }
- /**
- * 获取交易客户端
- */
- getTradingClient(accountId) {
- const client = this.clients.get(accountId)
- if (!client) {
- throw new Error(`未找到账户${accountId}的交易客户端`)
- }
- return client
- }
- /**
- * 获取账户专用代理配置
- */
- getProxyForAccount(accountId) {
- // 支持为不同账户配置不同的代理会话
- // 这样可以模拟不同的网络来源,减少被识别为同一用户的风险
- if (!Config.proxy.isConfigured(this.exchange)) {
- return undefined
- }
- // 为每个账户生成唯一的会话标识
- const accountSpecificProxy = Config.proxy.getUrl(this.exchange)
- if (!accountSpecificProxy) return undefined
- // 如果配置了会话管理,为每个账户创建不同的会话
- if (this.exchange === 'aster' && Config.proxy.aster.sessionPrefix()) {
- // 为不同账户生成不同的会话后缀
- const accountSuffix = this.generateAccountSuffix(accountId)
- // 这里可以扩展为每个账户使用不同的会话配置
- return accountSpecificProxy // 暂时返回相同的URL,未来可以优化
- }
- return accountSpecificProxy
- }
- /**
- * 为账户生成唯一后缀
- */
- generateAccountSuffix(accountId) {
- // 基于账户ID生成稳定的后缀
- let hash = 0
- for (let i = 0; i < accountId.length; i++) {
- const char = accountId.charCodeAt(i)
- hash = (hash << 5) - hash + char
- hash = hash & hash // 转换为32位整数
- }
- return Math.abs(hash).toString(36).substring(0, 6)
- }
- /**
- * 更新账户仓位
- */
- async updateAccountPositions(accountId) {
- const account = this.accounts.get(accountId)
- if (!account) throw new Error(`账户不存在: ${accountId}`)
- try {
- // 使用账户专用代理请求仓位信息
- const positions = await this.fetchPositions(account)
- // 更新仓位缓存
- account.positions.clear()
- positions.forEach(pos => {
- account.positions.set(pos.symbol, pos)
- })
- account.lastUpdate = Date.now()
- logger.debug(`更新${this.exchange}账户仓位`, {
- accountId,
- positionCount: positions.length,
- proxy: account.proxyConfig ? '启用' : '禁用',
- })
- } catch (error) {
- logger.error(`更新账户仓位失败`, {
- exchange: this.exchange,
- accountId,
- error: error.message,
- })
- throw error
- }
- }
- /**
- * 获取仓位信息(根据交易所实现)
- */
- async fetchPositions(account) {
- const baseUrl = this.getExchangeApiUrl()
- const endpoint = this.getPositionsEndpoint()
- const response = await httpClient.get(`${baseUrl}${endpoint}`, {
- exchange: this.exchange,
- accountId: account.id,
- timeout: 10000,
- retries: 2,
- headers: this.buildAuthHeaders(account),
- })
- if (!response.ok) {
- throw new Error(`获取仓位失败: ${response.status}`)
- }
- return this.parsePositions(response.data)
- }
- /**
- * 获取交易所API基础URL
- */
- getExchangeApiUrl() {
- switch (this.exchange) {
- case 'aster':
- return Config.aster.httpBase
- case 'pacifica':
- return Config.pacifica.baseUrl
- case 'binance':
- return Config.binance.baseUrl
- default:
- throw new Error(`不支持的交易所: ${this.exchange}`)
- }
- }
- /**
- * 获取仓位查询端点
- */
- getPositionsEndpoint() {
- switch (this.exchange) {
- case 'aster':
- return '/api/v1/positions'
- case 'pacifica':
- return '/api/v1/positions'
- case 'binance':
- return '/fapi/v2/positionRisk'
- default:
- throw new Error(`不支持的交易所: ${this.exchange}`)
- }
- }
- /**
- * 构建认证头(根据交易所实现)
- */
- buildAuthHeaders(account) {
- // 这里需要根据具体交易所的认证方式实现
- // 暂时返回空对象,实际使用时需要具体实现
- return {}
- }
- /**
- * 解析仓位数据(根据交易所格式实现)
- */
- parsePositions(data) {
- // 这里需要根据具体交易所的返回格式实现
- // 暂时返回空数组,实际使用时需要具体实现
- return []
- }
- /**
- * 计算对冲对的净敞口
- */
- calculateNetExposure(pairId) {
- const pair = this.hedgePairs.get(pairId)
- if (!pair) throw new Error(`对冲对不存在: ${pairId}`)
- const longAccount = this.accounts.get(pair.longAccountId)
- const shortAccount = this.accounts.get(pair.shortAccountId)
- if (!longAccount || !shortAccount) {
- throw new Error(`对冲对账户不完整: ${pairId}`)
- }
- const longPosition = longAccount.positions.get(pair.symbol)
- const shortPosition = shortAccount.positions.get(pair.symbol)
- const longSize = longPosition?.size || 0
- const shortSize = shortPosition?.size || 0
- pair.currentLongPosition = longSize
- pair.currentShortPosition = shortSize
- pair.netExposure = longSize + shortSize // 注意:空头为负数
- return pair.netExposure
- }
- /**
- * 执行对冲再平衡
- */
- async rebalanceHedgePair(pairId, tolerance = 0.01) {
- const pair = this.hedgePairs.get(pairId)
- if (!pair || !pair.isActive) return false
- // 更新仓位信息
- await Promise.all([
- this.updateAccountPositions(pair.longAccountId),
- this.updateAccountPositions(pair.shortAccountId),
- ])
- const netExposure = this.calculateNetExposure(pairId)
- // 获取账户状态进行资金利用率分析
- const longPosition = this.accountPositions.get(pair.longAccountId)?.get(pair.symbol) || 0
- const shortPosition = this.accountPositions.get(pair.shortAccountId)?.get(pair.symbol) || 0
- // 多维度平衡检查
- const exposureDiff = Math.abs(netExposure)
- const positionImbalance = Math.abs(Math.abs(longPosition) - Math.abs(shortPosition))
- console.log(`🔍 [平衡检查] 净敞口: ${netExposure.toFixed(4)}, 仓位不平衡: ${positionImbalance.toFixed(4)}`)
- // 更严格的平衡标准
- const needsRebalance = exposureDiff > tolerance || positionImbalance > 0.002
- if (!needsRebalance) {
- logger.debug(`对冲对无需再平衡`, {
- pairId,
- netExposure,
- positionImbalance,
- tolerance,
- exchange: this.exchange,
- })
- return false
- }
- console.log(
- `🚨 [需要平衡] 净敞口: ${exposureDiff.toFixed(4)} > ${tolerance} 或仓位不平衡: ${positionImbalance.toFixed(
- 4,
- )} > 0.002`,
- )
- // 执行再平衡交易
- try {
- const success = await this.executeRebalanceTrades(pair, netExposure)
- if (success) {
- pair.lastRebalance = Date.now()
- logger.info(`对冲再平衡成功`, {
- pairId,
- netExposure,
- exchange: this.exchange,
- })
- }
- return success
- } catch (error) {
- logger.error(`对冲再平衡失败`, {
- pairId,
- error: error.message,
- exchange: this.exchange,
- })
- return false
- }
- }
- /**
- * 执行再平衡交易 - 改进版:真正的双向平衡
- */
- async executeRebalanceTrades(pair, netExposure) {
- console.log(`🔄 [再平衡] 开始双向平衡,当前净敞口: ${netExposure.toFixed(4)} BTC`)
- // 获取两个账户的当前状态
- const longPosition = this.accountPositions.get(pair.longAccountId)?.get(pair.symbol) || 0
- const shortPosition = this.accountPositions.get(pair.shortAccountId)?.get(pair.symbol) || 0
- console.log(`📊 [再平衡] 当前仓位 - 多头账户: ${longPosition.toFixed(4)}, 空头账户: ${shortPosition.toFixed(4)}`)
- // 计算理想的平衡状态:两个账户仓位大小相等,方向相反
- const totalAbsPosition = Math.abs(longPosition) + Math.abs(shortPosition)
- const idealPositionSize = totalAbsPosition / 2
- if (idealPositionSize < 0.0005) {
- console.log(`ℹ️ [再平衡] 仓位太小,无需平衡`)
- return false
- }
- // 计算每个账户需要的调整量
- const longAdjustment = idealPositionSize - Math.abs(longPosition)
- const shortAdjustment = idealPositionSize - Math.abs(shortPosition)
- console.log(`🎯 [再平衡] 理想仓位: ${idealPositionSize.toFixed(4)} BTC`)
- console.log(`📈 [再平衡] 调整量 - 多头: ${longAdjustment.toFixed(4)}, 空头: ${shortAdjustment.toFixed(4)}`)
- const minOrderSize = 0.0003 // 降低最小订单量
- const orders = []
- // 生成双向调整订单
- if (Math.abs(longAdjustment) >= minOrderSize) {
- if (longAdjustment > 0) {
- // 多头账户需要增加多头仓位
- orders.push({
- accountId: pair.longAccountId,
- side: 'bid',
- amount: Math.abs(longAdjustment),
- reason: '再平衡-增加多头仓位',
- })
- } else {
- // 多头账户需要减少仓位
- orders.push({
- accountId: pair.longAccountId,
- side: 'ask',
- amount: Math.abs(longAdjustment),
- reason: '再平衡-减少多头仓位',
- })
- }
- }
- if (Math.abs(shortAdjustment) >= minOrderSize) {
- if (shortAdjustment > 0) {
- // 空头账户需要增加空头仓位
- orders.push({
- accountId: pair.shortAccountId,
- side: 'ask',
- amount: Math.abs(shortAdjustment),
- reason: '再平衡-增加空头仓位',
- })
- } else {
- // 空头账户需要减少仓位
- orders.push({
- accountId: pair.shortAccountId,
- side: 'bid',
- amount: Math.abs(shortAdjustment),
- reason: '再平衡-减少空头仓位',
- })
- }
- }
- if (orders.length === 0) {
- console.log(`ℹ️ [再平衡] 调整量都太小,跳过再平衡`)
- return false
- }
- // 执行双向调整订单
- try {
- console.log(`🔄 [再平衡] 执行${orders.length}个调整订单`)
- for (const order of orders) {
- console.log(`📝 [再平衡] ${order.accountId}: ${order.side} ${order.amount.toFixed(4)} BTC - ${order.reason}`)
- await this.executeHedgeOrder(order.accountId, pair.symbol, order.amount, order.side)
- // 短暂延迟避免过快执行
- await new Promise(resolve => setTimeout(resolve, 200))
- }
- console.log(`✅ [再平衡] 双向平衡完成`)
- return true
- } catch (error) {
- logger.error(`执行双向再平衡交易失败`, {
- pair: pair.id,
- netExposure,
- orders,
- error: error.message,
- exchange: this.exchange,
- })
- return false
- }
- }
- /**
- * 风险检查
- */
- async checkRiskLimits(accountId, symbol, amount, side) {
- if (!this.riskLimits.enabled) {
- return { allowed: true }
- }
- try {
- // 检查订单量限制
- if (amount > this.riskLimits.maxPositionSize) {
- return {
- allowed: false,
- reason: `订单量${amount}超过单个仓位最大限制${this.riskLimits.maxPositionSize}`,
- }
- }
- // 检查账户余额
- const client = this.getTradingClient(accountId)
- const balances = await client.getBalances()
- if (balances && Array.isArray(balances)) {
- const usdBalance = balances.find(b => b.asset === 'USDT' || b.asset === 'USD' || b.asset === 'USDC')
- if (usdBalance) {
- const balance = parseFloat(usdBalance.free || usdBalance.available || '0')
- if (balance < this.riskLimits.minAccountBalance) {
- return {
- allowed: false,
- reason: `账户余额${balance}低于最小要求${this.riskLimits.minAccountBalance}`,
- }
- }
- }
- }
- // 检查总敞口
- const totalExposure = await this.calculateTotalExposure(accountId)
- const newExposure = side === 'bid' ? totalExposure + amount : totalExposure - amount
- if (Math.abs(newExposure) > this.riskLimits.maxTotalExposure) {
- return {
- allowed: false,
- reason: `新的总敞口${Math.abs(newExposure)}将超过最大限制${this.riskLimits.maxTotalExposure}`,
- }
- }
- logger.info('风险检查通过', {
- accountId,
- symbol,
- amount,
- side,
- currentExposure: totalExposure,
- newExposure,
- })
- return { allowed: true }
- } catch (error) {
- logger.error('风险检查失败', {
- accountId,
- symbol,
- error: error.message,
- })
- return {
- allowed: false,
- reason: `风险检查失败: ${error.message}`,
- }
- }
- }
- /**
- * 计算账户总敞口
- */
- async calculateTotalExposure(accountId) {
- try {
- const client = this.getTradingClient(accountId)
- const positions = await client.getPositions()
- let totalExposure = 0
- if (positions && Array.isArray(positions)) {
- for (const position of positions) {
- const size = parseFloat(position.size || position.amount || '0')
- totalExposure += Math.abs(size)
- }
- }
- return totalExposure
- } catch (error) {
- logger.error('计算总敞口失败', {
- accountId,
- error: error.message,
- })
- return 0
- }
- }
- /**
- * 紧急止损检查
- */
- async checkEmergencyStopLoss(accountId) {
- try {
- const client = this.getTradingClient(accountId)
- const positions = await client.getPositions()
- if (!positions || !Array.isArray(positions)) {
- return false
- }
- for (const position of positions) {
- const pnlPercent = parseFloat(position.pnlPercent || position.unrealizedPnlPercent || '0')
- if (Math.abs(pnlPercent) > this.riskLimits.emergencyStopLoss) {
- logger.warn('触发紧急止损', {
- accountId,
- symbol: position.symbol,
- pnlPercent,
- emergencyStopLoss: this.riskLimits.emergencyStopLoss,
- })
- // 执行紧急平仓
- await this.emergencyClosePosition(accountId, position)
- return true
- }
- }
- return false
- } catch (error) {
- logger.error('紧急止损检查失败', {
- accountId,
- error: error.message,
- })
- return false
- }
- }
- /**
- * 紧急平仓
- */
- async emergencyClosePosition(accountId, position) {
- try {
- const client = this.getTradingClient(accountId)
- const account = this.accounts.get(accountId)
- if (!account) {
- throw new Error(`账户${accountId}不存在`)
- }
- const size = Math.abs(parseFloat(position.size || position.amount || '0'))
- const isLong = parseFloat(position.size || position.amount || '0') > 0
- const side = isLong ? 'ask' : 'bid' // 平多头用卖单,平空头用买单
- const payload = {
- account: account.config.account || '',
- symbol: position.symbol,
- amount: size.toString(),
- side,
- reduceOnly: true,
- slippagePercent: '1.0', // 紧急情况允许更大滑点
- }
- logger.warn('执行紧急平仓', {
- accountId,
- symbol: position.symbol,
- size,
- side,
- isLong,
- })
- await client.createMarketOrder(payload)
- logger.info('紧急平仓执行成功', {
- accountId,
- symbol: position.symbol,
- size,
- side,
- })
- } catch (error) {
- logger.error('紧急平仓失败', {
- accountId,
- position: position.symbol,
- error: error.message,
- })
- }
- }
- /**
- * 执行对冲订单
- */
- async executeHedgeOrder(accountId, symbol, amount, side) {
- const client = this.getTradingClient(accountId)
- const account = this.accounts.get(accountId)
- if (!account) {
- throw new Error(`账户${accountId}不存在`)
- }
- // 执行风险检查
- const riskCheck = await this.checkRiskLimits(accountId, symbol, amount, side)
- if (!riskCheck.allowed) {
- throw new Error(`风险控制拒绝: ${riskCheck.reason}`)
- }
- // 执行紧急止损检查
- const emergencyTriggered = await this.checkEmergencyStopLoss(accountId)
- if (emergencyTriggered) {
- throw new Error(`触发紧急止损,暂停交易`)
- }
- if (this.exchange === 'pacifica') {
- const payload = {
- account: account.config.account || '',
- symbol,
- amount: amount.toString(),
- side,
- reduceOnly: false,
- slippagePercent: '0.5', // 0.5%滑点
- }
- logger.info(`执行Pacifica对冲订单`, {
- accountId,
- symbol,
- amount,
- side,
- proxy: !!account.proxyConfig,
- })
- // 使用市价单确保成交
- const result = await client.createMarketOrder(payload)
- logger.info(`Pacifica对冲订单执行成功`, {
- accountId,
- symbol,
- side,
- orderId: result.orderId || result.order_id,
- success: result.success,
- })
- return result
- } else {
- throw new Error(`交易所${this.exchange}的下单功能暂未实现`)
- }
- }
- /**
- * 批量执行对冲交易
- */
- async executeBatchHedge(orders) {
- const results = []
- logger.info(`开始批量对冲执行`, {
- orderCount: orders.length,
- exchange: this.exchange,
- })
- for (const order of orders) {
- try {
- const result = await this.executeHedgeOrder(order.accountId, order.symbol, order.amount, order.side)
- results.push({
- success: true,
- orderId: result.orderId || result.order_id,
- })
- } catch (error) {
- logger.error(`批量对冲订单执行失败`, {
- accountId: order.accountId,
- symbol: order.symbol,
- error: error.message,
- })
- results.push({
- success: false,
- error: error.message,
- })
- }
- }
- logger.info(`批量对冲执行完成`, {
- total: orders.length,
- successful: results.filter(r => r.success).length,
- failed: results.filter(r => !r.success).length,
- exchange: this.exchange,
- })
- return results
- }
- /**
- * 获取所有对冲对状态
- */
- getHedgePairStatuses() {
- const statuses = []
- for (const [pairId, pair] of this.hedgePairs) {
- const netExposure = this.calculateNetExposure(pairId)
- statuses.push({
- pairId,
- symbol: pair.symbol,
- longAccount: pair.longAccountId,
- shortAccount: pair.shortAccountId,
- longPosition: pair.currentLongPosition,
- shortPosition: pair.currentShortPosition,
- netExposure,
- targetRatio: pair.targetRatio,
- isActive: pair.isActive,
- lastRebalance: pair.lastRebalance,
- exchange: this.exchange,
- })
- }
- return statuses
- }
- /**
- * 停用对冲对
- */
- deactivateHedgePair(pairId) {
- const pair = this.hedgePairs.get(pairId)
- if (pair) {
- pair.isActive = false
- logger.info(`停用对冲对`, { pairId, exchange: this.exchange })
- }
- }
- /**
- * 激活对冲对
- */
- activateHedgePair(pairId) {
- const pair = this.hedgePairs.get(pairId)
- if (pair) {
- pair.isActive = true
- logger.info(`激活对冲对`, { pairId, exchange: this.exchange })
- }
- }
- }
|