Merge branch 'glms/feature/1.4.2' into glms/feature/0812_zmr_divPower
# Conflicts: # YLErpDAL/Modules/EodModule/BondPaymentService.cs # YLErpDAL/Modules/SwapModule/SwapDealService.cs
This commit is contained in:
@@ -161,72 +161,13 @@ namespace YLErp.Modules.SwapModule
|
||||
: ConsGlobal.SwapDeliveryPriceRound;
|
||||
}
|
||||
|
||||
// 日终利息待实现需跨日累计,按表设计保留 12 位;已实现结算仍按金额两位处理。
|
||||
private const int EodInterestStoragePrecision = 12;
|
||||
|
||||
private static decimal RoundMoney(decimal value)
|
||||
{
|
||||
return Math.Round(value, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
private static decimal RoundEodInterest(decimal value)
|
||||
{
|
||||
return Math.Round(value, EodInterestStoragePrecision, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅在写入 eod_swap_position 前统一快照精度。
|
||||
/// 浮动腿收益最终以金额两位展示和存储;利息腿的待实现、计息基数及利率保留 12 位,
|
||||
/// 使部分结算后的尾差可继续参与后续计息。
|
||||
/// </summary>
|
||||
private static void NormalizeEodPositionForStorage(eod_swap_position position)
|
||||
{
|
||||
if (string.IsNullOrEmpty(position.UnderlyingCode))
|
||||
{
|
||||
// 利息腿没有标的代码:待实现字段保留高精度,已实现结算字段收敛到金额两位。
|
||||
position.InterestPrincipalFix = RoundEodInterest(position.InterestPrincipalFix);
|
||||
position.InterestRateDefault = RoundEodInterest(position.InterestRateDefault);
|
||||
position.InterestFeePending = RoundEodInterest(position.InterestFeePending);
|
||||
position.TdInterestPrincipal = RoundEodInterest(position.TdInterestPrincipal);
|
||||
position.TdInterestRate = RoundEodInterest(position.TdInterestRate);
|
||||
position.TdInterestIncome = RoundEodInterest(position.TdInterestIncome);
|
||||
position.TdInterestFee = RoundEodInterest(position.TdInterestFee);
|
||||
position.InterestIncomeSum = RoundEodInterest(position.InterestIncomeSum);
|
||||
position.InterestFeeSum = RoundEodInterest(position.InterestFeeSum);
|
||||
position.InterestProfitSum = RoundEodInterest(position.InterestProfitSum);
|
||||
position.FloatRate = RoundEodInterest(position.FloatRate);
|
||||
position.SwapPositionValue = RoundEodInterest(position.SwapPositionValue);
|
||||
position.TdCloseInterest = RoundMoney(position.TdCloseInterest);
|
||||
position.TdCloseInterestFee = RoundMoney(position.TdCloseInterestFee);
|
||||
position.RealizedInterest = RoundMoney(position.RealizedInterest);
|
||||
position.RealizedInterestFee = RoundMoney(position.RealizedInterestFee);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 浮动腿有标的代码:其损益作为金额结果落库,统一按两位四舍五入。
|
||||
position.TdPosiDividend = RoundMoney(position.TdPosiDividend);
|
||||
position.PosiMtmPnL = RoundMoney(position.PosiMtmPnL);
|
||||
position.PosiDividendSum = RoundMoney(position.PosiDividendSum);
|
||||
position.PosiFeePending = RoundMoney(position.PosiFeePending);
|
||||
position.PosiProfitSum = RoundMoney(position.PosiProfitSum);
|
||||
position.TdCloseMtmPnl = RoundMoney(position.TdCloseMtmPnl);
|
||||
position.TdCloseDividend = RoundMoney(position.TdCloseDividend);
|
||||
position.TdCloseFee = RoundMoney(position.TdCloseFee);
|
||||
position.RealizedMtmPnL = RoundMoney(position.RealizedMtmPnL);
|
||||
position.RealizedDividend = RoundMoney(position.RealizedDividend);
|
||||
position.RealizedFee = RoundMoney(position.RealizedFee);
|
||||
position.SwapPositionValue = RoundMoney(position.SwapPositionValue);
|
||||
}
|
||||
position.RealizedPnl = RoundMoney(position.RealizedPnl);
|
||||
}
|
||||
|
||||
#region 可测试化接缝(Seams)——override 这些虚方法可在测试中替换 DB/外部调用,生产代码行为不变
|
||||
|
||||
/// <summary>持久化 eod 持仓记录(生产: DbContext.Add;测试: 收集到列表)</summary>
|
||||
protected virtual void PersistEodSwapPosition(eod_swap_position position)
|
||||
{
|
||||
// 所有新增或更新的日终持仓都经过此入口,避免不同日终分支出现精度差异。
|
||||
NormalizeEodPositionForStorage(position);
|
||||
EodPnlCalculator.NormalizeEodPositionForStorage(position);
|
||||
var storagePriceRound = GetStorageDeliveryPriceRound(position.UnderlyingInstrumentType, position.UnderlyingCode);
|
||||
position.PosiGrossPrice = Math.Round(position.PosiGrossPrice, storagePriceRound, MidpointRounding.AwayFromZero);
|
||||
position.UnderlyingPrice = Math.Round(position.UnderlyingPrice, storagePriceRound, MidpointRounding.AwayFromZero);
|
||||
@@ -254,26 +195,49 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算利息腿利息明细(生产: new SwapDealService(this).GetInterests;测试: 用StubSwapDealService内存算)
|
||||
/// 计算利息腿利息明细(生产: new SwapDealService(this).GetInterests;测试: 用StubSwapDealService内存算)。
|
||||
/// 参数与 SwapDealService.GetInterests 完全一致,保证行为不变。
|
||||
/// (needPrice/grossPrice 死参数已随 2026-08 收口删除,两侧同步。)
|
||||
/// </summary>
|
||||
protected virtual List<swap_flow_event> CalcSwapInterests(
|
||||
trade td, trade_extend tradeExtend,
|
||||
DateTime valueDate, DateTime unwindDate,
|
||||
List<eod_swap_position> eodPositions, List<swap_position> positions,
|
||||
decimal posiNotionalValue, decimal posiLongNotionalValue, decimal posiShortNotionalValue,
|
||||
decimal posiNotionalValue,
|
||||
decimal closePosiNotionalValue, decimal closePrecent,
|
||||
int eventType, bool tdClose, bool needPrice,
|
||||
decimal grossPrice, decimal orginPv,
|
||||
int eventType, bool tdClose,
|
||||
decimal orginPv,
|
||||
bool add = false, bool settment = true, bool newCalcLast = false,
|
||||
List<swap_flow_event> closeList = null)
|
||||
{
|
||||
return new SwapDealService(this).GetInterests(td, tradeExtend, valueDate, unwindDate,
|
||||
eodPositions, positions, posiNotionalValue, posiLongNotionalValue, posiShortNotionalValue,
|
||||
closePosiNotionalValue, closePrecent, eventType, tdClose, needPrice,
|
||||
grossPrice, orginPv, add, settment, newCalcLast, closeList);
|
||||
eodPositions, positions, posiNotionalValue,
|
||||
closePosiNotionalValue, closePrecent, eventType, tdClose,
|
||||
orginPv, add, settment, newCalcLast, closeList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【EOD 当日有平仓后的收盘结息】显式入口——原 SaveAutoEodWithCloseInterestPosition 直调
|
||||
/// CalcSwapInterests(settment:false) 的具名封装(2026-08 显式化重构)。
|
||||
|
||||
/// 语义契约见 InterestCalcRequest.EodPostCloseSettle 工厂注释(平仓后剩余 + 实际平掉额 + 恒1全额结息,
|
||||
/// 触发 GetInterests 内 mode2/mode9 本金修正)。计息走 CalcUnwindInterest 全区间重放。
|
||||
/// 默认实现仍经 CalcSwapInterests 转发,保持既有测试替身对该虚接缝的拦截不变。
|
||||
/// </summary>
|
||||
protected virtual List<swap_flow_event> CalcEodPostCloseSettleInterests(InterestCalcRequest req)
|
||||
=> CalcSwapInterests(req.Td, req.TradeExtend, req.ValueDate, req.UnwindDate, req.EodPositions, req.Positions,
|
||||
req.PosiNotionalValue,
|
||||
req.ClosePosiNotionalValue, req.ClosePercent, req.EventType, req.TdClose,
|
||||
req.OrginPv, req.Add, settment: false, req.NewCalcLast, req.CloseList);
|
||||
|
||||
/// <summary>
|
||||
/// 持仓延续腿重置日再定盘(EQD-6968 口径自洽化接缝)。
|
||||
/// 生产:Fr007IndexFixer.GetFixingOrThrow——缺价抛异常,与 ByEod 重置日再定盘/EodCheckSettlePrice
|
||||
/// 同口径(EOD 时点当日 FR007 已由收盘前检查把关);测试:override 注入受控定盘。
|
||||
/// </summary>
|
||||
protected virtual decimal ResolveOngoingResetFixing(swap_position position, DateTime valueDate)
|
||||
=> Fr007IndexFixer.Instance.GetFixingOrThrow(valueDate, position.interest_rule, position.FloatRateUnderlyingCode);
|
||||
|
||||
// FindTrade 已上提到基类 SwapTradeBaseService(三子类实现一致,消除重复)
|
||||
|
||||
/// <summary>查找交易扩展(生产: DbContext.trade_extend;测试: 内存字典)</summary>
|
||||
@@ -291,7 +255,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <summary>查找交易持仓(生产: DbContext.swap_position.Where;测试: 内存列表)</summary>
|
||||
protected virtual List<swap_position> FindSwapPositions(int swapTradeId)
|
||||
{
|
||||
return DbContext.swap_position.Where(x => x.SwapTradeId == swapTradeId && !x.Invalid).ToList();
|
||||
return DbContext.swap_position.ActiveByTrade(swapTradeId).ToList();
|
||||
}
|
||||
|
||||
/// <summary>查找框架合约日终汇总(生产: DbContext.eod_swap.FirstOrDefault;测试: 内存字典)</summary>
|
||||
@@ -306,6 +270,12 @@ namespace YLErp.Modules.SwapModule
|
||||
return new SwapEventService(this).AddSwapEventDate(tradeDate, swapTradeId, eventType, data, clientCashId, save, reason);
|
||||
}
|
||||
|
||||
/// <summary>持久化互换流水事件(生产: DbContext.swap_flow_event.Add;测试: 收集到列表)</summary>
|
||||
protected virtual void PersistFlowEvent(swap_flow_event flowEvent)
|
||||
{
|
||||
DbContext.swap_flow_event.Add(flowEvent);
|
||||
}
|
||||
|
||||
/// <summary>在事务中执行(生产: BeginTransaction/Commit/Rollback;测试: 直接执行不包事务)</summary>
|
||||
protected virtual void ExecuteInTransaction(Action action)
|
||||
{
|
||||
@@ -677,17 +647,17 @@ namespace YLErp.Modules.SwapModule
|
||||
var closePosiNotional = curEodPosis.Where(s => s.TdCloseQty > 0).Sum(s => s.TdCloseQty * s.ContractSize * s.PosiGrossPrice);
|
||||
var grossPrice = curEodPosis.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice ?? 0;
|
||||
//处理利息腿
|
||||
DealInterests(interestList, eodPositions, todyEodPositions, settleDate, td, flowEvents, autoInterests, lastEodSwap, posiLongNotional, posiShortNotional, closePosiNotional, grossPrice, orginPv);
|
||||
//获取自动互换的 interval 信息,用于确定结算日期
|
||||
IntervalModel autoInterval = null;
|
||||
DealInterests(interestList, eodPositions, todyEodPositions, settleDate, td, flowEvents, autoInterests, lastEodSwap, posiLongNotional + posiShortNotional, closePosiNotional, grossPrice, orginPv);
|
||||
//获取自动互换的观察日信息,用于确定结算日期
|
||||
IntervalModel observationInterval = null;
|
||||
foreach (var interest in interestList)
|
||||
{
|
||||
autoInterval = interest.SwapIntervalList.FirstOrDefault(x => x.Date == settleDate && x.Settlement == 1);
|
||||
if (autoInterval != null)
|
||||
observationInterval = InterestEodScenarioDispatch.FindObservationInterval(interest, settleDate);
|
||||
if (observationInterval != null)
|
||||
break;
|
||||
}
|
||||
// 自动互换(仅利息/预付金,不含分红)
|
||||
DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional, autoInterval);
|
||||
DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional, observationInterval);
|
||||
// 分红独立处理:只要当天有债券需要分红,则生成分红自动互换,与利息互换无关
|
||||
DealDividends(curEodPosis, td, settleDate, tradeExtend);
|
||||
//多空组合判断是否已到到期日且无持仓信息
|
||||
@@ -1342,8 +1312,7 @@ namespace YLErp.Modules.SwapModule
|
||||
List<swap_flow_event> flowEvents,
|
||||
List<swap_flow_event> autoInterests,
|
||||
eod_swap lastEodSwap,
|
||||
decimal posiLongNational,
|
||||
decimal posiShortNational,
|
||||
decimal posiTotalNotional,
|
||||
decimal closeNational,
|
||||
decimal grossPrice,
|
||||
decimal orginPv)
|
||||
@@ -1354,7 +1323,7 @@ namespace YLErp.Modules.SwapModule
|
||||
Log.Info($"[DealInterests] 参数验证 - settleDate: {settleDate:yyyy-MM-dd}, td.id: {td?.id}, td.TradeNumber: {td?.TradeNumber}");
|
||||
Log.Info($"[DealInterests] 参数验证 - interestList.Count: {interestList?.Count ?? 0}, eodPositions.Count: {eodPositions?.Count ?? 0}, todyEodPositions.Count: {todyEodPositions?.Count ?? 0}");
|
||||
Log.Info($"[DealInterests] 参数验证 - flowEvents.Count: {flowEvents?.Count ?? 0}, autoInterests.Count: {autoInterests?.Count ?? 0}");
|
||||
Log.Info($"[DealInterests] 参数验证 - posiLongNational: {posiLongNational}, posiShortNational: {posiShortNational}, closeNational: {closeNational}, grossPrice: {grossPrice}, orginPv: {orginPv}");
|
||||
Log.Info($"[DealInterests] 参数验证 - posiTotalNotional: {posiTotalNotional}, closeNational: {closeNational}, grossPrice: {grossPrice}, orginPv: {orginPv}");
|
||||
|
||||
// 验证关键参数
|
||||
if (td == null)
|
||||
@@ -1391,18 +1360,20 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
var eodPosition = eodPositions.FirstOrDefault(x => x.PositionId == interest.id);//上一日日终利息信息 可能不存在
|
||||
var tdEodPosition = todyEodPositions.FirstOrDefault(x => x.PositionId == interest.id);//当前结算日日终利息信息
|
||||
var insterval = interest.SwapIntervalList.FirstOrDefault(x => x.Date == settleDate && x.Settlement == 1);//自动互换观察日信息
|
||||
var observationInterval = InterestEodScenarioDispatch.FindObservationInterval(interest, settleDate);//自动互换观察日信息
|
||||
List<swap_flow_event> dealInterests = new List<swap_flow_event>();
|
||||
dealInterests.AddRange(flowEvents);
|
||||
var dealInterest = dealInterests.FirstOrDefault(n => n.PositionId == interest.id);//当日是否做过互换或平仓
|
||||
var swapEvents = flowEvents.Where(x => (x.EventType == (int)SwapEventTypeEnum.互换 || x.EventType == (int)SwapEventTypeEnum.平仓) && x.PositionId == interest.id).ToList();
|
||||
//如果当日有互换/当日有平仓 不再重新生成或更新
|
||||
Log.Info($"insterval is {insterval},hasSwap is {hasSwap},hasClose is {hasClose}");
|
||||
if (insterval != null && !hasSwap)
|
||||
Log.Info($"observationInterval is {observationInterval},hasSwap is {hasSwap},hasClose is {hasClose}");
|
||||
// 分派优先级与粒度说明见 ResolveInterestScenario;8 组合表驱动覆盖见 InterestEodScenarioDispatchTest。
|
||||
// 仅观察日两个分支把返回值收进 autoInterests(→资金记录)——分派错序=静默少结。
|
||||
if (observationInterval != null && !hasSwap)
|
||||
{
|
||||
if (!hasClose)//当日无平仓
|
||||
{
|
||||
var _autoInterests = SaveAutoEodInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, insterval, lastEodSwap, posiLongNational, posiShortNational, grossPrice, orginPv);
|
||||
var _autoInterests = SaveAutoEodInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, observationInterval, lastEodSwap, posiTotalNotional, grossPrice, orginPv);
|
||||
if (_autoInterests.Count > 0)
|
||||
{
|
||||
autoInterests.AddRange(_autoInterests);
|
||||
@@ -1410,7 +1381,7 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
else
|
||||
{
|
||||
var _autoInterests = SaveAutoEodWithCloseInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, insterval, posiLongNational, posiShortNational, swapEvents, closeNational, true, grossPrice, orginPv);
|
||||
var _autoInterests = SaveAutoEodWithCloseInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, observationInterval, posiTotalNotional, swapEvents, closeNational, autoSwap: true, grossPrice, orginPv);
|
||||
if (_autoInterests.Count > 0)
|
||||
{
|
||||
autoInterests.AddRange(_autoInterests);
|
||||
@@ -1423,14 +1394,15 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
else if (hasClose)
|
||||
{
|
||||
SaveAutoEodWithCloseInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, insterval, posiLongNational, posiShortNational, swapEvents, closeNational, false, grossPrice, orginPv);
|
||||
SaveAutoEodWithCloseInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, observationInterval, posiTotalNotional, swapEvents, closeNational, autoSwap: false, grossPrice, orginPv);
|
||||
}
|
||||
else//无自动互换、互换/平仓,复制上一日终信息,并计算当日新增利息
|
||||
{
|
||||
SaveEodInterestPositionCopy(eodPosition, tdEodPosition, settleDate, td, interest, lastEodSwap, true, posiLongNational, posiShortNational, grossPrice, orginPv);
|
||||
SaveEodInterestPositionCopy(eodPosition, tdEodPosition, settleDate, td, interest, lastEodSwap, true, posiTotalNotional, grossPrice, orginPv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理浮动腿归档
|
||||
/// </summary>
|
||||
@@ -1517,7 +1489,7 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
autoInterests.ForEach(x => x.PayDate = settleDate);
|
||||
|
||||
var premiumModes = new List<int>() { (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 };
|
||||
var premiumModes = MarginModes.ForLinq;
|
||||
var premiumInterests = autoInterests.Where(x => premiumModes.Contains(x.InterestMode)).ToList();
|
||||
var interestLegs = autoInterests.Where(x => !premiumModes.Contains(x.InterestMode)).ToList();
|
||||
|
||||
@@ -1664,24 +1636,26 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
unwindData.ClientCashIds = clientCashIds;
|
||||
string data = JsonConvert.SerializeObject(unwindData);
|
||||
var swapEvent = new SwapEventService(this).AddSwapEventDate(unwindData.ValueDate, unwindData.SwapTradeId, (int)SwapEventTypeEnum.自动互换, data, clientCashId, true, "系统操作-自动互换");//将互换总额存入事件
|
||||
// 走虚方法 AddSwapEvent(与 ComposePage:800 一致),让测试可 override 捕获事件;
|
||||
// 默认实现仍是 new SwapEventService(this).AddSwapEventDate,生产行为不变。
|
||||
var swapEvent = AddSwapEvent(unwindData.ValueDate, unwindData.SwapTradeId, (int)SwapEventTypeEnum.自动互换, data, clientCashId, true, "系统操作-自动互换");//将互换总额存入事件
|
||||
if (flowEvents!=null)
|
||||
{
|
||||
flowEvents.ForEach(x =>
|
||||
{
|
||||
x.EventId = swapEvent.id;
|
||||
DbContext.swap_flow_event.Add(x);
|
||||
PersistFlowEvent(x);
|
||||
});
|
||||
UpdateInitalPostion(flowEvents, td.id);
|
||||
}
|
||||
|
||||
|
||||
// 保存分红事件
|
||||
if (dividendEvents != null)
|
||||
{
|
||||
dividendEvents.ForEach(x =>
|
||||
{
|
||||
x.EventId = swapEvent.id;
|
||||
DbContext.swap_flow_event.Add(x);
|
||||
PersistFlowEvent(x);
|
||||
});
|
||||
UpdateInitalPostion(dividendEvents, td.id);
|
||||
}
|
||||
@@ -1881,6 +1855,46 @@ namespace YLErp.Modules.SwapModule
|
||||
trans?.Dispose();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 利息腿字段拷贝(SaveEodInterestPosition / SaveAutoEodInterestPosition / SaveAutoEodWithCloseInterestPosition 共用)。
|
||||
/// FloatRate 来源随场景不同(手工互换=当日流水;自动互换/平仓=计息结果),由调用方算好传入,勿在本方法内统一。
|
||||
/// 场景差异字段(PosiStatus / InterestFeePending / TdInterestPrincipal / TdInterestRate)留在各调用点。
|
||||
/// </summary>
|
||||
private static void CopyInterestLegFields(eod_swap_position newEodPayPosition, swap_position position, decimal floatRate)
|
||||
{
|
||||
newEodPayPosition.InterestDirection = position.InterestDirection;
|
||||
newEodPayPosition.InterestMode = position.InterestMode;
|
||||
newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
|
||||
newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
|
||||
newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
|
||||
newEodPayPosition.IsAnnualized = position.IsAnnualized;
|
||||
newEodPayPosition.HappenDate = position.HappenDate;
|
||||
newEodPayPosition.Currency = position.Currency;
|
||||
newEodPayPosition.InterestType = position.InterestType;
|
||||
newEodPayPosition.interest_rest_days = position.interest_rest_days;
|
||||
newEodPayPosition.interest_rule = position.interest_rule;
|
||||
newEodPayPosition.FloatRate = floatRate;
|
||||
newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 利息腿日终滚存收尾(四个 Save* 共用):RollRealized 滚累计已实现 → SetFixedLegRealizedPnl → 汇率 → TdCurrency。
|
||||
/// RealizedInterest 只增不回滚:上日累计已实现 + 当日结息按方向后的金额。
|
||||
/// interestDirection 是 RateType 的方向来源——三个方法取 position.InterestDirection,
|
||||
/// SaveEodInterestPositionCopy 取 eodPayPosition.InterestDirection(现状差异,勿统一)。
|
||||
/// PersistEodSwapPosition 与各自日志留在调用点(持久化边界 + 日志顺序各不相同)。
|
||||
/// </summary>
|
||||
private void FinalizeInterestEodRoll(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, int ratio, trade td, DateTime valueDate, int interestDirection)
|
||||
{
|
||||
var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
|
||||
newEodPayPosition.RealizedInterest = rolled.Interest;
|
||||
newEodPayPosition.RealizedInterestFee = rolled.Fee;
|
||||
SetFixedLegRealizedPnl(newEodPayPosition);
|
||||
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
|
||||
DirectionRatio.RateType(interestDirection));
|
||||
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 产生互换用
|
||||
/// </summary>
|
||||
@@ -1924,20 +1938,8 @@ namespace YLErp.Modules.SwapModule
|
||||
UpdateDbOption(newEodPayPosition);
|
||||
newEodPayPosition.PosiStatus = 0;
|
||||
newEodPayPosition.Invalid = false;
|
||||
//持仓内容-利息腿
|
||||
newEodPayPosition.InterestDirection = position.InterestDirection;
|
||||
newEodPayPosition.InterestMode = position.InterestMode;
|
||||
newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
|
||||
newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
|
||||
newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
|
||||
newEodPayPosition.IsAnnualized = position.IsAnnualized;
|
||||
newEodPayPosition.HappenDate = position.HappenDate;
|
||||
newEodPayPosition.Currency = position.Currency;
|
||||
newEodPayPosition.InterestType = position.InterestType;
|
||||
newEodPayPosition.interest_rest_days = position.interest_rest_days;
|
||||
newEodPayPosition.interest_rule = position.interest_rule;
|
||||
newEodPayPosition.FloatRate = flowEvents.FirstOrDefault()?.FloatRate ?? 0;
|
||||
newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
|
||||
//持仓内容-利息腿(FloatRate 取当日互换/平仓流水)
|
||||
CopyInterestLegFields(newEodPayPosition, position, flowEvents.FirstOrDefault()?.FloatRate ?? 0);
|
||||
newEodPayPosition.InterestFeePending = 0;
|
||||
//利息端估值用信息
|
||||
newEodPayPosition.TdInterestPrincipal = flowEvents.FirstOrDefault()?.InterestPrincipal ?? 0;
|
||||
@@ -1956,8 +1958,8 @@ namespace YLErp.Modules.SwapModule
|
||||
var interestFeeBeforeSettlement = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee;
|
||||
var isMaturityFinalSettlement = valueDate.Date >= td.ExerciseDate.Value.Date
|
||||
&& flowEvents.Any()
|
||||
&& RoundMoney(interestIncomeBeforeSettlement) == RoundMoney(newEodPayPosition.TdCloseInterest)
|
||||
&& RoundMoney(interestFeeBeforeSettlement) == RoundMoney(newEodPayPosition.TdCloseInterestFee);
|
||||
&& EodPnlCalculator.RoundMoney(interestIncomeBeforeSettlement) == EodPnlCalculator.RoundMoney(newEodPayPosition.TdCloseInterest)
|
||||
&& EodPnlCalculator.RoundMoney(interestFeeBeforeSettlement) == EodPnlCalculator.RoundMoney(newEodPayPosition.TdCloseInterestFee);
|
||||
|
||||
if (isMaturityFinalSettlement)
|
||||
{
|
||||
@@ -1968,21 +1970,15 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
else
|
||||
{
|
||||
newEodPayPosition.InterestIncomeSum = RoundEodInterest(interestIncomeBeforeSettlement - newEodPayPosition.TdCloseInterest);
|
||||
newEodPayPosition.InterestFeeSum = RoundEodInterest(interestFeeBeforeSettlement - newEodPayPosition.TdCloseInterestFee);
|
||||
newEodPayPosition.InterestIncomeSum = EodPnlCalculator.RoundEodInterest(interestIncomeBeforeSettlement - newEodPayPosition.TdCloseInterest);
|
||||
newEodPayPosition.InterestFeeSum = EodPnlCalculator.RoundEodInterest(interestFeeBeforeSettlement - newEodPayPosition.TdCloseInterestFee);
|
||||
}
|
||||
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
|
||||
//持仓价值
|
||||
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
|
||||
|
||||
//累计已实现
|
||||
var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
|
||||
newEodPayPosition.RealizedInterest = rolled.Interest;
|
||||
newEodPayPosition.RealizedInterestFee = rolled.Fee;
|
||||
SetFixedLegRealizedPnl(newEodPayPosition);
|
||||
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
|
||||
DirectionRatio.RateType(position.InterestDirection));
|
||||
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
|
||||
//累计已实现(滚存收尾见 FinalizeInterestEodRoll)
|
||||
FinalizeInterestEodRoll(eodPayPosition, newEodPayPosition, ratio, td, valueDate, position.InterestDirection);
|
||||
PersistEodSwapPosition(newEodPayPosition);
|
||||
}
|
||||
/// <summary>
|
||||
@@ -1997,7 +1993,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <param name="preDealDate">上一平仓/互换日期</param>
|
||||
/// <param name="closeAmount">当日平仓金额</param>
|
||||
/// <param name="lastEodSwap">上一日终框架合约估值</param>
|
||||
protected List<swap_flow_event> SaveAutoEodInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, eod_swap lastEodSwap, decimal posiLongNotional, decimal posiShortNational, decimal grossPrice, decimal orginPv)
|
||||
protected List<swap_flow_event> SaveAutoEodInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, eod_swap lastEodSwap, decimal posiTotalNotional, decimal grossPrice, decimal orginPv)
|
||||
{
|
||||
Log.Info($"[SaveAutoEodInterestPosition] 开始执行 - valueDate: {valueDate:yyyy-MM-dd}, td.id: {td?.id}, position.id: {position?.id}");
|
||||
|
||||
@@ -2036,7 +2032,7 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
|
||||
var tradeExtend = td.trade_extend.ExtendObj;
|
||||
decimal posiNotionalValue = posiLongNotional + posiShortNational;
|
||||
decimal posiNotionalValue = posiTotalNotional;
|
||||
decimal closePercent = 1;
|
||||
var ratio = DirectionRatio.InterestLegPnl(position.InterestDirection, position.InterestMode);
|
||||
if (eodPayPosition == null)
|
||||
@@ -2056,12 +2052,14 @@ namespace YLErp.Modules.SwapModule
|
||||
positions.Add(position);
|
||||
List<eod_swap_position> preEodPositions = new List<eod_swap_position>();
|
||||
preEodPositions.Add(eodPayPosition);
|
||||
// orginPv 在此仅对固定值腿(mode 1)生效;保证金腿(5/6)的 orginPv 虽在此赋值,
|
||||
// 但 GetInterests 保证金分支已走 CalcMarginInterest(内部自算 orginPv=PreviousBalance),忽略此处传入值。
|
||||
var interestModes = MarginModes.FixedAmountAndMargin;
|
||||
if (interestModes.Contains(position.InterestMode))
|
||||
{
|
||||
orginPv = eodPayPosition.InterestPrincipalFix;
|
||||
}
|
||||
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, posiNotionalValue, closePercent, (int)SwapEventTypeEnum.自动互换, false, true, grossPrice, orginPv, true);
|
||||
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiNotionalValue, closePercent, (int)SwapEventTypeEnum.自动互换, false, orginPv, true);
|
||||
decimal interestAmountBeforeSettlement = interests.Sum(x => x.InterestAmount);
|
||||
decimal tdInterestAmount = interests.Sum(x => x.TdInterestAmount);
|
||||
|
||||
@@ -2069,8 +2067,8 @@ namespace YLErp.Modules.SwapModule
|
||||
// 日终快照仍使用上面的高精度应结金额计算待实现尾差,避免把舍入差提前丢掉。
|
||||
interests.ForEach(x =>
|
||||
{
|
||||
x.InterestAmount = RoundMoney(x.InterestAmount);
|
||||
x.InterestClosePnL = RoundMoney(x.InterestClosePnL);
|
||||
x.InterestAmount = EodPnlCalculator.RoundMoney(x.InterestAmount);
|
||||
x.InterestClosePnL = EodPnlCalculator.RoundMoney(x.InterestClosePnL);
|
||||
});
|
||||
decimal settledInterestAmount = interests.Sum(x => x.InterestAmount);
|
||||
|
||||
@@ -2078,20 +2076,8 @@ namespace YLErp.Modules.SwapModule
|
||||
newEodPayPosition.PositionId = position.id;
|
||||
UpdateDbOption(newEodPayPosition);
|
||||
newEodPayPosition.Invalid = false;
|
||||
//持仓内容-利息腿
|
||||
newEodPayPosition.InterestDirection = position.InterestDirection;
|
||||
newEodPayPosition.InterestMode = position.InterestMode;
|
||||
newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
|
||||
newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
|
||||
newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
|
||||
newEodPayPosition.IsAnnualized = position.IsAnnualized;
|
||||
newEodPayPosition.HappenDate = position.HappenDate;
|
||||
newEodPayPosition.Currency = position.Currency;
|
||||
newEodPayPosition.InterestType = position.InterestType;
|
||||
newEodPayPosition.interest_rest_days = position.interest_rest_days;
|
||||
newEodPayPosition.interest_rule = position.interest_rule;
|
||||
newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0;
|
||||
newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
|
||||
//持仓内容-利息腿(FloatRate 取计息结果)
|
||||
CopyInterestLegFields(newEodPayPosition, position, interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0);
|
||||
newEodPayPosition.InterestFeePending = 0;
|
||||
//利息端估值用信息
|
||||
newEodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiNotionalValue;
|
||||
@@ -2107,22 +2093,16 @@ namespace YLErp.Modules.SwapModule
|
||||
// 到期自动互换是最后一次自动结算:两位实际金额已落流水/资金,待实现不再滚入下一日。
|
||||
newEodPayPosition.InterestIncomeSum = isMaturityFinalAutoSettlement
|
||||
? 0
|
||||
: RoundEodInterest(interestAmountBeforeSettlement - settledInterestAmount);
|
||||
: EodPnlCalculator.RoundEodInterest(interestAmountBeforeSettlement - settledInterestAmount);
|
||||
newEodPayPosition.InterestFeeSum = isMaturityFinalAutoSettlement
|
||||
? 0
|
||||
: RoundEodInterest(eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee);
|
||||
: EodPnlCalculator.RoundEodInterest(eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee);
|
||||
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
|
||||
//持仓价值
|
||||
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
|
||||
|
||||
//累计已实现
|
||||
var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
|
||||
newEodPayPosition.RealizedInterest = rolled.Interest;
|
||||
newEodPayPosition.RealizedInterestFee = rolled.Fee;
|
||||
SetFixedLegRealizedPnl(newEodPayPosition);
|
||||
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
|
||||
DirectionRatio.RateType(position.InterestDirection));
|
||||
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
|
||||
//累计已实现(滚存收尾见 FinalizeInterestEodRoll)
|
||||
FinalizeInterestEodRoll(eodPayPosition, newEodPayPosition, ratio, td, valueDate, position.InterestDirection);
|
||||
PersistEodSwapPosition(newEodPayPosition);
|
||||
Log.Info($"the last newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
|
||||
return interests;
|
||||
@@ -2146,7 +2126,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <param name="closeAmount">当日平仓金额</param>
|
||||
/// <param name="lastEodSwap">上一日终框架合约估值</param>
|
||||
/// <param name="unwintotal">平仓主信息</param>
|
||||
protected List<swap_flow_event> SaveAutoEodWithCloseInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, decimal posiLongNotional, decimal posiShortNational, List<swap_flow_event> flowEvents, decimal closeNational, bool autoSwap, decimal grossPrice, decimal orginPv)
|
||||
protected List<swap_flow_event> SaveAutoEodWithCloseInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, decimal posiTotalNotional, List<swap_flow_event> flowEvents, decimal closeNational, bool autoSwap, decimal grossPrice, decimal orginPv)
|
||||
{
|
||||
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
|
||||
var tradeExtend = td.trade_extend.ExtendObj;
|
||||
@@ -2157,8 +2137,8 @@ namespace YLErp.Modules.SwapModule
|
||||
// 调用共享计息器。因此策略的 "posiNotional × closePercent" 在本例会得到 212197382.46,
|
||||
// 而本次实际应结的平仓本金是 closeNational=90941735.34。该语义错位由
|
||||
// SwapDealService.GetInterests 的模式2无条件修正、模式9全平零值兜底分流处理,不能删除。
|
||||
decimal oriPosiNotionalValue = posiLongNotional + posiShortNational + closeNational;
|
||||
decimal posiNotionalValue = posiLongNotional + posiShortNational;
|
||||
decimal oriPosiNotionalValue = posiTotalNotional + closeNational;
|
||||
decimal posiNotionalValue = posiTotalNotional;
|
||||
// ratio 只负责把腿内原始金额转换为本方盈亏方向,不参与计息金额本身的计算。
|
||||
var ratio = DirectionRatio.InterestLegPnl(position.InterestDirection, position.InterestMode);
|
||||
// 首次日终结算可能包含当日收盘,因此尚无先前的日终利息持仓。
|
||||
@@ -2194,6 +2174,8 @@ namespace YLErp.Modules.SwapModule
|
||||
newEodPayPosition = eodPayPosition.Clone();
|
||||
newEodPayPosition.id = 0;
|
||||
}
|
||||
// orginPv 在此仅对固定值腿(mode 1)生效;保证金腿(5/6)的 orginPv 虽在此赋值,
|
||||
// 但 GetInterests 保证金分支已走 CalcMarginInterest(内部自算 orginPv=PreviousBalance),忽略此处传入值。
|
||||
var interestModes = MarginModes.FixedAmountAndMargin;
|
||||
if (interestModes.Contains(position.InterestMode))
|
||||
{
|
||||
@@ -2219,10 +2201,19 @@ namespace YLErp.Modules.SwapModule
|
||||
positions.Add(position);
|
||||
List<eod_swap_position> preEodPositions = new List<eod_swap_position>();
|
||||
preEodPositions.Add(eodPayPosition);
|
||||
var calcLast = tradeExtend?.InterestCalcMode?.EndsWith("1") ?? true;
|
||||
// 此处 closePercent=1 表示 EOD 计算本次事件时走全额结息;它不是 closeNational / oriPosiNotionalValue。
|
||||
// 与上方“收盘后剩余本金”同时传入会触发共享计息器的模式2/9本金修正,见 GetInterests。
|
||||
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true, settment: false, newCalcLast: autoSwap || calcLast);
|
||||
var calcLast = tradeExtend?.CalcLast ?? true;
|
||||
// 显式入口:平仓后剩余本金 + 实际平掉额 + 恒1全额结息(语义见 InterestCalcRequest.EodPostCloseSettle)。
|
||||
// 该组合触发 GetInterests 内共享计息器的模式2/9本金修正(见其"根因位置"注释,勿删)。
|
||||
// 恒1 重算的 InterestAmount 是结算现金流的直接输入(非无害中间值):系统端到端结算结果由 DI_EXCEL_SCENARIO4 家族对账确认书公式保障(最终全平=剩余额×∏利率,2026-08-18 手算复核)。改动本口径前必读该测试家族——任何破坏 ∏ 恒等式的调整都会被其拦截。
|
||||
// 口径选择常驻记录(快速定位第一入口):出问题先看这行确认当日本次事件的金额输入,再顺着
|
||||
// SwapCalcTrace 分段过程日志追计算;autoSwap=观察日结现路径。
|
||||
Log.Info($"[EOD平仓后收盘结息] tradeId={td.id} valueDate={valueDate:yyyy-MM-dd} autoSwap={autoSwap} " +
|
||||
$"口径=全额结息(恒1惯例) " +
|
||||
$"oriPosi(平仓前)={oriPosiNotionalValue} posi(剩余)={posiNotionalValue} close(平掉)={closeNational}");
|
||||
var interests = CalcEodPostCloseSettleInterests(InterestCalcRequest.EodPostCloseSettle(
|
||||
td, td.trade_extend, valueDate, valueDate, preEodPositions, positions,
|
||||
posiNotionalValue, closeNational,
|
||||
eventType, tdClose: false, orginPv, add: true, newCalcLast: autoSwap || calcLast));
|
||||
// TdInterestAmount:计息器返回的全腿当日/累计参考值,用于拆出 EOD 的当日新增。
|
||||
// interestAmountBeforeSettlement:本次事件发生前理论应结的高精度利息。
|
||||
// manualSettledInterestAmount:swap_flow_event 实际落库的手工结息,金额已按分处理。
|
||||
@@ -2232,7 +2223,7 @@ namespace YLErp.Modules.SwapModule
|
||||
decimal autoSettledInterestAmount = 0m;
|
||||
if (autoSwap && interests.Count > 0)
|
||||
{
|
||||
autoSettledInterestAmount = RoundMoney(interestAmountBeforeSettlement - manualSettledInterestAmount);
|
||||
autoSettledInterestAmount = EodPnlCalculator.RoundMoney(interestAmountBeforeSettlement - manualSettledInterestAmount);
|
||||
var autoInterest = interests[0];
|
||||
autoInterest.InterestAmount = autoSettledInterestAmount;
|
||||
autoInterest.InterestClosePnL = autoSettledInterestAmount
|
||||
@@ -2242,22 +2233,23 @@ namespace YLErp.Modules.SwapModule
|
||||
newEodPayPosition.PositionId = position.id;
|
||||
UpdateDbOption(newEodPayPosition);
|
||||
newEodPayPosition.Invalid = false;
|
||||
//持仓内容-利息腿
|
||||
newEodPayPosition.InterestDirection = position.InterestDirection;
|
||||
newEodPayPosition.InterestMode = position.InterestMode;
|
||||
// ResolveInterestLegPositions 已提供平仓后的实时剩余本金,日终不再重复扣减。
|
||||
newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
|
||||
// newEodPayPosition.InterestPrincipalFix *= (1 - closePercent);
|
||||
newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
|
||||
newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
|
||||
newEodPayPosition.IsAnnualized = position.IsAnnualized;
|
||||
newEodPayPosition.HappenDate = position.HappenDate;
|
||||
newEodPayPosition.Currency = position.Currency;
|
||||
newEodPayPosition.InterestType = position.InterestType;
|
||||
newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0;
|
||||
newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
|
||||
newEodPayPosition.interest_rest_days = position.interest_rest_days;
|
||||
newEodPayPosition.interest_rule = position.interest_rule;
|
||||
//持仓内容-利息腿(FloatRate 取计息结果)。InterestPrincipalFix 保持腿现值:
|
||||
// ResolveInterestLegPositions 已提供平仓后的实时剩余本金,日终不再重复扣减(勿恢复 *(1-closePercent))。
|
||||
CopyInterestLegFields(newEodPayPosition, position, interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0);
|
||||
// ── 持仓延续腿重置日再定盘(EQD-6968 自洽化)──
|
||||
// 排除日取价已收口为"纯跳过":事件利率=末段已消费利率。但剩余持仓自当日起进入新计息周期,
|
||||
// 快照 FloatRate 是后续非重置日(ByEod 沿用 preEod.FloatRate)与当日应计(intersetAcmount)的
|
||||
// 利率载体——平仓日恰为重置日时必须显式取当日新定盘(与 ByEod 日增路径的重置日行为同构)。
|
||||
// 全平(剩余=0)/算尾(事件利率已是新定盘)/观察日(autoSwap 恒1已含当日)无需再定盘。
|
||||
if (!autoSwap && !calcLast && posiNotionalValue > 0m
|
||||
&& !string.IsNullOrEmpty(position.FloatRateUnderlyingCode)
|
||||
&& SwapDealService.IsResetDay(valueDate, td.StartDate.Value, position.interest_rest_days ?? 1))
|
||||
{
|
||||
var ongoingFixing = ResolveOngoingResetFixing(position, valueDate);
|
||||
SwapCalcTrace.Critical(
|
||||
$"FIX EodCloseRefix p{position.id} {valueDate:yyyy-MM-dd} 平仓日=重置日→剩余持仓快照再定盘 {newEodPayPosition.FloatRate:P6}→{ongoingFixing:P6}");
|
||||
newEodPayPosition.FloatRate = ongoingFixing;
|
||||
}
|
||||
//利息端估值用信息
|
||||
// TdInterestPrincipal 是“下一日继续计息的收盘后本金”,不是原始合同规模,也不是本次平仓本金。
|
||||
// 模式9单利直接取剩余名义本金;复利还要保留重置时已经并入本金的待实现利息。
|
||||
@@ -2362,13 +2354,13 @@ namespace YLErp.Modules.SwapModule
|
||||
// InterestIncomeSum 是收盘后仍未结算的尾差/剩余利息。
|
||||
// 部分平仓:扣款前待实现 - TdCloseInterest;最终全平且两位金额已覆盖时直接清零。
|
||||
newEodPayPosition.InterestIncomeSum = closePercent == 1
|
||||
&& RoundMoney(pendingInterestBeforeSettlement) == RoundMoney(newEodPayPosition.TdCloseInterest)
|
||||
&& EodPnlCalculator.RoundMoney(pendingInterestBeforeSettlement) == EodPnlCalculator.RoundMoney(newEodPayPosition.TdCloseInterest)
|
||||
? 0m
|
||||
: RoundEodInterest(pendingInterestBeforeSettlement - newEodPayPosition.TdCloseInterest);
|
||||
: EodPnlCalculator.RoundEodInterest(pendingInterestBeforeSettlement - newEodPayPosition.TdCloseInterest);
|
||||
newEodPayPosition.InterestFeeSum = closePercent == 1
|
||||
&& RoundMoney(pendingInterestFeeBeforeSettlement) == RoundMoney(newEodPayPosition.TdCloseInterestFee)
|
||||
&& EodPnlCalculator.RoundMoney(pendingInterestFeeBeforeSettlement) == EodPnlCalculator.RoundMoney(newEodPayPosition.TdCloseInterestFee)
|
||||
? 0m
|
||||
: RoundEodInterest(pendingInterestFeeBeforeSettlement - newEodPayPosition.TdCloseInterestFee);
|
||||
: EodPnlCalculator.RoundEodInterest(pendingInterestFeeBeforeSettlement - newEodPayPosition.TdCloseInterestFee);
|
||||
//持仓内容-利息腿-损益统计(本方视角)
|
||||
// InterestProfitSum 是利息腿待实现总额,包含利息和费用;无费用时等于 InterestIncomeSum。
|
||||
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
|
||||
@@ -2379,16 +2371,8 @@ namespace YLErp.Modules.SwapModule
|
||||
$",TdCloseInterest is {newEodPayPosition.TdCloseInterest}");
|
||||
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" +
|
||||
$",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
|
||||
//累计已实现
|
||||
// RealizedInterest 只增不回滚:上日累计已实现 + 当日结息按方向后的金额。
|
||||
// 收取腿的 -37119.14 会把累计已实现更新为 -37119.14;后续普通 EOD 保持该值。
|
||||
var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
|
||||
newEodPayPosition.RealizedInterest = rolled.Interest;
|
||||
newEodPayPosition.RealizedInterestFee = rolled.Fee;
|
||||
SetFixedLegRealizedPnl(newEodPayPosition);
|
||||
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
|
||||
DirectionRatio.RateType(position.InterestDirection));
|
||||
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
|
||||
//累计已实现(滚存语义见 FinalizeInterestEodRoll:只增不回滚)
|
||||
FinalizeInterestEodRoll(eodPayPosition, newEodPayPosition, ratio, td, valueDate, position.InterestDirection);
|
||||
Log.Info($"即将插入数据库的 newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
|
||||
PersistEodSwapPosition(newEodPayPosition);
|
||||
return interests;
|
||||
@@ -2402,11 +2386,13 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <param name="preSettleDate">上一交易日</param>
|
||||
/// <param name="valueDate">当前结算日</param>
|
||||
/// <param name="td">互换交易主干</param>
|
||||
protected void SaveEodInterestPositionCopy(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, DateTime valueDate, trade td, swap_position position, eod_swap lastEodSwap, bool needPrice, decimal posiLongNational, decimal posiShortNational, decimal grossPrice, decimal orginPv)
|
||||
protected void SaveEodInterestPositionCopy(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, DateTime valueDate, trade td, swap_position position, eod_swap lastEodSwap, bool needPrice, decimal posiTotalNotional, decimal grossPrice, decimal orginPv)
|
||||
{
|
||||
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
|
||||
List<IntervalModel> intervals = position.SwapIntervalList;
|
||||
var tradeExtend = td.trade_extend.ExtendObj;
|
||||
// orginPv 在此仅对固定值腿(mode 1)生效;保证金腿(5/6)的 orginPv 虽在此赋值,
|
||||
// 但 GetInterests 保证金分支已走 CalcMarginInterest(内部自算 orginPv=PreviousBalance),忽略此处传入值。
|
||||
var interestModes = MarginModes.FixedAmountAndMargin;
|
||||
if (eodPayPosition == null)
|
||||
{
|
||||
@@ -2424,7 +2410,7 @@ namespace YLErp.Modules.SwapModule
|
||||
eodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
|
||||
eodPayPosition.InterestRateDefault = position.InterestRateDefault;
|
||||
eodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
|
||||
eodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiLongNational + posiShortNational;
|
||||
eodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiTotalNotional;
|
||||
eodPayPosition.PosiStartDate = td.StartDate.Value;
|
||||
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
|
||||
eodPayPosition.IsAnnualized = position.IsAnnualized;
|
||||
@@ -2447,7 +2433,7 @@ namespace YLErp.Modules.SwapModule
|
||||
orginPv = eodPayPosition.InterestPrincipalFix;
|
||||
}
|
||||
bool longShort = td.StructureType == ClientMarginTypeEnum.多空组合.ToString();
|
||||
decimal oriPosiNotionalValue = posiLongNational + posiShortNational;
|
||||
decimal oriPosiNotionalValue = posiTotalNotional;
|
||||
decimal posiNotionalValue = oriPosiNotionalValue;
|
||||
if (lastEodSwap == null)
|
||||
{
|
||||
@@ -2472,7 +2458,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
preEodPositions.Add(eodPayPosition);
|
||||
}
|
||||
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNational, posiShortNational, posiNotionalValue, closePercent, 0, false, needPrice, grossPrice, orginPv);
|
||||
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiNotionalValue, closePercent, 0, false, orginPv);
|
||||
UpdateDbOption(newEodPayPosition);
|
||||
|
||||
newEodPayPosition.PosiStatus = 0;
|
||||
@@ -2500,14 +2486,8 @@ namespace YLErp.Modules.SwapModule
|
||||
//持仓价值
|
||||
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
|
||||
|
||||
//累计已实现
|
||||
var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
|
||||
newEodPayPosition.RealizedInterest = rolled.Interest;
|
||||
newEodPayPosition.RealizedInterestFee = rolled.Fee;
|
||||
SetFixedLegRealizedPnl(newEodPayPosition);
|
||||
var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
|
||||
DirectionRatio.RateType(eodPayPosition.InterestDirection));
|
||||
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
|
||||
//累计已实现(滚存收尾见 FinalizeInterestEodRoll;方向源=eodPayPosition,与其他三方法不同,勿统一)
|
||||
FinalizeInterestEodRoll(eodPayPosition, newEodPayPosition, ratio, td, valueDate, eodPayPosition.InterestDirection);
|
||||
Log.Info($"the last newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
|
||||
PersistEodSwapPosition(newEodPayPosition);
|
||||
|
||||
@@ -2603,7 +2583,7 @@ namespace YLErp.Modules.SwapModule
|
||||
newEodPayPosition.RealizedFee = closeFee;
|
||||
newEodPayPosition.RealizedMtmPnL = newEodPayPosition.TdCloseMtmPnl;
|
||||
newEodPayPosition.RealizedDividend = newEodPayPosition.TdCloseDividend;
|
||||
SetFloatingRealizedPnl(newEodPayPosition);
|
||||
EodPnlCalculator.SetFloatingRealizedPnl(newEodPayPosition);
|
||||
|
||||
newEodPayPosition.PosiStatus = payQty == 0 ? 1 : 0;
|
||||
UpdateDbOption(newEodPayPosition);
|
||||
@@ -2658,6 +2638,11 @@ namespace YLErp.Modules.SwapModule
|
||||
curretEod.TdPosiDividend = DividendCalc.AfterTax(payment, tax);
|
||||
}
|
||||
curretEod.PosiDividendSum = eod.PosiQuantity > 0 ? Math.Round(eod.PosiDividendSum + curretEod.TdPosiDividend, 2) : 0;
|
||||
// 分红递推过程常驻记录(快速定位):窗口/数量/税率/当日新计/累计前后值——
|
||||
// 配合 BondPaymentService 的[分红-登记日口径]窗口命中日志,构成"命中哪些登记日→算出多少→账滚到多少"全链
|
||||
Log.Info($"[分红-EOD计提Copy] tradeId={td.id} posiId={eod.PositionId} valueDate={valueDate:yyyy-MM-dd} " +
|
||||
$"window=({eod.ValueDate:yyyy-MM-dd},{valueDate:yyyy-MM-dd}] qty={curretEod.PosiQuantity} tax={tax} " +
|
||||
$"TdPosiDividend={curretEod.TdPosiDividend} PosiDividendSum {eod.PosiDividendSum}->{curretEod.PosiDividendSum}");
|
||||
curretEod.PosiQuantity = eod.PosiQuantity;
|
||||
if (curretEod.PosiStatus == 1)
|
||||
{
|
||||
@@ -2677,7 +2662,7 @@ namespace YLErp.Modules.SwapModule
|
||||
curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl;
|
||||
curretEod.RealizedDividend = eod.RealizedDividend + curretEod.TdCloseDividend;
|
||||
curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee;
|
||||
SetFloatingRealizedPnl(curretEod);
|
||||
EodPnlCalculator.SetFloatingRealizedPnl(curretEod);
|
||||
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value
|
||||
, seekPreday: true, currencyRateType: DirectionRatio.RateType(curretEod.PosiDirection));
|
||||
curretEod.TdCurrency = Convert.ToDecimal(currencyRate);
|
||||
@@ -2692,17 +2677,6 @@ namespace YLErp.Modules.SwapModule
|
||||
return curretEod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浮动腿累计已实现盈亏由盯市、分红和费用三个已实现组成项汇总。
|
||||
/// 各组成项已经按本方视角落库,此处不再额外转换方向。
|
||||
/// </summary>
|
||||
private static void SetFloatingRealizedPnl(eod_swap_position position)
|
||||
{
|
||||
position.RealizedPnl = position.RealizedMtmPnL
|
||||
+ position.RealizedDividend
|
||||
+ position.RealizedFee;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新虚拟交易费用
|
||||
/// </summary>
|
||||
@@ -2741,11 +2715,11 @@ namespace YLErp.Modules.SwapModule
|
||||
int shortRatio = DirectionRatio.LongShort(eod.PositionType);
|
||||
int directionRatio = DirectionRatio.ReceivePay(eod.PosiDirection);
|
||||
var price = GetSwapValuationPrice(eod.UnderlyingCode, dealDate, out decimal vobp);
|
||||
var todayConsumedDividend = CalcConsumedDividend(curretEod, unwindEvents);
|
||||
var originNotional = (decimal)td.OriginalStockEqvNotional / swapPosition.PosiNetPrice;
|
||||
decimal totalPayment = CalcBondPayment(curretEod.UnderlyingCode, td.StartDate.Value, valueDate, (decimal)originNotional, shortRatio, directionRatio);
|
||||
// 历史遗留死代码已删(2026-08-16,论证+边界测试见 DividendEodNoDoubleCountTest.脏数据边界_*):
|
||||
// todayConsumedDividend / originNotional / totalPayment / totalInterest 自 0910969e(2026-07-02
|
||||
// 改递推式) 起计算结果从未被消费,仅残留一次全历史 CalcBondPayment 只读查询+日志副作用,
|
||||
// 且构成脏数据(OriginalStockEqvNotional=null/PosiNetPrice=0)下的 EOD 崩溃点。回退=git revert 本提交。
|
||||
decimal tax = um.ValueAddedTax ?? 0;
|
||||
decimal totalInterest = DividendCalc.AfterTaxRaw(totalPayment, tax);
|
||||
SetPriceInfoByFlowEvent(eod, curretEod, unwindEvents, swapPosition);
|
||||
curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp);
|
||||
curretEod.UnderlyingPrice = price;
|
||||
@@ -2778,7 +2752,12 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
curretEod.PosiDividendSum = 0;
|
||||
}
|
||||
SetFloatingRealizedPnl(curretEod);
|
||||
// 分红递推过程常驻记录(快速定位):当日事件路径含实现扣减(前日+新计-当日实现)
|
||||
Log.Info($"[分红-EOD计提Update] tradeId={td.id} posiId={eod.PositionId} valueDate={valueDate:yyyy-MM-dd} " +
|
||||
$"window=({eod.ValueDate:yyyy-MM-dd},{valueDate:yyyy-MM-dd}] qty={curretEod.PosiQuantity} tax={tax} " +
|
||||
$"TdPosiDividend={curretEod.TdPosiDividend} TdCloseDividend={curretEod.TdCloseDividend} " +
|
||||
$"PosiDividendSum {eod.PosiDividendSum}->{curretEod.PosiDividendSum}");
|
||||
EodPnlCalculator.SetFloatingRealizedPnl(curretEod);
|
||||
curretEod.SwapPositionValue -= curretEod.TdCloseDividend;
|
||||
|
||||
curretEod.PosiProfitSum = MtmCalc.ReturnLegProfitSum(curretEod.PosiMtmPnL, curretEod.PosiDividendSum, curretEod.PosiFeePending);
|
||||
@@ -2800,20 +2779,6 @@ namespace YLErp.Modules.SwapModule
|
||||
return curretEod;
|
||||
}
|
||||
|
||||
private decimal CalcConsumedDividend(eod_swap_position curretEod, List<swap_flow_event> events)
|
||||
{
|
||||
decimal consumedDividend = 0;
|
||||
|
||||
List<int> swapEventTypes = new List<int>() { (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
|
||||
//这里要剔除掉平仓产生的分红
|
||||
consumedDividend = events
|
||||
.Where(x => x.SwapTradeId == curretEod.SwapTradeId
|
||||
&& swapEventTypes.Contains(x.EventType)
|
||||
&& x.DataState == (int)SwapFlowDateStateEnum.完成)
|
||||
.Sum(s => s.DividendIn);
|
||||
return consumedDividend;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据开平仓事件算价格及后付费用
|
||||
/// </summary>
|
||||
@@ -2958,7 +2923,7 @@ namespace YLErp.Modules.SwapModule
|
||||
curretEod.RealizedMtmPnL = curretEod.TdCloseMtmPnl;
|
||||
curretEod.RealizedDividend = curretEod.TdCloseDividend;
|
||||
curretEod.RealizedFee = curretEod.TdCloseFee;
|
||||
SetFloatingRealizedPnl(curretEod);
|
||||
EodPnlCalculator.SetFloatingRealizedPnl(curretEod);
|
||||
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
|
||||
if (curretEod.PosiStatus == 1)
|
||||
{
|
||||
@@ -3037,7 +3002,7 @@ namespace YLErp.Modules.SwapModule
|
||||
var tradeSpan = DbContext.trade_span.FirstOrDefault(x => x.TradeId == td.id && x.ValueDate == settleDate);
|
||||
// eod_swap 是交易级汇总;eod_swap_position 是浮动腿、利息腿和保证金腿的明细。
|
||||
// 以下先按日终明细拆腿,再按框架合约展示口径汇总。
|
||||
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate == settleDate && !x.Invalid).ToList();
|
||||
var eodSwapPositions = DbContext.eod_swap_position.ActiveByTradeAndDate(td.id, settleDate).ToList();
|
||||
var interestPositions = eodSwapPositions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//利息腿
|
||||
var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿
|
||||
// 框架合约的方向约定:多头为正、空头为负;总名义本金取交易原始规模,
|
||||
@@ -3049,8 +3014,8 @@ namespace YLErp.Modules.SwapModule
|
||||
eod_Swap.BookId = td.AssetId;
|
||||
eod_Swap.ValueDate = settleDate;
|
||||
eod_Swap.StructureType = td.StructureType;
|
||||
FillPositionLegSummary(eod_Swap, positions);
|
||||
eod_Swap.InterestPnL = SumInterestPnL(interestPositions);
|
||||
EodPnlCalculator.FillPositionLegSummary(eod_Swap, positions);
|
||||
eod_Swap.InterestPnL = EodPnlCalculator.SumInterestPnL(interestPositions);
|
||||
eod_Swap.PostionValue = eodSwapPositions.Sum(s => s.SwapPositionValue);
|
||||
// 保证金腿的利息现金流方向与保证金本金方向相反。
|
||||
// 不能直接汇总 RealizedPnl,否则“收取客户保证金”的腿会把应支付给客户的
|
||||
@@ -3099,12 +3064,12 @@ namespace YLErp.Modules.SwapModule
|
||||
DbContext.eod_swap.Add(eod_Swap);
|
||||
}
|
||||
// 单标的调整与首次归档使用同一套框架合约汇总口径,避免重算后多空和名义本金展示不一致。
|
||||
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate == settleDate && !x.Invalid).ToList();
|
||||
var eodSwapPositions = DbContext.eod_swap_position.ActiveByTradeAndDate(td.id, settleDate).ToList();
|
||||
var interestPositions = eodSwapPositions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//利息腿
|
||||
var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿
|
||||
eod_Swap.NotionalValue = Math.Round(Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
FillPositionLegSummary(eod_Swap, positions);
|
||||
eod_Swap.InterestPnL += SumInterestPnL(interestPositions);
|
||||
EodPnlCalculator.FillPositionLegSummary(eod_Swap, positions);
|
||||
eod_Swap.InterestPnL += EodPnlCalculator.SumInterestPnL(interestPositions);
|
||||
eodSwapPositions.ForEach(x =>
|
||||
{
|
||||
var ratio = DirectionRatio.InterestLegPnl(x.InterestDirection, x.InterestMode);
|
||||
@@ -3130,38 +3095,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// 我方支付给对手方的成本计入,而不会错误增加框架合约已实现收益。
|
||||
/// 抽为静态纯函数以支持无库单测(marginTypes 等价于 ConsTrade.InterestMarginModels)。
|
||||
/// </summary>
|
||||
public static decimal CalculateSwapRealizedPnl(eod_swap_position position)
|
||||
{
|
||||
var interestRatio = DirectionRatio.InterestLegPnl(position.InterestDirection, position.InterestMode);
|
||||
|
||||
return position.RealizedMtmPnL
|
||||
+ position.RealizedDividend
|
||||
+ position.RealizedFee
|
||||
+ position.RealizedInterest * interestRatio
|
||||
+ position.RealizedInterestFee;
|
||||
}
|
||||
|
||||
/// <summary>填充框架合约的持仓腿汇总字段(多空名义本金/市值/浮动盈亏/dv01/平仓量)。
|
||||
/// SaveEodSwap 与 UpdateEodSwap 共用,消除 ~10 行重复。</summary>
|
||||
private static void FillPositionLegSummary(eod_swap eod_Swap, List<eod_swap_position> positions)
|
||||
{
|
||||
eod_Swap.NotionalValueLong = Math.Round(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.NotionalValueShort = Math.Round(-Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue)), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.MarketValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.UnderlyingMarketValue);
|
||||
eod_Swap.MarketValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.UnderlyingMarketValue);
|
||||
eod_Swap.FloatingPnL = positions.Sum(s => s.PosiProfitSum);
|
||||
eod_Swap.dv01 = positions.Sum(s => s.dv01 ?? 0);
|
||||
eod_Swap.TdCloseQty = positions.Sum(s => s.TdCloseQty);
|
||||
}
|
||||
|
||||
/// <summary>利息腿 PnL 汇总(按方向比例 + 保证金翻转)。原 SaveEodSwap/UpdateEodSwap 各一段 ForEach。</summary>
|
||||
private static decimal SumInterestPnL(List<eod_swap_position> interestPositions)
|
||||
{
|
||||
decimal interestPnL = 0;
|
||||
foreach (var x in interestPositions)
|
||||
interestPnL += x.InterestProfitSum * DirectionRatio.InterestLegPnl(x.InterestDirection, x.InterestMode);
|
||||
return interestPnL;
|
||||
}
|
||||
public static decimal CalculateSwapRealizedPnl(eod_swap_position position) => EodPnlCalculator.CalculateSwapRealizedPnl(position);
|
||||
|
||||
/// <summary>
|
||||
/// 风险报表符号归一化:把历史两种符号口径的 TdCloseInterest/RealizedInterest
|
||||
@@ -3170,20 +3104,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// 抽为 public static 纯函数以支持无库单测(见 SwapReportInterestSignNormalizeTest)。
|
||||
/// 仅当 InterestDirection > 0 时执行(与原内联逻辑等价)。
|
||||
/// </summary>
|
||||
public static void NormalizeInterestSignForReport(eod_swap_position position)
|
||||
{
|
||||
if (position.InterestDirection <= 0) return;
|
||||
|
||||
if (position.InterestMode == (int)InterestModeEnum.标的期初全价)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var interestRatio = DirectionRatio.InterestLegPnl(position.InterestDirection, position.InterestMode);
|
||||
position.TdCloseInterest = Math.Abs(position.TdCloseInterest) * interestRatio;
|
||||
position.RealizedInterest = Math.Abs(position.RealizedInterest) * interestRatio;
|
||||
// 兼容修复前已落库的利息腿:当时只累计了明细字段,未同步写入 RealizedPnl。
|
||||
position.RealizedPnl = position.RealizedInterest + position.RealizedInterestFee;
|
||||
}
|
||||
public static void NormalizeInterestSignForReport(eod_swap_position position) => EodPnlCalculator.NormalizeInterestSignForReport(position);
|
||||
|
||||
/// <summary>
|
||||
/// 获取多空组合 平仓详细
|
||||
@@ -3194,7 +3115,7 @@ namespace YLErp.Modules.SwapModule
|
||||
public SwapLongShortCloseModel GetCloseDetails(int tradeId, DateTime valueDate)
|
||||
{
|
||||
SwapLongShortCloseModel closeModel = new SwapLongShortCloseModel();
|
||||
var eodPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId && x.ValueDate == valueDate && !x.Invalid).ToList();
|
||||
var eodPositions = DbContext.eod_swap_position.ActiveByTradeAndDate(tradeId, valueDate).ToList();
|
||||
var flowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.EventDate == valueDate && x.DataState == (int)SwapFlowDateStateEnum.完成 && x.EventType == (int)SwapEventTypeEnum.平仓 && string.IsNullOrEmpty(x.UnderlyingCode)).ToList();
|
||||
closeModel.DealPositions = eodPositions.Where(x => x.TdCloseQty != 0).ToList();
|
||||
closeModel.DealInterests = flowEvents;
|
||||
@@ -3435,7 +3356,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <returns></returns>
|
||||
public List<eod_swap_position> GetPreEodPositions(int tradeId, DateTime valueDate)
|
||||
{
|
||||
return DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId && x.ValueDate == valueDate && !x.Invalid).ToList();
|
||||
return DbContext.eod_swap_position.ActiveByTradeAndDate(tradeId, valueDate).ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取互换交易日终持仓数据集合
|
||||
@@ -3673,7 +3594,7 @@ namespace YLErp.Modules.SwapModule
|
||||
item.position.FloatRateUnderlyingCode = floatRateInterest?.FloatRateUnderlyingCode;
|
||||
item.position.FloatRate = floatRateInterest?.FloatRate ?? 0;
|
||||
item.OpenMarginAmount = initialMargins.Sum(s => s.InterestPrincipalFix * DirectionRatio.ReceivePay(s.InterestDirection));
|
||||
item.OpenMarginRate = CalculateWeightedMarginRate(tradeMargins);
|
||||
item.OpenMarginRate = EodPnlCalculator.CalculateWeightedMarginRate(tradeMargins);
|
||||
item.AdditionalMarginAmount = additionalMargins.Sum(s => s.InterestPrincipalFix * DirectionRatio.ReceivePay(s.InterestDirection));
|
||||
item.MarginInterestAmount = CalculateWeightedMarginInterest(eodMargins);
|
||||
item.InterestAmount = eodInterests.Sum(s => s.InterestIncomeSum * (-DirectionRatio.ReceivePay(s.InterestDirection)));
|
||||
@@ -3698,29 +3619,12 @@ namespace YLErp.Modules.SwapModule
|
||||
return retListResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算预付金利率。多条初始/追加预付金腿按本金绝对值加权,
|
||||
/// 不按收付方向轧差,避免相反方向本金抵消后放大利率。
|
||||
/// </summary>
|
||||
private static decimal CalculateWeightedMarginRate(IEnumerable<swap_position> margins)
|
||||
{
|
||||
var marginList = margins.ToList();
|
||||
var totalWeight = marginList.Sum(x => Math.Abs(x.InterestPrincipalFix));
|
||||
return totalWeight == 0
|
||||
? 0
|
||||
: marginList.Sum(x => x.InterestRateDefault * Math.Abs(x.InterestPrincipalFix)) / totalWeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算预付金利息金额。InterestIncomeSum 已是各腿利息金额,
|
||||
/// 按收取为正、支付为负直接轧差求和,不做本金加权。
|
||||
/// 抽为 public static 纯函数以支持无库单测(见 SwapWeightedMarginInterestTest)。
|
||||
/// </summary>
|
||||
public static decimal CalculateWeightedMarginInterest(IEnumerable<eod_swap_position> margins)
|
||||
{
|
||||
return margins.Sum(x =>
|
||||
x.InterestIncomeSum * DirectionRatio.ReceivePay(x.InterestDirection));
|
||||
}
|
||||
public static decimal CalculateWeightedMarginInterest(IEnumerable<eod_swap_position> margins) => EodPnlCalculator.CalculateWeightedMarginInterest(margins);
|
||||
|
||||
/// <summary>
|
||||
/// 固定利息腿的累计已实现盈亏 = 累计已实现利息 + 累计已实现利息费用。
|
||||
@@ -3728,10 +3632,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// 抽为 public static 纯函数以支持无库单测(见 SwapFixedLegRealizedPnlTest),
|
||||
/// 并消除复制粘贴带来的笔误风险(如 L1296 历史双分号)。
|
||||
/// </summary>
|
||||
public static void SetFixedLegRealizedPnl(eod_swap_position position)
|
||||
{
|
||||
position.RealizedPnl = position.RealizedInterest + position.RealizedInterestFee;
|
||||
}
|
||||
public static void SetFixedLegRealizedPnl(eod_swap_position position) => EodPnlCalculator.SetFixedLegRealizedPnl(position);
|
||||
/// <summary>
|
||||
/// 将数据库中以公司/交易簿记方向保存的日终字段转换为客户视角。
|
||||
/// 该转换必须在拆分浮动收益、费用和期间付息/分红之前完成,
|
||||
|
||||
Reference in New Issue
Block a user