443 lines
20 KiB
C#
443 lines
20 KiB
C#
using Newtonsoft.Json.Linq;
|
||
using YLErp.Core.Helpers;
|
||
using YLErp.DBModels;
|
||
using YLErp.DBModels.Consts;
|
||
using YLErp.DBModels.Enums;
|
||
using YLErp.Modules.UnderlyingModule;
|
||
using YLErp.Plugins.TradeDocGenerator;
|
||
using YLErp.Plugins.TradeDocGenerator.Abstracts;
|
||
|
||
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>
|
||
/// 获取收益互换模板路径
|
||
/// </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();
|
||
|
||
// 上交所债券ETF:511开头.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.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}";
|
||
|
||
dic["主协议编号"] = client.MainProtocolCode ?? "";
|
||
dic["名义本金"] = trade.OriginalStockEqvNotional?.ToString("N2") ?? "0.00";
|
||
|
||
// 银行账户信息
|
||
dic["户名"] = bank?.ClientName ?? "";
|
||
dic["银行账号"] = bank?.Card ?? "";
|
||
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["票面利率"] = (bond.CouponRate ?? 0).ToString("N4");
|
||
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("N4");
|
||
dic["参考标的期初净价"] = ((double)(swapPosition.PosiNetNoFeePrice ?? 0m)).ToString("N4");
|
||
|
||
dic["参考标的期初全价%"] = ((double)swapPosition.PosiGrossPrice * 100).ToString("N4");
|
||
dic["参考标的期初净价%"] = ((double)(swapPosition.PosiNetNoFeePrice ?? 0m) * 100).ToString("N4");
|
||
|
||
// 固定收益率(年化)- 债券期初到期收益率
|
||
//dic["固定收益率(年化)"] = swapPosition.InitYtm.HasValue
|
||
// ? ((double)swapPosition.InitYtm.Value * 100).ToString("N4")
|
||
// : "0.0000";
|
||
dic["固定收益率(年化)"] = "0.0000"; //需求说直接都是0
|
||
|
||
// 获取客户适用的保证金率
|
||
var clientMarginRate = UnderlyingHelper.GetApplicableMarginRate(
|
||
client.id, swapPosition.UnderlyingCode, trade.TradeDate ?? DateTime.Now);
|
||
|
||
if (clientMarginRate != null)
|
||
{
|
||
dic["初始保障金率"] = ((double)clientMarginRate.init_rate * 100).ToString("N4");
|
||
dic["维持保障金率"] = ((double)clientMarginRate.maintain_rate * 100).ToString("N4");
|
||
|
||
// 期初预付比例和金额
|
||
dic["期初预付比例"] = ((double)clientMarginRate.init_rate * 100).ToString("N4");
|
||
dic["期初预付金额"] = ((trade.OriginalStockEqvNotional ?? 0) * (double)clientMarginRate.init_rate).ToString("N2");
|
||
}
|
||
else
|
||
{
|
||
dic["初始保障金率"] = "0.0000";
|
||
dic["维持保障金率"] = "0.0000";
|
||
dic["期初预付比例"] = "0.0000";
|
||
dic["期初预付金额"] = "0.00";
|
||
}
|
||
bool posiLong = IsCustomerLong(swapPosition);
|
||
// 计算平仓线、预警线、档位值(利率债TRS,固定4档)
|
||
var maintainRatePercent = (double)(clientMarginRate?.maintain_rate ?? 0) * 100; // 维持保证金率 A(%)
|
||
if (maintainRatePercent > 0)
|
||
{
|
||
if (posiLong)
|
||
{
|
||
// 客户看多:档位n = 100 - n * A
|
||
dic["平仓线"] = (100 - 4 * maintainRatePercent).ToString("0.##");
|
||
dic["预警线"] = (100 - 3 * maintainRatePercent).ToString("0.##");
|
||
dic["档位1"] = (100 - 1 * maintainRatePercent).ToString("0.##");
|
||
dic["档位2"] = (100 - 2 * maintainRatePercent).ToString("0.##");
|
||
dic["档位3"] = (100 - 3 * maintainRatePercent).ToString("0.##");
|
||
dic["档位4"] = (100 - 4 * maintainRatePercent).ToString("0.##");
|
||
}
|
||
else
|
||
{
|
||
// 客户看空:档位n = 100 + n * A
|
||
dic["平仓线"] = (100 + 4 * maintainRatePercent).ToString("0.##");
|
||
dic["预警线"] = (100 + 3 * maintainRatePercent).ToString("0.##");
|
||
dic["档位1"] = (100 + 1 * maintainRatePercent).ToString("0.##");
|
||
dic["档位2"] = (100 + 2 * maintainRatePercent).ToString("0.##");
|
||
dic["档位3"] = (100 + 3 * maintainRatePercent).ToString("0.##");
|
||
dic["档位4"] = (100 + 4 * maintainRatePercent).ToString("0.##");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
dic["平仓线"] = "";
|
||
dic["预警线"] = "";
|
||
dic["档位1"] = "";
|
||
dic["档位2"] = "";
|
||
dic["档位3"] = "";
|
||
dic["档位4"] = "";
|
||
}
|
||
|
||
// 期初预付金利率(InterestMode == 初始预付金)
|
||
var initialMarginPosition = swapPositions
|
||
.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金)
|
||
.FirstOrDefault();
|
||
dic["期初预付金利率"] = initialMarginPosition != null
|
||
? ((double)initialMarginPosition.InterestRateDefault * 100).ToString("N4")
|
||
: "0.0000";
|
||
|
||
// 利率类型判断(固定/浮动)
|
||
var 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"] = "□";
|
||
if (interestMargin != null)
|
||
{
|
||
if (string.IsNullOrEmpty(interestMargin.FloatRateUnderlyingCode))
|
||
{
|
||
dic["利率类型"] = "固定利率";
|
||
dic["IsFixed"] = "☑"; //☑
|
||
dic["固定利率"] = ((double)interestMargin.InterestRateDefault * 100).ToString("N4");
|
||
dic["利差"] = "";
|
||
}
|
||
else
|
||
{
|
||
dic["利率类型"] = "浮动利率";
|
||
dic["IsFloat"] = "☑";
|
||
dic["固定利率"] = "";
|
||
dic["利差"] = ((double)interestMargin.InterestRateDefault * 10000).ToString("N2");
|
||
dic["重置频率"] = (interestMargin.interest_rest_days ?? 0) + "天";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
dic["利率类型"] = "固定利率";
|
||
dic["固定利率"] = "0.0000";
|
||
dic["利差"] = "";
|
||
}
|
||
|
||
// 基本费率 = PosiTradingFeePending / 名义本金 * 100
|
||
var notional = trade.OriginalStockEqvNotional ?? 0;
|
||
var tradingFee = (double)swapPosition.PosiTradingFeePending;
|
||
var basicFeeRate = notional == 0 ? 0 : tradingFee / notional * 100;
|
||
dic["基本费率"] = basicFeeRate.ToString("N4");
|
||
|
||
// 期初现金交换比例和金额(使用初始预付金数据)
|
||
dic["期初现金交换比例"] = initialMarginPosition != null
|
||
? ((double)initialMarginPosition.InterestRateDefault * 100).ToString("N4")
|
||
: "0.0000";
|
||
dic["期初现金交换金额"] = initialMarginPosition != null
|
||
? ((double)initialMarginPosition.InterestPrincipalFix).ToString("N2")
|
||
: "0.00";
|
||
}
|
||
else
|
||
{
|
||
dic["参考标的期初全价"] = "0.0000";
|
||
dic["参考标的期初净价"] = "0.0000";
|
||
dic["初始保障金率"] = "0.0000";
|
||
dic["维持保障金率"] = "0.0000";
|
||
dic["期初预付比例"] = "0.0000";
|
||
dic["期初预付金额"] = "0.00";
|
||
dic["期初预付金利率"] = "0.0000";
|
||
dic["利率类型"] = "固定利率";
|
||
dic["固定利率"] = "0.0000";
|
||
dic["利差"] = "";
|
||
dic["基本费率"] = "0.0000";
|
||
dic["期初现金交换比例"] = "0.0000";
|
||
dic["期初现金交换金额"] = "0.00";
|
||
}
|
||
|
||
// 参考标的券面总额(名义本金)
|
||
dic["参考标的券面总额"] = trade.OriginalStockEqvNotional?.ToString("N2") ?? "0.00";
|
||
|
||
// 参考标的证券全称和参考标的名义份额(复用上方已声明的bond)
|
||
dic["参考标的证券全称"] = underlying != null
|
||
? (JsonHelper.Deserialize<UnderlyingBond>(underlying.ExJson)?.UnderlyingFullName ?? underlying.UnderlyingName)
|
||
: "";
|
||
dic["参考标的名义份额"] = swapPosition != null
|
||
? ((double)swapPosition.PosiQuantity).ToString("N2")
|
||
: "0.00";
|
||
|
||
// 乙方联系人信息
|
||
var clientDuties = Context.GetClientDuties();
|
||
var contact = clientDuties.FirstOrDefault();
|
||
dic["联系人"] = contact?.ContactName ?? "";
|
||
dic["电子邮件"] = contact?.Email ?? "";
|
||
dic["电话"] = contact?.PhoneNumber ?? "";
|
||
|
||
// 表格数据示例
|
||
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";
|
||
}
|
||
}
|
||
}
|