test(swap): DealFloatPositions分红场景测试(盯市盈亏+分红+增值税)
扩展StubEodService override 3个外部依赖虚方法: - GetUnderlyingPrice: 注入固定标的价格 - GetUnderlyingData: 注入标的缓存(增值税率) - CalcBondPayment: 注入固定债券付息 新增3个分红场景测试: - DF_005: CopyEodPosition盯市盈亏=(price-grossPrice)×qty×方向 - DF_006: 分红计算 TdPosiDividend=payment/(1+tax)×(1-tax) - DF_007: 增值税调整(tax=6%): 100/1.06×0.94≈88.68 覆盖了cf2f8b6e分红重复计算修复所在的浮动腿归档链路。 验证: 108+3=111全通过。
This commit is contained in:
@@ -27,10 +27,39 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
private sealed class StubEodService : SwapEodPositionService
|
||||
{
|
||||
// 可注入的外部数据
|
||||
public decimal UnderlyingPrice { get; set; } = 1.00m;
|
||||
public decimal Vobp { get; set; } = 0m;
|
||||
public decimal BondPayment { get; set; } = 0m;
|
||||
public decimal TaxRate { get; set; } = 0m;
|
||||
public string UnderlyingCode { get; set; } = "210210.IB";
|
||||
|
||||
public StubEodService() : base(new OptUserInfo(0, nameof(DealFloatPositionsScenarioTest), OptUserFrom.UnitTest))
|
||||
{
|
||||
}
|
||||
|
||||
// override 外部依赖
|
||||
protected override decimal GetUnderlyingPrice(string code, DateTime settleDate, out decimal vobp)
|
||||
{
|
||||
vobp = Vobp;
|
||||
return UnderlyingPrice;
|
||||
}
|
||||
|
||||
protected override underlying_manager GetUnderlyingData(string underlyingCode)
|
||||
{
|
||||
return new underlying_manager { ValueAddedTax = TaxRate };
|
||||
}
|
||||
|
||||
protected override decimal CalcBondPayment(string underlyingCode, DateTime fromDate, DateTime toDate, decimal qty, int shortRatio, int directionRatio)
|
||||
{
|
||||
return BondPayment;
|
||||
}
|
||||
|
||||
protected override void SaveAllChanges() { }
|
||||
|
||||
protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType)
|
||||
=> 1.0;
|
||||
|
||||
// DealFloatPositions 和子方法都是 protected,通过 public 包装暴露
|
||||
public List<eod_swap_position> ExecuteDealFloatPositions(
|
||||
List<swap_position> posiList, List<swap_position> realPosiList,
|
||||
@@ -47,6 +76,12 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
return SaveCurrentEodInitalPosi(position, td, settleDate, preSettleDate, unwindEvents);
|
||||
}
|
||||
|
||||
public eod_swap_position ExecuteCopyEodPosition(
|
||||
eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate)
|
||||
{
|
||||
return CopyEodPosition(eod, curretEod, td, valueDate, preSettleDate);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -221,5 +256,132 @@ namespace YLErp.Modules.SwapModule
|
||||
Assert.Inconclusive($"CopyEodPosition 依赖外部数据源,需额外接缝。异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 场景5:CopyEodPosition 盯市盈亏计算(注入固定标的价格)
|
||||
// ================================================================
|
||||
|
||||
/// <summary>
|
||||
/// [DF_005] 无平仓日,标的价格变动 → PosiMtmPnL 正确反映浮动盈亏
|
||||
/// ---------------------------------------------------------------
|
||||
/// 前日持仓全价=1.002,当日标的价格=1.010(涨了)。
|
||||
/// 多头收取方向,PosiMtmPnL = (1.010 - 1.002) × 10000 × 1(ContractSize) × 1(shortRatio) × 1(directionRatio)
|
||||
/// = 0.008 × 10000 = 80
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DF_005_Copy分支盯市盈亏计算()
|
||||
{
|
||||
var service = new StubEodService();
|
||||
service.UnderlyingPrice = 1.010m; // 当日标的价格
|
||||
service.TaxRate = 0m;
|
||||
service.BondPayment = 0m;
|
||||
|
||||
var td = CreateTrade();
|
||||
var position = CreateFloatPosition();
|
||||
|
||||
var preEod = new eod_swap_position
|
||||
{
|
||||
id = 5001, SwapTradeId = SwapTradeId, PositionId = position.id,
|
||||
ValueDate = PreSettleDate, PosiQuantity = 10000m,
|
||||
PosiGrossPrice = 1.002m, PosiNetPrice = 1.005m,
|
||||
UnderlyingCode = "210210.IB", ContractSize = 1m,
|
||||
PositionType = (int)PositionTypeFlag.Long, PosiDirection = (int)SwapDirectionEnum.收取,
|
||||
PosiDividendSum = 0m, PosiFeePending = 0m, PosiProfitSum = 0m
|
||||
};
|
||||
|
||||
var result = service.ExecuteCopyEodPosition(preEod, null, td, TradeDate, PreSettleDate);
|
||||
|
||||
// PosiMtmPnL = (price - grossPrice) × qty × contractSize × shortRatio × directionRatio
|
||||
// 收取方向 directionRatio=1, 多头 shortRatio=1
|
||||
// = (1.010 - 1.002) × 10000 × 1 × 1 × 1 = 80
|
||||
AssertDecimalEqual(80m, result.PosiMtmPnL, 0.01m, "盯市盈亏");
|
||||
Console.WriteLine($"Copy分支盯市: PosiMtmPnL={result.PosiMtmPnL}((1.010-1.002)×10000=80)✅");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 场景6:CopyEodPosition 分红计算(注入固定付息)
|
||||
// ================================================================
|
||||
|
||||
/// <summary>
|
||||
/// [DF_006] 无平仓日,债券付息 → TdPosiDividend 和 PosiDividendSum 正确
|
||||
/// ---------------------------------------------------------------
|
||||
/// 注入 BondPayment=100(付息总额),增值税率=0。
|
||||
/// TdPosiDividend = 100 / (1+0) × (1-0) = 100。
|
||||
/// PosiDividendSum = preEod.PosiDividendSum(0) + TdPosiDividend(100) = 100。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DF_006_Copy分支分红计算()
|
||||
{
|
||||
var service = new StubEodService();
|
||||
service.UnderlyingPrice = 1.002m; // 价格不变
|
||||
service.TaxRate = 0m;
|
||||
service.BondPayment = 100m; // 付息100
|
||||
|
||||
var td = CreateTrade();
|
||||
var position = CreateFloatPosition();
|
||||
|
||||
var preEod = new eod_swap_position
|
||||
{
|
||||
id = 5001, SwapTradeId = SwapTradeId, PositionId = position.id,
|
||||
ValueDate = PreSettleDate, PosiQuantity = 10000m,
|
||||
PosiGrossPrice = 1.002m, PosiNetPrice = 1.005m,
|
||||
UnderlyingCode = "210210.IB", ContractSize = 1m,
|
||||
PositionType = (int)PositionTypeFlag.Long, PosiDirection = (int)SwapDirectionEnum.收取,
|
||||
PosiDividendSum = 0m, PosiFeePending = 0m, PosiProfitSum = 0m
|
||||
};
|
||||
|
||||
var result = service.ExecuteCopyEodPosition(preEod, null, td, TradeDate, PreSettleDate);
|
||||
|
||||
// TdPosiDividend = payment / (1+tax) × (1-tax) = 100 / 1 × 1 = 100
|
||||
AssertDecimalEqual(100m, result.TdPosiDividend, 0.01m, "当日分红");
|
||||
// PosiDividendSum = 前日(0) + 当日(100) = 100
|
||||
AssertDecimalEqual(100m, result.PosiDividendSum, 0.01m, "待实现分红累计");
|
||||
Console.WriteLine($"Copy分支分红: TdPosiDividend={result.TdPosiDividend}, PosiDividendSum={result.PosiDividendSum} ✅");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 场景7:分红增值税调整(税率≠0)
|
||||
// ================================================================
|
||||
|
||||
/// <summary>
|
||||
/// [DF_007] 分红含增值税调整:BondPayment=100, tax=6%(0.06)
|
||||
/// ---------------------------------------------------------------
|
||||
/// TdPosiDividend = 100 / (1+0.06) × (1-0.06) = 100/1.06×0.94 ≈ 88.68
|
||||
/// 验证增值税调整公式 cs:1508 payment / (1+tax) * (1-tax)。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DF_007_分红增值税调整()
|
||||
{
|
||||
var service = new StubEodService();
|
||||
service.UnderlyingPrice = 1.002m;
|
||||
service.TaxRate = 0.06m; // 增值税率6%
|
||||
service.BondPayment = 100m;
|
||||
|
||||
var td = CreateTrade();
|
||||
var position = CreateFloatPosition();
|
||||
|
||||
var preEod = new eod_swap_position
|
||||
{
|
||||
id = 5001, SwapTradeId = SwapTradeId, PositionId = position.id,
|
||||
ValueDate = PreSettleDate, PosiQuantity = 10000m,
|
||||
PosiGrossPrice = 1.002m, PosiNetPrice = 1.005m,
|
||||
UnderlyingCode = "210210.IB", ContractSize = 1m,
|
||||
PositionType = (int)PositionTypeFlag.Long, PosiDirection = 2,
|
||||
PosiDividendSum = 0m, PosiFeePending = 0m, PosiProfitSum = 0m
|
||||
};
|
||||
|
||||
var result = service.ExecuteCopyEodPosition(preEod, null, td, TradeDate, PreSettleDate);
|
||||
|
||||
// TdPosiDividend = 100 / 1.06 × 0.94 = 88.6792...
|
||||
decimal expected = Math.Round(100m / 1.06m * 0.94m, 2);
|
||||
AssertDecimalEqual(expected, result.TdPosiDividend, 0.01m, "增值税调整后分红");
|
||||
Console.WriteLine($"分红增值税调整: 付息100, 税率6% → TdPosiDividend={result.TdPosiDividend}(期望{expected})✅");
|
||||
}
|
||||
|
||||
private static void AssertDecimalEqual(decimal expected, decimal actual, decimal tolerance, string message = "")
|
||||
{
|
||||
Assert.IsTrue(Math.Abs(expected - actual) <= tolerance,
|
||||
$"{message} Expected: {expected}, Actual: {actual}, Diff: {expected - actual}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user