import db from '../config/database'; import { logger } from '../utils/logger'; export interface FXRate { pair: string; // e.g., 'USD/CNY' rate: number; volatility: number; // 波动率 lastUpdated: Date; } export interface HedgingOrder { id: string; tenantId: string; pair: string; amount: number; strikeRate: number; expiryDate: Date; status: 'PENDING' | 'EXECUTED' | 'EXPIRED' | 'CANCELLED'; } /** * [BIZ_FIN_03] 汇率自动对冲执行 (Auto FX Hedging Execution) * [BIZ_FIN_12] 多币种汇率避险与结算中枢 (FX Engine) * @description 基于波动率自动计算安全边际,并模拟生成对冲头寸 */ export class FXHedgingService { private static readonly RATES_TABLE = 'cf_fx_rates'; private static readonly HEDGING_TABLE = 'cf_fx_hedging_orders'; /** * 初始化数据库表 */ static async initTable() { const hasRatesTable = await db.schema.hasTable(this.RATES_TABLE); if (!hasRatesTable) { console.log(`📦 Creating ${this.RATES_TABLE} table...`); await db.schema.createTable(this.RATES_TABLE, (table) => { table.string('pair', 16).primary(); table.decimal('rate', 10, 4); table.float('volatility').defaultTo(0.01); table.timestamp('last_updated').defaultTo(db.fn.now()); }); } const hasHedgingTable = await db.schema.hasTable(this.HEDGING_TABLE); if (!hasHedgingTable) { console.log(`📦 Creating ${this.HEDGING_TABLE} table...`); await db.schema.createTable(this.HEDGING_TABLE, (table) => { table.string('id', 64).primary(); table.string('tenant_id', 64).index(); table.string('pair', 16); table.decimal('amount', 15, 2); table.decimal('strike_rate', 10, 4); table.timestamp('expiry_date'); table.string('status', 16).defaultTo('PENDING'); table.string('trace_id', 64); table.timestamps(true, true); }); } } /** * 获取当前动态安全边际 (BIZ_FIN_03) */ static async getDynamicSafetyMargin(pair: string = 'USD/CNY'): Promise { try { const rateInfo = await db(this.RATES_TABLE).where({ pair }).first(); if (!rateInfo) return 0.03; const baseMargin = 0.02; const volatilityFactor = rateInfo.volatility * 0.5; return baseMargin + volatilityFactor; } catch (err: any) { return 0.03; } } /** * 自动生成对冲头寸 (BIZ_FIN_03) */ static async autoHedge(params: { tenantId: string; pair: string; amount: number; traceId: string; }): Promise { const { tenantId, pair, amount, traceId } = params; if (amount < 1000) return null; const rateInfo = await db(this.RATES_TABLE).where({ pair }).first(); const strikeRate = rateInfo ? rateInfo.rate : 7.25; const hedgingId = `HEDGE-${Date.now()}`; await db(this.HEDGING_TABLE).insert({ id: hedgingId, tenant_id: tenantId, pair, amount, strike_rate: strikeRate, expiry_date: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), status: 'PENDING', trace_id: traceId, created_at: new Date(), updated_at: new Date() }); return hedgingId; } /** * 更新汇率快照 */ static async updateRates(pair: string, rate: number, volatility: number): Promise { const existing = await db(this.RATES_TABLE).where({ pair }).first(); if (existing) { await db(this.RATES_TABLE).where({ pair }).update({ rate, volatility, last_updated: new Date() }); } else { await db(this.RATES_TABLE).insert({ pair, rate, volatility, last_updated: new Date() }); } } /** * [BIZ_FIN_12] 锁汇申请 */ static async lockRate(tenantId: string, pair: string, amount: number, durationDays: number = 7): Promise { const rateInfo = await db(this.RATES_TABLE).where({ pair }).first(); if (!rateInfo) throw new Error(`Rate info not found for ${pair}`); const lockId = `LOCK-${Date.now()}`; await db(this.HEDGING_TABLE).insert({ id: lockId, tenant_id: tenantId, pair, amount, strike_rate: rateInfo.rate, expiry_date: new Date(Date.now() + durationDays * 24 * 60 * 60 * 1000), status: 'PENDING', trace_id: `lock-${Date.now()}`, created_at: new Date(), updated_at: new Date() }); return lockId; } /** * [BIZ_FIN_12] 计算汇率损益 */ static async calculateGainLoss(tenantId: string): Promise<{ totalGainLoss: number; details: any[] }> { const executedOrders = await db(this.HEDGING_TABLE).where({ tenant_id: tenantId, status: 'EXECUTED' }); let totalGainLoss = 0; const details = []; for (const order of executedOrders) { const currentRateInfo = await db(this.RATES_TABLE).where({ pair: order.pair }).first(); if (currentRateInfo) { const gainLoss = (order.strike_rate - currentRateInfo.rate) * order.amount; totalGainLoss += gainLoss; details.push({ orderId: order.id, pair: order.pair, strikeRate: order.strike_rate, currentRate: currentRateInfo.rate, gainLoss: Number(gainLoss.toFixed(2)) }); } } return { totalGainLoss: Number(totalGainLoss.toFixed(2)), details }; } /** * [BIZ_FIN_12] 自动执行到期的锁汇订单 */ static async executeMaturedOrders(tenantId: string) { logger.info(`[FX] Checking matured orders for Tenant: ${tenantId}`); const matured = await db(this.HEDGING_TABLE) .where({ tenant_id: tenantId, status: 'PENDING' }) .andWhere('expiry_date', '<', new Date()); for (const order of matured) { await db(this.HEDGING_TABLE).where({ id: order.id }).update({ status: 'EXECUTED', updated_at: new Date() }); logger.info(`[FX] Executed matured order ${order.id}`); } } /** * [BIZ_FIN_12] 实时汇率同步 (模拟多源获取) */ static async syncRates() { const pairs = ['USD/CNY', 'EUR/USD', 'GBP/USD']; for (const pair of pairs) { const baseRate = pair === 'USD/CNY' ? 7.2 : 1.1; const rate = baseRate + (Math.random() - 0.5) * 0.1; const volatility = 0.01 + Math.random() * 0.05; await this.updateRates(pair, rate, volatility); } logger.info('[FX] Market rates synchronized.'); } }