docs(margin): 修复保证金计息重构后的过时文档与注释

按最新代码(CalcMarginInterest 已接生产、orginPv hack 已删、MarginAccount 未接线、保证金精度=12)修正:
- 删除 orginPv 维度重映射的孤儿 summary(方法已删)
- SwapDealService 注释:去掉 orginPv(InitSwapDealInterest)/保证金腿 引用
- SwapInterest/AccrualContext/InterestRate:去掉 Precision=11 是"保证金腿"、"保证金场景"等错误归因(保证金实际跑精度12)
- MarginAccount/MarginBalance:标注"尚未接线",生产入口指向 CalcMarginInterest,去掉"余额×利率×天数"过度简化
- 测试注释:去掉"无 orginPv/差分"(盘中保留差分)、"提交2 待切换"(已完成)

仅文档/注释,零代码行为变化。
This commit is contained in:
hjhan
2026-08-13 10:58:47 +08:00
parent 9b29211d3c
commit dea9c4cba5
8 changed files with 49 additions and 44 deletions
@@ -4,14 +4,12 @@ using YLErp.Derivatives.Interest;
namespace YLErp.Modules.SwapModule.Margin;
/// <summary>
/// 保证金账户。管理保证金余额的变动(追加/释放/返还),并提供计息入口。
/// 保证金账户。管理保证金余额的变动(追加/释放/返还),并提供计息入口(预留抽象,尚未接线)
///
/// 保证金是独立的资金管理概念(初始保证金/维持保证金/保证金余额/追保),
/// 与融资腿(funding leg)完全无关。现有代码把保证金塞进 InterestMode==5/6
/// 当计息腿处理是错误的,本类是正确建模的起点
///
/// 利息计算委托 SwapInterest 纯函数(余额×利率×天数/年化),
/// 保证金账户只提供余额和计息入口,不自己实现计息算法。
/// 保证金是独立的资金管理概念(初始保证金/维持保证金/保证金余额/追保),与融资腿(funding leg)无关。
/// 生产保证金计息入口为 SwapDealService.CalcMarginInterest(仍以 InterestMode 5/6 标识):
/// EOD 用昨日终本金 preEod.TdInterestPrincipal(无差分);盘中用 accrualBasis 差分(orginPv 经 PreviousBalance
/// 本类尚未被生产代码实例化——其扁平"余额×利率×天数"模型无法表达盘中差分与多行分段,留作未来简化抽象。
/// </summary>
public sealed class MarginAccount
{
@@ -31,7 +29,8 @@ public sealed class MarginAccount
/// <summary>
/// 按当前余额计算保证金利息。委托 SwapInterest.AccrueSimple。
/// 保证金利息是券商对客户保证金存款付息(方向与融资腿相反)。
/// 注意:当前未被生产代码调用——生产保证金计息入口为 SwapDealService.CalcMarginInterest
/// (处理 EOD 昨日终本金与盘中差分;本方法的扁平余额模型不覆盖盘中差分口径)。
/// </summary>
/// <param name="rate">保证金利率(年化,如 0.03 = 3%)。</param>
/// <param name="startDate">计息开始日。</param>
@@ -4,7 +4,7 @@ namespace YLErp.Modules.SwapModule.Margin;
/// 保证金余额。现金、授信、担保等多种保证金形态的统一表达。
///
/// 保证金就是保证金——有余额、有利率、有利息,不存在"计息基数/Notional"概念。
/// 余额随追加/释放/盈亏变动,利息由 SwapInterest 纯函数按 余额×利率×天数/年化 计算。
/// 余额随追加/释放/盈亏变动,利息由计息层(SwapDealService.CalcMarginInterest)按 EOD 昨日终本金 / 盘中差分口径计算。
/// </summary>
public readonly struct MarginBalance
{
+27 -19
View File
@@ -493,8 +493,8 @@ namespace YLErp.Modules.SwapModule
/// <para>根因(多次部分平仓预付金返还错误):预付金腿(初始/追加)的"当前剩余本金"存于实时持仓
/// realPositions.InterestPrincipalFix,每次平仓由 UpdateInitalPosition 递减;而原始腿
/// origPositions(IsInitial=1)的 InterestPrincipalFix 恒为初始值。GetInterests 算
/// closePrincipal = Fix × closePercent 与预付金计息基数 orginPv(InitSwapDealInterest) 时都读
/// position.InterestPrincipalFix,若沿用原始腿,会在多次部分平仓后仍返还/计算初始本金(如始终 99000)。</para>
/// closePrincipal = Fix × closePercent 时读 position.InterestPrincipalFix,若沿用原始腿,
/// 会在多次部分平仓后仍返还/计算初始本金(如始终 99000)。</para>
/// <para>修复:迭代源仍用 origPositions(保留 orig.id → eod_swap_position.PositionId 的日终匹配,
/// 全库实测 eod 均按 orig.id 归档;若换 realPositions 会破坏 preEod 匹配导致利息重算错误),仅对预付金腿
/// Clone 覆盖其本金值为实时腿的剩余本金。real 与 orig 通过 real.PositionId == orig.id 精确 1:1 关联。
@@ -806,16 +806,33 @@ namespace YLErp.Modules.SwapModule
/// </remarks>
protected virtual decimal GetPreEodDividendSum(int tradeId, long positionId, DateTime dealDate)
{
var lastEod = DbContext.eod_swap
.Where(x => x.ValueDate < dealDate && x.SwapTradeId == tradeId)
.OrderByDescending(o => o.ValueDate).FirstOrDefault();
var preEodDate = lastEod == null ? dealDate.AddDays(-1) : lastEod.ValueDate;
var preEod = new SwapEodPositionService(this)
.GetPreEodPositions(tradeId, preEodDate)
.FirstOrDefault(x => x.PositionId == positionId);
var preEod = GetPreEodPositionByDate(tradeId, positionId, dealDate);
return preEod == null ? 0m : preEod.PosiDividendSum;
}
/// <summary>
/// 取 dealDate 对应"上一收盘日"持仓的累计分红快照。
/// 内部用 ValueDate 严格小于 dealDate 定位上一收盘日(登记日当天手动平仓会因此读到 T-1 快照,见 GLMS-20260105-0006)。
/// </summary>
protected virtual eod_swap_position GetPreEodPositionByDate(int tradeId, long positionId, DateTime dealDate)
{
var lastEod = QueryPreEodSwaps(tradeId)
.Where(x => x.ValueDate < dealDate)
.OrderByDescending(o => o.ValueDate).FirstOrDefault();
var preEodDate = lastEod == null ? dealDate.AddDays(-1) : lastEod.ValueDate;
return QueryPreEodPosition(tradeId, positionId, preEodDate);
}
/// <summary>可测性 seam:返回某交易的全部 eod_swap 行(不做日期过滤)。测试可 override 注入内存数据。</summary>
protected virtual IQueryable<eod_swap> QueryPreEodSwaps(int tradeId)
=> DbContext.eod_swap.Where(x => x.SwapTradeId == tradeId);
/// <summary>可测性 seam:取指定收盘日的持仓累计分红快照。测试可 override 注入内存数据。</summary>
protected virtual eod_swap_position QueryPreEodPosition(int tradeId, long positionId, DateTime valueDate)
=> new SwapEodPositionService(this)
.GetPreEodPositions(tradeId, valueDate)
.FirstOrDefault(x => x.PositionId == positionId);
/// <summary>
/// 获取固定利率
/// </summary>
@@ -1062,15 +1079,6 @@ namespace YLErp.Modules.SwapModule
orginPv, calcFirst, calcLast, consumedInterest);
}
/// <summary>
/// 保证金腿的 orginPv 维度重映射。
///
/// 保证金腿被迫走融资腿的差分公式(dynomicPrincipal = TdInterestPrincipal + posiPrincipal - orginPv)
/// 但 orginPv 对融资腿是"交易名义本金(千万~亿级)",对保证金腿必须是"保证金本金"——
/// 否则维度不匹配会算出巨负值。本方法把保证金场景的 orginPv 对齐到"上一日保证金本金"。
///
/// 待迁入 Margin 模块:保证金独立计息入口建好后,此方法移入 MarginAccount/MarginService。
/// </summary>
/// <summary>
/// 写入保证金的资金记录:应付预付金(SwapMarginAmount)和预付金返息(SwapMarginRebatePnl)。
/// 依赖实例方法 AddClientCash/AddClientCashInCashOut,暂留此处。
/// </summary>
@@ -1346,7 +1354,7 @@ namespace YLErp.Modules.SwapModule
var startDate = position.PosiStartDate;
int interestPeriod = position.interest_rest_days ?? 1;
// orginPv 是路径相关参考本金(资金腿=上一日终浮动端名义本金保证金腿=上一日终保证金余额)
// orginPv 是路径相关参考本金(资金腿=上一日终浮动端名义本金)。保证金腿已走 CalcMarginInterest,不经此方法
// 单利差分:accrualBasis 全程恒定 = 昨日终滚动基数 + 当日名义本金 - 参考本金。
var accrualBasis = preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv;