- EOD追保生成主流程:入口汇总/六处跳过原因/决策中间量(维持·已缴·目标·已补足·增量)/建腿落库/幂等清理/收尾汇总,顶层异常Error留栈 - 预付金模板取数:三级层级命中来源(交易绑定/客户默认/全局默认)+明细行命中(品种·期限档·x/y),null返回各留一句 - 规则15引擎:成功路径补收盘价→维持保证金,与既有无价格告警闭环 - EOD/实时客户资金:每客户一行维度分流结果(MarginWatchRule·可用资金·追保金额·可取资金及全部输入) - 预付金缺口查询:非规则15剔除数与按客户追加合计;预付金簿记:授信/现金拆分结果
543 lines
26 KiB
C#
543 lines
26 KiB
C#
using YLErp.Abstract.DataProviders;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.Enums;
|
|
using YLErp.Helpers;
|
|
using YLErp.Model.Enum;
|
|
using YLErp.Modules;
|
|
using YLErp.DBModels.Enums;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.Modules.MarginModule;
|
|
using YLErp.Modules.SwapModule.Margin;
|
|
using YLErp.Modules.TradeModule;
|
|
|
|
namespace YLErp.BLL.MarginCalculation
|
|
{
|
|
/// <summary>
|
|
/// 预付金计算
|
|
/// </summary>
|
|
public partial class MarginCalculationBase
|
|
{
|
|
protected static readonly IYcLogger logger = LogFactory.GetLogger("预付金计算");
|
|
|
|
protected readonly UnderlyingDataProvider _underlyingDataProvider;
|
|
|
|
protected MarginCalculationBase()
|
|
{
|
|
_underlyingDataProvider = new UnderlyingDataProvider();
|
|
}
|
|
|
|
public virtual List<trade_span> CalcClientMargin(CalcClientMarginReq req)
|
|
{
|
|
var clientSpanNews = new List<ClientSpan>();
|
|
|
|
using (var db = new YLContext())
|
|
{
|
|
if (req.tradeSpans != null && req.tradeSpans.Count > 0)
|
|
{
|
|
var tradeIds = req.tradeSpans.Select(t => t.TradeId).ToList();
|
|
var tradeList = db.trade.AsNoTracking().Where(t => tradeIds.Contains(t.id)).ToList();
|
|
var tradeSpanInfo = (from tradeSpan in req.tradeSpans
|
|
join trade in tradeList on tradeSpan.TradeId equals trade.id
|
|
where tradeSpan.ValueDate == req.settleDate
|
|
select new { trade, tradeSpan }).ToList();
|
|
|
|
var clientGroups = tradeSpanInfo.GroupBy(t => t.trade.ClientId);
|
|
foreach (var clientGroup in clientGroups)
|
|
{
|
|
var client = DataCacheProvider.GetClientDataSource().GetData(clientGroup.Key);
|
|
var underlyingGroup = clientGroup.GroupBy(t => t.trade.UnderlyingId).Select(t => new ClientSpan
|
|
{
|
|
UnderlyingId = t.Key,
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
Spv1 = t.Sum(g => g.tradeSpan.Spv1) * (-1),
|
|
Spv2 = t.Sum(g => g.tradeSpan.Spv2) * (-1),
|
|
Spv3 = t.Sum(g => g.tradeSpan.Spv3) * (-1),
|
|
Spv4 = t.Sum(g => g.tradeSpan.Spv4) * (-1),
|
|
SwapWorstCastClientPayable = t.Where(g => g.trade.TradeType == "收益互换").Sum(g => g.tradeSpan.WorstCastClientPayable) * (-1),
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
SpanType = req.SpanType
|
|
}).ToList();
|
|
foreach (var item in underlyingGroup)
|
|
{
|
|
item.WorstCastClientPayable = Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0);
|
|
|
|
item.TwoSideMargin = Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0);
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和客户预付金计算用的Spv组保持一致
|
|
|
|
var tradeIdList = clientGroup.Select(x => x.tradeSpan.TradeId);
|
|
var tradeSpansUpdate = db.trade_span.Where(x => tradeIdList.Contains(x.TradeId) && x.ClientId == item.ClientId && x.UnderlyingId == item.UnderlyingId && x.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(x => tradeIdList.Contains(x.TradeId) && x.ClientId == item.ClientId && x.UnderlyingId == item.UnderlyingId && x.ValueDate == req.settleDate).ToList();
|
|
|
|
if (item.WorstCastClientPayable == item.Spv1)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv1);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv1);
|
|
}
|
|
else if (item.WorstCastClientPayable == item.Spv2)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv2);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv2);
|
|
}
|
|
else if (item.WorstCastClientPayable == item.Spv3)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv3);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv3);
|
|
}
|
|
else if (item.WorstCastClientPayable == item.Spv4)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv4);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv4);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
var clientSpan = new ClientSpan
|
|
{
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
Spv1 = underlyingGroup.Sum(g => g.Spv1),
|
|
Spv2 = underlyingGroup.Sum(g => g.Spv2),
|
|
Spv3 = underlyingGroup.Sum(g => g.Spv3),
|
|
Spv4 = underlyingGroup.Sum(g => g.Spv4),
|
|
//负数代表客户应缴预付金,正数代表客户应收预付金
|
|
WorstCastClientPayable = underlyingGroup.Sum(g => g.WorstCastClientPayable),
|
|
SwapWorstCastClientPayable = underlyingGroup.Sum(g => g.SwapWorstCastClientPayable),
|
|
MySideMargin = underlyingGroup.Sum(g => g.WorstCastClientPayable),
|
|
TwoSideMargin = underlyingGroup.Sum(g => g.TwoSideMargin),
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
SpanType = req.SpanType,
|
|
AdditionalWorstCastClientPayable = req.clientAdditionalMarginDic != null
|
|
&& req.clientAdditionalMarginDic.TryGetValue(clientGroup.Key, out var dd) ? dd : 0
|
|
};
|
|
|
|
if (client.MarginOptionType == (int)MarginOptionEnum.单向追保)
|
|
{
|
|
clientSpan.WorstCastClientPayable = Math.Min(clientSpan.WorstCastClientPayable.Value, 0);
|
|
}
|
|
else if (client.MarginOptionType == (int)MarginOptionEnum.对手方单向追保)
|
|
{
|
|
clientSpan.WorstCastClientPayable = Math.Max(clientSpan.WorstCastClientPayable.Value, 0);
|
|
}
|
|
|
|
clientSpanNews.Add(clientSpan);
|
|
}
|
|
}
|
|
|
|
//span类型为实时删除所有实时计算的交易的预付金信息
|
|
if (req.SpanType == ClientSpan.SpanType_RealTime)
|
|
{
|
|
if (req.RefreshClientIds != null)
|
|
{
|
|
db.BulkDelete<ClientSpan>($"{nameof(ClientSpan.ClientId)} in @ids", new { ids = req.RefreshClientIds });
|
|
}
|
|
else
|
|
{
|
|
db.BulkDelete<ClientSpan>($"{nameof(ClientSpan.SpanType)}={req.SpanType}");
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (req.ClientIds != null)
|
|
{
|
|
var sql = $"{nameof(ClientSpan.ClientId)} in @ids and {nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0";
|
|
db.BulkDelete<ClientSpan>(sql, new { ids = req.ClientIds });
|
|
}
|
|
else
|
|
{
|
|
var sql = $"{nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0";
|
|
db.BulkDelete<ClientSpan>(sql);
|
|
}
|
|
|
|
var clientSpanOldsWithFlag = db.client_span.Where(t => t.ValueDate == req.settleDate && t.SpanType == req.SpanType && t.ModifiedFlag)
|
|
.Select(n => new { n.ValueDate, n.ClientId }).ToList();
|
|
//筛选出可以修改的clientSpan
|
|
clientSpanNews = clientSpanNews.Where(c => !clientSpanOldsWithFlag.Any(t => t.ValueDate == c.ValueDate && t.ClientId == c.ClientId)).ToList();
|
|
}
|
|
if (clientSpanNews.Count > 0)
|
|
{
|
|
//MySqlBulkExtensions.BulkInsert(db, clientSpanNews);
|
|
db.client_span.AddRange(clientSpanNews);
|
|
}
|
|
|
|
db.SaveChanges();
|
|
|
|
return req.tradeSpans;
|
|
}
|
|
}
|
|
|
|
public virtual List<trade_span> RunMarginCalculation(RunMarginCalculationReq req)
|
|
{
|
|
if (req.tradeList == null || !req.tradeList.Any())
|
|
{
|
|
return new List<trade_span>();
|
|
}
|
|
|
|
var tradeSpanList = new List<trade_span>();
|
|
|
|
//收益互换交易单独计算trade_span
|
|
if (req.tradeList.Any(t => t.TradeType == "收益互换"))
|
|
{
|
|
var tradeList = req.tradeList.Where(t => t.TradeType == "收益互换").ToList();
|
|
tradeSpanList.AddRange(SwapTradeMarginCalculation(req.Clone(tradeList)));
|
|
}
|
|
|
|
//剔除收益互换交易
|
|
req.tradeList = req.tradeList.Where(t => t.TradeType != "收益互换").ToList();
|
|
|
|
var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider);
|
|
|
|
var resultMap = new Dictionary<int, trade_span>();
|
|
|
|
//为了算客户角度的一个预付金数值
|
|
helper.ReverseTradeSide();
|
|
|
|
helper.SetFieldsByTradeType();
|
|
|
|
helper.GetUpDownLimitPrices(out var upLimitPrices, out var downLimitPrices);
|
|
|
|
helper.GetTradVolRateDic(out var tradeVolRateDic);
|
|
|
|
var vols = new[] { null, tradeVolRateDic };
|
|
var prices = new (string, IPriceProvider)[] { ("up", upLimitPrices), ("down", downLimitPrices) };
|
|
var loops = prices.SelectMany(n => vols.Select(m => new
|
|
{
|
|
pricekey = n.Item1,
|
|
priceProvider = n.Item2,
|
|
addVolRateDic = m
|
|
})).ToArray();
|
|
|
|
foreach (var loop in loops)
|
|
{
|
|
var calcReq = helper.GetCalculateRisksForTradesReq(priceProvider: loop.priceProvider, addVolRateDic: loop.addVolRateDic, overrideVols: null);
|
|
|
|
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(calcReq);
|
|
|
|
if (tradeRiskResult.Results == null || tradeRiskResult.Results.Count < 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var key = $"{loop.pricekey}_{(loop.addVolRateDic == null ? 0 : 1)}";
|
|
|
|
foreach (var item in tradeRiskResult.Results)
|
|
{
|
|
var pv = item.ValueResult.Pv;
|
|
|
|
if (!helper.GetSpecialMargin(item.Trade, pv, out var value))
|
|
{
|
|
var clientRatio = helper.GetClient(item.Trade)?.Ratio ?? 1.0;
|
|
|
|
value = double.IsNaN(pv) ? 0 : pv * clientRatio;
|
|
}
|
|
|
|
var contains = resultMap.TryGetValue(item.Trade.id, out var tempTradeSpan);
|
|
|
|
if (!contains)
|
|
{
|
|
resultMap[item.Trade.id] = tempTradeSpan = helper.CreateTradeSpan(item.Trade);
|
|
}
|
|
|
|
switch (key)
|
|
{
|
|
case "up_0":
|
|
tempTradeSpan.Spv1 = value; break;
|
|
case "up_1":
|
|
tempTradeSpan.Spv2 = value; break;
|
|
case "down_0":
|
|
tempTradeSpan.Spv3 = value; break;
|
|
case "down_1":
|
|
tempTradeSpan.Spv4 = value; break;
|
|
}
|
|
|
|
if (contains)
|
|
{
|
|
tempTradeSpan.SetWorstCastClientPayable();
|
|
}
|
|
}
|
|
}
|
|
|
|
tradeSpanList.AddRange(resultMap.Values.ToList());
|
|
|
|
return tradeSpanList;
|
|
}
|
|
|
|
public virtual double GetTradeMargin(GetTradeMarginReq req)
|
|
{
|
|
using (var db = new YLContext())
|
|
{
|
|
var marginReq = req.GetRunMarginCalculationReq();
|
|
if (req.trade.IsGroup == 1)
|
|
{
|
|
marginReq.tradeList = db.trade.Where(x => x.ParentTradeId == req.realTradeId).ToList();
|
|
if (marginReq.CalcMarginType == CalcMarginTypeEnum.InitialMargin)
|
|
{
|
|
foreach (var item in marginReq.tradeList)
|
|
{
|
|
item.id = 0;
|
|
}
|
|
}
|
|
}
|
|
var tradeMargin = RunMarginCalculation(marginReq);
|
|
if (null != tradeMargin)
|
|
{
|
|
var margin = req.trade.IsGroup == 1 ? tradeMargin.Sum(x => x.WorstCastClientPayable ?? 0) : (tradeMargin.FirstOrDefault()?.WorstCastClientPayable ?? 0);
|
|
return margin;
|
|
}
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
//为了算客户角度的一个预付金数值
|
|
//RunMarginCalculation时forOtherSide为true时调用
|
|
protected List<trade_span> RunMarginCalculationOtherSide(RunMarginCalculationReq req)
|
|
{
|
|
void RevertBuySell()
|
|
{
|
|
foreach (var x in req.tradeList)
|
|
{
|
|
x.BuySell = x.BuySell == "买入" ? "卖出" : "买入";
|
|
if (x.SubTrades != null && x.SubTrades.Any())
|
|
{
|
|
foreach (var xs in x.SubTrades)
|
|
{
|
|
xs.BuySell = xs.BuySell == "买入" ? "卖出" : "买入";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RevertBuySell();
|
|
|
|
req.forOtherSide = false;
|
|
var results = RunMarginCalculation(req);
|
|
RevertBuySell();
|
|
return results;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 是否客户有双向预付金
|
|
/// </summary>
|
|
protected bool HasTwoSideMargin(int clientId)
|
|
{
|
|
if (PS.Config.ErpElement.TwoSideMargin)
|
|
{
|
|
var client = DataCacheProvider.GetClientDataSource().GetData(clientId);
|
|
return client?.MarginOptionType == (int)MarginOptionEnum.双向追保;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取客户信息
|
|
/// </summary>
|
|
protected InnerClient GetClientInfo(int clientId)
|
|
{
|
|
if (clientId < 1)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using (var db = DbContextFactory.GetClientDbContext(null))
|
|
{
|
|
var query = from c in db.client
|
|
join cl in db.clientlevel on c.LevelId equals cl.id into t_cl
|
|
from cl in t_cl.DefaultIfEmpty()
|
|
where clientId == c.id
|
|
select new InnerClient
|
|
{
|
|
ClientId = c.id,
|
|
Ratio = cl == null ? null : cl.Ratio,
|
|
Ratio1 = cl == null ? null : cl.Ratio1,
|
|
AddRatio = cl == null ? null : cl.AddRatio,
|
|
MarginOptionType = c.MarginOptionType,
|
|
ProperClientClass = c.ProperClientClass,
|
|
QuestionnaireScore = c.QuestionnaireScore,
|
|
RuleT0orT1 = c.RuleT0orT1
|
|
};
|
|
|
|
return query.FirstOrDefault();
|
|
}
|
|
}
|
|
|
|
//收益互换预付金计算
|
|
//预付金模板V2迁移:按 R1 三层级(交易绑定→客户默认→全局默认,ResolveTieredTemplates 找到即停)解析模板——
|
|
//无预付金=0;区间追保结构=名义本金×x(初始)/名义本金×y(持仓),x/y 由 MarginTemplateV2RateHelper 按 期限档+资产类型 匹配明细取得。
|
|
//三级均未命中或命中其他规则的互换交易维持现状(不产出 trade_span),与确认书/取数口径一致(BUG-02 修正)。
|
|
//阶段三 §3.1:区间追保结构明细按方案B录入新区间结构(SpanConfig.LongSpans/ShortSpans)时切换新引擎——
|
|
//每日取标的收盘价落档(债券净价/指数及ETF收盘价),维持保证金 = (初始保证金 + 总追加保证金) × 我方净收取方向;
|
|
//存量 x/y 配置(无新区间结构)维持 名义×y 公式不变。
|
|
private static List<trade_span> SwapTradeMarginCalculation(RunMarginCalculationReq req)
|
|
{
|
|
var result = new List<trade_span>();
|
|
|
|
var calcTrades = req.tradeList.Where(t => t.ParentTradeId == 0 && t.id > 0).ToList();
|
|
if (calcTrades.Count == 0)
|
|
{
|
|
return result;
|
|
}
|
|
var swapTradeIds = calcTrades.Select(t => t.id).ToList();
|
|
|
|
using (var db = new YLContext())
|
|
{
|
|
//R1 三层级找到即停(BUG-02 修正):交易绑定 → 客户默认 → 全局默认 统一解析,
|
|
//只配了客户/全局默认模板的交易与交易级绑定同权生效,与确认书/取数口径一致
|
|
var templatesByTrade = MarginTemplateV2RateHelper.ResolveTieredTemplates(calcTrades, req.settleDate, db);
|
|
|
|
//期初腿数据:标的腿(多空)取 期初价格/数量/方向,初始预付金腿取 初始保证金与收取方向
|
|
var swapPositions = db.swap_position.AsNoTracking()
|
|
.Where(x => swapTradeIds.Contains(x.SwapTradeId) && !x.Invalid && x.IsInitial)
|
|
.ToList();
|
|
var positionsByTrade = swapPositions.GroupBy(x => x.SwapTradeId).ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
foreach (var trade in req.tradeList)
|
|
{
|
|
//剔除多空组合子交易
|
|
if (trade.ParentTradeId > 0 || trade.id <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!templatesByTrade.TryGetValue(trade.id, out var template))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
double? margin;
|
|
if (template.RuleType == (int)MarginRuleTypeEnum.无预付金)
|
|
{
|
|
margin = 0;
|
|
}
|
|
else if (template.RuleType == (int)MarginRuleTypeEnum.区间追保结构)
|
|
{
|
|
var rateResult = MarginTemplateV2RateHelper.GetRateByTemplate(template, trade.UnderlyingCode, trade.UnderlyingInstrumentType, req.settleDate, db);
|
|
if (rateResult == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var spanCfg = rateResult.Detail?.SpanConfig;
|
|
if (SwapSpanMarginCalc.HasSpanConfig(spanCfg))
|
|
{
|
|
//方案B新结构:收盘价落档 → 维持保证金 = (初始 + 总追加) × 方向
|
|
margin = CalcSwapSpanMaintenanceMargin(trade, spanCfg,
|
|
positionsByTrade.TryGetValue(trade.id, out var legs) ? legs : new List<swap_position>(),
|
|
req.CalcMarginType == CalcMarginTypeEnum.InitialMargin, req.settleDate);
|
|
}
|
|
else
|
|
{
|
|
//存量 x/y 配置:初始预付金=名义本金×x;持仓预付金=名义本金×y
|
|
var rate = (double)(req.CalcMarginType == CalcMarginTypeEnum.InitialMargin
|
|
? rateResult.InitRate ?? 0m
|
|
: rateResult.MaintainRate ?? 0m);
|
|
margin = rate * trade.StockEqvNotional;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//其他规则不在默认引擎支持范围,显式跳过
|
|
continue;
|
|
}
|
|
|
|
if (margin == null)
|
|
{
|
|
//新引擎无法计算(缺标的腿等),不产出 trade_span,与未匹配明细行为一致
|
|
continue;
|
|
}
|
|
|
|
result.Add(new trade_span()
|
|
{
|
|
TradeId = trade.id,
|
|
OptDate = DateTime.Now,
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
ClientId = trade.ClientId,
|
|
UnderlyingId = trade.UnderlyingId,
|
|
UnderlyingCode = trade.UnderlyingCode,
|
|
ValueDate = req.settleDate,
|
|
Spv1 = margin.Value,
|
|
Spv2 = margin.Value,
|
|
Spv3 = margin.Value,
|
|
Spv4 = margin.Value,
|
|
Spv5 = margin.Value,
|
|
Spv6 = margin.Value,
|
|
Spv7 = margin.Value,
|
|
Spv8 = margin.Value,
|
|
Spv = margin.Value,
|
|
WorstCastClientPayable = margin.Value,
|
|
Margin = 0
|
|
});
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 阶段三 §3.1 新引擎:规则15 方案B配置的维持保证金(DB 胶水层——只负责解析收盘价,计算在 SwapSpanMarginCalc.CalcTradeMaintenanceMargin 纯函数,单测覆盖)。
|
|
/// 收盘价口径:债券取中债估值净价(GetBondPrice 口径 SettlePrice=净价;取 ≤计算日 最近一条,盘中跑用最近已有估值),
|
|
/// 指数/ETF取收盘价(InnerGetEodPrice 将日期规整到最近交易日);未取到 → closePrice=0(追加按0、维持=初始),仅记日志不抛错。
|
|
/// </summary>
|
|
private static double? CalcSwapSpanMaintenanceMargin(trade trade, SpanConfig spanCfg, List<swap_position> legs, bool isInitialCalc, DateTime settleDate)
|
|
{
|
|
//标的腿(多空):期初价格、数量、客户方向
|
|
var underlyingLeg = legs.FirstOrDefault(x => x.PositionType == (int)PositionTypeFlag.Long || x.PositionType == (int)PositionTypeFlag.Short);
|
|
if (underlyingLeg == null || underlyingLeg.PosiGrossPrice <= 0)
|
|
{
|
|
logger.Info($"【警告】规则15新引擎:交易{trade.id}无有效标的腿(期初价),跳过新公式计算");
|
|
return null;
|
|
}
|
|
|
|
double closePrice;
|
|
if (isInitialCalc)
|
|
{
|
|
//试算初始不依赖收盘价
|
|
closePrice = 0;
|
|
}
|
|
else
|
|
{
|
|
var underlyingCode = !string.IsNullOrEmpty(underlyingLeg.UnderlyingCode) ? underlyingLeg.UnderlyingCode : trade.UnderlyingCode;
|
|
var isBond = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode)?.IsBond() ?? false;
|
|
if (isBond)
|
|
{
|
|
if (!EodPriceQueryService.TryGetBondEodPrice(settleDate, underlyingCode, out var bondPrice))
|
|
{
|
|
logger.Info($"【警告】规则15新引擎:交易{trade.id}标的{underlyingCode}无债券估值净价,追加保证金按0计(维持=初始)");
|
|
closePrice = 0;
|
|
}
|
|
else
|
|
{
|
|
closePrice = bondPrice.GetPrice(SettlementTypeEnum.SettlePrice);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!EodPriceQueryService.TryGetEodPrice(settleDate, underlyingCode, out var eodPrice))
|
|
{
|
|
logger.Info($"【警告】规则15新引擎:交易{trade.id}标的{underlyingCode}无收盘价,追加保证金按0计(维持=初始)");
|
|
closePrice = 0;
|
|
}
|
|
else
|
|
{
|
|
closePrice = eodPrice.GetPrice(SettlementTypeEnum.ClosePrice);
|
|
}
|
|
}
|
|
}
|
|
|
|
var margin = SwapSpanMarginCalc.CalcTradeMaintenanceMargin(trade.InitialMargin, spanCfg, legs, isInitialCalc, closePrice);
|
|
if (margin.HasValue)
|
|
{
|
|
logger.Info($"规则15新引擎:交易{trade.id} 标的{trade.UnderlyingCode} 收盘价={closePrice:0.####}(试算初始={isInitialCalc})→ 维持保证金={margin.Value:0.00}");
|
|
}
|
|
return margin;
|
|
}
|
|
}
|
|
}
|