feat(bond): 支持股票和基金现金分红纳入债券支付计算 - 收紧过度开发行为

- 实现股票/基金现金分红数据从 ex_dividend_info 同步到 BondPayment
- 新增公司行为去重机制,避免镜像任务完成后重复计息
- 统一现金分红存储口径为"每 10 份派现金额",保持与同步任务一致性
- 修改 CalcPayment 方法,股票/基金分红需除以 10 转换实际现金金额
- 添加单元测试验证债券票息和股票/基金分红的不同计算方式
- 更新文档注释说明"每 10 份派现金额"存储规范
- 修复公司行为生效日处理逻辑,确保正确应用除权系数
- 扩展测试覆盖股票类证券的公司行为处理场景
This commit is contained in:
张名锐
2026-08-20 13:30:36 +08:00
parent aa3548e77f
commit b2f4e16782
13 changed files with 384 additions and 368 deletions
@@ -78,7 +78,7 @@ namespace YLErp.Modules.SwapModule
}
/// <summary>
/// 从已经确认的 Fund EOD 快照恢复实时浮动腿的有效基线。
/// 从已经确认的 Stock/Fund EOD 快照恢复实时浮动腿的有效基线。
/// 该方法只复制 EOD 已落库的数量、价格、名义本金及累计分红/待结费用,不再次计算
/// 公司行动系数,因此是幂等的。例:原 1000 份、期初价 100,10 送 10 后 EOD 为
/// 2000 份、50;下一日盘中直接恢复 2000/50,不能再变成 4000/25。
@@ -112,7 +112,7 @@ namespace YLErp.Modules.SwapModule
}
/// <summary>
/// 查询 valueDate 之前最近一份有效 Fund EOD 快照,作为盘中操作的日初基线。
/// 查询 valueDate 之前最近一份有效 Stock/Fund EOD 快照,作为盘中操作的日初基线。
/// 必须严格使用 &lt; valueDate:试算日当天的 EOD 可能尚未完成,或是重收盘留下的待重建数据,
/// 不能反向覆盖盘中实时持仓。例:D 日 10 送 10 后 EOD 为 2000 份/50D+1 盘中读取 D
/// D 日盘中只读取 D-1,不会误把 D 日半成品当成已生效基线。Invalid 明细始终排除。
@@ -446,25 +446,7 @@ namespace YLErp.Modules.SwapModule
.ToList();
}
/// <summary>
/// 查找结算日有效的公司行为记录;测试可替换为内存数据。
/// settleDate 必须是收盘作业使用的日期边界(通常为 00:00:00),这里沿用完整
/// DateTime 相等匹配;历史数据若带时分秒或为空,不会被静默归入当天,需在作业前
/// 通过数据预检查处理,而不是让收盘在错误基线上继续计算。
/// </summary>
protected virtual List<ex_dividend_info> FindExDividendInfos(DateTime settleDate)
{
return DbContext.ex_dividend_info
.Where(x => x.ValidStatus
&& x.EffectiveDate.HasValue
&& x.EffectiveDate.Value == settleDate.Date)
.ToList();
}
/// <summary>
/// 查询登记日或真实生效日命中的公司行为。保留 FindExDividendInfos 这个
/// 可替换入口,测试和历史调用方可以继续注入内存数据。
/// </summary>
/// <summary>查询登记日或真实生效日命中的有效公司行为。</summary>
protected virtual List<ex_dividend_info> FindCorporateActionInfos(DateTime settleDate)
{
return DbContext.ex_dividend_info
@@ -490,19 +472,6 @@ namespace YLErp.Modules.SwapModule
UpdateDbOption(swapEvent);
}
/// <summary>
/// 查找登记日公司行为。登记日只创建待生效审计事件,不参与当日持仓系数计算;
/// EffectiveDate 到达后才由 FindExDividendInfos 命中并改变 Stock/Fund 基线。
/// </summary>
protected virtual List<ex_dividend_info> FindRegistrationExDividendInfos(DateTime settleDate)
{
return FindCorporateActionInfos(settleDate)
.Where(x => x.ValidStatus
&& x.ExDividendDate.HasValue
&& x.ExDividendDate.Value.Date == settleDate.Date)
.ToList();
}
/// <summary>
/// 获取公司行为公式使用的收盘价。
/// EffectiveDate 是真正切换持仓基线的日期,但除权系数的收盘价仍属于登记日
@@ -523,16 +492,6 @@ namespace YLErp.Modules.SwapModule
return Convert.ToDecimal(closePrice);
}
/// <summary>
/// 公司行为现金分红使用的系统税率,小数形式。
/// 系统配置按百分数存储(例如 13 表示 13%),公司行为公式需要 0.13;送股本身
/// 不受该税率影响;此处只负责读取并换算,不在异常时擅自默认为 0。
/// </summary>
protected virtual decimal GetDividendTaxRate()
{
return new DividendService(this).GetDividendTaxRateDecimal();
}
public static bool IsCorporateActionInstrument(string instrumentType)
{
// TRS 公司行为本期只覆盖 Stock/Fund。TBonds 等类型继续走原债券付息链路,
@@ -700,7 +659,7 @@ namespace YLErp.Modules.SwapModule
flowEvents);
// 现金分红不在登记日直接读取 ex_dividend_info 累加。
// 同步任务会把 GiveCashAmount/10 写入 bond_payment_infoCopy/Update EOD 在
// 同步任务会把 GiveCashAmount(每 10 份派现金额)写入 bond_payment_infoCopy/Update EOD 在
// EffectiveDate 通过 CalcBondPayment 命中该行并生成 TdPosiDividend。
// 这样登记日快照不提前变化,也不会与债券付息/平仓链路重复计算。
RecordCorporateActionEvents(
@@ -710,6 +669,9 @@ namespace YLErp.Modules.SwapModule
registrationInfos,
exDividendInfos,
settleDate);
// 登记日 EOD 仍保存除权前快照,但下一交易日开盘读取的实时浮动腿需要
// 先切换到生效后的 Q/P。该更新基于当日 EOD 恢复后再套系数,重收盘不会重复放大。
UpdateRealtimeCorporateActionPositions(td, curEodPosis, registrationInfos, exDividendInfos, settleDate);
var posiLongNotional = curEodPosis.Where(s => s.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
var posiShortNotional = curEodPosis.Where(s => s.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue);
var closePosiNotional = curEodPosis.Where(s => s.TdCloseQty > 0).Sum(s => s.TdCloseQty * s.ContractSize * s.PosiGrossPrice);
@@ -741,7 +703,7 @@ namespace YLErp.Modules.SwapModule
}
/// <summary>
/// 把上一实际 EOD 复制成“当日开盘基线”,并在需要时套用当日生效的 Fund 公司行为。
/// 把上一实际 EOD 复制成“当日开盘基线”,并在需要时套用当日生效的 Stock/Fund 公司行为。
/// 原始上一 EOD 只读保留在数据库中,确保登记日 EOD 报表仍展示除权前 Q/P。
/// 例如 1000 份/100 元、10 送 10 的记录在 8 月 14 日 EOD 仍是 1000/100
/// 8 月 17 日处理当日流水前,内存基线先转为 2000/50,再平仓 300 份得到 1700/50。
@@ -765,10 +727,8 @@ namespace YLErp.Modules.SwapModule
.Select(x => x.Clone())
.ToList();
// 应用公司行为
ApplyFundCorporateActions(
ApplyCorporateActions(
openingPositions,
Array.Empty<eod_swap_position>(),
Array.Empty<swap_flow_event>(),
exDividendByCode,
settleDate);
return openingPositions;
@@ -791,10 +751,8 @@ namespace YLErp.Modules.SwapModule
/// EffectiveDate 再生成开盘基线。
/// </para>
/// </summary>
protected void ApplyFundCorporateActions(
protected void ApplyCorporateActions(
IEnumerable<eod_swap_position> positions,
IReadOnlyCollection<eod_swap_position> previousEodPositions,
IReadOnlyCollection<swap_flow_event> flowEvents,
IReadOnlyDictionary<string, ex_dividend_info> exDividendByCode,
DateTime settleDate)
{
@@ -803,8 +761,9 @@ namespace YLErp.Modules.SwapModule
return;
}
// 获取系统税率
var dividendTaxRate = GetDividendTaxRate();
// TODO: 现金分红税率接入后,仅价格调整模式需要读取税率;TRS 现金模式下不参与除权系数。
// var dividendTaxRate = GetDividendTaxRate();
var dividendTaxRate = 0m;
foreach (var position in positions)
{
if (position.PosiDirection <= 0
@@ -824,41 +783,7 @@ namespace YLErp.Modules.SwapModule
if (corporateActionClosePrice <= 0)
{
throw new InvalidOperationException(
$"Fund 标的【{position.UnderlyingCode}】登记日【{dividendInfo.ExDividendDate:yyyy-MM-dd}】缺少有效收盘价,无法执行除权");
}
// 当天已有 EOD 且该腿没有流水时,Copy 分支不会恢复价格字段。
// 先还原前一日基线,避免同一结算日重跑时再次除权。
var hasPositionFlow = flowEvents.Any(x => x.PositionId == position.PositionId);
var previousPosition = previousEodPositions.FirstOrDefault(
x => x.PositionId == position.PositionId);
// 没有流水 且 有前一日持仓时,恢复前一日价格字段
if (!hasPositionFlow && previousPosition != null)
{
position.PosiQuantity = previousPosition.PosiQuantity;
position.PosiGrossPrice = previousPosition.PosiGrossPrice;
position.PosiNetPrice = previousPosition.PosiNetPrice;
position.PosiNetFeePrice = previousPosition.PosiNetFeePrice;
position.PosiNetNoFeePrice = previousPosition.PosiNetNoFeePrice;
}
var denominator = 10m + dividendInfo.GiveShareAmount + dividendInfo.RationedSharesAmount;
if (denominator == 0)
{
throw new InvalidOperationException(
$"Fund 标的【{position.UnderlyingCode}】在【{settleDate:yyyy-MM-dd}】的除权份额参数导致除数为 0");
}
// 计算除权系数
var factors = DividendService.CalculateCorporateActionFactors(
dividendInfo,
corporateActionClosePrice,
dividendTaxRate,
adjustCashDividendPrice: false);
if (factors.PriceRatio <= 0)
{
throw new InvalidOperationException(
$"Fund 标的【{position.UnderlyingCode}】在【{settleDate:yyyy-MM-dd}】计算得到无效除权系数");
$"Stock/Fund 标的【{position.UnderlyingCode}】登记日【{dividendInfo.ExDividendDate:yyyy-MM-dd}】缺少有效收盘价,无法执行除权");
}
// Excel 公式 口径:PriceRatio 是“登记日收盘价 / 除权参考价”,
@@ -866,37 +791,24 @@ namespace YLErp.Modules.SwapModule
// 配股已经进入 价格参考价,所以即使没有送股,配股也会调整 TRS 数量;
// 现金分红不影响 TRS Stock/Fund 期初价格,现金权益由独立分红字段处理。
var originalQuantity = position.PosiQuantity;
position.PosiQuantity = Math.Round(
originalQuantity * factors.PriceRatio,
12,
MidpointRounding.AwayFromZero);
// 计算公司行为发生后的 Q/P
var adjusted = CalculateCorporateActionValues(
position.PosiQuantity,
position.PosiGrossPrice,
position.PosiNetPrice,
position.PosiNetFeePrice,
position.PosiNetNoFeePrice,
dividendInfo,
corporateActionClosePrice,
dividendTaxRate,
GetStorageDeliveryPriceRound(position.UnderlyingInstrumentType, position.UnderlyingCode));
position.PosiQuantity = adjusted.Quantity;
position.TdChangedQty = position.PosiQuantity - originalQuantity;
var storagePriceRound = GetStorageDeliveryPriceRound(
position.UnderlyingInstrumentType,
position.UnderlyingCode);
position.PosiGrossPrice = Math.Round(
position.PosiGrossPrice / factors.PriceRatio,
storagePriceRound,
MidpointRounding.AwayFromZero);
position.PosiNetPrice = Math.Round(
position.PosiNetPrice / factors.PriceRatio,
ConsGlobal.PriceRound,
MidpointRounding.AwayFromZero);
if (position.PosiNetFeePrice.HasValue)
{
position.PosiNetFeePrice = Math.Round(
position.PosiNetFeePrice.Value / factors.PriceRatio,
ConsGlobal.PriceRound,
MidpointRounding.AwayFromZero);
}
if (position.PosiNetNoFeePrice.HasValue)
{
position.PosiNetNoFeePrice = Math.Round(
position.PosiNetNoFeePrice.Value / factors.PriceRatio,
ConsGlobal.PriceRound,
MidpointRounding.AwayFromZero);
}
position.PosiGrossPrice = adjusted.GrossPrice;
position.PosiNetPrice = adjusted.NetPrice;
position.PosiNetFeePrice = adjusted.NetFeePrice;
position.PosiNetNoFeePrice = adjusted.NetNoFeePrice;
var shortRatio = DirectionRatio.LongShort(position.PositionType);
var directionRatio = DirectionRatio.ReceivePay(position.PosiDirection);
@@ -998,16 +910,79 @@ namespace YLErp.Modules.SwapModule
}
var closePrice = GetFundCorporateActionClosePrice(info, position.PosiGrossPrice);
ApplyFundCorporateActionToPosition(
ApplyCorporateActionToPosition(
position,
info,
closePrice,
GetDividendTaxRate());
0m);
}
return positions;
}
/// <summary>
/// 同步公司行为后的实时浮动腿。
/// 登记日只更新下一交易日 BOD 使用的实时 Q/P,不改当日已落库的 EOD;
/// 生效日则把已调整的 EOD 复制到实时腿。每次都先从当日 EOD 恢复,保证重跑幂等。
/// </summary>
private void UpdateRealtimeCorporateActionPositions(
trade td,
IReadOnlyCollection<eod_swap_position> currentEodPositions,
IReadOnlyCollection<ex_dividend_info> registrationInfos,
IReadOnlyCollection<ex_dividend_info> effectiveInfos,
DateTime settleDate)
{
if (td == null || currentEodPositions == null || currentEodPositions.Count == 0)
{
return;
}
// 登记日收盘后即切换实时 BOD。EffectiveDate 只用于确认这条记录仍是未来生效的
// 公司行为;无论登记日与生效日之间有一个还是多个非交易日,都不能漏掉这次切换。
var pendingInfos = (registrationInfos ?? Array.Empty<ex_dividend_info>())
.Where(x => x.EffectiveDate.HasValue && x.EffectiveDate.Value.Date > settleDate.Date)
.ToList();
var appliedInfos = effectiveInfos ?? Array.Empty<ex_dividend_info>();
foreach (var eod in currentEodPositions.Where(x => x != null
&& x.PosiDirection > 0
&& IsTrsCorporateActionInstrument(x.UnderlyingInstrumentType)
&& !string.IsNullOrWhiteSpace(x.UnderlyingCode)))
{
var realtime = DbContext.swap_position.FirstOrDefault(x => x.SwapTradeId == td.id
&& !x.Invalid
&& !x.IsInitial
&& x.PositionId == eod.PositionId);
if (realtime == null)
{
continue;
}
var pending = pendingInfos.FirstOrDefault(x => string.Equals(
x.UnderlyingCode, eod.UnderlyingCode, StringComparison.OrdinalIgnoreCase));
if (pending != null)
{
// 必须从登记日 EOD 基线生成下一交易日 BOD,而不是在旧实时腿上继续套系数;
// 这样 100000/100 只会变成一次 200000/50,并且不会把初始腿改掉。
// 先将实时腿恢复为登记日 EOD 的旧基线,再只对实时腿应用一次公司行为。
// EOD 仍保持除权前快照;因此 7/10 EOD=100000/100,而 7/13 BOD=200000/50。
var baseline = eod.Clone();
UpdateSwapPositionWithRealTime(baseline);
var closePrice = GetFundCorporateActionClosePrice(pending, baseline.PosiGrossPrice);
ApplyCorporateActionToPosition(realtime, pending, closePrice, 0m);
continue;
}
var applied = appliedInfos.FirstOrDefault(x => string.Equals(
x.UnderlyingCode, eod.UnderlyingCode, StringComparison.OrdinalIgnoreCase));
if (applied != null)
{
// 生效日 EOD 已经完成 Q/P 调整,实时腿直接同步最终快照,不再二次套系数。
UpdateSwapPositionWithRealTime(eod.Clone());
}
}
}
/// <summary>
/// 写入公司行为生命周期审计事件。
/// 登记日:保存调整前快照并标记 Applied=false
@@ -1212,72 +1187,66 @@ namespace YLErp.Modules.SwapModule
return SwapEventService.BuildCorporateActionEventReason(data);
}
/// <summary>
/// 兼容旧测试/扩展调用的直接现金分红辅助方法。
/// GiveCashAmount 按每 10 份金额计算:1000 份、每 10 份派 10,结果为 1000。
/// 当前生产 SwapPositionCompose 不再调用此方法:公司行为现金分红由同步任务
/// 写入 bond_payment_infoEffectiveDate 收盘通过 CalcBondPayment 进入 EOD
/// 以避免登记日提前入账及与债券付息链路重复。保留方法是为了不破坏已有测试替身
/// 或外部扩展类的编译契约;新增业务代码不得再直接传入 ex_dividend_info。
/// </summary>
protected void ApplyFundCashDividends(
IReadOnlyCollection<eod_swap_position> currentEodPositions,
IReadOnlyCollection<eod_swap_position> previousEodPositions,
IReadOnlyDictionary<string, ex_dividend_info> exDividendByCode,
DateTime settleDate)
/// <summary>公司行为调整后的持仓 Q/P 结果,供 EOD、实时腿和盘中平仓共用。</summary>
private readonly struct CorporateActionValues
{
if (currentEodPositions == null
|| exDividendByCode == null
|| exDividendByCode.Count == 0)
public CorporateActionValues(decimal quantity, decimal grossPrice, decimal netPrice, decimal? netFeePrice, decimal? netNoFeePrice)
{
return;
Quantity = quantity;
GrossPrice = grossPrice;
NetPrice = netPrice;
NetFeePrice = netFeePrice;
NetNoFeePrice = netNoFeePrice;
}
var dividendTaxRate = GetDividendTaxRate();
var previousList = previousEodPositions ?? Array.Empty<eod_swap_position>();
foreach (var current in currentEodPositions)
public decimal Quantity { get; }
public decimal GrossPrice { get; }
public decimal NetPrice { get; }
public decimal? NetFeePrice { get; }
public decimal? NetNoFeePrice { get; }
}
/// <summary>
/// 统一计算公司行为后的 Q/P。EOD、实时腿和盘中平仓只负责提供基线,
/// 不再各自复制数量、毛价和净价的调整公式。
/// </summary>
private static CorporateActionValues CalculateCorporateActionValues(
decimal quantity,
decimal grossPrice,
decimal netPrice,
decimal? netFeePrice,
decimal? netNoFeePrice,
ex_dividend_info dividendInfo,
decimal closePrice,
decimal dividendTaxRate,
int grossPriceRound)
{
var factors = DividendService.CalculateCorporateActionFactors(
dividendInfo,
closePrice,
dividendTaxRate,
adjustCashDividendPrice: false);
if (factors.PriceRatio <= 0)
{
if (current == null
|| current.PosiDirection == 0
|| !IsTrsCorporateActionInstrument(current.UnderlyingInstrumentType)
|| string.IsNullOrWhiteSpace(current.UnderlyingCode)
|| !exDividendByCode.TryGetValue(current.UnderlyingCode, out var dividendInfo)
|| !dividendInfo.ExDividendDate.HasValue
|| dividendInfo.ExDividendDate.Value.Date != settleDate.Date)
{
continue;
}
var previous = previousList.FirstOrDefault(
x => x != null && x.PositionId == current.PositionId);
var entitlementQuantity = previous?.PosiQuantity ?? current.PosiQuantity;
var directionRatio = DirectionRatio.ReceivePay(current.PosiDirection);
var currentDividend = entitlementQuantity > 0m
? entitlementQuantity / 10m
* dividendInfo.GiveCashAmount
* (1m - dividendTaxRate)
* directionRatio
: 0m;
// 当日浮动端分红。公司行为现金分红采用现金模式:不调期初价格,
// 只增加待实现分红,支付日仍由既有 DealDividends/付息链路结算。
current.TdPosiDividend = RoundMoney(currentDividend);
var previousDividendSum = previous?.PosiDividendSum ?? 0m;
// 浮动端平仓盈亏·分红未实现 = 前日待实现 + 当日公司行为分红
// - 当日已实现分红;本次公司行为尚未支付,因此不能写入 RealizedDividend。
current.PosiDividendSum = current.PosiQuantity > 0m
? RoundMoney(previousDividendSum + current.TdPosiDividend - current.TdCloseDividend)
: 0m;
// 现金模式不从 PosiMtmPnL 剥离分红:价格没有被除权,分红只存在于待实现字段。
current.PosiProfitSum = RoundMoney(MtmCalc.ReturnLegProfitSum(
current.PosiMtmPnL,
current.PosiDividendSum,
current.PosiFeePending));
SetFloatingRealizedPnl(current);
current.SwapPositionValue = RoundMoney(PositionValueCalc.Calc(
current.InterestProfitSum,
current.PosiProfitSum));
throw new InvalidOperationException(
$"标的【{dividendInfo?.UnderlyingCode}】计算得到无效除权系数");
}
var adjustedQuantity = Math.Round(quantity * factors.PriceRatio, 12, MidpointRounding.AwayFromZero);
var adjustedGrossPrice = Math.Round(grossPrice / factors.PriceRatio, grossPriceRound, MidpointRounding.AwayFromZero);
var adjustedNetPrice = Math.Round(netPrice / factors.PriceRatio, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
var adjustedNetFeePrice = netFeePrice.HasValue
? Math.Round(netFeePrice.Value / factors.PriceRatio, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero)
: (decimal?)null;
var adjustedNetNoFeePrice = netNoFeePrice.HasValue
? Math.Round(netNoFeePrice.Value / factors.PriceRatio, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero)
: (decimal?)null;
return new CorporateActionValues(
adjustedQuantity,
adjustedGrossPrice,
adjustedNetPrice,
adjustedNetFeePrice,
adjustedNetNoFeePrice);
}
/// <summary>
@@ -1288,7 +1257,7 @@ namespace YLErp.Modules.SwapModule
/// 现金模式调用公式时使用 adjustCashDividendPrice=false,现金权益只进入分红字段,
/// 不改变 Stock/Fund 的期初价格。
/// </summary>
public static bool ApplyFundCorporateActionToPosition(
public static bool ApplyCorporateActionToPosition(
swap_position position,
ex_dividend_info dividendInfo,
decimal corporateActionClosePrice,
@@ -1303,44 +1272,21 @@ namespace YLErp.Modules.SwapModule
return false;
}
var factors = DividendService.CalculateCorporateActionFactors(
var adjusted = CalculateCorporateActionValues(
position.PosiQuantity,
position.PosiGrossPrice,
position.PosiNetPrice,
position.PosiNetFeePrice,
position.PosiNetNoFeePrice,
dividendInfo,
corporateActionClosePrice,
dividendTaxRate,
adjustCashDividendPrice: false);
if (factors.PriceRatio <= 0)
{
throw new InvalidOperationException(
$"Fund 标的【{position.UnderlyingCode}】计算得到无效除权系数");
}
var originalQuantity = position.PosiQuantity;
position.PosiQuantity = Math.Round(
originalQuantity * factors.PriceRatio,
12,
MidpointRounding.AwayFromZero);
position.PosiGrossPrice = Math.Round(
position.PosiGrossPrice / factors.PriceRatio,
ConsGlobal.SwapDeliveryPriceRound,
MidpointRounding.AwayFromZero);
position.PosiNetPrice = Math.Round(
position.PosiNetPrice / factors.PriceRatio,
ConsGlobal.PriceRound,
MidpointRounding.AwayFromZero);
if (position.PosiNetFeePrice.HasValue)
{
position.PosiNetFeePrice = Math.Round(
position.PosiNetFeePrice.Value / factors.PriceRatio,
ConsGlobal.PriceRound,
MidpointRounding.AwayFromZero);
}
if (position.PosiNetNoFeePrice.HasValue)
{
position.PosiNetNoFeePrice = Math.Round(
position.PosiNetNoFeePrice.Value / factors.PriceRatio,
ConsGlobal.PriceRound,
MidpointRounding.AwayFromZero);
}
ConsGlobal.SwapDeliveryPriceRound);
position.PosiQuantity = adjusted.Quantity;
position.PosiGrossPrice = adjusted.GrossPrice;
position.PosiNetPrice = adjusted.NetPrice;
position.PosiNetFeePrice = adjusted.NetFeePrice;
position.PosiNetNoFeePrice = adjusted.NetNoFeePrice;
position.PosiNotionalValue = Math.Round(
position.PosiGrossPrice * position.PosiQuantity * position.ContractSize,
ConsGlobal.MoneyRound,