- 新增文档模板和导航结构 - 实现服务器基础API路由和控制器 - 添加扩展插件配置和前端框架 - 引入多租户和权限管理模块 - 集成日志和数据库配置 - 添加核心业务模型和类型定义
202 lines
6.2 KiB
TypeScript
202 lines
6.2 KiB
TypeScript
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<number> {
|
|
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<string | null> {
|
|
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<void> {
|
|
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<string> {
|
|
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.');
|
|
}
|
|
}
|