Files
zszq-trs/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeConfirmationGenerator.cs
T
张名锐 1f3fb9240b fix(trade): 修正交易确认书中的基本费率计算逻辑
- 注释掉原有的基本费率计算公式
- 直接使用PosiTradingFeeUnit字段获取基础费率
- 避免通过PosiTradingFeePending除以名义本金的方式计算
- 确保基本费率显示的准确性
2026-08-28 14:36:54 +08:00

661 lines
35 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Newtonsoft.Json.Linq;
using YLErp.BLL;
using YLErp.Core.Helpers;
using YLErp.DBModels;
using YLErp.DBModels.Consts;
using YLErp.DBModels.Enums;
using YLErp.Modules.MarginModule;
using YLErp.Modules.UnderlyingModule;
using YLErp.Plugins.TradeDocGenerator;
using YLErp.Plugins.TradeDocGenerator.Abstracts;
using YieldChain.Helpers;
namespace YLErp.Plugins.GuoLian.DocumentGenerator
{
/// <summary>
/// 国联证券交易确认书生成器(单个生成)
/// </summary>
internal class TradeConfirmationGenerator : BaseConfirmationGenerator, ITradeConfirmationGenerator
{
public TradeConfirmationGenerator()
: base(ContractTypeEnum.Trade)
{
}
/// <summary>
/// 获取模板文件路径
/// 收益互换模板选择逻辑:
/// 1. 客户看多/看空:根据 swap_position 的 PosiDirection(收支方向)和 PositionType(多空方向)
/// 组合出我方方向,再取反得到客户方向。
/// 2. 标的类型:
/// - 债券ETF.SH后缀且511开头,或.SZ后缀且59开头
/// - 现券:其他
/// </summary>
protected override string GetTemplateFilePath()
{
var trade = Context.Trade;
if (trade.TradeType == "收益互换")
{
return GetSwapTemplatePath();
}
else if (trade.TradeType.Contains("期权"))
{
// 期权使用期权模板
return Path.Combine(GlobalConfig.PluginFolder, "App_Docs\\contract_template\\option_01.docx");
}
else
{
throw new Exception($"不支持的贸易类型: {trade.TradeType}");
}
}
/// <summary>
/// 判断客户是否为看多方向
/// 规则:根据 PosiDirection(收支方向)和 PositionType(多空方向)组合出我方方向,再取反得到客户方向
/// </summary>
private bool IsCustomerLong(swap_position position)
{
if (position == null)
return false;
bool isOurLong = position.PosiDirection == (int)SwapDirectionEnum.支付
? position.PositionType == (int)PositionTypeFlag.Short // 支付端:我方方向与 PositionType 相反
: position.PositionType == (int)PositionTypeFlag.Long; // 收取端:我方方向与 PositionType 相同
return !isOurLong;
}
/// <summary>
/// 阶段三 §3.3 决策①:方案B手工录入结构直接赋值(不再用 100∓n×(x−y) 推导)。
/// 模板追保表 4 行 ↔ SpanConfig 4 层:多头 第1层=[Lower,+∞)(模板行0只用下限)、第n层=[Lower,Upper)
/// 空头 第1层=(−∞,Upper](模板行0只用上限)、第n层=(Lower,Upper]
/// 追保金额比例 = AmountRate×100(累计到该层的追保比例,直接取值),金额文字口径 n%×期初全价×券面总额 由模板静态文字承载;
/// 预警线/平仓线直接取录入值,未录时按 docx 口径取边界(平仓线=最深层边界、预警线=次深层边界);
/// 配置层不足 4 层时余下行置空;返还追保事件表与追保事件同值(模板静态文字区分方向措辞)。
/// </summary>
private static void FillSpanConfigLines(Dictionary<string, JToken> dic, SpanConfig spanCfg, bool posiLong)
{
var tiers = (posiLong ? spanCfg.LongSpans : spanCfg.ShortSpans) ?? new List<SpanTierConfig>();
var valid = tiers.Where(t => t != null && (t.Lower.HasValue || t.Upper.HasValue)).ToList();
var last = valid.LastOrDefault();
var warnLine = spanCfg.WarnLine;
var closeLine = spanCfg.CloseLine;
if (last != null)
{
closeLine ??= posiLong ? last.Lower : last.Upper;
warnLine ??= posiLong ? last.Upper : last.Lower;
}
dic["预警线"] = warnLine.HasValue ? (warnLine.Value * 100).ToString("0.##") : "";
dic["平仓线"] = closeLine.HasValue ? (closeLine.Value * 100).ToString("0.##") : "";
//档位n = 第n层触发线(多头取各层 Lower、空头取各层 Upper),现用模板无档位占位符,保留赋值兼容旧模板
for (int n = 1; n <= 4; n++)
{
var tier = n <= valid.Count ? valid[n - 1] : null;
var line = tier == null ? null : (posiLong ? tier.Lower : tier.Upper);
dic[$"档位{n}"] = line.HasValue ? (line.Value * 100).ToString("0.##") : "";
}
//追保事件/返还追保事件区间表:行0=第1层,行1..3=第2..4层;
//与上方档位循环同用过滤后的 valid 列表(BUG-26:配置中间夹空层时两类占位符不错位)
for (int i = 0; i <= 3; i++)
{
var tier = i < valid.Count ? valid[i] : null;
var hasTier = tier != null && (tier.Lower.HasValue || tier.Upper.HasValue);
dic[$"追保区间{i}下限"] = hasTier && tier.Lower.HasValue ? (tier.Lower.Value * 100).ToString("0.##") : "";
dic[$"追保区间{i}上限"] = hasTier && tier.Upper.HasValue ? (tier.Upper.Value * 100).ToString("0.##") : "";
dic[$"追保金额比例{i}"] = hasTier && tier.AmountRate.HasValue ? (tier.AmountRate.Value * 100).ToString("0.##") : "";
dic[$"返还区间{i}下限"] = dic[$"追保区间{i}下限"];
dic[$"返还区间{i}上限"] = dic[$"追保区间{i}上限"];
dic[$"返还金额比例{i}"] = dic[$"追保金额比例{i}"];
}
}
/// <summary>
/// 获取收益互换模板路径
/// </summary>
private string GetSwapTemplatePath()
{
var trade = Context.Trade;
var underlying = Context.GetTradeUnderlying();
// 判断客户看多/看空方向
var swapPosition = Context.GetSwapPositions(trade.id, true)
.Where(x => x.PositionType == (int)PositionTypeFlag.Long || x.PositionType == (int)PositionTypeFlag.Short)
.FirstOrDefault();
bool isCustomerLong = IsCustomerLong(swapPosition);
// 判断标的类型(债券ETF vs 现券)
bool isEtf = IsBondEtf(underlying?.UnderlyingCode ?? string.Empty);
// 选择对应模板(使用客户提供的原始文件名)
string templateName;
if (isCustomerLong && !isEtf)
templateName = "国联民生-收益互换交易确认书-境内模板-【客户看多】-【现券】-清洁版.docx";
else if (isCustomerLong && isEtf)
templateName = "国联民生-收益互换交易确认书-境内模板-【客户看多】-【债券ETF】-清洁版.docx";
else if (!isCustomerLong && !isEtf)
templateName = "国联民生-收益互换交易确认书-境内模板-【客户看空】-【现券】-清洁版.docx";
else
templateName = "国联民生-收益互换交易确认书-境内模板-【客户看空】-【债券ETF】-清洁版.docx";
return Path.Combine(GlobalConfig.PluginFolder, "App_Docs/contract_template", templateName);
}
/// <summary>
/// 判断是否为债券ETF
/// - .SH后缀且511开头 -> 债券ETF
/// - .SZ后缀且159开头 -> 债券ETF
/// - 其他 -> 现券
/// </summary>
private bool IsBondEtf(string underlyingCode)
{
if (string.IsNullOrWhiteSpace(underlyingCode))
return false;
underlyingCode = underlyingCode.Trim().ToUpper();
// 上交所债券ETF511开头.SH后缀
if (underlyingCode.EndsWith(".SH") && underlyingCode.StartsWith("511"))
return true;
// 深交所债券ETF:59开头.SZ后缀
if (underlyingCode.EndsWith(".SZ") && underlyingCode.StartsWith("159"))
return true;
return false;
}
/// <summary>
/// 获取输出文件名
/// </summary>
protected override string GetOutputFileName(string contractNo, string contractIndex)
{
var trade = Context.Trade;
var rule = $"【待用印】{trade.TradeType}交易确认书_{contractNo}";
return $"{rule}.{DocType.ToLower()}";
}
/// <summary>
/// 获取合同编号
/// </summary>
protected override string GetContractNo(out string contractIndex)
{
var contractNo = Context.GenerateContractNo(out contractIndex);
return contractNo;
}
/// <summary>
/// 准备视图数据(Word模板数据)
/// </summary>
protected override void PrepareViewData(Dictionary<string, JToken> dic)
{
var trade = Context.Trade;
var client = Context.GetClient();
// 基础信息
// 注意:合同编号由基类统一设置,此处不再重复生成
dic["交易编号"] = trade.TradeNumber;
dic["客户名称"] = client.Name;
dic["乙方名称"] = client.Name;
// 交易日期相关
dic["成交日期"] = trade.TradeDate?.ToString("【yyyy】年【M】月【d】日");
dic["成交日期/"] = trade.TradeDate?.ToString("yyyy/M/d");
dic["开始日期"] = trade.StartDate?.ToString("【yyyy】年【M】月【d】日");
dic["到期日期"] = trade.ExerciseDate?.ToString("【yyyy】年【M】月【d】日");
// 根据交易类型填充不同数据
switch (trade.TradeType)
{
case "收益互换":
SetSwapData(dic);
break;
case "香草期权":
case "亚式期权":
case "障碍期权":
SetOptionData(dic);
break;
default:
// 默认基础数据
SetBaseData(dic);
break;
}
}
/// <summary>
/// 设置收益互换数据
/// </summary>
private void SetSwapData(Dictionary<string, JToken> dic)
{
var trade = Context.Trade;
var client = Context.GetClient();
var bank = Context.GetClientBankCard(false);
dic["交易对手方全称"] = client.ClientType != "产品"
? client.Name
: $"{client.Manager}作为管理人代表{client.Name}";
// 主协议编号:优先取 client 表字段,为空时从 client_meta 表兜底
var mainProtocolCode = client.MainProtocolCode;
if (string.IsNullOrWhiteSpace(mainProtocolCode))
{
mainProtocolCode = Context.GetClientMeta(client.id, ConsTradeMetaKey.MainProtocolCode)?.MetaValue;
}
dic["主协议编号"] = mainProtocolCode ?? "";
// 主协议类型取客户开户维护的下拉选择(client_meta.MainProtocolType):1=NAFMII,其余按 SAC。
var mainProtocolType = Context.GetClientMeta(client.id, "MainProtocolType")?.MetaValue;
var isNafmii = mainProtocolType == "1";
if (string.IsNullOrWhiteSpace(mainProtocolType))
{
LogFactory.GetLogger("确认书生成").Info($"客户 {client.Number} {client.Name} 未维护主协议类型,确认书协议段按 SAC 勾选展示");
}
// 协议段勾选框对应模板占位符,按客户签署的主协议类型动态勾选,替代模板中硬编码的 Wingdings 2 复选框
static string Check(bool on) => on ? "☑" : "□";
dic["主协议SAC勾选"] = Check(!isNafmii);
dic["主协议NAFMII勾选"] = Check(isNafmii);
dic["补充协议SAC勾选"] = Check(!isNafmii);
dic["补充协议NAFMII勾选"] = Check(isNafmii);
// 定义文件签署维度暂无客户协议数据,统一展示空框,待客户资料补齐后接入
dic["协会证券业勾选"] = "□";
dic["协会交易商勾选"] = "□";
dic["定义文件商品勾选"] = "□";
dic["定义文件利率勾选"] = "□";
dic["定义文件债券勾选"] = "□";
// 补充协议编号:优先取 client 表字段,为空时从 client_meta 表兜底
var supProtocolCode = client.SupProtocolCode;
if (string.IsNullOrWhiteSpace(supProtocolCode))
{
supProtocolCode = Context.GetClientMeta(client.id, ConsTradeMetaKey.SupProtocolCode)?.MetaValue;
}
dic["补充协议编号"] = supProtocolCode ?? "";
dic["名义本金"] = trade.OriginalStockEqvNotional?.ToString("N2") ?? "0.00";
// 银行账户信息
dic["户名"] = bank?.ClientName ?? "";
dic["银行账号"] = bank?.Card ?? "";
dic["支付系统号"] = bank?.Payment ?? "";
dic["开户行"] = bank?.Bank ?? "";
dic["大额行号"] = bank?.Payment ?? "";
// 标的相关的数据可以通过 Context 获取
var underlying = Context.GetTradeUnderlying();
if (underlying != null)
{
dic["标的代码"] = underlying.UnderlyingCode;
dic["标的名称"] = underlying.UnderlyingName;
// 标的发行人(从债券信息中获取)
var bond = JsonHelper.Deserialize<UnderlyingBond>(underlying.ExJson) ?? new UnderlyingBond();
dic["参考标的发行人"] = bond.UnderlyingIssuer ?? "";
dic["参考标的担保人"] = "";
dic["票面利率"] = ((double)(bond.CouponRate ?? 0) * 100).ToString("0.00");
dic["参考标的到期日"] = underlying.MaturityDate?.ToString("【yyyy】年【M】月【d】日") ?? "";
}
// 从swap_position获取期初价格、保证金率等信息
var swapPositions = Context.GetSwapPositions(trade.id, true);
var swapPosition = swapPositions
.Where(x => x.PositionType == (int)PositionTypeFlag.Long || x.PositionType == (int)PositionTypeFlag.Short)
.FirstOrDefault();
if (swapPosition != null)
{
// 期初全价和净价(转换为百分比格式)
dic["参考标的期初全价"] = ((double)swapPosition.PosiGrossPrice).ToString("0.000");
dic["参考标的期初净价"] = ((double)(swapPosition.PosiNetNoFeePrice ?? 0m)).ToString("N4");
dic["参考标的期初全价%"] = ((double)swapPosition.PosiGrossPrice * 100).ToString("N4");
dic["参考标的期初净价%"] = ((double)(swapPosition.PosiNetNoFeePrice ?? 0m) * 100).ToString("N4");
// 固定收益率(年化)仅对应增强收益腿;互换利率腿在模板中应留空。
bool isEtf = IsBondEtf(underlying?.UnderlyingCode ?? string.Empty);
var enhancePosition = swapPositions
.FirstOrDefault(x => ConsTrade.InterestModels.Contains(x.InterestMode) && x.category_tag == "增强收益");
var swapRatePosition = swapPositions
.FirstOrDefault(x => ConsTrade.InterestModels.Contains(x.InterestMode) && x.category_tag == "互换利率");
dic["固定收益率"] = enhancePosition != null
? ((double)enhancePosition.InterestRateDefault * 100).ToString("N4")
: swapRatePosition != null ? "" : "0.0000";
// 获取客户适用的保证金率
//阶段三 §3.3 决策①:档位/预警线/平仓线/追保区间统一按交易绑定的预付金模板V2参数赋值——
//方案B手工录入结构(SpanConfig.WarnLine/CloseLine/LongSpans/ShortSpans)直接取录入值,不再用 100∓n×(x−y) 推导;
//旧互换预付金率维护(client_margin_config / UnderlyingHelper.GetApplicableMarginRate)回退链路已移除
//(本注释保留一个观察周期后删除):取不到模板参数时率显示 0、档位与区间置空。
var templateRate = MarginTemplateV2RateHelper.GetTradeMarginRate(
trade.id, swapPosition.UnderlyingCode, swapPosition.UnderlyingInstrumentType, trade.TradeDate ?? DateTime.Now);
var initRate = templateRate?.InitRate ?? 0m;
var maintainRate = templateRate?.MaintainRate ?? 0m;
var spanCfg = templateRate?.Detail?.SpanConfig;
dic["初始保障金率"] = ((double)initRate * 100).ToString("N4");
dic["维持保障金率"] = ((double)maintainRate * 100).ToString("N4");
// 期初预付/期初现金交换:预付=初始预付金腿合计(模板口径:可全部或部分授信替代),
// 现金=资金记录实付现金(ClientCashInCashOutAction=应付预付金且 Money<0 即客户期初实付;
// 授信部分不产生资金流水,平仓返还为正数、追加保证金是独立Action,均不落入该口径)
var notional = trade.OriginalStockEqvNotional ?? 0;
var initialLegs = swapPositions.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金).ToList();
var initialLegIds = initialLegs.Select(x => x.id).ToList();
double cashPaid;
using (var db = new YLContext())
{
cashPaid = db.ClientCashInCashOut
.Where(x => x.TradeId == trade.id && x.Action == ClientCashInCashOut.系统操作_应付预付金
&& x.ValidState != ConsGlobal.InValid && x.Money < 0
&& (x.Deal == 0 || initialLegIds.Contains(x.Deal)))
.Sum(x => x.Money) ?? 0;
}
var cashRatio = notional == 0 ? 0 : -cashPaid / notional * 100;
dic["期初现金交换比例"] = cashRatio.ToString("0.##");
dic["期初现金交换金额"] = (-cashPaid).ToString("N2");
// 期初预付比例和金额:有应付腿取腿合计(合约录入值),无应付腿(无预付金模板/合约维度盯市)走模板率兜底
var totalPayable = initialLegs
.Select(x => x.InterestPrincipalFix * (x.InterestDirection == 1 ? 1m : -1m))
.Where(x => x > 0)
.Sum();
if (totalPayable > 0)
{
dic["期初预付比例"] = (notional == 0 ? 0 : (double)totalPayable / notional * 100).ToString("0.##");
dic["期初预付金额"] = ((double)totalPayable).ToString("N2");
}
else
{
dic["期初预付比例"] = ((double)initRate * 100).ToString("0.##");
dic["期初预付金额"] = (notional * (double)initRate).ToString("N2");
}
bool posiLong = IsCustomerLong(swapPosition);
var maintainRatePercent = (double)maintainRate * 100; // A(%)
if (YLErp.Modules.SwapModule.Margin.SwapSpanMarginCalc.HasSpanConfig(spanCfg))
{
//方案B:直接取录入的预警/平仓线与多空区间(未录线时按 docx 口径取最深/次深档位边界)
FillSpanConfigLines(dic, spanCfg, posiLong);
}
else
{
//存量 x/y 配置:维持 100∓n×(B−A) 推导(B=初始率,A=维持率)
var initRatePercent = (double)initRate * 100; // B(%)
var diff = initRatePercent - maintainRatePercent; // B - A
if (maintainRatePercent > 0)
{
if (posiLong)
{
// 客户看多:档位n = 100 - n * (B-A)
dic["档位1"] = (100 - 1 * diff).ToString("0.##");
dic["档位2"] = (100 - 2 * diff).ToString("0.##");
dic["档位3"] = (100 - 3 * diff).ToString("0.##");
dic["档位4"] = (100 - 4 * diff).ToString("0.##");
dic["预警线"] = dic["档位4"]; // 预警线 = 最高档位
dic["平仓线"] = (100 - 5 * diff).ToString("0.##"); // 平仓线 = 最高档位 - (B-A)
}
else
{
// 客户看空:档位n = 100 + n * (B-A)
dic["档位1"] = (100 + 1 * diff).ToString("0.##");
dic["档位2"] = (100 + 2 * diff).ToString("0.##");
dic["档位3"] = (100 + 3 * diff).ToString("0.##");
dic["档位4"] = (100 + 4 * diff).ToString("0.##");
dic["预警线"] = dic["档位4"]; // 预警线 = 最高档位
dic["平仓线"] = (100 + 5 * diff).ToString("0.##"); // 平仓线 = 最高档位 + (B-A)
}
}
else
{
dic["平仓线"] = "";
dic["预警线"] = "";
dic["档位1"] = "";
dic["档位2"] = "";
dic["档位3"] = "";
dic["档位4"] = "";
}
if (maintainRatePercent > 0)
{
if (posiLong)
{
// === 客户看多 追保事件 ===
// 区间0:当前净价 ≥ {100-(B-A)}% × 期初净价,追保0%
dic["追保区间0下限"] = (100 - 1 * diff).ToString("0.##");
dic["追保金额比例0"] = "0";
// 区间1{100-2(B-A)}% ≤ 当前净价 < {100-(B-A)}%,追保{B-A}%
dic["追保区间1下限"] = (100 - 2 * diff).ToString("0.##");
dic["追保区间1上限"] = (100 - 1 * diff).ToString("0.##");
dic["追保金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100-3(B-A)}% ≤ 当前净价 < {100-2(B-A)}%,追保{2(B-A)}%
dic["追保区间2下限"] = (100 - 3 * diff).ToString("0.##");
dic["追保区间2上限"] = (100 - 2 * diff).ToString("0.##");
dic["追保金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100-4(B-A)}% ≤ 当前净价 < {100-3(B-A)}%,追保{3(B-A)}%
dic["追保区间3下限"] = (100 - 4 * diff).ToString("0.##");
dic["追保区间3上限"] = (100 - 3 * diff).ToString("0.##");
dic["追保金额比例3"] = (3 * diff).ToString("0.##");
// === 客户看多 返还追保事件 ===
for (int n = 1; n <= 3; n++)
{
dic[$"返还区间{n}下限"] = (100 - (n + 1) * diff).ToString("0.##");
dic[$"返还区间{n}上限"] = (100 - n * diff).ToString("0.##");
dic[$"返还金额比例{n}"] = (n * diff).ToString("0.##");
}
dic["返还区间0下限"] = (100 - 1 * diff).ToString("0.##");
dic["返还金额比例0"] = "0";
}
else
{
// === 客户看空 追保事件 ===
// 区间0:当前净价 ≤ {100+(B-A)}% × 期初净价,追保0%
dic["追保区间0上限"] = (100 + 1 * diff).ToString("0.##");
dic["追保金额比例0"] = "0";
// 区间1{100+(B-A)}% < 当前净价 ≤ {100+2(B-A)}%,追保{B-A}%
dic["追保区间1下限"] = (100 + 1 * diff).ToString("0.##");
dic["追保区间1上限"] = (100 + 2 * diff).ToString("0.##");
dic["追保金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100+2(B-A)}% < 当前净价 ≤ {100+3(B-A)}%,追保{2(B-A)}%
dic["追保区间2下限"] = (100 + 2 * diff).ToString("0.##");
dic["追保区间2上限"] = (100 + 3 * diff).ToString("0.##");
dic["追保金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100+3(B-A)}% < 当前净价 ≤ {100+4(B-A)}%,追保{3(B-A)}%
dic["追保区间3下限"] = (100 + 3 * diff).ToString("0.##");
dic["追保区间3上限"] = (100 + 4 * diff).ToString("0.##");
dic["追保金额比例3"] = (3 * diff).ToString("0.##");
// === 客户看空 返还追保事件 ===
for (int n = 1; n <= 3; n++)
{
dic[$"返还区间{n}下限"] = (100 + n * diff).ToString("0.##");
dic[$"返还区间{n}上限"] = (100 + (n + 1) * diff).ToString("0.##");
dic[$"返还金额比例{n}"] = (n * diff).ToString("0.##");
}
dic["返还区间0上限"] = (100 + 1 * diff).ToString("0.##");
dic["返还金额比例0"] = "0";
}
}
else
{
// 清空追保/返还相关字典值
for (int i = 0; i <= 3; i++)
{
dic[$"追保区间{i}下限"] = "";
dic[$"追保区间{i}上限"] = "";
dic[$"追保金额比例{i}"] = "";
dic[$"返还区间{i}下限"] = "";
dic[$"返还区间{i}上限"] = "";
dic[$"返还金额比例{i}"] = "";
}
}
}
// 期初预付金利率(InterestMode == 初始预付金)
var initialMarginPosition = initialLegs.FirstOrDefault();
dic["期初预付金利率"] = initialMarginPosition != null
? ((double)initialMarginPosition.InterestRateDefault * 100).ToString("N4")
: "0.0000";
// 利率类型判断(固定/浮动)
swap_position interestMargin = null;
// 优先取互换利率腿,避免同时存在增强收益腿时受集合顺序影响。
interestMargin = swapPositions
.Where(x => ConsTrade.InterestModels.Contains(x.InterestMode) && x.category_tag == "互换利率")
.FirstOrDefault();
if (interestMargin == null)
interestMargin = swapPositions
.Where(x => ConsTrade.InterestModels.Contains(x.InterestMode) && x.interest_rest_days != null)
.FirstOrDefault();
if (interestMargin == null)
interestMargin = swapPositions
.Where(x => ConsTrade.InterestModels.Contains(x.InterestMode) && string.IsNullOrWhiteSpace(x.FloatRateUnderlyingCode))
.FirstOrDefault();
if (interestMargin == null)
interestMargin = swapPositions
.Where(x => ConsTrade.InterestModels.Contains(x.InterestMode))
.FirstOrDefault();
dic["IsFixed"] = "□";
dic["IsFloat"] = "□";
dic["期初观察日"] = trade.StartDate?.ToString("【yyyy】年【M】月【d】日") ?? "";
dic["期末观察日"] = trade.ExerciseDate?.ToString("【yyyy】年【M】月【d】日") ?? "";
if (interestMargin != null)
{
// 增强收益腿在确认书中固定展示为固定利率,不展示其浮动利率标的或利差。
if (interestMargin.category_tag == "增强收益" || string.IsNullOrEmpty(interestMargin.FloatRateUnderlyingCode))
{
dic["利率类型"] = "固定利率";
dic["IsFixed"] = "☑"; //☑
dic["固定利率"] = interestMargin.category_tag == "增强收益"
? "0.0000"
: ((double)interestMargin.InterestRateDefault * 100).ToString("N4");
dic["利差"] = "";
}
else
{
dic["利率类型"] = "浮动利率";
dic["IsFloat"] = "☑";
dic["固定利率"] = "";
dic["利差"] = ((double)interestMargin.InterestRateDefault * 10000).ToString("N0");
dic["重置频率"] = (interestMargin.interest_rest_days ?? 0) + "天";
}
if (interestMargin.SwapIntervalList.Count()>1)
{
var firstInterval = interestMargin.SwapIntervalList.OrderBy(o=>o.Date).First();
var lastInterval = interestMargin.SwapIntervalList.OrderBy(o => o.Date).Last();
dic["期初观察日"] = firstInterval.Date.ToString("【yyyy】年【M】月【d】日") ?? "";
dic["期末观察日"] = lastInterval.Date.ToString("【yyyy】年【M】月【d】日") ?? "";
}
}
else
{
dic["利率类型"] = "固定利率";
dic["固定利率"] = "0.0000";
dic["利差"] = "";
}
// 基本费率 = PosiTradingFeePending / 名义本金 * 100
// var tradingFee = (double)swapPosition.PosiTradingFeePending;
// var basicFeeRate = notional == 0 ? 0 : tradingFee / notional * 100;
// dic["基本费率"] = basicFeeRate.ToString("0.####");
// @张名锐: 直接取值PosiTradingFeeUnit(基础费率)
dic["基本费率"] = swapPosition.PosiTradingFeeUnit.ToString("0.####");
}
else
{
dic["参考标的期初全价"] = "0.000";
dic["参考标的期初净价"] = "0.0000";
dic["初始保障金率"] = "0.0000";
dic["维持保障金率"] = "0.0000";
dic["期初预付比例"] = "0";
dic["期初预付金额"] = "0.00";
dic["期初预付金利率"] = "0.0000";
dic["利率类型"] = "固定利率";
dic["固定利率"] = "0.0000";
dic["利差"] = "";
dic["基本费率"] = "0";
dic["期初现金交换比例"] = "0.0000";
dic["期初现金交换金额"] = "0.00";
}
// 参考标的券面总额(名义本金)
dic["参考标的券面总额"] = swapPosition != null ? ((double)swapPosition.PosiQuantity).ToString("N2") : "0";
// 参考标的证券全称和参考标的名义份额(复用上方已声明的bond)
dic["参考标的证券全称"] = underlying != null
// ? (JsonHelper.Deserialize<UnderlyingBond>(underlying.ExJson)?.UnderlyingFullName ?? underlying.UnderlyingName)
? (underlying.UnderlyingName ?? "")
: "";
dic["参考标的名义份额"] = swapPosition != null
? ((double)swapPosition.PosiQuantity).ToString("0.##")
: "0";
dic["参考标的基金管理人"] = underlying?.InvestAdvisorName ?? "";
var contractTypeId = (Context.GetContractTypes().FirstOrDefault(O => O.ContactType == "交易确认书接收")?.id) ?? 0;
// 乙方联系人信息
var clientDuties = Context.GetClientDuties().Where(O => O.ContactTypeIdsInt.Contains(contractTypeId)).ToList();
dic["联系人"] = string.Join(",", clientDuties.Select(O => O.ContactName)).Trim(',');
dic["电子邮件"] = string.Join(";", clientDuties.Select(O => O.Email)).Trim(';');
dic["电话"] = string.Join(";", clientDuties.Select(O => O.PhoneNumber)).Trim(';');
// 表格数据示例
var table1 = new JArray();
var row = new JObject
{
["序号"] = 1,
["标的代码"] = underlying?.UnderlyingCode ?? "",
["标的名称"] = underlying?.UnderlyingName ?? "",
["名义本金"] = trade.OriginalStockEqvNotional?.ToString("N2") ?? "0.00",
["成交日期"] = trade.TradeDate?.ToString("【yyyy】年【M】月【d】日"),
["到期日期"] = trade.ExerciseDate?.ToString("【yyyy】年【M】月【d】日")
};
table1.Add(row);
dic["table1"] = table1;
}
/// <summary>
/// 设置期权数据
/// </summary>
private void SetOptionData(Dictionary<string, JToken> dic)
{
var trade = Context.Trade;
var client = Context.GetClient();
var underlying = Context.GetTradeUnderlying();
dic["期权类型"] = trade.OptionType;
dic["买卖方向"] = trade.BuySell;
dic["行权价格"] = trade.Strike?.ToString("N4") ?? "0.0000";
dic["名义本金"] = trade.StockEqvNotional.ToString("N2");
dic["期权费"] = trade.TradePrice?.ToString("N2") ?? "0.00";
dic["期初价格"] = trade.SpotPrice?.ToString("N4") ?? "0.0000";
if (underlying != null)
{
dic["标的代码"] = underlying.UnderlyingCode;
dic["标的名称"] = underlying.UnderlyingName;
dic["报价单位"] = underlying.QuoteUnitString ?? "元";
}
// 美式/欧式
dic["行权方式"] = trade.ExerciseMode == "American" ? "美式" : "欧式";
}
/// <summary>
/// 设置基础数据
/// </summary>
private void SetBaseData(Dictionary<string, JToken> dic)
{
var trade = Context.Trade;
var client = Context.GetClient();
dic["交易类型"] = trade.TradeType;
dic["客户编号"] = client.Number;
dic["名义本金"] = trade.OriginalStockEqvNotional?.ToString("N2") ?? "0.00";
}
}
}