basisManager.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. import { EventEmitter } from 'events'
  2. import { logger } from '../../utils/logger.js'
  3. /**
  4. * 基差管理器 - 监控和管理现货与期货之间的基差风险
  5. */
  6. export class BasisManager extends EventEmitter {
  7. private basisHistory: Map<string, BasisDataPoint[]> = new Map()
  8. private alertConfig: BasisAlertConfig
  9. private isMonitoring: boolean = false
  10. private monitoringInterval?: NodeJS.Timeout
  11. constructor(alertConfig?: Partial<BasisAlertConfig>) {
  12. super()
  13. this.alertConfig = {
  14. maxBasisDeviation: alertConfig?.maxBasisDeviation || 200, // $200 基差预警阈值
  15. basisVolatilityThreshold: alertConfig?.basisVolatilityThreshold || 0.05, // 5% 波动阈值
  16. historyRetentionHours: alertConfig?.historyRetentionHours || 24, // 保留24小时历史
  17. monitoringIntervalMs: alertConfig?.monitoringIntervalMs || 5000, // 5秒监控间隔
  18. enabledSymbols: alertConfig?.enabledSymbols || ['BTC-USD', 'ETH-USD'],
  19. }
  20. }
  21. /**
  22. * 更新基差数据
  23. */
  24. updateBasis(symbol: string, spotPrice: number, futuresPrice: number, timestamp?: number): BasisSnapshot {
  25. const now = timestamp || Date.now()
  26. const basis = futuresPrice - spotPrice
  27. const basisPercent = (basis / spotPrice) * 100
  28. const snapshot: BasisSnapshot = {
  29. symbol,
  30. spotPrice,
  31. futuresPrice,
  32. basis,
  33. basisPercent,
  34. timestamp: now,
  35. }
  36. // 更新历史数据
  37. this.updateBasisHistory(symbol, snapshot)
  38. // 计算基差统计
  39. const stats = this.calculateBasisStats(symbol)
  40. // 检查风险预警
  41. const riskLevel = this.assessBasisRisk(symbol, snapshot, stats)
  42. // 发出事件
  43. this.emit('basisUpdate', { snapshot, stats, riskLevel })
  44. if (riskLevel !== 'normal') {
  45. this.emit('basisAlert', {
  46. symbol,
  47. riskLevel,
  48. snapshot,
  49. stats,
  50. message: this.generateAlertMessage(symbol, riskLevel, snapshot, stats),
  51. })
  52. logger.warn('基差风险预警', {
  53. symbol,
  54. basis,
  55. basisPercent: basisPercent.toFixed(4),
  56. riskLevel,
  57. spotPrice,
  58. futuresPrice,
  59. })
  60. }
  61. return snapshot
  62. }
  63. /**
  64. * 批量更新多个交易对的基差
  65. */
  66. updateMultipleBasis(updates: Array<{ symbol: string; spotPrice: number; futuresPrice: number }>): BasisSnapshot[] {
  67. return updates.map(update => this.updateBasis(update.symbol, update.spotPrice, update.futuresPrice))
  68. }
  69. /**
  70. * 获取基差快照
  71. */
  72. getBasisSnapshot(symbol: string): BasisSnapshot | null {
  73. const history = this.basisHistory.get(symbol)
  74. if (!history || history.length === 0) return null
  75. const latest = history[history.length - 1]
  76. return {
  77. symbol,
  78. spotPrice: latest.spotPrice,
  79. futuresPrice: latest.futuresPrice,
  80. basis: latest.basis,
  81. basisPercent: latest.basisPercent,
  82. timestamp: latest.timestamp,
  83. }
  84. }
  85. /**
  86. * 获取基差统计信息
  87. */
  88. getBasisStats(symbol: string): BasisStats | null {
  89. return this.calculateBasisStats(symbol)
  90. }
  91. /**
  92. * 评估基差风险等级
  93. */
  94. assessBasisRisk(symbol: string, snapshot: BasisSnapshot, stats?: BasisStats): BasisRiskLevel {
  95. const currentStats = stats || this.calculateBasisStats(symbol)
  96. if (!currentStats) return 'normal'
  97. const { basis, basisPercent } = snapshot
  98. const { mean, standardDeviation, volatility } = currentStats
  99. // 检查绝对基差是否超过阈值
  100. if (Math.abs(basis) > this.alertConfig.maxBasisDeviation) {
  101. return 'high'
  102. }
  103. // 检查基差是否偏离历史均值过多(2个标准差)
  104. const deviationFromMean = Math.abs(basis - mean)
  105. if (deviationFromMean > 2 * standardDeviation) {
  106. return 'medium'
  107. }
  108. // 检查基差波动率是否过高
  109. if (volatility > this.alertConfig.basisVolatilityThreshold) {
  110. return 'medium'
  111. }
  112. return 'normal'
  113. }
  114. /**
  115. * 计算基差调整建议
  116. */
  117. calculateAdjustmentSuggestion(symbol: string, targetNetExposure: number = 0): BasisAdjustmentSuggestion | null {
  118. const snapshot = this.getBasisSnapshot(symbol)
  119. const stats = this.getBasisStats(symbol)
  120. if (!snapshot || !stats) return null
  121. // 根据基差趋势和风险等级计算调整建议
  122. const riskLevel = this.assessBasisRisk(symbol, snapshot, stats)
  123. const { basis, basisPercent } = snapshot
  124. const { trend, volatility } = stats
  125. let suggestion: BasisAdjustmentSuggestion = {
  126. symbol,
  127. adjustmentType: 'none',
  128. suggestedAction: 'hold',
  129. confidence: 0.5,
  130. reasoning: '基差风险正常',
  131. urgency: 'low',
  132. }
  133. if (riskLevel === 'high') {
  134. if (basis > 0 && trend === 'widening') {
  135. // 正基差扩大,建议减少期货多头或增加现货多头
  136. suggestion = {
  137. symbol,
  138. adjustmentType: 'reduce_futures_long',
  139. suggestedAction: 'reduce_position',
  140. confidence: 0.8,
  141. reasoning: `正基差过大(${basis.toFixed(2)})且趋于扩大,建议减少期货多头敞口`,
  142. urgency: 'high',
  143. targetAdjustmentPercent: Math.min(0.3, Math.abs(basisPercent) / 100), // 最多调整30%
  144. }
  145. } else if (basis < 0 && trend === 'widening') {
  146. // 负基差扩大,建议减少期货空头或增加现货空头
  147. suggestion = {
  148. symbol,
  149. adjustmentType: 'reduce_futures_short',
  150. suggestedAction: 'reduce_position',
  151. confidence: 0.8,
  152. reasoning: `负基差过大(${basis.toFixed(2)})且趋于扩大,建议减少期货空头敞口`,
  153. urgency: 'high',
  154. targetAdjustmentPercent: Math.min(0.3, Math.abs(basisPercent) / 100),
  155. }
  156. }
  157. } else if (riskLevel === 'medium') {
  158. suggestion = {
  159. symbol,
  160. adjustmentType: 'monitor_closely',
  161. suggestedAction: 'monitor',
  162. confidence: 0.6,
  163. reasoning: `基差风险中等,波动率${(volatility * 100).toFixed(2)}%,建议密切监控`,
  164. urgency: 'medium',
  165. }
  166. }
  167. return suggestion
  168. }
  169. /**
  170. * 开始监控基差
  171. */
  172. startMonitoring(): void {
  173. if (this.isMonitoring) return
  174. this.isMonitoring = true
  175. this.monitoringInterval = setInterval(() => {
  176. this.performRoutineChecks()
  177. }, this.alertConfig.monitoringIntervalMs)
  178. logger.info('基差监控已启动', {
  179. interval: this.alertConfig.monitoringIntervalMs,
  180. symbols: this.alertConfig.enabledSymbols,
  181. })
  182. this.emit('monitoringStarted')
  183. }
  184. /**
  185. * 停止监控基差
  186. */
  187. stopMonitoring(): void {
  188. if (!this.isMonitoring) return
  189. this.isMonitoring = false
  190. if (this.monitoringInterval) {
  191. clearInterval(this.monitoringInterval)
  192. this.monitoringInterval = undefined
  193. }
  194. logger.info('基差监控已停止')
  195. this.emit('monitoringStopped')
  196. }
  197. /**
  198. * 执行例行检查
  199. */
  200. private performRoutineChecks(): void {
  201. for (const symbol of this.alertConfig.enabledSymbols) {
  202. const snapshot = this.getBasisSnapshot(symbol)
  203. if (!snapshot) continue
  204. const stats = this.getBasisStats(symbol)
  205. if (!stats) continue
  206. const riskLevel = this.assessBasisRisk(symbol, snapshot, stats)
  207. if (riskLevel !== 'normal') {
  208. const suggestion = this.calculateAdjustmentSuggestion(symbol)
  209. this.emit('routineAlert', {
  210. symbol,
  211. riskLevel,
  212. snapshot,
  213. stats,
  214. suggestion,
  215. })
  216. }
  217. }
  218. // 清理过期数据
  219. this.cleanupOldData()
  220. }
  221. /**
  222. * 更新基差历史数据
  223. */
  224. private updateBasisHistory(symbol: string, snapshot: BasisSnapshot): void {
  225. if (!this.basisHistory.has(symbol)) {
  226. this.basisHistory.set(symbol, [])
  227. }
  228. const history = this.basisHistory.get(symbol)!
  229. const dataPoint: BasisDataPoint = {
  230. spotPrice: snapshot.spotPrice,
  231. futuresPrice: snapshot.futuresPrice,
  232. basis: snapshot.basis,
  233. basisPercent: snapshot.basisPercent,
  234. timestamp: snapshot.timestamp,
  235. }
  236. history.push(dataPoint)
  237. // 限制历史数据长度
  238. const maxPoints = (this.alertConfig.historyRetentionHours * 3600 * 1000) / this.alertConfig.monitoringIntervalMs
  239. if (history.length > maxPoints) {
  240. history.splice(0, history.length - maxPoints)
  241. }
  242. }
  243. /**
  244. * 计算基差统计信息
  245. */
  246. private calculateBasisStats(symbol: string): BasisStats | null {
  247. const history = this.basisHistory.get(symbol)
  248. if (!history || history.length < 2) return null
  249. const basisValues = history.map(h => h.basis)
  250. const recentValues = history.slice(-10).map(h => h.basis) // 最近10个点
  251. // 计算均值
  252. const mean = basisValues.reduce((sum, val) => sum + val, 0) / basisValues.length
  253. // 计算标准差
  254. const variance = basisValues.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / basisValues.length
  255. const standardDeviation = Math.sqrt(variance)
  256. // 计算波动率(基于最近数据)
  257. let volatility = 0
  258. if (recentValues.length >= 2) {
  259. const returns = []
  260. for (let i = 1; i < recentValues.length; i++) {
  261. const previousValue = recentValues[i - 1]
  262. if (previousValue !== 0) {
  263. returns.push((recentValues[i] - previousValue) / Math.abs(previousValue))
  264. }
  265. }
  266. if (returns.length > 0) {
  267. const meanReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length
  268. const returnVariance = returns.reduce((sum, ret) => sum + Math.pow(ret - meanReturn, 2), 0) / returns.length
  269. volatility = Math.sqrt(returnVariance)
  270. }
  271. }
  272. // 判断趋势
  273. let trend: 'widening' | 'narrowing' | 'stable' = 'stable'
  274. if (recentValues.length >= 5) {
  275. const firstHalf = recentValues.slice(0, Math.floor(recentValues.length / 2))
  276. const secondHalf = recentValues.slice(Math.floor(recentValues.length / 2))
  277. const firstMean = firstHalf.reduce((sum, val) => sum + Math.abs(val), 0) / firstHalf.length
  278. const secondMean = secondHalf.reduce((sum, val) => sum + Math.abs(val), 0) / secondHalf.length
  279. const trendThreshold = standardDeviation * 0.5
  280. if (secondMean - firstMean > trendThreshold) {
  281. trend = 'widening'
  282. } else if (firstMean - secondMean > trendThreshold) {
  283. trend = 'narrowing'
  284. }
  285. }
  286. return {
  287. mean,
  288. standardDeviation,
  289. volatility,
  290. min: Math.min(...basisValues),
  291. max: Math.max(...basisValues),
  292. trend,
  293. dataPoints: basisValues.length,
  294. }
  295. }
  296. /**
  297. * 生成预警消息
  298. */
  299. private generateAlertMessage(
  300. symbol: string,
  301. riskLevel: BasisRiskLevel,
  302. snapshot: BasisSnapshot,
  303. stats: BasisStats,
  304. ): string {
  305. const { basis, basisPercent } = snapshot
  306. const { trend, volatility } = stats
  307. switch (riskLevel) {
  308. case 'high':
  309. return `${symbol} 基差风险高:${basis > 0 ? '正' : '负'}基差 ${Math.abs(basis).toFixed(2)} (${Math.abs(
  310. basisPercent,
  311. ).toFixed(2)}%),趋势${trend === 'widening' ? '扩大' : trend === 'narrowing' ? '收窄' : '稳定'}`
  312. case 'medium':
  313. return `${symbol} 基差风险中等:基差 ${basis.toFixed(2)},波动率 ${(volatility * 100).toFixed(2)}%`
  314. default:
  315. return `${symbol} 基差正常`
  316. }
  317. }
  318. /**
  319. * 清理过期数据
  320. */
  321. private cleanupOldData(): void {
  322. const cutoffTime = Date.now() - this.alertConfig.historyRetentionHours * 3600 * 1000
  323. for (const [symbol, history] of this.basisHistory) {
  324. const validData = history.filter(point => point.timestamp > cutoffTime)
  325. this.basisHistory.set(symbol, validData)
  326. }
  327. }
  328. /**
  329. * 获取监控状态
  330. */
  331. getMonitoringStatus(): BasisMonitoringStatus {
  332. return {
  333. isActive: this.isMonitoring,
  334. monitoredSymbols: this.alertConfig.enabledSymbols,
  335. alertConfig: this.alertConfig,
  336. dataPointCounts: new Map(
  337. Array.from(this.basisHistory.entries()).map(([symbol, history]) => [symbol, history.length]),
  338. ),
  339. }
  340. }
  341. }
  342. // 类型定义
  343. export interface BasisSnapshot {
  344. symbol: string
  345. spotPrice: number
  346. futuresPrice: number
  347. basis: number // futuresPrice - spotPrice
  348. basisPercent: number // (basis / spotPrice) * 100
  349. timestamp: number
  350. }
  351. export interface BasisDataPoint {
  352. spotPrice: number
  353. futuresPrice: number
  354. basis: number
  355. basisPercent: number
  356. timestamp: number
  357. }
  358. export interface BasisStats {
  359. mean: number // 平均基差
  360. standardDeviation: number // 标准差
  361. volatility: number // 波动率
  362. min: number // 最小基差
  363. max: number // 最大基差
  364. trend: 'widening' | 'narrowing' | 'stable' // 趋势
  365. dataPoints: number // 数据点数量
  366. }
  367. export interface BasisAlertConfig {
  368. maxBasisDeviation: number // 最大基差偏差阈值 (USD)
  369. basisVolatilityThreshold: number // 基差波动率阈值
  370. historyRetentionHours: number // 历史数据保留小时数
  371. monitoringIntervalMs: number // 监控间隔毫秒
  372. enabledSymbols: string[] // 启用监控的交易对
  373. }
  374. export type BasisRiskLevel = 'normal' | 'medium' | 'high'
  375. export interface BasisAdjustmentSuggestion {
  376. symbol: string
  377. adjustmentType: 'none' | 'reduce_futures_long' | 'reduce_futures_short' | 'increase_spot_hedge' | 'monitor_closely'
  378. suggestedAction: 'hold' | 'reduce_position' | 'increase_hedge' | 'monitor'
  379. confidence: number // 0-1
  380. reasoning: string
  381. urgency: 'low' | 'medium' | 'high'
  382. targetAdjustmentPercent?: number // 建议调整比例
  383. }
  384. export interface BasisMonitoringStatus {
  385. isActive: boolean
  386. monitoredSymbols: string[]
  387. alertConfig: BasisAlertConfig
  388. dataPointCounts: Map<string, number>
  389. }
  390. export const basisManager = new BasisManager()