refactor(swap): Phase1 提取UnwindNormalizer+TradingFeeCalc出SwapDealService
SwapDealService的10个private static纯逻辑方法搬到两个新文件: - UnwindNormalizer.cs: NormalizeNotionalValues/FullCloseRequest/Recalculate/IsFullClose/SettledInterestAmounts/EventUnwindDate (6个) - TradingFeeCalc.cs: CalcInitTradingFee/CalcInitTradingFeePending (2个) SwapDealService内21处调用点加类名前缀, 反射测试改为直接调用(public) SwapModule零回归(7基线/510通过)
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
using System.Reflection;
|
||||
using YLErp.DBModels;
|
||||
|
||||
namespace YLErp.Modules.SwapModule
|
||||
@@ -7,26 +6,10 @@ namespace YLErp.Modules.SwapModule
|
||||
public class InitUnwindTradingFeeTest
|
||||
{
|
||||
private static decimal InvokeCalcInitTradingFee(swap_position position, UnwindData unwindData)
|
||||
{
|
||||
var method = typeof(SwapDealService).GetMethod(
|
||||
"CalcInitTradingFee",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
Assert.IsNotNull(method, "未找到 CalcInitTradingFee 私有静态方法");
|
||||
|
||||
return (decimal)method.Invoke(null, new object[] { position, unwindData });
|
||||
}
|
||||
=> TradingFeeCalc.CalcInitTradingFee(position, unwindData);
|
||||
|
||||
private static decimal InvokeCalcInitTradingFeePending(swap_position oriPosition, swap_position position, UnwindData unwindData)
|
||||
{
|
||||
var method = typeof(SwapDealService).GetMethod(
|
||||
"CalcInitTradingFeePending",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
Assert.IsNotNull(method, "CalcInitTradingFeePending was not found");
|
||||
|
||||
return (decimal)method.Invoke(null, new object[] { oriPosition, position, unwindData });
|
||||
}
|
||||
=> TradingFeeCalc.CalcInitTradingFeePending(oriPosition, position, unwindData);
|
||||
|
||||
[TestMethod]
|
||||
public void 百分比模式_按平仓名义本金计算并四舍五入到两位()
|
||||
|
||||
@@ -46,106 +46,17 @@ namespace YLErp.Modules.SwapModule
|
||||
/// 原 private 改 protected virtual,使测试 stub 可整体 override,规避内部 new SwapEventService 连库。</summary>
|
||||
protected virtual long SaveSwapDeal(UnwindData unwindData, int eventType, int clientCashId, string eventResason = "", bool approve = false)
|
||||
{
|
||||
NormalizeNotionalValues(unwindData);
|
||||
UnwindNormalizer.NormalizeNotionalValues(unwindData);
|
||||
return SaveSwapDealInternal(unwindData, eventType, clientCashId, eventResason, approve);
|
||||
}
|
||||
|
||||
private static void NormalizeNotionalValues(UnwindData unwindData)
|
||||
{
|
||||
unwindData.NotionalValue = Math.Round(unwindData.NotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
unwindData.PosiNotionalValue = Math.Round(unwindData.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
unwindData.CloseNotionalValue = Math.Round(unwindData.CloseNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
private static bool NormalizeFullCloseRequest(UnwindData unwindData)
|
||||
{
|
||||
if (unwindData.CloseMethod != (int)CloseMethodEnum.全部平仓
|
||||
&& unwindData.ClosePercent < 1
|
||||
&& !(unwindData.PositionQty > 0 && unwindData.CloseQty >= unwindData.PositionQty)
|
||||
&& !(unwindData.PosiNotionalValue > 0 && unwindData.CloseNotionalValue >= unwindData.PosiNotionalValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var closeQty = unwindData.CloseQty;
|
||||
var closeNotionalValue = unwindData.CloseNotionalValue;
|
||||
unwindData.ClosePercent = 1;
|
||||
if (unwindData.PositionQty > 0) unwindData.CloseQty = unwindData.PositionQty;
|
||||
if (unwindData.PosiNotionalValue > 0) unwindData.CloseNotionalValue = unwindData.PosiNotionalValue;
|
||||
return closeQty != unwindData.CloseQty || closeNotionalValue != unwindData.CloseNotionalValue;
|
||||
}
|
||||
|
||||
private static void RecalculateNormalizedUnwindAmounts(UnwindData unwindData)
|
||||
{
|
||||
var floatLeg = unwindData.FlowEvents.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode));
|
||||
if (floatLeg == null || floatLeg.PosiGrossPrice == 0) return;
|
||||
|
||||
var input = new UnwindInput
|
||||
{
|
||||
Multiplier = ConsGlobal.InstrumentType.IsBond(floatLeg.UnderlyingInstrumentType) ? 100 : 1,
|
||||
PosiGrossPrice = floatLeg.PosiGrossPrice,
|
||||
TradingAmountAvg = floatLeg.TradingAmountAvg,
|
||||
CloseQty = unwindData.CloseQty,
|
||||
PositionQty = unwindData.PositionQty,
|
||||
ContractSize = floatLeg.ContractSize,
|
||||
CloseNotionalValue = unwindData.CloseNotionalValue,
|
||||
PayDirection = floatLeg.PayDirection,
|
||||
PositionType = floatLeg.PositionType,
|
||||
TradingFee = floatLeg.TradingFee.ToString(),
|
||||
TradingFeePending = floatLeg.TradingFeePending.ToString(),
|
||||
DividendIn = floatLeg.DividendIn.ToString()
|
||||
};
|
||||
foreach (var leg in unwindData.FlowEvents.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)))
|
||||
{
|
||||
var target = MarginModes.Contains(leg.InterestMode)
|
||||
? input.MarginLegs
|
||||
: input.InterestLegs;
|
||||
target.Add(new LegInput { InterestClosePnL = leg.InterestClosePnL });
|
||||
}
|
||||
|
||||
var result = FrontendCalcReference.CalcUnwind(input);
|
||||
floatLeg.MarkClosePnl = result.MarkClosePnl;
|
||||
unwindData.SwapCloseAmount = result.SwapCloseAmount;
|
||||
unwindData.SwapRealizedPnL = result.SwapRealizedPnL;
|
||||
unwindData.SwapMarginRebatePnl = result.SwapMarginRebatePnl;
|
||||
}
|
||||
|
||||
private static bool IsFullCloseAfterDeduction(UnwindData unwindData, double remainingNotional, double remainingQuantity)
|
||||
{
|
||||
return unwindData.ClosePercent == 1 || (remainingNotional == 0 && remainingQuantity == 0);
|
||||
}
|
||||
|
||||
// 待实现利息会进入 decimal(30,12) 日终快照;精度常量统一引用 SwapInterest.FundingLegPrecision,消除重复定义。
|
||||
private const int InterestCalculationPrecision = SwapInterest.FundingLegPrecision;
|
||||
|
||||
/// <summary>
|
||||
/// 手工平仓、手工互换及收益结算的利息事件按金额两位落库。
|
||||
/// 自动平仓保留原有计算与落库口径,不适用本阶段的手工结算规则。
|
||||
/// </summary>
|
||||
private static bool NormalizeSettledInterestAmounts(IEnumerable<swap_flow_event> flowEvents, int eventType, string eventReason)
|
||||
{
|
||||
if ((eventType != (int)SwapEventTypeEnum.平仓 && eventType != (int)SwapEventTypeEnum.互换)
|
||||
|| eventReason == "系统操作_自动平仓")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var flowEvent in flowEvents.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)))
|
||||
{
|
||||
// 只处理利息腿;浮动腿损益在日终快照入口统一按两位落库。
|
||||
flowEvent.InterestPrincipal = Math.Round(flowEvent.InterestPrincipal, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
flowEvent.InterestAmount = Math.Round(flowEvent.InterestAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
flowEvent.TdInterestAmount = Math.Round(flowEvent.TdInterestAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
flowEvent.InterestClosePnL = Math.Round(flowEvent.InterestClosePnL, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
flowEvent.InterestFee = Math.Round(flowEvent.InterestFee, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 客户现金在 SaveSwapDeal 之前创建,手工结算必须先收敛流水并重算汇总金额。
|
||||
private void NormalizeManualSettlementAmounts(UnwindData unwindData, int eventType, string eventReason)
|
||||
{
|
||||
if (!NormalizeSettledInterestAmounts(unwindData.FlowEvents, eventType, eventReason))
|
||||
if (!UnwindNormalizer.NormalizeSettledInterestAmounts(unwindData.FlowEvents, eventType, eventReason))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -372,7 +283,7 @@ namespace YLErp.Modules.SwapModule
|
||||
floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType;
|
||||
floatEvent.CloseFee = 0;
|
||||
floatEvent.BeforeCloseFee = oriPosition.PosiTradingFeePending;
|
||||
floatEvent.TradingFee = CalcInitTradingFee(oriPosition, unwindData);
|
||||
floatEvent.TradingFee = TradingFeeCalc.CalcInitTradingFee(oriPosition, unwindData);
|
||||
floatEvent.PosiTradingFeeUnit = oriPosition?.PosiTradingFeeUnit ?? 0;
|
||||
floatEvent.PosiFeeType = oriPosition?.PosiFeeType ?? 0;
|
||||
floatEvent.MarkClosePnl = 0;
|
||||
@@ -387,7 +298,7 @@ namespace YLErp.Modules.SwapModule
|
||||
floatEvent.ContractSize = position.ContractSize;
|
||||
floatEvent.TradingAmount = floatEvent.Quantity * floatEvent.ContractSize;
|
||||
var ratio = -DirectionRatio.ReceivePay(position.PosiDirection);
|
||||
floatEvent.TradingFeePending = CalcInitTradingFeePending(oriPosition, position, unwindData);
|
||||
floatEvent.TradingFeePending = TradingFeeCalc.CalcInitTradingFeePending(oriPosition, position, unwindData);
|
||||
floatEvent.DataState = (int)SwapFlowDateStateEnum.完成;
|
||||
floatEvent.InterestMode = position.InterestMode;
|
||||
floatEvent.ClientId = td.ClientId;
|
||||
@@ -397,37 +308,6 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
return unwindData;
|
||||
}
|
||||
private static decimal CalcInitTradingFee(swap_position oriPosition, UnwindData unwindData)
|
||||
{
|
||||
if (oriPosition == null || unwindData == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (oriPosition.PosiFeeType == 1)
|
||||
{
|
||||
return Math.Round(oriPosition.PosiTradingFeeUnit * unwindData.CloseQty, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
return Math.Round(oriPosition.PosiTradingFeeUnit / 100m * unwindData.CloseNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
private static decimal CalcInitTradingFeePending(swap_position oriPosition, swap_position position, UnwindData unwindData)
|
||||
{
|
||||
if (oriPosition == null || unwindData == null || oriPosition.PosiTradingFeeUnit == 0)
|
||||
{
|
||||
return position?.PosiTradingFeePending ?? 0;
|
||||
}
|
||||
|
||||
var closeBase = oriPosition.PosiFeeType == 1 ? unwindData.CloseQty : unwindData.CloseNotionalValue;
|
||||
var originalBase = oriPosition.PosiFeeType == 1 ? unwindData.NotionalQty : unwindData.NotionalValue;
|
||||
if (originalBase <= 0)
|
||||
{
|
||||
return position?.PosiTradingFeePending ?? 0;
|
||||
}
|
||||
|
||||
return Math.Round(oriPosition.PosiTradingFeePending * closeBase / originalBase, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
/// <summary>
|
||||
/// 校验上日是否收盘
|
||||
/// </summary>
|
||||
@@ -1573,16 +1453,16 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
NormalizeEventUnwindDate(unwindData);
|
||||
NormalizeNotionalValues(unwindData);
|
||||
UnwindNormalizer.NormalizeEventUnwindDate(unwindData);
|
||||
UnwindNormalizer.NormalizeNotionalValues(unwindData);
|
||||
NormalizeManualSettlementAmounts(unwindData, (int)SwapEventTypeEnum.平仓, "系统操作_平仓");
|
||||
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
|
||||
// 前端按"占期初(original)"语义传 ClosePercent(A);后端全链路按"占剩余(remaining)"语义(B)消费。
|
||||
// 入口统一转换为 B,落库展示用的 A 由 SaveSwapDealInternal 还原。
|
||||
unwindData.ClosePercent = ToRemainingClosePercent(unwindData.ClosePercent, unwindData.NotionalValue, unwindData.PosiNotionalValue);
|
||||
if (NormalizeFullCloseRequest(unwindData))
|
||||
if (UnwindNormalizer.NormalizeFullCloseRequest(unwindData))
|
||||
{
|
||||
RecalculateNormalizedUnwindAmounts(unwindData);
|
||||
UnwindNormalizer.RecalculateNormalizedUnwindAmounts(unwindData);
|
||||
}
|
||||
ValidateFrontendPnL(unwindData, isIncome: false); // 只读校验告警,不阻断交易
|
||||
bool cofirm = false;
|
||||
@@ -1595,7 +1475,7 @@ namespace YLErp.Modules.SwapModule
|
||||
var eventId = SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.平仓, clientCashId, "系统操作_平仓");
|
||||
var remainingStockEqvNotional = Math.Round(td.StockEqvNotional - Convert.ToDouble(unwindData.CloseNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
var remainingTradeAmount = td.TradeAmount - Convert.ToDouble(unwindData.CloseQty);
|
||||
var isFullClose = IsFullCloseAfterDeduction(unwindData, remainingStockEqvNotional, remainingTradeAmount);
|
||||
var isFullClose = UnwindNormalizer.IsFullCloseAfterDeduction(unwindData, remainingStockEqvNotional, remainingTradeAmount);
|
||||
if (isFullClose)
|
||||
{
|
||||
td.TradeStatus = "已平仓";
|
||||
@@ -1968,7 +1848,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
NormalizeEventUnwindDate(unwindData);
|
||||
UnwindNormalizer.NormalizeEventUnwindDate(unwindData);
|
||||
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
|
||||
NormalizeManualSettlementAmounts(unwindData, (int)SwapEventTypeEnum.平仓, "系统操作_平仓");
|
||||
var trans = DbContext.Database.BeginTransaction();
|
||||
@@ -2006,7 +1886,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
NormalizeEventUnwindDate(unwindData);
|
||||
UnwindNormalizer.NormalizeEventUnwindDate(unwindData);
|
||||
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
|
||||
NormalizeManualSettlementAmounts(unwindData, (int)SwapEventTypeEnum.互换, "系统操作_互换");
|
||||
var trans = DbContext.Database.BeginTransaction();
|
||||
@@ -2046,7 +1926,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
NormalizeEventUnwindDate(unwindData);
|
||||
UnwindNormalizer.NormalizeEventUnwindDate(unwindData);
|
||||
ValidateIncomeValueDate(unwindData, td);
|
||||
NormalizeManualSettlementAmounts(unwindData, (int)SwapEventTypeEnum.互换, "系统操作_互换");
|
||||
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
|
||||
@@ -2084,8 +1964,8 @@ namespace YLErp.Modules.SwapModule
|
||||
throw new Exception("该笔交易状态为平仓待复核,未找到相关记录,请检查该笔交易是否有效");
|
||||
}
|
||||
swapEvent.unwindData = JsonConvert.DeserializeObject<UnwindData>(swapEvent.EventData);
|
||||
NormalizeEventUnwindDate(swapEvent.unwindData);
|
||||
NormalizeNotionalValues(swapEvent.unwindData);
|
||||
UnwindNormalizer.NormalizeEventUnwindDate(swapEvent.unwindData);
|
||||
UnwindNormalizer.NormalizeNotionalValues(swapEvent.unwindData);
|
||||
// Stored events keep display ratio A; approval calculations consume remaining ratio B.
|
||||
swapEvent.unwindData.ClosePercent = ToRemainingClosePercent(
|
||||
swapEvent.unwindData.ClosePercent,
|
||||
@@ -2100,9 +1980,9 @@ namespace YLErp.Modules.SwapModule
|
||||
swapEvent.unwindData.FlowEvents = flowList;
|
||||
if (eventType == (int)SwapEventTypeEnum.平仓)
|
||||
{
|
||||
if (NormalizeFullCloseRequest(swapEvent.unwindData))
|
||||
if (UnwindNormalizer.NormalizeFullCloseRequest(swapEvent.unwindData))
|
||||
{
|
||||
RecalculateNormalizedUnwindAmounts(swapEvent.unwindData);
|
||||
UnwindNormalizer.RecalculateNormalizedUnwindAmounts(swapEvent.unwindData);
|
||||
}
|
||||
}
|
||||
if (eventType == (int)SwapEventTypeEnum.互换)
|
||||
@@ -2131,7 +2011,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
var remainingStockEqvNotional = Math.Round(td.StockEqvNotional - Convert.ToDouble(swapEvent.unwindData.CloseNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
var remainingTradeAmount = td.TradeAmount - Convert.ToDouble(swapEvent.unwindData.CloseQty);
|
||||
var isFullClose = IsFullCloseAfterDeduction(swapEvent.unwindData, remainingStockEqvNotional, remainingTradeAmount);
|
||||
var isFullClose = UnwindNormalizer.IsFullCloseAfterDeduction(swapEvent.unwindData, remainingStockEqvNotional, remainingTradeAmount);
|
||||
if (isFullClose)
|
||||
{
|
||||
td.TradeStatus = "已平仓";
|
||||
@@ -2176,7 +2056,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
NormalizeEventUnwindDate(unwindData);
|
||||
UnwindNormalizer.NormalizeEventUnwindDate(unwindData);
|
||||
if (eventType == (int)SwapEventTypeEnum.互换)
|
||||
{
|
||||
ValidateIncomeValueDate(unwindData, td);
|
||||
@@ -2190,9 +2070,9 @@ namespace YLErp.Modules.SwapModule
|
||||
unwindData.ClosePercent = ToRemainingClosePercent(unwindData.ClosePercent, unwindData.NotionalValue, unwindData.PosiNotionalValue);
|
||||
if (eventType == (int)SwapEventTypeEnum.平仓)
|
||||
{
|
||||
if (NormalizeFullCloseRequest(unwindData))
|
||||
if (UnwindNormalizer.NormalizeFullCloseRequest(unwindData))
|
||||
{
|
||||
RecalculateNormalizedUnwindAmounts(unwindData);
|
||||
UnwindNormalizer.RecalculateNormalizedUnwindAmounts(unwindData);
|
||||
}
|
||||
}
|
||||
string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费;
|
||||
@@ -2225,11 +2105,6 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
}
|
||||
|
||||
private static void NormalizeEventUnwindDate(UnwindData unwindData)
|
||||
{
|
||||
unwindData.UnwindDate = unwindData.ValueDate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存平仓/互换事件
|
||||
/// </summary>
|
||||
@@ -2243,7 +2118,7 @@ namespace YLErp.Modules.SwapModule
|
||||
throw new ServiceException("未找到交易信息");
|
||||
}
|
||||
var flowList = new List<swap_flow_event>(unwindData.FlowEvents);
|
||||
NormalizeSettledInterestAmounts(flowList, eventType, eventResason);
|
||||
UnwindNormalizer.NormalizeSettledInterestAmounts(flowList, eventType, eventResason);
|
||||
unwindData.FlowEvents.Clear();
|
||||
// 落库展示用"占期初(original)"语义(A);计算链(费用递减/全平判定)用"占剩余(remaining)"语义(B)。
|
||||
// 序列化前把 ClosePercent 还原为 A,序列化后立即还原回 B 供后续使用。
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Consts;
|
||||
|
||||
namespace YLErp.Modules.SwapModule;
|
||||
|
||||
/// <summary>
|
||||
/// 平仓手续费计算——纯 static,无 this 依赖。
|
||||
/// 从 SwapDealService 提取,零行为变更。
|
||||
/// </summary>
|
||||
public static class TradingFeeCalc
|
||||
{
|
||||
public static decimal CalcInitTradingFee(swap_position oriPosition, UnwindData unwindData)
|
||||
{
|
||||
if (oriPosition == null || unwindData == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (oriPosition.PosiFeeType == 1)
|
||||
{
|
||||
return Math.Round(oriPosition.PosiTradingFeeUnit * unwindData.CloseQty, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
return Math.Round(oriPosition.PosiTradingFeeUnit / 100m * unwindData.CloseNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
public static decimal CalcInitTradingFeePending(swap_position oriPosition, swap_position position, UnwindData unwindData)
|
||||
{
|
||||
if (oriPosition == null || unwindData == null || oriPosition.PosiTradingFeeUnit == 0)
|
||||
{
|
||||
return position?.PosiTradingFeePending ?? 0;
|
||||
}
|
||||
|
||||
var closeBase = oriPosition.PosiFeeType == 1 ? unwindData.CloseQty : unwindData.CloseNotionalValue;
|
||||
var originalBase = oriPosition.PosiFeeType == 1 ? unwindData.NotionalQty : unwindData.NotionalValue;
|
||||
if (originalBase <= 0)
|
||||
{
|
||||
return position?.PosiTradingFeePending ?? 0;
|
||||
}
|
||||
|
||||
return Math.Round(oriPosition.PosiTradingFeePending * closeBase / originalBase, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using YLErp.Helpers;
|
||||
using YLErp.Modules.SwapModule.Margin;
|
||||
|
||||
namespace YLErp.Modules.SwapModule;
|
||||
|
||||
/// <summary>
|
||||
/// 平仓数据(UnwindData)规范化——纯 static,无 this 依赖。
|
||||
/// 从 SwapDealService 提取,零行为变更。
|
||||
/// </summary>
|
||||
internal static class UnwindNormalizer
|
||||
{
|
||||
internal static void NormalizeNotionalValues(UnwindData unwindData)
|
||||
{
|
||||
unwindData.NotionalValue = Math.Round(unwindData.NotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
unwindData.PosiNotionalValue = Math.Round(unwindData.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
unwindData.CloseNotionalValue = Math.Round(unwindData.CloseNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
internal static bool NormalizeFullCloseRequest(UnwindData unwindData)
|
||||
{
|
||||
if (unwindData.CloseMethod != (int)CloseMethodEnum.全部平仓
|
||||
&& unwindData.ClosePercent < 1
|
||||
&& !(unwindData.PositionQty > 0 && unwindData.CloseQty >= unwindData.PositionQty)
|
||||
&& !(unwindData.PosiNotionalValue > 0 && unwindData.CloseNotionalValue >= unwindData.PosiNotionalValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var closeQty = unwindData.CloseQty;
|
||||
var closeNotionalValue = unwindData.CloseNotionalValue;
|
||||
unwindData.ClosePercent = 1;
|
||||
if (unwindData.PositionQty > 0) unwindData.CloseQty = unwindData.PositionQty;
|
||||
if (unwindData.PosiNotionalValue > 0) unwindData.CloseNotionalValue = unwindData.PosiNotionalValue;
|
||||
return closeQty != unwindData.CloseQty || closeNotionalValue != unwindData.CloseNotionalValue;
|
||||
}
|
||||
|
||||
internal static void RecalculateNormalizedUnwindAmounts(UnwindData unwindData)
|
||||
{
|
||||
var floatLeg = unwindData.FlowEvents.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode));
|
||||
if (floatLeg == null || floatLeg.PosiGrossPrice == 0) return;
|
||||
|
||||
var input = new UnwindInput
|
||||
{
|
||||
Multiplier = ConsGlobal.InstrumentType.IsBond(floatLeg.UnderlyingInstrumentType) ? 100 : 1,
|
||||
PosiGrossPrice = floatLeg.PosiGrossPrice,
|
||||
TradingAmountAvg = floatLeg.TradingAmountAvg,
|
||||
CloseQty = unwindData.CloseQty,
|
||||
PositionQty = unwindData.PositionQty,
|
||||
ContractSize = floatLeg.ContractSize,
|
||||
CloseNotionalValue = unwindData.CloseNotionalValue,
|
||||
PayDirection = floatLeg.PayDirection,
|
||||
PositionType = floatLeg.PositionType,
|
||||
TradingFee = floatLeg.TradingFee.ToString(),
|
||||
TradingFeePending = floatLeg.TradingFeePending.ToString(),
|
||||
DividendIn = floatLeg.DividendIn.ToString()
|
||||
};
|
||||
foreach (var leg in unwindData.FlowEvents.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)))
|
||||
{
|
||||
var target = MarginModes.Contains(leg.InterestMode)
|
||||
? input.MarginLegs
|
||||
: input.InterestLegs;
|
||||
target.Add(new LegInput { InterestClosePnL = leg.InterestClosePnL });
|
||||
}
|
||||
|
||||
var result = FrontendCalcReference.CalcUnwind(input);
|
||||
floatLeg.MarkClosePnl = result.MarkClosePnl;
|
||||
unwindData.SwapCloseAmount = result.SwapCloseAmount;
|
||||
unwindData.SwapRealizedPnL = result.SwapRealizedPnL;
|
||||
unwindData.SwapMarginRebatePnl = result.SwapMarginRebatePnl;
|
||||
}
|
||||
|
||||
internal static bool IsFullCloseAfterDeduction(UnwindData unwindData, double remainingNotional, double remainingQuantity)
|
||||
{
|
||||
return unwindData.ClosePercent == 1 || (remainingNotional == 0 && remainingQuantity == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手工平仓、手工互换及收益结算的利息事件按金额两位落库。
|
||||
/// 自动平仓保留原有计算与落库口径,不适用本阶段的手工结算规则。
|
||||
/// </summary>
|
||||
internal static bool NormalizeSettledInterestAmounts(IEnumerable<swap_flow_event> flowEvents, int eventType, string eventReason)
|
||||
{
|
||||
if ((eventType != (int)SwapEventTypeEnum.平仓 && eventType != (int)SwapEventTypeEnum.互换)
|
||||
|| eventReason == "系统操作_自动平仓")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var flowEvent in flowEvents.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)))
|
||||
{
|
||||
flowEvent.InterestPrincipal = Math.Round(flowEvent.InterestPrincipal, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
flowEvent.InterestAmount = Math.Round(flowEvent.InterestAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
flowEvent.TdInterestAmount = Math.Round(flowEvent.TdInterestAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
flowEvent.InterestClosePnL = Math.Round(flowEvent.InterestClosePnL, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
flowEvent.InterestFee = Math.Round(flowEvent.InterestFee, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void NormalizeEventUnwindDate(UnwindData unwindData)
|
||||
{
|
||||
unwindData.UnwindDate = unwindData.ValueDate;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user