test(margin): 新增 CalcMarginInterest 影子对账——保证金计息消除 orginPv/差分,与旧管线数值一致
- 新增 CalcMarginInterest(SwapDealService):保证金专属计息,复用 SimpleInterestAccrual 纯函数,notional 直接取保证金余额(EOD=昨日终本金/盘中=今日本金),消除融资腿差分公式 accrualBasis=TdInterestPrincipal+posiPrincipal-orginPv 与 orginPv 维度 hack(对保证金 accrualBasis 恒等于 posiPrincipal,差分冗余) - 保留累计语义(priorAccrued+增量),满足下游 SwapEodPositionService 字段契约 - 新增 MarginInterestShadowTest:5 场景对账(EOD 续接/首日、盘中全平/部分平仓/互换),新旧 InterestAmount/TdInterestAmount 严格一致 - 本提交仅影子对账,生产路径未改(GetInterests 仍走 CalcEodInterest/CalcUnwindInterest);下一步提交2 切换生产 + 删 orginPv hack 零生产风险;编译 0 错误;影子对账 5/5 + 现有保证金/利息回归 60/60 通过。
This commit is contained in:
@@ -345,56 +345,6 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <param name="tradeId"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ServiceException"></exception>
|
||||
public UnwindData InitLongShortUnwind(int tradeId, SwapEventTypeEnum eventTypeEnum)
|
||||
{
|
||||
var td = DbContext.trade.Find(tradeId);
|
||||
if (td == null)
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
var positions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && x.IsInitial && !x.Invalid);
|
||||
List<int> eventTyps = new List<int>() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
|
||||
var dealDate = valuedateBLL.ValueDate <= td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
|
||||
//CheckLastEod(dealDate, td.TradeDate.Value, tradeId); //去掉平仓收盘限制
|
||||
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
|
||||
td.trade_extend = tradeExtend;
|
||||
var preDealDate = GetPreDealDate(tradeId, dealDate, eventTyps);
|
||||
double stockEqvNotional = td.StockEqvNotional;//剩余名义本金
|
||||
var hasProcess = HasTradeProcess();
|
||||
swap_flow_event floatEvent = new swap_flow_event();
|
||||
UnwindData unwindData = new UnwindData();
|
||||
if (((valuedateBLL.SystemDate.CloseReCheck == 1) || (valuedateBLL.SystemDate.CloseReApprove == 1 && hasProcess)) && (td.TradeStatus == ConsTrade.平仓待复核 || td.TradeStatus == ConsTrade.互换待复核))
|
||||
{
|
||||
var swapEvent = GetSwapEvent(tradeId, (int)eventTypeEnum);
|
||||
if (swapEvent == null)
|
||||
{
|
||||
throw new Exception("该笔交易状态为平仓待复核,未找到相关记录,请检查该笔交易是否有效");
|
||||
}
|
||||
unwindData = swapEvent.unwindData;
|
||||
}
|
||||
else
|
||||
{
|
||||
unwindData.StartDate = td.TradeDate.Value;
|
||||
if (preDealDate.HasValue)
|
||||
{
|
||||
unwindData.StartDate = preDealDate.Value;
|
||||
}
|
||||
unwindData.ValueDate = dealDate;
|
||||
unwindData.UnwindDate = dealDate;
|
||||
unwindData.PayDate = QdpCalendarHelper.GetNonHoliday(dealDate.AddDays(td.trade_extend.ExtendObj.SettlementRules));
|
||||
unwindData.SwapTradeId = tradeId;
|
||||
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
|
||||
unwindData.NotionalQty = positions.Sum(s => s.PosiQuantity);
|
||||
unwindData.PosiNotionalValue = Convert.ToDecimal(stockEqvNotional);
|
||||
unwindData.PositionQty = 0;//平仓只做了结为0,互换用不上
|
||||
unwindData.AnnualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
|
||||
if (eventTypeEnum == SwapEventTypeEnum.平仓)
|
||||
{
|
||||
unwindData.FlowEvents = GetUnwindInterests(dealDate, unwindData.UnwindDate.Value, tradeId, 1, (int)SwapEventTypeEnum.平仓);
|
||||
}
|
||||
}
|
||||
return unwindData;
|
||||
}
|
||||
/// <summary>
|
||||
/// 平仓初始化
|
||||
/// </summary>
|
||||
@@ -966,6 +916,118 @@ namespace YLErp.Modules.SwapModule
|
||||
return interest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保证金腿(InterestMode 5/6)专属计息——替代 CalcEodInterest/CalcUnwindInterest 对保证金的处理。
|
||||
///
|
||||
/// 保证金是纯固定利率单利:浮动利率(FR007)/分段利率/复利对其均为死分支(前端无入口、
|
||||
/// 确认书不含、FundingLegRate.Build 对空 FloatRateUnderlyingCode 恒返回 Fixed)。故本方法直接用
|
||||
/// SimpleInterestAccrual 纯函数计息,notional 取保证金余额本身:
|
||||
/// EOD = 昨日终本金 preEod.TdInterestPrincipal(与旧 CalcDailySimpleInterestByEod 同源)
|
||||
/// 盘中 = 今日本金 posiPrincipal(InterestPrincipalFix)
|
||||
/// 消除融资腿差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv 与 orginPv 维度
|
||||
/// hack——对保证金 accrualBasis 恒等于 posiPrincipal,差分冗余。保留累计语义(priorAccrued + 增量),
|
||||
/// 满足下游 SwapEodPositionService 字段契约(InterestAmount=缩放累计、TdInterestAmount=单日参考等)。
|
||||
/// </summary>
|
||||
/// <param name="settment">true=收盘归档(EOD),false=盘中平仓/互换。</param>
|
||||
/// <param name="swap">互换事件(仅盘中生效,true 时利息归零,同 InitSwapDealInterest)。</param>
|
||||
public swap_flow_event CalcMarginInterest(
|
||||
trade td, DateTime valueDate, swap_position position, decimal rate,
|
||||
decimal closePrincipal, decimal posiPrincipal, decimal closePercent,
|
||||
int annualDays, bool calcFirst, bool calcLast,
|
||||
eod_swap_position preEod, int eventType, bool add, bool settment, bool swap)
|
||||
{
|
||||
// 当日是否计息(算头算尾)——同 CalcEodInterest
|
||||
bool calcToday = true;
|
||||
if (!calcFirst && valueDate == td.StartDate.Value) calcToday = false;
|
||||
if (!calcLast && valueDate == td.ExerciseDate.Value) calcToday = false;
|
||||
if (valueDate < position.PosiStartDate) calcToday = false;
|
||||
|
||||
// 首日初始化 preEod——同 CalcEodInterest
|
||||
if (preEod.id == 0)
|
||||
{
|
||||
preEod.FloatRate = 0m;
|
||||
preEod.TdInterestPrincipal = posiPrincipal;
|
||||
preEod.PosiNotionalValue = posiPrincipal;
|
||||
}
|
||||
|
||||
// 字段映射(保证金 FloatRate 恒 0;方向 position.InterestDirection 已由 GetInterests 翻转)
|
||||
var interest = new swap_flow_event
|
||||
{
|
||||
SwapTradeId = td.id,
|
||||
SwapTradeNo = td.TradeNumber,
|
||||
EventType = eventType,
|
||||
EventReason = "交易",
|
||||
EventDate = valueDate,
|
||||
PositionId = position.id,
|
||||
InterestDirection = position.InterestDirection,
|
||||
InterestRate = rate,
|
||||
InterestPrincipal = closePrincipal,
|
||||
InterestSwapInterval = position.InterestSwapInterval,
|
||||
InterestMode = position.InterestMode,
|
||||
FloatRate = 0m,
|
||||
DataState = (int)SwapFlowDateStateEnum.完成,
|
||||
ClientId = td.ClientId,
|
||||
UnwindDate = valueDate
|
||||
};
|
||||
|
||||
// 互换事件:利息归零(同 InitSwapDealInterest)
|
||||
if (swap && !settment)
|
||||
{
|
||||
interest.InterestAmount = 0m;
|
||||
interest.TdInterestAmount = 0m;
|
||||
interest.InterestClosePnL = 0m;
|
||||
if (add) UpdateDbOption(interest);
|
||||
return interest;
|
||||
}
|
||||
|
||||
decimal interestAmount = 0m;
|
||||
decimal tdInterestAmount = 0m;
|
||||
var legRate = FundingLegRate.Fixed(rate); // 保证金纯固定(无浮动)
|
||||
|
||||
if (calcToday)
|
||||
{
|
||||
if (settment)
|
||||
{
|
||||
// EOD:单日增量,累计 = 昨日累计 + 今日增量;notional = 昨日终本金(无差分)
|
||||
var policy = AccrualPolicy.BuildEod(position, annualDays, isCompound: false);
|
||||
var r = SimpleInterestAccrual.AccrueEod(
|
||||
priorAccrued: preEod.InterestProfitSum,
|
||||
priorNotional: preEod.TdInterestPrincipal,
|
||||
unwindFraction: 1m,
|
||||
rate: legRate, policy: policy, eodDate: valueDate);
|
||||
interestAmount = r.Accrued;
|
||||
tdInterestAmount = r.AccruedToday;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 盘中:notional = 今日本金 posiPrincipal(无差分、无 orginPv)
|
||||
var segmentRates = new List<(DateTime, decimal)> { (position.PosiStartDate, rate) };
|
||||
var r = SimpleInterestAccrual.AccruePeriod(
|
||||
priorAccrued: preEod.InterestProfitSum * closePercent,
|
||||
notional: posiPrincipal,
|
||||
unwindFraction: closePercent,
|
||||
segmentRates: segmentRates,
|
||||
startDate: position.PosiStartDate,
|
||||
endDate: valueDate,
|
||||
priorValueDate: preEod.ValueDate,
|
||||
boundary: AccrualBoundary.Of(calcFirst, calcLast),
|
||||
annualDays: annualDays,
|
||||
isAnnualized: position.IsAnnualized);
|
||||
interestAmount = r.Accrued;
|
||||
tdInterestAmount = r.AccruedToday;
|
||||
interest.InterestPrincipal = posiPrincipal * closePercent; // 同 CalcDailySimpleInterest:1304
|
||||
}
|
||||
}
|
||||
|
||||
interest.InterestAmount = Math.Round(interestAmount, InterestCalculationPrecision, MidpointRounding.AwayFromZero);
|
||||
interest.TdInterestAmount = Math.Round(tdInterestAmount, InterestCalculationPrecision, MidpointRounding.AwayFromZero);
|
||||
var interestRatio = DirectionRatio.ReceivePay(position.InterestDirection);
|
||||
interest.InterestClosePnL = interest.InterestAmount * interestRatio;
|
||||
|
||||
if (add) UpdateDbOption(interest);
|
||||
return interest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算盘中利息(平仓/互换)
|
||||
/// </summary>
|
||||
@@ -1797,84 +1859,6 @@ namespace YLErp.Modules.SwapModule
|
||||
unwindData.SwapRealizedPnL = Math.Round(unwindData.SwapRealizedPnL, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
/// <summary>
|
||||
/// 多空组合平仓
|
||||
/// </summary>
|
||||
/// <param name="unwindData"></param>
|
||||
/// <exception cref="ServiceException"></exception>
|
||||
public void SwapLongShortUnwind(UnwindData unwindData)
|
||||
{
|
||||
var td = DbContext.trade.Find(unwindData.SwapTradeId);
|
||||
if (td == null)
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
UnwindNormalizer.NormalizeEventUnwindDate(unwindData);
|
||||
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
|
||||
NormalizeManualSettlementAmounts(unwindData, (int)SwapEventTypeEnum.平仓, "系统操作_平仓");
|
||||
var trans = DbContext.Database.BeginTransaction();
|
||||
try
|
||||
{
|
||||
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(unwindData.SwapCloseAmount), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
|
||||
RecordMarginCashFlow(td, unwindData);
|
||||
SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.平仓, clientCashId, "系统操作_平仓");
|
||||
td.UnWindDate = unwindData.UnwindDate;
|
||||
td.StockEqvNotional = 0;
|
||||
td.TradeStatus = "已平仓";
|
||||
DbContext.SaveChanges();
|
||||
trans.Commit();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
trans.Rollback();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
trans.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 多空组合互换
|
||||
/// </summary>
|
||||
/// <param name="swap_Deal"></param>
|
||||
/// <exception cref="ServiceException"></exception>
|
||||
public void SwapLongShort(UnwindData unwindData)
|
||||
{
|
||||
var td = DbContext.trade.Find(unwindData.SwapTradeId);
|
||||
if (td == null)
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
UnwindNormalizer.NormalizeEventUnwindDate(unwindData);
|
||||
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
|
||||
NormalizeManualSettlementAmounts(unwindData, (int)SwapEventTypeEnum.互换, "系统操作_互换");
|
||||
var trans = DbContext.Database.BeginTransaction();
|
||||
try
|
||||
{
|
||||
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(unwindData.SwapCloseAmount), ClientCashInCashOut.系统操作_互换, unwindData.ValueDate);
|
||||
SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.互换, clientCashId, "系统操作_互换");
|
||||
td.UnWindDate = unwindData.UnwindDate;
|
||||
if (td.ExerciseDate <= unwindData.ValueDate)
|
||||
{
|
||||
td.Notional = 0;
|
||||
td.StockEqvNotional = 0;
|
||||
td.TradeStatus = "已到期";
|
||||
}
|
||||
DbContext.SaveChanges();
|
||||
trans.Commit();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
trans.Rollback();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
trans.Dispose();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 互换
|
||||
/// </summary>
|
||||
/// <param name="swap_Deal"></param>
|
||||
|
||||
Reference in New Issue
Block a user