#EQD-6947 国联民生-保证金计算整体改造-实现第一版盯市规则(不支持同一客户多部门)(1)保证金规则的设置 阶段3

This commit is contained in:
锦麟 王
2026-08-20 14:20:29 +08:00
parent 2e58ca2498
commit febd0ce02e
19 changed files with 1378 additions and 772 deletions
+7
View File
@@ -648,6 +648,13 @@ namespace YLErp.DBModels
[DataChange]
public int MarginOptionType { get; set; }
/// <summary>
/// 追保规则(0-合约维度盯市 1-客户维度盯市;NULL-未设置,按客户维度盯市/现状处理)
/// </summary>
[DisplayName("追保规则")]
[DataChange]
public int? MarginWatchRule { get; set; }
/// <summary>
/// 是否自动撤单(通道业务)
/// </summary>
@@ -1,3 +1,5 @@
using YLErp.Enums;
namespace YLErp.DBModels
{
/// <summary>
@@ -5,6 +7,16 @@ namespace YLErp.DBModels
/// </summary>
public static class ConsMarginTerm
{
/// <summary>
/// 允许配置期限档(SpanConfig.BondTerm)的标的资产类型标志位。
/// 区间追保结构"按资产类型分类"时,仅这些类型允许设置非空期限档;
/// 本期仅含 利率债(TBonds=1&lt;&lt;4=16),后续按资产分类扩展(转债ETF/科创债ETF 等)时在此补充。
/// </summary>
public static readonly UnderlyingTypeEnum[] TermTierEnabledUnderlyingTypes =
{
UnderlyingTypeEnum.TBonds
};
/// <summary>
/// 5年以下(同时也是兜底默认期限)
/// </summary>
@@ -25,6 +25,18 @@ namespace YLErp.DBModels
[DisplayName("授信额度")]
public double? Credit { get; set; }
/// <summary>
/// 原始授信值
/// </summary>
[DisplayName("原始授信值")]
public double? OriginalCredit { get; set; }
/// <summary>
/// 最大授信可用比例(0-1,NULL按1)
/// </summary>
[DisplayName("最大授信可用比例")]
public double? MaxCreditUseRatio { get; set; }
/// <summary>
/// PFE授信
/// </summary>
@@ -213,5 +213,51 @@ namespace YLErp.DBModels
/// 存于 SpanConfigJson 文本列,无需 DDL。
/// </summary>
public string BondTerm { get; set; }
/// <summary>
/// ETF 细分种类(预留给"按资产类型分类"扩展:转债ETF / 科创债ETF / 其他)。
/// 本期不落 UI、不参与取数匹配,仅为后续需求预留扩展键;
/// 届时服务端校验与取数匹配的分组键设计为可扩展元组 (BondTerm, EtfKind)。
/// 序列化沿用 JsonHelper(默认忽略 null),null 时不写入 SpanConfigJson,不影响存量 JSON 读写。
/// </summary>
public string EtfKind { get; set; }
/// <summary>
/// 指数细分种类(预留给"按资产类型分类"扩展,如中债指数相关细分)。
/// 本期不落 UI、不参与取数匹配,仅为后续需求预留扩展键。
/// 序列化沿用 JsonHelper(默认忽略 null),null 时不写入 SpanConfigJson,不影响存量 JSON 读写。
/// </summary>
public string IndexKind { get; set; }
/// <summary>预警线(小数,0.95=95%</summary>
public double? WarnLine { get; set; }
/// <summary>平仓线(小数)</summary>
public double? CloseLine { get; set; }
/// <summary>多头方向追保区间(固定4层;第1层只用 Lower=上不封顶)</summary>
public List<SpanTierConfig> LongSpans { get; set; }
/// <summary>空头方向追保区间(固定4层;第1层只用 Upper</summary>
public List<SpanTierConfig> ShortSpans { get; set; }
}
/// <summary>
/// 区间追保结构单层追保区间配置(规则15 客户原型全量手工录入)。
/// 区间边界 Lower/Upper 为"×期初净价的百分比"小数(0.95=95%);
/// AmountRate 为追保金额比例,追保金额=AmountRate×期初全价×券面总额(债券)
/// 或 AmountRate×参考标的期初价格×参考标的名义份额(基金/债券指数)。
/// 多头方向 Lower 逐层严格递减、空头方向 Upper 逐层严格递增(服务端保存时校验)。
/// </summary>
public class SpanTierConfig
{
/// <summary>区间下边界(×期初净价的小数);多头第1层仅用本字段(上不封顶)</summary>
public double? Lower { get; set; }
/// <summary>区间上边界(×期初净价的小数);空头第1层仅用本字段(下不设限)</summary>
public double? Upper { get; set; }
/// <summary>追保金额比例(小数);金额=AmountRate×期初全价×券面总额 / ×参考标的期初价格×名义份额</summary>
public double? AmountRate { get; set; }
}
}
@@ -94,6 +94,23 @@ namespace System
return ToJson(obj, ignoreNullValue, camelCase);
}
/// <summary>
/// 序列化为可安全内嵌进 &lt;script&gt; 的 JSONHTML 敏感字符(&lt; &gt; &amp; 等)以 \uXXXX 形式转义,
/// 供 Razor 页 @Html.Raw(obj.ToInlineJson()) 内联页面对象使用。
/// 背景:全局 AntiXssConverterYLErpWeb Program.cs 注册)在 @Json.Serialize 渲染时把 &lt; &gt; 实体编码为 &amp;lt; &amp;gt;
/// JS 侧拿到的是实体文本而非原值(如 SpanConfig.BondTerm "&lt;5y" 变 "&amp;lt;5y"),保存回写即脏数据。
/// </summary>
public static string ToInlineJson(this object obj, bool ignoreNullValue = false)
{
if (obj == null)
{
return "null";
}
var setting = ((JsonSerializerSettingsEx)(ignoreNullValue ? settings[(int)SerializeSetting.ignoreNull] : settings[(int)SerializeSetting.none])).Clone();
setting.StringEscapeHandling = StringEscapeHandling.EscapeHtml;
return JsonConvert.SerializeObject(obj, setting);
}
//---------------------------------------------------------------------------------
/// <summary>
@@ -136,7 +136,8 @@ namespace YLErp.BLL.Eod
CreditRating = ratingStr,
//客户应付预付金(要么为负,要么为0)
//最新概念:负数代表客户应缴预付金,正数代表客户应收预付金
PayableMargin = clientSpan == null || clientSpan.WorstCastClientPayable == null ? 0 : clientSpan.WorstCastClientPayable.Value,
//合约维度盯市(MarginWatchRule==0)客户成交时已产生出入金,保证金不再计入占用(PayableMargin),避免双重体现
PayableMargin = client.MarginWatchRule == 0 ? 0 : (clientSpan == null || clientSpan.WorstCastClientPayable == null ? 0 : clientSpan.WorstCastClientPayable.Value),
DeltaMargin = clientSpan == null ? 0d : (clientSpan.DeltaMargin ?? 0d),
//新增互换预付金
SwapPayableMargin = clientSpan == null || clientSpan.SwapWorstCastClientPayable == null ? 0 : clientSpan.SwapWorstCastClientPayable.Value,
@@ -28,6 +28,8 @@ namespace YLErp.Modules.ClientModule
{
id = source.id,
Credit = source.Credit,
OriginalCredit = source.OriginalCredit,
MaxCreditUseRatio = source.MaxCreditUseRatio,
PFECredit = source.PFECredit,
IMCredit = source.IMCredit,
AuditCredit = source.AuditCredit,
@@ -21,6 +21,13 @@ namespace YLErp.Modules.ClientModule
throw new ServiceException("授信起始日不能早于授信到期日");
}
//原始授信值 × 最大授信可用比例(NULL按1) 折算出最终授信值;
//OriginalCredit 为空时不触碰 Credit,维持手工授信值(存量兼容)
if (req.OriginalCredit.HasValue)
{
req.Credit = req.OriginalCredit.Value * (req.MaxCreditUseRatio ?? 1);
}
if (req.Credit > req.AuditCredit)
{
throw new ServiceException("授信额度不能大于审批额度");
@@ -60,6 +67,8 @@ namespace YLErp.Modules.ClientModule
throw new ServiceException("数据已不存在");
}
updateComments = dbModel.Credit == req.Credit
&& dbModel.OriginalCredit == req.OriginalCredit
&& dbModel.MaxCreditUseRatio == req.MaxCreditUseRatio
&& dbModel.PFECredit == req.PFECredit
&& dbModel.IMCredit == req.IMCredit
&& dbModel.AuditCredit == req.AuditCredit
@@ -909,7 +909,12 @@ namespace YLErp.Modules.EodModule.SettlementModule
var clientSpan = clientSpanList == null || clientSpanList.Count == 0 ? null : clientSpanList.FirstOrDefault(t => t.ClientId == client.id && t.ValueDate == balanceDate && t.SpanType == ClientSpan.SpanType_Eod);
if (clientSpan != null)
{
PayableMargin += clientSpan.WorstCastClientPayable ?? 0d;
// 合约维度盯市(MarginWatchRule==0)客户成交时已产生出入金,保证金不再计入占用(PayableMargin),避免双重体现;
// 客户维度盯市(null/1)维持现状,计入占用。存量客户为 null,行为不变。
if (client.MarginWatchRule != 0)
{
PayableMargin += clientSpan.WorstCastClientPayable ?? 0d;
}
DeltaMargin = clientSpan.DeltaMargin ?? 0d;
SwapPayableMargin = clientSpan.SwapWorstCastClientPayable ?? 0d;
SwapUnPayableMargin = clientSpan.SwapUnMargin ?? 0d;
@@ -16,7 +16,7 @@ namespace YLErp.Modules.MarginModule
public static class MarginTemplateV2RateHelper
{
/// <summary>
/// 取数结果。率已统一换算为小数(如 0.1 表示 10%),调用方直接用。
/// 取数结果。率为小数(如 0.1 表示 10%,与数据库一致),调用方直接用。
/// </summary>
public class MarginRateResult
{
@@ -113,6 +113,14 @@ namespace YLErp.Modules.MarginModule
}
//5.标的资产类型匹配(仅当模板选了"按资产类型分类"):精确标志位 → 通配行(None/All)兜底
//标的细分分类判定钩子:本期默认返回 null → 走下方原有标志位匹配/通配行兜底,行为与现状一致;
//后续需求(转债ETF/科创债ETF/中债指数等细分)实现判定规则后,此处按 (BondTerm, EtfKind) 元组匹配明细行。
var underlyingCategory = GetUnderlyingCategory(underlyingCode, underlyingInstrumentType);
if (underlyingCategory != null)
{
//TODO: 后续需求实现:按 (SpanConfig.BondTerm, SpanConfig.EtfKind) 元组匹配明细行,替代/补充下方标志位匹配
}
if (template.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType
&& Enum.TryParse<UnderlyingTypeEnum>(underlyingInstrumentType, out var instrumentFlag))
{
@@ -142,11 +150,20 @@ namespace YLErp.Modules.MarginModule
}
/// <summary>
/// 模板页录入的百分数(如 10 表示 10%)换算为小数(0.1)
/// 标的细分分类判定钩子(转债ETF/科创债ETF/中债指数等)。
/// 本期默认返回 null → 走通配行兜底,行为与现状一致;后续需求按业务给的判定规则(代码段/标的维护字段)实现。
/// </summary>
private static string GetUnderlyingCategory(string underlyingCode, string underlyingInstrumentType)
{
return null;
}
/// <summary>
/// 明细率值换算:页面上 vue-number-input 以 % 展示,数据库存的就是小数(如 0.05 表示 5%),直接取用
/// </summary>
private static decimal? ToDecimalRate(double? ratio)
{
return ratio.HasValue ? (decimal)(ratio.Value / 100d) : (decimal?)null;
return ratio.HasValue ? (decimal)ratio.Value : (decimal?)null;
}
}
}
@@ -1658,6 +1658,19 @@ namespace YLErp.Modules.SwapModule
}
}
}
// 合约维度盯市+无预付金腿:重建交易级(positionId=0)初始预付金记录(与 SwapTradeConfirm 一致,回退重补场景)。
// 有预付金腿的互换由上面 foreach 按腿重建,不在此重复生成。
var resetWatchClient = DataCacheProvider.GetClientDataSource().GetData(td.ClientId);
var hasPrepayLeg = swapPositions != null && swapPositions.Any(x => x.InterestMode == (int)InterestModeEnum. || x.InterestMode == (int)InterestModeEnum.);
if (resetWatchClient != null && resetWatchClient.MarginWatchRule == 0 && !hasPrepayLeg)
{
var cashSvc = new ClientCashInCashOutService(this);
var initMargin = cashSvc.GetInitialMarginAmount(td);
if (initMargin > 0)
{
cashSvc.SaveSwapTradeClientCash(td, initMargin, happenDate, 0, ClientCashInCashOut._应付预付金);
}
}
}
/// <summary>
/// 获取持仓腿信息
@@ -9,6 +9,7 @@ using YLErp.Helpers;
using YLErp.Model;
using YLErp.Modules.CalculationModule;
using YLErp.Modules.ClientModule;
using YLErp.Modules.MarginModule;
using YLErp.Modules.TradeModule.DealModule;
namespace YLErp.Modules.TradeModule
@@ -185,6 +186,42 @@ namespace YLErp.Modules.TradeModule
}
DbContext.SaveChanges();
// 追保规则-合约维度盯市:非互换交易(期权等)成交确认时,按计算保证金生成初始预付金资金记录(入金)。
// 客户维度盯市(null/1)不生成出入金,保证金只进 clientbalancedaily.PayableMargin(占用),维持现状。
var marginClient = DataCacheProvider.GetClientDataSource().GetData(newtrade.ClientId);
if (marginClient != null && marginClient.MarginWatchRule == 0)
{
var initMargin = GetInitialMarginAmount(newtrade);
if (initMargin > 0)
{
SaveSwapTradeClientCash(newtrade, initMargin, newtrade.TradeDate ?? DateTime.Now, 0, ClientCashInCashOut._应付预付金);
}
}
}
/// <summary>
/// 计算合约维度盯市客户成交时应生成的初始预付金金额:
/// 交易绑定了预付金模板V2 → 名义本金(StockEqvNotional) × 初始率x"无预付金"规则算出0,不生成记录);
/// 否则(老交易/期权)→ trade.InitialMargin。期权未放开V2绑定,恒走 InitialMargin。
/// </summary>
public double GetInitialMarginAmount(trade td)
{
if (td == null) return 0;
// 新交易(绑定了 V2 模板)→ 名义本金 × x
bool boundV2 = DbContext.trade_margin_template.AsNoTracking().Any(x => x.TradeId == td.id);
if (boundV2)
{
var valueDate = td.TradeDate ?? DateTime.Today;
var rate = MarginTemplateV2RateHelper.GetTradeMarginRate(td.id, td.UnderlyingCode, td.UnderlyingInstrumentType, valueDate, DbContext);
if (rate != null)
{
return Math.Abs((double)(rate.InitRate ?? 0m) * td.StockEqvNotional);
}
// 绑定了模板但取率失败(模板无效/规则不支持/明细无匹配)→ 兜底用 InitialMargin
}
// 老交易(未绑 V2/期权 → trade.InitialMargin
return Math.Abs(td.InitialMargin ?? 0);
}
/// <summary>
+4 -1
View File
@@ -15,6 +15,7 @@
{ name: "SalesDepartmentId", label: "归属营业部", type: "select" },
{ name: "DefaultLoginName", label: "默认登录名", type: "text" },
{ name: "MarginOptionType", label: "追保方向", required: true, type: "select" },
{ name: "MarginWatchRule", label: "追保规则", required: true, type: "select" },
{ name: "Abbreviation", label: "客户简称", type: "text" },
{ name: "ProtocolSignVersion", label: "协议签署版本", type: "select", dictionaryKey: "协议签署版本" },
{ name: "AdminFullName", label: "管理人全称", type: "text" },
@@ -149,6 +150,7 @@
InstitutionalAttributes: ["国有", "民营", "外商", "其他"],
LicenseType: ["营业执照", "产品编号", "身份证"],
MarginOptionType: [{ text: "单向追保", value: 0 }, { text: "双向追保", value: 1 }, { text: "对手方单向追保", value: 2 }, { text: "其他", value: 3 }],
MarginWatchRule: [{ text: "合约维度盯市", value: 0 }, { text: "客户维度盯市", value: 1 }],
SamePeer: [{ text: "同业", value: 0 }, { text: "非同业", value: 1 }],
IsTradeCredit: [{ text: "否", value: 0 }, { text: "是", value: 1 }],
IsDocShowParentName: [{ text: "否", value: 0 }, { text: "是", value: 1 }],
@@ -173,7 +175,7 @@
},
defaults: {
ClientType: "机构", InstitutionalAttributes: "民营", LicenseType: "营业执照",
AccessRule: "2", MarginOptionType: "0",
AccessRule: "2", MarginOptionType: "0", MarginWatchRule: "1",
SamePeer: "1", IsTradeCredit: "1", IsDocShowParentName: "0", RiskPreference: "有限",
InvestmentExperience: "无", FundsSource: "1", BadFaithRecord: "1", DerivativesInvestmentVarieties: "1,2,3,4",
IsRealControl: "0", InvestmentTerm: "1", ExpectedReturn2: "5%-10%", IsIndustryConnectVariety: "1",
@@ -277,6 +279,7 @@
{ name: 'AgreementBookNo', label: '履约协议编号', hidden: false, sortable: false, align: 'left', width: '90' },
{ name: 'AgreementBookType', label: '履约协议类型', hidden: false, sortable: false, align: 'left', width: '90' },
{ name: 'MarginOptionType', label: '追保方向', hidden: false, sortable: false, align: 'left', width: '90' },
{ name: 'MarginWatchRule', label: '追保规则', hidden: false, sortable: false, align: 'left', width: '90' },
{ name: 'MainProtocolCode', label: '主协议编号', hidden: false, sortable: false, align: 'left', width: '150' },
{ name: 'SupProtocolCode', label: '补充协议编号', hidden: false, sortable: false, align: 'left', width: '150' },
{ name: 'ProductName', label: '产品名称', hidden: false, sortable: false, align: 'left', width: '150' },
@@ -299,12 +299,69 @@ namespace YLErp.Web.Controllers
}
}
//区间追保结构 + 按资产类型分类时,期限档仅允许"允许分期限档的资产类型"(本期仅利率债)配置,防止信用债等非分档类型误配期限档;
//该校验按行生效,与明细行数无关(单行明细同样拦截);
//按严格掩码判定:行标的类型位必须全部落在可分档类型内(混合标志位如 利率债|信用债 配期限档同样拦截,与取数侧整行期限档过滤语义一致);
//UnderlyingType 为空的通配行允许配期限档(取数侧期限档过滤在前、通配匹配在后,语义自洽)
if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.
&& marginTemplate.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType)
{
var enabledMask = ConsMarginTerm.TermTierEnabledUnderlyingTypes.Aggregate(UnderlyingTypeEnum.None, (a, t) => a | t);
foreach (var detail in marginTemplate.Details)
{
if (!string.IsNullOrEmpty(detail.SpanConfig?.BondTerm)
&& (detail.UnderlyingType & ~enabledMask) != UnderlyingTypeEnum.None)
{
throw new Exception("标的类型不允许配置期限档:" + UnderlyingTypeUtil.GetDesc(detail.UnderlyingType));
}
}
}
//区间追保结构:客户原型全量手工录入的预警线/平仓线与多空各4层追保区间校验。
//预警线/平仓线/两层区间全部为空视为未配置(兼容存量 x/y 过渡),任一值存在即按规则校验:
//数值非负;同层上下边界都有时上边界>下边界;单层部分填写报错提示完整填写
//(第1层边界特殊:多头第1层只需下边界Lower、空头第1层只需上边界Upper);
//多头各层下边界逐层严格递减、空头各层上边界逐层严格递增(填了才比)。
if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.)
{
for (var i = 0; i < marginTemplate.Details.Count; i++)
{
var cfg = marginTemplate.Details[i].SpanConfig;
if (cfg == null)
{
continue;
}
var longSpans = cfg.LongSpans ?? new List<SpanTierConfig>();
var shortSpans = cfg.ShortSpans ?? new List<SpanTierConfig>();
var hasAnyValue = cfg.WarnLine.HasValue || cfg.CloseLine.HasValue
|| longSpans.Any(t => t != null && (t.Lower.HasValue || t.Upper.HasValue || t.AmountRate.HasValue))
|| shortSpans.Any(t => t != null && (t.Lower.HasValue || t.Upper.HasValue || t.AmountRate.HasValue));
if (!hasAnyValue)
{
continue;
}
var label = "参数组" + (i + 1);
//区块化结构下利率债区块占连续4行(固定4档),报错带上期限档便于定位
if (!string.IsNullOrEmpty(cfg.BondTerm))
{
label += "(利率债期限档 " + cfg.BondTerm + "";
}
if (cfg.WarnLine < 0 || cfg.CloseLine < 0)
{
throw new Exception(label + ":预警线/平仓线不能为负数");
}
ValidateSpanTiers(longSpans, true, label);
ValidateSpanTiers(shortSpans, false, label);
}
}
if (marginTemplate.Details.Count() > 1)
{
if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.)
{
//区间追保结构:按 利率债期限档SpanConfig.BondTerm)分组校验,同一期限档内标的类型不允许重复,不同期限档允许相同标的类型
foreach (var termGroup in marginTemplate.Details.GroupBy(x => x.SpanConfig?.BondTerm ?? ""))
//区间追保结构:按 (利率债期限档, ETF细分) 元组分组校验(EtfKind 为预留扩展键,本期无 UI 写入,等价于仅按期限档分组),
//同一分组内标的类型不允许重复,不同分组允许相同标的类型
foreach (var termGroup in marginTemplate.Details.GroupBy(x => (x.SpanConfig?.BondTerm ?? "", x.SpanConfig?.EtfKind ?? "")))
{
var termUnderlyingType = UnderlyingTypeEnum.None;
foreach (var detail in termGroup)
@@ -432,6 +489,61 @@ namespace YLErp.Web.Controllers
}
}
/// <summary>
/// 规则15 区间追保结构单方向(多头/空头)4层追保区间校验,报错文案风格与"存在重复的标的类型:xxx"一致。
/// isLong=true 多头:第1层只需下边界 Lower(上不封顶),各层 Lower 逐层严格递减;
/// isLong=false 空头:第1层只需上边界 Upper(下不设限),各层 Upper 逐层严格递增。
/// </summary>
private static void ValidateSpanTiers(List<SpanTierConfig> tiers, bool isLong, string label)
{
var direction = isLong ? "多头方向" : "空头方向";
double? prevBoundary = null;
for (var n = 0; n < tiers.Count; n++)
{
var tier = tiers[n];
if (tier == null)
{
continue;
}
var tierLabel = label + "" + direction + "第" + (n + 1) + "层";
if (tier.Lower < 0 || tier.Upper < 0 || tier.AmountRate < 0)
{
throw new Exception(tierLabel + "追保区间不能为负数");
}
var hasAny = tier.Lower.HasValue || tier.Upper.HasValue || tier.AmountRate.HasValue;
if (!hasAny)
{
continue;
}
if (tier.Lower.HasValue && tier.Upper.HasValue && tier.Upper <= tier.Lower)
{
throw new Exception(tierLabel + "追保区间上边界必须大于下边界");
}
//第1层边界特殊:多头只需 Lower、空头只需 Upper;其余层 Lower/Upper/AmountRate 需完整填写
if (n == 0)
{
if ((isLong && !tier.Lower.HasValue) || (!isLong && !tier.Upper.HasValue) || !tier.AmountRate.HasValue)
{
throw new Exception(tierLabel + "请完整填写追保价格区间与追保金额");
}
}
else if (!tier.Lower.HasValue || !tier.Upper.HasValue || !tier.AmountRate.HasValue)
{
throw new Exception(tierLabel + "请完整填写追保价格区间与追保金额");
}
//逐层单调性:多头 Lower 逐层严格递减、空头 Upper 逐层严格递增(填了才比)
var boundary = isLong ? tier.Lower : tier.Upper;
if (boundary.HasValue)
{
if (prevBoundary.HasValue && (isLong ? boundary >= prevBoundary : boundary <= prevBoundary))
{
throw new Exception(tierLabel + (isLong ? "追保区间下边界须小于上一层的下边界" : "追保区间上边界须大于上一层的上边界"));
}
prevBoundary = boundary;
}
}
}
[HttpPost]
public JsonResult deleteEditmargin_template_v2(string enid)
{
@@ -20,8 +20,10 @@
@section JS{
<script type="text/javascript">
const pageVue = @Json.Serialize(Model);
const page = @Json.Serialize(pageObj);
@*内联页面对象用 ToInlineJsonNewtonsoft + EscapeHtml,敏感字符 \uXXXX 转义)替代 @Json.Serialize
全局 AntiXssConverter 会把 < > 实体编码为 &lt; &gt;,导致 SpanConfig.BondTerm 等字段回读后变脏值入库*@
const pageVue = @Html.Raw(JsonHelper.ToInlineJson(Model));
const page = @Html.Raw(JsonHelper.ToInlineJson(pageObj));
</script>
<script src="~/front/calendar?v=@(HtmlUtil.JsVersion)"></script>
@@ -154,8 +156,118 @@
</div>
<div class="col form-layout" style="height: 720px; overflow-y:auto;">
<template v-for="(detail,index) in marginTemplate.Details">
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="border detail">
<table class="table table-bordered" style="margin-bottom:0;">
<thead>
<tr>
<th v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">标的资产类型</th>
<th style="width:120px;">期限档位</th>
<th style="width:80px;">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="row in ruleRangeDetailRows" v-bind:key="row.index">
<td v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType) && row.rowspan > 0" v-bind:rowspan="row.rowspan" style="vertical-align:middle;">
@*chosen 选中变化通过 jQuery trigger("change") 通知(chosen.jquery.js trigger_form_field_change),
Vue 的 v-on:change(原生监听)收不到,改由 JS 内 jQuery 委托监听(见 marginTemplateV2DefaultEdit.js 底部)*@
<select class="chosen-select" :id="'UnderlyingType'+row.index" multiple>
<option value="16" :selected="(row.detail.UnderlyingType & 16)>0?true:false">利率债</option>
<option value="32" :selected="(row.detail.UnderlyingType & 32)>0?true:false">信用债</option>
<option value="64" :selected="(row.detail.UnderlyingType & 64)>0?true:false">其它债券</option>
<option value="128" :selected="(row.detail.UnderlyingType & 128)>0?true:false">股票</option>
<option value="256" :selected="(row.detail.UnderlyingType & 256)>0?true:false">股指</option>
<option value="512" :selected="(row.detail.UnderlyingType & 512)>0?true:false">股指期货</option>
<option value="1024" :selected="(row.detail.UnderlyingType & 1024)>0?true:false">商品期货</option>
<option value="2048" :selected="(row.detail.UnderlyingType & 2048)>0?true:false">商品现货</option>
<option value="4096" :selected="(row.detail.UnderlyingType & 4096)>0?true:false">新三板挂牌股票</option>
<option value="8192" :selected="(row.detail.UnderlyingType & 8192)>0?true:false">香港股票</option>
<option value="16384" :selected="(row.detail.UnderlyingType & 16384)>0?true:false">香港股指</option>
<option value="32768" :selected="(row.detail.UnderlyingType & 32768)>0?true:false">基金及基金专户</option>
<option value="65536" :selected="(row.detail.UnderlyingType & 65536)>0?true:false">黄金期货</option>
<option value="131072" :selected="(row.detail.UnderlyingType & 131072)>0?true:false">国债期货</option>
<option value="262144" :selected="(row.detail.UnderlyingType & 262144)>0?true:false">其他期货</option>
<option value="524288" :selected="(row.detail.UnderlyingType & 524288)>0?true:false">黄金现货</option>
<option value="1048576" :selected="(row.detail.UnderlyingType & 1048576)>0?true:false">其他现货</option>
<option value="2097152" :selected="(row.detail.UnderlyingType & 2097152)>0?true:false">境外期货</option>
<option value="4194304" :selected="(row.detail.UnderlyingType & 4194304)>0?true:false">境外现货</option>
<option value="8388608" :selected="(row.detail.UnderlyingType & 8388608)>0?true:false">境外股票</option>
<option value="16777216" :selected="(row.detail.UnderlyingType & 16777216)>0?true:false">境外股指</option>
<option value="33554432" :selected="(row.detail.UnderlyingType & 33554432)>0?true:false">汇率</option>
<option value="67108864" :selected="(row.detail.UnderlyingType & 67108864)>0?true:false">Shibor</option>
<option value="134217728" :selected="(row.detail.UnderlyingType & 134217728)>0?true:false">银行间回购定盘</option>
<option value="268435456" :selected="(row.detail.UnderlyingType & 268435456)>0?true:false">利率收益率</option>
<option value="536870912" :selected="(row.detail.UnderlyingType & 536870912)>0?true:false">债券指数</option>
</select>
</td>
@*期限档由区块结构固定承载(纯利率债区块固定4档、其他区块为空=全部),只读展示不再下拉选择*@
<td>{{bondTermLabel(row.detail)}}</td>
<td v-if="row.rowspan > 0" v-bind:rowspan="row.rowspan" style="vertical-align:middle;"><button class="delBtn" type="button" v-on:click="deleteDetail(row.index)">- 刪除区块</button></td>
</tr>
</tbody>
</table>
</div>
@*规则15区间追保录入(客户原型全量手工录入,资产类型区块式):纯利率债区块固定展开4个期限档、每档一套参数;其他资产类型区块一套参数、无期限档;x/y 字段保留兼容存量数据*@
<div v-for="blk in ruleRangeBlocks" v-bind:key="'spanblk'+blk.key" class="border" style="padding:6px 10px; margin-top:10px;">
<p style="margin:4px 0; font-weight:bold;">
参数组{{blk.no}}<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span><span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、&gt;30y,每个档位独立设置)</span>
</p>
<p style="margin:4px 0; color:#888;">存量 x/y 配置保留但不再展示,请按新区间结构录入。</p>
<div v-for="sec in blk.sections" v-bind:key="sec.key">
<p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
<template v-if="sec.detail && sec.detail.SpanConfig">
<p style="margin:4px 0;">
预警线:<vue-number-input v-model="sec.detail.SpanConfig.WarnLine" v-bind:format="inputFormatPercent"></vue-number-input>
 平仓线:<vue-number-input v-model="sec.detail.SpanConfig.CloseLine" v-bind:format="inputFormatPercent"></vue-number-input>
</p>
<p style="margin:4px 0;">多头方向(客户看多)</p>
<table class="table table-bordered" style="margin-bottom:6px;">
<thead>
<tr><th>追保价格区间</th><th>追保金额</th></tr>
</thead>
<tbody>
<tr v-for="(tier, ti) in sec.detail.SpanConfig.LongSpans" v-bind:key="'long'+ti">
<td>
<template v-if="ti === 0">
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}} ≤ {{spanText(sec.detail).priceCur}}
</template>
<template v-else>
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}} ≤ {{spanText(sec.detail).priceCur}} &lt; <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}}
</template>
</td>
<td>
<vue-number-input v-model="tier.AmountRate" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).amountBase}}
</td>
</tr>
</tbody>
</table>
<p style="margin:4px 0;">空头方向(客户看空)</p>
<table class="table table-bordered" style="margin-bottom:0;">
<thead>
<tr><th>追保价格区间</th><th>追保金额</th></tr>
</thead>
<tbody>
<tr v-for="(tier, ti) in sec.detail.SpanConfig.ShortSpans" v-bind:key="'short'+ti">
<td>
<template v-if="ti === 0">
{{spanText(sec.detail).priceCurShort1}} ≤ <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}}
</template>
<template v-else>
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}} &lt; {{spanText(sec.detail).priceCur}} ≤ <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}}
</template>
</td>
<td>
<vue-number-input v-model="tier.AmountRate" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).amountBase}}
</td>
</tr>
</tbody>
</table>
</template>
</div>
</div>
</template>
<template v-for="(detail,index) in marginTemplate.Details">
<div class="border detail" v-show="marginTemplate.RuleType != @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="row no-gutters">
<div class="col">
@if (PS.Config.Company == CompanyEnum.广发商贸)
@@ -202,58 +314,6 @@
</select>
</div>
</template>
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="form-group" v-if="detail.SpanConfig">
<label class="formlabel">利率债期限</label>
<select v-model="detail.SpanConfig.BondTerm">
<option value="">全部</option>
<option value="&lt;5y">&lt;5y</option>
<option value="5y-10y">5y-10y</option>
<option value="10y-30y">10y-30y</option>
<option value="&gt;30y">&gt;30y</option>
</select>
</div>
<div class="form-group" v-show="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">
<label class="formlabel">当标的资产类型为</label>
<select class="chosen-select" :id="'UnderlyingType'+index" multiple>
<option value="16" :selected="(detail.UnderlyingType & 16)>0?true:false">利率债</option>
<option value="32" :selected="(detail.UnderlyingType & 32)>0?true:false">信用债</option>
<option value="64" :selected="(detail.UnderlyingType & 64)>0?true:false">其它债券</option>
<option value="128" :selected="(detail.UnderlyingType & 128)>0?true:false">股票</option>
<option value="256" :selected="(detail.UnderlyingType & 256)>0?true:false">股指</option>
<option value="512" :selected="(detail.UnderlyingType & 512)>0?true:false">股指期货</option>
<option value="1024" :selected="(detail.UnderlyingType & 1024)>0?true:false">商品期货</option>
<option value="2048" :selected="(detail.UnderlyingType & 2048)>0?true:false">商品现货</option>
<option value="4096" :selected="(detail.UnderlyingType & 4096)>0?true:false">新三板挂牌股票</option>
<option value="8192" :selected="(detail.UnderlyingType & 8192)>0?true:false">香港股票</option>
<option value="16384" :selected="(detail.UnderlyingType & 16384)>0?true:false">香港股指</option>
<option value="32768" :selected="(detail.UnderlyingType & 32768)>0?true:false">基金及基金专户</option>
<option value="65536" :selected="(detail.UnderlyingType & 65536)>0?true:false">黄金期货</option>
<option value="131072" :selected="(detail.UnderlyingType & 131072)>0?true:false">国债期货</option>
<option value="262144" :selected="(detail.UnderlyingType & 262144)>0?true:false">其他期货</option>
<option value="524288" :selected="(detail.UnderlyingType & 524288)>0?true:false">黄金现货</option>
<option value="1048576" :selected="(detail.UnderlyingType & 1048576)>0?true:false">其他现货</option>
<option value="2097152" :selected="(detail.UnderlyingType & 2097152)>0?true:false">境外期货</option>
<option value="4194304" :selected="(detail.UnderlyingType & 4194304)>0?true:false">境外现货</option>
<option value="8388608" :selected="(detail.UnderlyingType & 8388608)>0?true:false">境外股票</option>
<option value="16777216" :selected="(detail.UnderlyingType & 16777216)>0?true:false">境外股指</option>
<option value="33554432" :selected="(detail.UnderlyingType & 33554432)>0?true:false">汇率</option>
<option value="67108864" :selected="(detail.UnderlyingType & 67108864)>0?true:false">Shibor</option>
<option value="134217728" :selected="(detail.UnderlyingType & 134217728)>0?true:false">银行间回购定盘</option>
<option value="268435456" :selected="(detail.UnderlyingType & 268435456)>0?true:false">利率收益率</option>
<option value="536870912" :selected="(detail.UnderlyingType & 536870912)>0?true:false">债券指数</option>
</select> 时,
</div>
<div class="form-group">
<label class="formlabel">初始预付金率x</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">维持预付金率y</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.类香草预付金规则)">
<div class="form-group">
<label class="formlabel">初始预付金率</label>
@@ -446,180 +506,3 @@
<button class="btn btn-primary" type="button" onclick="layer.closeMe()">关闭</button>
</div>
</form>
<div class="modal fade" id="myModal" data-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<!-- 模态框头部 -->
<div class="modal-header">
<h4 class="modal-title">参数设置</h4>
<button type="button" class="close" v-on:click="closeModal()">&times;</button>
</div>
<!-- 模态框主体 -->
<div class="modal-body">
<div class="form-group">
<label class="formlabel">生效日期</label>
<vue-datepicker v-model="detail.ValueDate" class="text-box" v-bind:disabled="!isAdd"></vue-datepicker>
</div>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.按固定利率) || ruleType == @((int)MarginRuleTypeEnum.按浮动盈亏)">
<div class="form-group">
<label class="formlabel">持仓预付金率</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用持仓名义本金</label>
<select v-model="detail.UsePositionStockEqvNotional">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="form-group" v-if="detail.SpanConfig">
<label class="formlabel">利率债期限</label>
<select v-model="detail.SpanConfig.BondTerm">
<option value="">全部</option>
<option value="&lt;5y">&lt;5y</option>
<option value="5y-10y">5y-10y</option>
<option value="10y-30y">10y-30y</option>
<option value="&gt;30y">&gt;30y</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">初始预付金率x</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">维持预付金率y</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.类香草预付金规则)">
<div class="form-group">
<label class="formlabel">初始预付金率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">临时预付金率1</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">临时预付金率2</label>
<vue-number-input v-model="detail.MarginRatio3" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">临时预付金率3</label>
<vue-number-input v-model="detail.MarginRatio4" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否内含价值</label>
<select v-model="detail.IsIntrinsicValue">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">组合预付金率</label>
<vue-number-input v-model="detail.GroupRatio" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">总名义本金</label>
<vue-number-input v-model="detail.StockEqvNotional"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">单一标的持仓名义本金</label>
<vue-number-input v-model="detail.SingleStockEqvNotional"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">持仓标的分散程度</label>
<vue-number-input v-model="detail.PositionUnderlyingAmount"></vue-number-input>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.触发条件预付金规则1) || ruleType == @((int)MarginRuleTypeEnum.触发条件预付金规则2)">
<div class="form-group">
<label class="formlabel">初始预付金率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">维持预付金率</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">触发追保跌幅</label>
<vue-number-input v-model="detail.RedeemPriceLimit" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用行权价</label>
<select v-model="detail.IsUseStrike">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">最大跌幅限制</label>
<vue-number-input v-model="detail.RedeemPriceMax" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">持仓标的分散程度</label>
<vue-number-input v-model="detail.PositionUnderlyingAmount"></vue-number-input>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.触发条件预付金规则3)">
<div class="form-group">
<label class="formlabel">预付金比率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">持仓预付金比率</label>
<vue-number-input v-model="detail.MarginRatio3" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用持仓名义本金</label>
<select v-model="detail.UsePositionStockEqvNotional">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">涨跌幅类型</label>
<select v-model="detail.PriceLimitType">
<option value="0">跌幅</option>
<option value="1">涨幅</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">触发追保比例</label>
<vue-number-input v-model="detail.RedeemPriceLimit" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用行权价</label>
<select v-model="detail.IsUseStrike">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">最大涨跌幅限制</label>
<vue-number-input v-model="detail.RedeemPriceMax" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">追加比例</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">持仓标的分散程度</label>
<vue-number-input v-model="detail.PositionUnderlyingAmount"></vue-number-input>
</div>
</template>
</div>
<!-- 模态框底部 -->
<div class="modal-footer">
<button type="button" class="btn" v-on:click="saveModal()">保存</button>
</div>
</div>
</div>
</div>
@@ -19,8 +19,10 @@
@section JS{
<script type="text/javascript">
const pageVue = @Json.Serialize(Model);
const page = @Json.Serialize(pageObj);
@*内联页面对象用 ToInlineJsonNewtonsoft + EscapeHtml,敏感字符 \uXXXX 转义)替代 @Json.Serialize
全局 AntiXssConverter 会把 < > 实体编码为 &lt; &gt;,导致 SpanConfig.BondTerm 等字段回读后变脏值入库*@
const pageVue = @Html.Raw(JsonHelper.ToInlineJson(Model));
const page = @Html.Raw(JsonHelper.ToInlineJson(pageObj));
page.marginTemplate.ValueDate = page.marginTemplate.ValueDate.substr(0, 10);
</script>
@@ -155,8 +157,118 @@
</div>
</div>
<div class="col form-layout" style="height: 720px; overflow-y:auto;">
<template v-for="(detail,index) in marginTemplate.Details">
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="border detail">
<table class="table table-bordered" style="margin-bottom:0;">
<thead>
<tr>
<th v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">标的资产类型</th>
<th style="width:120px;">期限档位</th>
<th style="width:80px;">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="row in ruleRangeDetailRows" v-bind:key="row.index">
<td v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType) && row.rowspan > 0" v-bind:rowspan="row.rowspan" style="vertical-align:middle;">
@*chosen 选中变化通过 jQuery trigger("change") 通知(chosen.jquery.js trigger_form_field_change),
Vue 的 v-on:change(原生监听)收不到,改由 JS 内 jQuery 委托监听(见 marginTemplateV2DefaultEdit.js 底部)*@
<select class="chosen-select" :id="'UnderlyingType'+row.index" multiple>
<option value="16" :selected="(row.detail.UnderlyingType & 16)>0?true:false">利率债</option>
<option value="32" :selected="(row.detail.UnderlyingType & 32)>0?true:false">信用债</option>
<option value="64" :selected="(row.detail.UnderlyingType & 64)>0?true:false">其它债券</option>
<option value="128" :selected="(row.detail.UnderlyingType & 128)>0?true:false">股票</option>
<option value="256" :selected="(row.detail.UnderlyingType & 256)>0?true:false">股指</option>
<option value="512" :selected="(row.detail.UnderlyingType & 512)>0?true:false">股指期货</option>
<option value="1024" :selected="(row.detail.UnderlyingType & 1024)>0?true:false">商品期货</option>
<option value="2048" :selected="(row.detail.UnderlyingType & 2048)>0?true:false">商品现货</option>
<option value="4096" :selected="(row.detail.UnderlyingType & 4096)>0?true:false">新三板挂牌股票</option>
<option value="8192" :selected="(row.detail.UnderlyingType & 8192)>0?true:false">香港股票</option>
<option value="16384" :selected="(row.detail.UnderlyingType & 16384)>0?true:false">香港股指</option>
<option value="32768" :selected="(row.detail.UnderlyingType & 32768)>0?true:false">基金及基金专户</option>
<option value="65536" :selected="(row.detail.UnderlyingType & 65536)>0?true:false">黄金期货</option>
<option value="131072" :selected="(row.detail.UnderlyingType & 131072)>0?true:false">国债期货</option>
<option value="262144" :selected="(row.detail.UnderlyingType & 262144)>0?true:false">其他期货</option>
<option value="524288" :selected="(row.detail.UnderlyingType & 524288)>0?true:false">黄金现货</option>
<option value="1048576" :selected="(row.detail.UnderlyingType & 1048576)>0?true:false">其他现货</option>
<option value="2097152" :selected="(row.detail.UnderlyingType & 2097152)>0?true:false">境外期货</option>
<option value="4194304" :selected="(row.detail.UnderlyingType & 4194304)>0?true:false">境外现货</option>
<option value="8388608" :selected="(row.detail.UnderlyingType & 8388608)>0?true:false">境外股票</option>
<option value="16777216" :selected="(row.detail.UnderlyingType & 16777216)>0?true:false">境外股指</option>
<option value="33554432" :selected="(row.detail.UnderlyingType & 33554432)>0?true:false">汇率</option>
<option value="67108864" :selected="(row.detail.UnderlyingType & 67108864)>0?true:false">Shibor</option>
<option value="134217728" :selected="(row.detail.UnderlyingType & 134217728)>0?true:false">银行间回购定盘</option>
<option value="268435456" :selected="(row.detail.UnderlyingType & 268435456)>0?true:false">利率收益率</option>
<option value="536870912" :selected="(row.detail.UnderlyingType & 536870912)>0?true:false">债券指数</option>
</select>
</td>
@*期限档由区块结构固定承载(纯利率债区块固定4档、其他区块为空=全部),只读展示不再下拉选择*@
<td>{{bondTermLabel(row.detail)}}</td>
<td v-if="row.rowspan > 0" v-bind:rowspan="row.rowspan" style="vertical-align:middle;"><button class="delBtn" type="button" v-on:click="deleteDetail(row.index)">- 刪除区块</button></td>
</tr>
</tbody>
</table>
</div>
@*规则15区间追保录入(客户原型全量手工录入,资产类型区块式):纯利率债区块固定展开4个期限档、每档一套参数;其他资产类型区块一套参数、无期限档;x/y 字段保留兼容存量数据*@
<div v-for="blk in ruleRangeBlocks" v-bind:key="'spanblk'+blk.key" class="border" style="padding:6px 10px; margin-top:10px;">
<p style="margin:4px 0; font-weight:bold;">
参数组{{blk.no}}<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span><span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、&gt;30y,每个档位独立设置)</span>
</p>
<p style="margin:4px 0; color:#888;">存量 x/y 配置保留但不再展示,请按新区间结构录入。</p>
<div v-for="sec in blk.sections" v-bind:key="sec.key">
<p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
<template v-if="sec.detail && sec.detail.SpanConfig">
<p style="margin:4px 0;">
预警线:<vue-number-input v-model="sec.detail.SpanConfig.WarnLine" v-bind:format="inputFormatPercent"></vue-number-input>
 平仓线:<vue-number-input v-model="sec.detail.SpanConfig.CloseLine" v-bind:format="inputFormatPercent"></vue-number-input>
</p>
<p style="margin:4px 0;">多头方向(客户看多)</p>
<table class="table table-bordered" style="margin-bottom:6px;">
<thead>
<tr><th>追保价格区间</th><th>追保金额</th></tr>
</thead>
<tbody>
<tr v-for="(tier, ti) in sec.detail.SpanConfig.LongSpans" v-bind:key="'long'+ti">
<td>
<template v-if="ti === 0">
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}} ≤ {{spanText(sec.detail).priceCur}}
</template>
<template v-else>
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}} ≤ {{spanText(sec.detail).priceCur}} &lt; <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}}
</template>
</td>
<td>
<vue-number-input v-model="tier.AmountRate" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).amountBase}}
</td>
</tr>
</tbody>
</table>
<p style="margin:4px 0;">空头方向(客户看空)</p>
<table class="table table-bordered" style="margin-bottom:0;">
<thead>
<tr><th>追保价格区间</th><th>追保金额</th></tr>
</thead>
<tbody>
<tr v-for="(tier, ti) in sec.detail.SpanConfig.ShortSpans" v-bind:key="'short'+ti">
<td>
<template v-if="ti === 0">
{{spanText(sec.detail).priceCurShort1}} ≤ <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}}
</template>
<template v-else>
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}} &lt; {{spanText(sec.detail).priceCur}} ≤ <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}}
</template>
</td>
<td>
<vue-number-input v-model="tier.AmountRate" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).amountBase}}
</td>
</tr>
</tbody>
</table>
</template>
</div>
</div>
</template>
<template v-for="(detail,index) in marginTemplate.Details">
<div class="border detail" v-show="marginTemplate.RuleType != @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="row no-gutters">
<div class="col">
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.按固定利率) || marginTemplate.RuleType == @((int)MarginRuleTypeEnum.按浮动盈亏)">
@@ -182,58 +294,6 @@
</select>
</div>
</template>
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="form-group" v-if="detail.SpanConfig">
<label class="formlabel">利率债期限</label>
<select v-model="detail.SpanConfig.BondTerm">
<option value="">全部</option>
<option value="&lt;5y">&lt;5y</option>
<option value="5y-10y">5y-10y</option>
<option value="10y-30y">10y-30y</option>
<option value="&gt;30y">&gt;30y</option>
</select>
</div>
<div class="form-group" v-show="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">
<label class="formlabel">当标的资产类型为</label>
<select class="chosen-select" :id="'UnderlyingType'+index" multiple>
<option value="16" :selected="(detail.UnderlyingType & 16)>0?true:false">利率债</option>
<option value="32" :selected="(detail.UnderlyingType & 32)>0?true:false">信用债</option>
<option value="64" :selected="(detail.UnderlyingType & 64)>0?true:false">其它债券</option>
<option value="128" :selected="(detail.UnderlyingType & 128)>0?true:false">股票</option>
<option value="256" :selected="(detail.UnderlyingType & 256)>0?true:false">股指</option>
<option value="512" :selected="(detail.UnderlyingType & 512)>0?true:false">股指期货</option>
<option value="1024" :selected="(detail.UnderlyingType & 1024)>0?true:false">商品期货</option>
<option value="2048" :selected="(detail.UnderlyingType & 2048)>0?true:false">商品现货</option>
<option value="4096" :selected="(detail.UnderlyingType & 4096)>0?true:false">新三板挂牌股票</option>
<option value="8192" :selected="(detail.UnderlyingType & 8192)>0?true:false">香港股票</option>
<option value="16384" :selected="(detail.UnderlyingType & 16384)>0?true:false">香港股指</option>
<option value="32768" :selected="(detail.UnderlyingType & 32768)>0?true:false">基金及基金专户</option>
<option value="65536" :selected="(detail.UnderlyingType & 65536)>0?true:false">黄金期货</option>
<option value="131072" :selected="(detail.UnderlyingType & 131072)>0?true:false">国债期货</option>
<option value="262144" :selected="(detail.UnderlyingType & 262144)>0?true:false">其他期货</option>
<option value="524288" :selected="(detail.UnderlyingType & 524288)>0?true:false">黄金现货</option>
<option value="1048576" :selected="(detail.UnderlyingType & 1048576)>0?true:false">其他现货</option>
<option value="2097152" :selected="(detail.UnderlyingType & 2097152)>0?true:false">境外期货</option>
<option value="4194304" :selected="(detail.UnderlyingType & 4194304)>0?true:false">境外现货</option>
<option value="8388608" :selected="(detail.UnderlyingType & 8388608)>0?true:false">境外股票</option>
<option value="16777216" :selected="(detail.UnderlyingType & 16777216)>0?true:false">境外股指</option>
<option value="33554432" :selected="(detail.UnderlyingType & 33554432)>0?true:false">汇率</option>
<option value="67108864" :selected="(detail.UnderlyingType & 67108864)>0?true:false">Shibor</option>
<option value="134217728" :selected="(detail.UnderlyingType & 134217728)>0?true:false">银行间回购定盘</option>
<option value="268435456" :selected="(detail.UnderlyingType & 268435456)>0?true:false">利率收益率</option>
<option value="536870912" :selected="(detail.UnderlyingType & 536870912)>0?true:false">债券指数</option>
</select> 时,
</div>
<div class="form-group">
<label class="formlabel">初始预付金率x</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">维持预付金率y</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.类香草预付金规则)">
<div class="form-group">
<label class="formlabel">初始预付金率</label>
@@ -426,184 +486,3 @@
<button class="btn btn-primary" type="button" onclick="layer.closeMe()">关闭</button>
</div>
</form>
<div class="modal fade" id="myModal" data-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<!-- 模态框头部 -->
<div class="modal-header">
<h4 class="modal-title">参数设置</h4>
<button type="button" class="close" v-on:click="closeModal()">&times;</button>
</div>
<!-- 模态框主体 -->
<div class="modal-body">
<div class="form-group">
<label class="formlabel">生效日期</label>
<vue-datepicker v-model="detail.ValueDate" class="text-box" v-bind:disabled="!isAdd"></vue-datepicker>
</div>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.按固定利率) || ruleType == @((int)MarginRuleTypeEnum.按浮动盈亏)">
@*<div class="form-group">
<label class="formlabel">初始预付金率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>*@
<div class="form-group">
<label class="formlabel">持仓预付金率</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用持仓名义本金</label>
<select v-model="detail.UsePositionStockEqvNotional">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="form-group" v-if="detail.SpanConfig">
<label class="formlabel">利率债期限</label>
<select v-model="detail.SpanConfig.BondTerm">
<option value="">全部</option>
<option value="&lt;5y">&lt;5y</option>
<option value="5y-10y">5y-10y</option>
<option value="10y-30y">10y-30y</option>
<option value="&gt;30y">&gt;30y</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">初始预付金率x</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">维持预付金率y</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.类香草预付金规则)">
<div class="form-group">
<label class="formlabel">初始预付金率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">临时预付金率1</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">临时预付金率2</label>
<vue-number-input v-model="detail.MarginRatio3" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">临时预付金率3</label>
<vue-number-input v-model="detail.MarginRatio4" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否内含价值</label>
<select v-model="detail.IsIntrinsicValue">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">组合预付金率</label>
<vue-number-input v-model="detail.GroupRatio" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">总名义本金</label>
<vue-number-input v-model="detail.StockEqvNotional"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">单一标的持仓名义本金</label>
<vue-number-input v-model="detail.SingleStockEqvNotional"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">持仓标的分散程度</label>
<vue-number-input v-model="detail.PositionUnderlyingAmount"></vue-number-input>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.触发条件预付金规则1) || ruleType == @((int)MarginRuleTypeEnum.触发条件预付金规则2)">
<div class="form-group">
<label class="formlabel">初始预付金率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">维持预付金率</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">触发追保跌幅</label>
<vue-number-input v-model="detail.RedeemPriceLimit" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用行权价</label>
<select v-model="detail.IsUseStrike">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">最大跌幅限制</label>
<vue-number-input v-model="detail.RedeemPriceMax" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">持仓标的分散程度</label>
<vue-number-input v-model="detail.PositionUnderlyingAmount"></vue-number-input>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.触发条件预付金规则3)">
<div class="form-group">
<label class="formlabel">预付金比率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">持仓预付金比率</label>
<vue-number-input v-model="detail.MarginRatio3" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用持仓名义本金</label>
<select v-model="detail.UsePositionStockEqvNotional">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">涨跌幅类型</label>
<select v-model="detail.PriceLimitType">
<option value="0">跌幅</option>
<option value="1">涨幅</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">触发追保比例</label>
<vue-number-input v-model="detail.RedeemPriceLimit" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用行权价</label>
<select v-model="detail.IsUseStrike">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">最大涨跌幅限制</label>
<vue-number-input v-model="detail.RedeemPriceMax" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">追加比例</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">持仓标的分散程度</label>
<vue-number-input v-model="detail.PositionUnderlyingAmount"></vue-number-input>
</div>
</template>
</div>
<!-- 模态框底部 -->
<div class="modal-footer">
<button type="button" class="btn" v-on:click="saveModal()">保存</button>
</div>
</div>
</div>
</div>
@@ -19,8 +19,10 @@
@section JS{
<script type="text/javascript">
const pageVue = @Json.Serialize(Model);
const page = @Json.Serialize(pageObj);
@*内联页面对象用 ToInlineJsonNewtonsoft + EscapeHtml,敏感字符 \uXXXX 转义)替代 @Json.Serialize
全局 AntiXssConverter 会把 < > 实体编码为 &lt; &gt;,导致 SpanConfig.BondTerm 等字段回读后变脏值入库*@
const pageVue = @Html.Raw(JsonHelper.ToInlineJson(Model));
const page = @Html.Raw(JsonHelper.ToInlineJson(pageObj));
</script>
<script src="~/front/calendar?v=@(HtmlUtil.JsVersion)"></script>
@@ -115,11 +117,134 @@
<textarea class="text-box" rows="3" style="width: 320px; text-align:left;" v-model="marginTemplate.Comments" v-bind:title="marginTemplate.Comments" disabled>
</textarea>
</div>
<div class="form-group">
<label class="formlabel">是否按标的分类设置参数</label>
<select v-model="marginTemplate.UnderlyingSeperateType" v-on:change="changeUnderlyingSeperateType()" v-bind:disabled="marginTemplate.RuleType != @((int)MarginRuleTypeEnum.按固定利率) && marginTemplate.RuleType != @((int)MarginRuleTypeEnum.标准SPAN) && marginTemplate.RuleType != @((int)MarginRuleTypeEnum.卖出自动赎回规则1) && marginTemplate.RuleType != @((int)MarginRuleTypeEnum.区间追保结构)">
<option value="@((int)UnderlyingSeperateTypeEnum.None)">不区分</option>
<option v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)" value="@((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">按资产类型分类</option>
</select>
</div>
<div class="form-group">
<label class="formlabel"></label>
<textarea class="text-box" rows="5" style="width: 320px; text-align:left;" v-model="marginTemplate.UnderlyingSeperateComments" v-bind:title="marginTemplate.UnderlyingSeperateComments" disabled>
</textarea>
</div>
</div>
</div>
<div class="col form-layout" style="height: 520px; overflow-y:auto;">
<template v-for="(detail,index) in marginTemplate.Details">
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="border detail">
<table class="table table-bordered" style="margin-bottom:0;">
<thead>
<tr>
<th v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">标的资产类型</th>
<th style="width:120px;">期限档位</th>
<th style="width:80px;">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="row in ruleRangeDetailRows" v-bind:key="row.index">
<td v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType) && row.rowspan > 0" v-bind:rowspan="row.rowspan" style="vertical-align:middle;">
@*chosen 选中变化通过 jQuery trigger("change") 通知(chosen.jquery.js trigger_form_field_change),
Vue 的 v-on:change(原生监听)收不到,改由 JS 内 jQuery 委托监听(见 marginTemplateV2Edit.js 底部)*@
<select class="chosen-select" :id="'UnderlyingType'+row.index" multiple>
<option value="16" :selected="(row.detail.UnderlyingType & 16)>0?true:false">利率债</option>
<option value="32" :selected="(row.detail.UnderlyingType & 32)>0?true:false">信用债</option>
<option value="64" :selected="(row.detail.UnderlyingType & 64)>0?true:false">其它债券</option>
<option value="128" :selected="(row.detail.UnderlyingType & 128)>0?true:false">股票</option>
<option value="256" :selected="(row.detail.UnderlyingType & 256)>0?true:false">股指</option>
<option value="512" :selected="(row.detail.UnderlyingType & 512)>0?true:false">股指期货</option>
<option value="1024" :selected="(row.detail.UnderlyingType & 1024)>0?true:false">商品期货</option>
<option value="2048" :selected="(row.detail.UnderlyingType & 2048)>0?true:false">商品现货</option>
<option value="4096" :selected="(row.detail.UnderlyingType & 4096)>0?true:false">新三板挂牌股票</option>
<option value="8192" :selected="(row.detail.UnderlyingType & 8192)>0?true:false">香港股票</option>
<option value="16384" :selected="(row.detail.UnderlyingType & 16384)>0?true:false">香港股指</option>
<option value="32768" :selected="(row.detail.UnderlyingType & 32768)>0?true:false">基金及基金专户</option>
<option value="65536" :selected="(row.detail.UnderlyingType & 65536)>0?true:false">黄金期货</option>
<option value="131072" :selected="(row.detail.UnderlyingType & 131072)>0?true:false">国债期货</option>
<option value="262144" :selected="(row.detail.UnderlyingType & 262144)>0?true:false">其他期货</option>
<option value="524288" :selected="(row.detail.UnderlyingType & 524288)>0?true:false">黄金现货</option>
<option value="1048576" :selected="(row.detail.UnderlyingType & 1048576)>0?true:false">其他现货</option>
<option value="2097152" :selected="(row.detail.UnderlyingType & 2097152)>0?true:false">境外期货</option>
<option value="4194304" :selected="(row.detail.UnderlyingType & 4194304)>0?true:false">境外现货</option>
<option value="8388608" :selected="(row.detail.UnderlyingType & 8388608)>0?true:false">境外股票</option>
<option value="16777216" :selected="(row.detail.UnderlyingType & 16777216)>0?true:false">境外股指</option>
<option value="33554432" :selected="(row.detail.UnderlyingType & 33554432)>0?true:false">汇率</option>
<option value="67108864" :selected="(row.detail.UnderlyingType & 67108864)>0?true:false">Shibor</option>
<option value="134217728" :selected="(row.detail.UnderlyingType & 134217728)>0?true:false">银行间回购定盘</option>
<option value="268435456" :selected="(row.detail.UnderlyingType & 268435456)>0?true:false">利率收益率</option>
<option value="536870912" :selected="(row.detail.UnderlyingType & 536870912)>0?true:false">债券指数</option>
</select>
</td>
@*期限档由区块结构固定承载(纯利率债区块固定4档、其他区块为空=全部),只读展示不再下拉选择*@
<td>{{bondTermLabel(row.detail)}}</td>
<td v-if="row.rowspan > 0" v-bind:rowspan="row.rowspan" style="vertical-align:middle;"><button class="delBtn" type="button" v-on:click="deleteDetail(row.index)">- 刪除区块</button></td>
</tr>
</tbody>
</table>
</div>
@*规则15区间追保录入(客户原型全量手工录入,资产类型区块式):纯利率债区块固定展开4个期限档、每档一套参数;其他资产类型区块一套参数、无期限档;x/y 字段保留兼容存量数据*@
<div v-for="blk in ruleRangeBlocks" v-bind:key="'spanblk'+blk.key" class="border" style="padding:6px 10px; margin-top:10px;">
<p style="margin:4px 0; font-weight:bold;">
参数组{{blk.no}}<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span><span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、&gt;30y,每个档位独立设置)</span>
</p>
<p style="margin:4px 0; color:#888;">存量 x/y 配置保留但不再展示,请按新区间结构录入。</p>
<div v-for="sec in blk.sections" v-bind:key="sec.key">
<p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
<template v-if="sec.detail && sec.detail.SpanConfig">
<p style="margin:4px 0;">
预警线:<vue-number-input v-model="sec.detail.SpanConfig.WarnLine" v-bind:format="inputFormatPercent"></vue-number-input>
 平仓线:<vue-number-input v-model="sec.detail.SpanConfig.CloseLine" v-bind:format="inputFormatPercent"></vue-number-input>
</p>
<p style="margin:4px 0;">多头方向(客户看多)</p>
<table class="table table-bordered" style="margin-bottom:6px;">
<thead>
<tr><th>追保价格区间</th><th>追保金额</th></tr>
</thead>
<tbody>
<tr v-for="(tier, ti) in sec.detail.SpanConfig.LongSpans" v-bind:key="'long'+ti">
<td>
<template v-if="ti === 0">
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}} ≤ {{spanText(sec.detail).priceCur}}
</template>
<template v-else>
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}} ≤ {{spanText(sec.detail).priceCur}} &lt; <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}}
</template>
</td>
<td>
<vue-number-input v-model="tier.AmountRate" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).amountBase}}
</td>
</tr>
</tbody>
</table>
<p style="margin:4px 0;">空头方向(客户看空)</p>
<table class="table table-bordered" style="margin-bottom:0;">
<thead>
<tr><th>追保价格区间</th><th>追保金额</th></tr>
</thead>
<tbody>
<tr v-for="(tier, ti) in sec.detail.SpanConfig.ShortSpans" v-bind:key="'short'+ti">
<td>
<template v-if="ti === 0">
{{spanText(sec.detail).priceCurShort1}} ≤ <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}}
</template>
<template v-else>
<vue-number-input v-model="tier.Lower" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}} &lt; {{spanText(sec.detail).priceCur}} ≤ <vue-number-input v-model="tier.Upper" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).priceInit}}
</template>
</td>
<td>
<vue-number-input v-model="tier.AmountRate" v-bind:format="inputFormatPercent"></vue-number-input> × {{spanText(sec.detail).amountBase}}
</td>
</tr>
</tbody>
</table>
</template>
</div>
</div>
</template>
<template v-for="(detail,index) in marginTemplate.Details">
<div class="border detail" v-show="marginTemplate.RuleType != @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="row no-gutters">
<div class="col">
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.按固定利率) || marginTemplate.RuleType == @((int)MarginRuleTypeEnum.按浮动盈亏)">
@@ -135,26 +260,6 @@
</select>
</div>
</template>
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="form-group" v-if="detail.SpanConfig">
<label class="formlabel">利率债期限</label>
<select v-model="detail.SpanConfig.BondTerm">
<option value="">全部</option>
<option value="&lt;5y">&lt;5y</option>
<option value="5y-10y">5y-10y</option>
<option value="10y-30y">10y-30y</option>
<option value="&gt;30y">&gt;30y</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">初始预付金率x</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">维持预付金率y</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.类香草预付金规则)">
<div class="form-group">
<label class="formlabel">初始预付金率</label>
@@ -292,6 +397,7 @@
</div>
</div>
</template>
<button class="addBtn" v-show="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)" v-on:click="addDetail()">+ 新增参数组</button>
</div>
</div>
<div class="form-buttons">
@@ -299,161 +405,3 @@
<button class="btn btn-primary" type="button" onclick="layer.closeMe()">关闭</button>
</div>
</form>
<div class="modal fade" id="myModal" data-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<!-- 模态框头部 -->
<div class="modal-header">
<h4 class="modal-title">参数设置</h4>
<button type="button" class="close" v-on:click="closeModal()">&times;</button>
</div>
<!-- 模态框主体 -->
<div class="modal-body">
<div class="form-group">
<label class="formlabel">生效日期</label>
<vue-datepicker v-model="detail.ValueDate" class="text-box" v-bind:disabled="!isAdd"></vue-datepicker>
</div>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.按固定利率) || ruleType == @((int)MarginRuleTypeEnum.按浮动盈亏)">
@*<div class="form-group">
<label class="formlabel">初始预付金率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>*@
<div class="form-group">
<label class="formlabel">持仓预付金率</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用持仓名义本金</label>
<select v-model="detail.UsePositionStockEqvNotional">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="form-group" v-if="detail.SpanConfig">
<label class="formlabel">利率债期限</label>
<select v-model="detail.SpanConfig.BondTerm">
<option value="">全部</option>
<option value="&lt;5y">&lt;5y</option>
<option value="5y-10y">5y-10y</option>
<option value="10y-30y">10y-30y</option>
<option value="&gt;30y">&gt;30y</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">初始预付金率x</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">维持预付金率y</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.类香草预付金规则)">
<div class="form-group">
<label class="formlabel">初始预付金率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">临时预付金率1</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">临时预付金率2</label>
<vue-number-input v-model="detail.MarginRatio3" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">临时预付金率3</label>
<vue-number-input v-model="detail.MarginRatio4" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否内含价值</label>
<select v-model="detail.IsIntrinsicValue">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.触发条件预付金规则1) || ruleType == @((int)MarginRuleTypeEnum.触发条件预付金规则2)">
<div class="form-group">
<label class="formlabel">预付金率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">维持预付金率</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">触发追保跌幅</label>
<vue-number-input v-model="detail.RedeemPriceLimit" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用行权价</label>
<select v-model="detail.IsUseStrike">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">最大跌幅限制</label>
<vue-number-input v-model="detail.RedeemPriceMax" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<template v-if="ruleType == @((int)MarginRuleTypeEnum.触发条件预付金规则3)">
<div class="form-group">
<label class="formlabel">预付金比率</label>
<vue-number-input v-model="detail.MarginRatio1" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">持仓预付金比率</label>
<vue-number-input v-model="detail.MarginRatio3" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用持仓名义本金</label>
<select v-model="detail.UsePositionStockEqvNotional">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">涨跌幅类型</label>
<select v-model="detail.PriceLimitType">
<option value="0">跌幅</option>
<option value="1">涨幅</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">触发追保比例</label>
<vue-number-input v-model="detail.RedeemPriceLimit" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">是否使用行权价</label>
<select v-model="detail.IsUseStrike">
<option value="false">否</option>
<option value="true">是</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">最大涨跌幅限制</label>
<vue-number-input v-model="detail.RedeemPriceMax" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group">
<label class="formlabel">追加比例</label>
<vue-number-input v-model="detail.MarginRatio2" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
</div>
<!-- 模态框底部 -->
<div class="modal-footer">
<button type="button" class="btn" v-on:click="saveModal()">保存</button>
</div>
</div>
</div>
</div>
@@ -28,16 +28,106 @@ const UnderlyingSeperateTypeEnum = {
CustomInstrumentType: "2"//按资产类型分类
};
//允许配置期限档(SpanConfig.BondTerm)的标的资产类型标志位掩码,
//与后端 ConsMarginTerm.TermTierEnabledUnderlyingTypes 保持一致(本期仅 利率债 TBonds=1<<4=16
const TermTierEnabledUnderlyingMask = 16;
//利率债区块固定展开的4个期限档([BondTerm值, 展示文案]),与 docx 原型 4.3.1 一致
const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']];
//区块键自增序号(仅前端分组用,不持久化)
var spanBlockSeq = 0;
//标的资产类型标志位 -> 名称(与明细表格 chosen 选项一致,用于规则15录入区块标题)
const UnderlyingTypeNames = [
[16, "利率债"], [32, "信用债"], [64, "其它债券"], [128, "股票"], [256, "股指"], [512, "股指期货"],
[1024, "商品期货"], [2048, "商品现货"], [4096, "新三板挂牌股票"], [8192, "香港股票"], [16384, "香港股指"],
[32768, "基金及基金专户"], [65536, "黄金期货"], [131072, "国债期货"], [262144, "其他期货"], [524288, "黄金现货"],
[1048576, "其他现货"], [2097152, "境外期货"], [4194304, "境外现货"], [8388608, "境外股票"], [16777216, "境外股指"],
[33554432, "汇率"], [67108864, "Shibor"], [134217728, "银行间回购定盘"], [268435456, "利率收益率"], [536870912, "债券指数"]
];
const vue = new Vue({
el: '#marginTemplateV2Form',
data: {
isAdd: page.isAdd,
marginTemplate: page.marginTemplate,
latestValueDate: page.marginTemplate.LatestValueDate
marginTemplate: page.marginTemplate
},
created: function () {
//区间追保结构:加载时按 资产类型+期限 排序一次,再重建资产类型区块(利率债区块补齐固定4档),
//编辑过程中不再重排,避免 chosen/选择器失序
if (this.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构 && this.marginTemplate.Details) {
var termOrder = ['', '<5y', '5y-10y', '10y-30y', '>30y'];
var that = this;
this.marginTemplate.Details.forEach(function (d) {
//兜底:历史数据可能没有 SpanConfig / 区间录入结构,补齐避免录入区块不渲染
that.ensureSpanDetail(d);
});
this.marginTemplate.Details.sort(function (a, b) {
var at = a.UnderlyingType || 0, bt = b.UnderlyingType || 0;
if (at !== bt) return at - bt;
var ai = termOrder.indexOf((a.SpanConfig && a.SpanConfig.BondTerm) || '');
var bi = termOrder.indexOf((b.SpanConfig && b.SpanConfig.BondTerm) || '');
return (ai < 0 ? termOrder.length : ai) - (bi < 0 ? termOrder.length : bi);
});
this.marginTemplate.Details = this.rebuildSpanBlocks(this.marginTemplate.Details);
}
this.changeRuleType(true);
},
computed: {
//区间追保结构:参数组按资产类型区块展示,相邻且区块键(_bk)相同的行合并(rowspan)。
//_bk 由 加载重建/新增/切换资产类型 时维护:利率债区块含固定4档行,其他区块单行。
ruleRangeDetailRows() {
var details = this.marginTemplate.Details || [];
var keyOf = function (d, i) {
return d._bk || ('idx_' + i);
};
var rows = [];
for (var i = 0; i < details.length; i++) {
var key = keyOf(details[i], i);
if (i > 0 && keyOf(details[i - 1], i - 1) === key) {
rows.push({ detail: details[i], index: i, rowspan: 0 });
continue;
}
var span = 1;
for (var j = i + 1; j < details.length; j++) {
if (keyOf(details[j], j) !== key) break;
span++;
}
rows.push({ detail: details[i], index: i, rowspan: span });
}
return rows;
},
//区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)区块固定展开4个期限档段(每段一条 detail),
//其他区块单段(BondTerm 为空)。段的 detail 缺失时模板侧 v-if 兜底不渲染该段
ruleRangeBlocks() {
if (this.marginTemplate.RuleType != MarginRuleTypeEnum.区间追保结构) return [];
var blocks = [];
var rows = this.ruleRangeDetailRows;
var details = this.marginTemplate.Details || [];
for (var i = 0; i < rows.length; i++) {
if (rows[i].rowspan <= 0) continue;
var head = rows[i];
var ut = head.detail.UnderlyingType || 0;
var isTBond = ut === 16;
var sections = [];
if (isTBond) {
for (var t = 0; t < SpanBondTerms.length; t++) {
var found = null;
for (var j = head.index; j < head.index + head.rowspan; j++) {
var bt = (details[j].SpanConfig && details[j].SpanConfig.BondTerm) || '';
if (bt === SpanBondTerms[t][0]) { found = details[j]; break; }
}
sections.push({ key: SpanBondTerms[t][0], termLabel: SpanBondTerms[t][1], detail: found });
}
} else {
sections.push({ key: 'single', termLabel: '', detail: head.detail });
}
blocks.push({ key: head.detail._bk || ('i' + head.index), no: blocks.length + 1, headIndex: head.index, ut: ut, isTBond: isTBond, sections: sections });
}
return blocks;
}
},
methods: {
changeRuleType(isInitial) {
switch (this.marginTemplate.RuleType.toString()) {
@@ -95,7 +185,7 @@ const vue = new Vue({
this.marginTemplate.Comments = "名义本金*Maxx,y-0.5*虚值比例)+ Flag*Max(0,PV)\n其中虚值比例 = max(0, P - 执行价)/期初价格,当计算日为成交日时,Flag = 0,P = 期初价格,当计算日不为成交日时,Flag = 1,P = 标的现价;\n若勾选了“取与保底金额孰小”,则取较小的一个;\n保底金额 =ABS(执行价格 - 保底价格)* ABS(交易数量)。";
break;
case MarginRuleTypeEnum.区间追保结构:
this.marginTemplate.Comments = "预付金占用为名义本金的固定比例。按利率债期限(<5y/5y-10y/10y-30y/>30y)分别设置初始预付金率x、维持预付金率y,支持再按标的资产类型(利率债/信用债/其它债券/股票/股指等全部资产类型)分组;以期初净价为基准,按(x-y)逐档推算预警线/平仓线,标的价格触及档位线时触发追保或平仓。";
this.marginTemplate.Comments = "预付金占用为名义本金的固定比例。按利率债期限(<5y/5y-10y/10y-30y/>30y)分别设置预警线/平仓线与多空各4层追保价格区间及追保金额比例,支持再按标的资产类型(利率债/信用债/其它债券/股票/股指等全部资产类型)分组;以期初净价为基准,标的价格落入录入的追保区间时按对应比例追保,触及预警线/平仓线时触发预警或平仓。";
break;
case MarginRuleTypeEnum.默认预付金规则:
this.marginTemplate.Comments = "考虑了14种场景下的Span算法与Delta算法比较的通用预付金规则,该规则可以调整Span涨跌幅度";
@@ -114,7 +204,7 @@ const vue = new Vue({
if (this.marginTemplate.UnderlyingSeperateType == UnderlyingSeperateTypeEnum.None) {
this.marginTemplate.UnderlyingSeperateComments = "无论挂钩标的代码为何,都采用同一套参数";
} else if (this.marginTemplate.UnderlyingSeperateType == UnderlyingSeperateTypeEnum.CustomInstrumentType) {
this.marginTemplate.UnderlyingSeperateComments = "按标的的资产类型分类:在每组参数中选择适用的资产类型(利率债/信用债/其它债券/股票/股指/商品期货等全部资产类型),按标的资产类型匹配对应参数组的初始预付金率x、维持预付金率y。";
this.marginTemplate.UnderlyingSeperateComments = "按标的的资产类型分类:在每组参数中选择适用的资产类型(利率债/信用债/其它债券/股票/股指/商品期货等全部资产类型),按标的资产类型匹配对应参数组的区间追保参数。";
} else {
this.marginTemplate.UnderlyingSeperateComments = "把所有标的根据代码所属区间段自动分为4类:\n【1-指数】沪深指数;\n【2-个股/ETF-核准制发行】主板股票、非科创版ETF;\n【3-个股/ETF-非核准制发行】科创版股票、科创版ETF、创业板股票;\n【4-其他】所有不满足以上分类的。";
}
@@ -138,7 +228,14 @@ const vue = new Vue({
needDetailRules.push(MarginRuleTypeEnum.默认预付金规则);
if (needDetailRules.indexOf(that.marginTemplate.RuleType.toString()) >= 0) {
new Promise((reslove) => {
that.marginTemplate.Details.push(_.cloneDeep(page.detail));
//规则15:新增的是"资产类型区块"(含区间录入结构与独立区块键);其他规则维持原样
if (that.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构) {
var d = that.newSpanDetail(0, '');
Vue.set(d, '_bk', 'bk_' + (++spanBlockSeq));
that.marginTemplate.Details.push(d);
} else {
that.marginTemplate.Details.push(_.cloneDeep(page.detail));
}
reslove();
}).then(() => {
SetAceDropDown();
@@ -161,11 +258,28 @@ const vue = new Vue({
if (that.marginTemplate.UnderlyingSeperateType == 0) {
detail.UnderlyingType = 15;
}
else {
detail.UnderlyingType = that.sum($("#UnderlyingType" + index).val());
}
detail.SpanConfigJson = JSON.stringify(detail.SpanConfig);
});
if (that.marginTemplate.UnderlyingSeperateType != 0) {
if (that.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构) {
//区间追保结构表格:按区块取值——区块首行读选择器,被合并的档位行跟随本区块(区块内资产类型一致)
var groupValue = 0;
that.ruleRangeDetailRows.forEach((row) => {
if (row.rowspan > 0) {
var ut = $("#UnderlyingType" + row.index).val();
groupValue = ut ? that.sum(ut) : (row.detail.UnderlyingType || 0);
//与 onUnderlyingTypeChange 同口径兜底:含利率债(位16)时只能单选利率债
if ((groupValue & TermTierEnabledUnderlyingMask) > 0) groupValue = TermTierEnabledUnderlyingMask;
}
row.detail.UnderlyingType = groupValue;
});
}
else {
that.marginTemplate.Details.forEach((detail, index) => {
detail.UnderlyingType = that.sum($("#UnderlyingType" + index).val());
});
}
}
main.post("/margin_template_v2/saveMarginTemplateV2", { marginTemplate: this.marginTemplate }).done(function (resp) {
try {
window.parent.reloadmargin_template();
@@ -173,26 +287,223 @@ const vue = new Vue({
} catch (e) { }
});
},
setTemplateDetail(detail) {
vueDetail.backupDetail = _.cloneDeep(detail);
detail.ValueDate = new moment(detail.ValueDate).format("YYYY-MM-DD");
detail.UsePositionStockEqvNotional = false;
vueDetail.detail = detail;
vueDetail.isAdd = false;
vueDetail.ruleType = vue.marginTemplate.RuleType;
$("#myModal").modal("show");
},
addDetail() {
let that = this
new Promise((reslove) => {
that.marginTemplate.Details.push(_.cloneDeep(page.detail));
//规则15:新增参数组=新增一个资产类型区块(默认未选资产类型,单行;选成纯利率债时自动展开4档)
if (that.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构) {
var d = that.newSpanDetail(0, '');
Vue.set(d, '_bk', 'bk_' + (++spanBlockSeq));
that.marginTemplate.Details.push(d);
} else {
that.marginTemplate.Details.push(_.cloneDeep(page.detail));
}
reslove();
}).then(() => {
SetAceDropDown();
that.refreshChosen();
})
},
deleteDetail(index) {
this.marginTemplate.Details.splice(index, 1);
//规则15:删除区块=删除该区块全部 detail 行(利率债区块4行、其他区块1行)
if (this.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构) {
var rows = this.ruleRangeDetailRows;
for (var i = 0; i < rows.length; i++) {
if (rows[i].index === index) {
this.marginTemplate.Details.splice(index, Math.max(rows[i].rowspan, 1));
break;
}
}
} else {
this.marginTemplate.Details.splice(index, 1);
}
this.refreshChosen();
},
//资产类型 chosen 变更(区块级):含利率债(位16)时强制单选利率债;
//纯利率债区块改为不含利率债时先弹确认(4档收缩为1条,其余3档录入数据将丢弃);
//反向(非利率债 → 利率债展开4档)保留已有数据,无需确认
onUnderlyingTypeChange(index) {
var ut = this.sum($("#UnderlyingType" + index).val());
if ((ut & TermTierEnabledUnderlyingMask) > 0) ut = TermTierEnabledUnderlyingMask;
var rows = this.ruleRangeDetailRows;
var head = null;
for (var i = 0; i < rows.length; i++) {
if (rows[i].index === index) { head = rows[i]; break; }
}
if (!head) return;
if ((head.detail.UnderlyingType || 0) === TermTierEnabledUnderlyingMask && ut !== TermTierEnabledUnderlyingMask) {
var that = this;
main.confirm("该区块当前为利率债分档配置,改为其他资产类型后其余期限档的录入内容将被清除,确认修改?", function () {
that.applyUnderlyingTypeChange(index, ut);
}, function () {
//取消:Vue 数据未动,翻转一次 UnderlyingType 强制重渲染 select,再由 chosen 重读选中态回退为纯利率债
head.detail.UnderlyingType = 0;
Vue.nextTick(function () {
head.detail.UnderlyingType = TermTierEnabledUnderlyingMask;
that.refreshChosen();
});
});
return;
}
this.applyUnderlyingTypeChange(index, ut);
},
//区块资产类型变更的实际落地(onUnderlyingTypeChange 确认后调用):
//ut=纯利率债 → 区块固定展开4个期限档 detail(保留已有档位行数据,缺档补新行);
//否则 → 区块收敛为单条 detail(BondTerm 置空,保留首行数据)
applyUnderlyingTypeChange(index, ut) {
var rows = this.ruleRangeDetailRows;
var head = null;
for (var i = 0; i < rows.length; i++) {
if (rows[i].index === index) { head = rows[i]; break; }
}
if (!head) return;
var details = this.marginTemplate.Details;
var headDetail = head.detail;
var bk = headDetail._bk;
if (!bk) {
bk = 'bk_' + (++spanBlockSeq);
Vue.set(headDetail, '_bk', bk);
}
var oldCount = Math.max(head.rowspan, 1);
if (ut === TermTierEnabledUnderlyingMask) {
var byTerm = {};
details.slice(index, index + oldCount).forEach(function (r) {
var bt = (r.SpanConfig && r.SpanConfig.BondTerm) || '';
if (!byTerm[bt]) byTerm[bt] = r;
});
var merged = [];
for (var t = 0; t < SpanBondTerms.length; t++) {
var r = byTerm[SpanBondTerms[t][0]] || (t === 0 ? headDetail : null);
if (!r) r = this.newSpanDetail(TermTierEnabledUnderlyingMask, SpanBondTerms[t][0]);
r.UnderlyingType = TermTierEnabledUnderlyingMask;
r.SpanConfig.BondTerm = SpanBondTerms[t][0];
Vue.set(r, '_bk', bk);
merged.push(r);
}
details.splice.apply(details, [index, oldCount].concat(merged));
} else {
headDetail.UnderlyingType = ut;
if (headDetail.SpanConfig) headDetail.SpanConfig.BondTerm = '';
if (oldCount > 1) details.splice(index + 1, oldCount - 1);
}
this.refreshChosen();
},
//行合并重组/删除后重渲染:SetAceDropDown 只增强新出现的 select(已初始化的会跳过,见 MyJs.js:137-138),
//chosen:updated 让被 Vue 复用的 select 按最新 :selected 重读选中态,两者需同时执行
refreshChosen() {
Vue.nextTick(function () {
SetAceDropDown();
$('.chosen-select').trigger('chosen:updated');
});
},
//新建一条规则15 detail:克隆 page.detail 模板行,设置资产类型/期限档,并补齐区间录入结构
newSpanDetail(ut, bondTerm) {
var d = _.cloneDeep(page.detail);
d.UnderlyingType = ut;
this.ensureSpanDetail(d);
d.SpanConfig.BondTerm = bondTerm || '';
return d;
},
//存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行归为一个区块并按固定4档补齐(缺档补空行);
//同档重复/异常档的利率债行、非利率债行均各自独立成块展示,不丢数据
rebuildSpanBlocks(details) {
var termValues = SpanBondTerms.map(function (t) { return t[0]; });
var result = [];
var that = this;
var i = 0;
while (i < details.length) {
var d = details[i];
var ut = d.UnderlyingType || 0;
if (ut === TermTierEnabledUnderlyingMask) {
var rows = [];
while (i < details.length && (details[i].UnderlyingType || 0) === TermTierEnabledUnderlyingMask) {
rows.push(details[i]);
i++;
}
var byTerm = {};
var anomalies = [];
rows.forEach(function (r) {
var bt = (r.SpanConfig && r.SpanConfig.BondTerm) || '';
if (termValues.indexOf(bt) >= 0 && !byTerm[bt]) byTerm[bt] = r; else anomalies.push(r);
});
var bk = 'bk_' + (++spanBlockSeq);
termValues.forEach(function (t) {
var r = byTerm[t];
if (!r) r = that.newSpanDetail(TermTierEnabledUnderlyingMask, t);
r.UnderlyingType = TermTierEnabledUnderlyingMask;
r.SpanConfig.BondTerm = t;
Vue.set(r, '_bk', bk);
result.push(r);
});
anomalies.forEach(function (r) {
Vue.set(r, '_bk', 'bk_' + (++spanBlockSeq));
result.push(r);
});
} else {
Vue.set(d, '_bk', 'bk_' + (++spanBlockSeq));
result.push(d);
i++;
}
}
return result;
},
underlyingTypeNames(mask) {
mask = parseInt(mask) || 0;
if (mask === 0) return "全部";
var names = [];
for (var i = 0; i < UnderlyingTypeNames.length; i++) {
if ((mask & UnderlyingTypeNames[i][0]) > 0) names.push(UnderlyingTypeNames[i][1]);
}
return names.length > 0 ? names.join("、") : "全部";
},
bondTermLabel(detail) {
var v = (detail.SpanConfig && detail.SpanConfig.BondTerm) || '';
var map = { '': '全部', '<5y': '≤5y', '5y-10y': '(5y-10y]', '10y-30y': '(10y-30y]', '>30y': '>30y' };
return map[v] !== undefined ? map[v] : v;
},
//规则15录入区块文案:按 detail 资产类型切换计价口径。
//债券类(利率债16/信用债32/其它债券64)及其他类型(默认债券口径):期初净价/当前净价,金额=×期初全价×券面总额;
//基金及基金专户(32768)/债券指数(536870912):参考标的期初净价/参考标的当前净价(空头第1层用"参考标的当前收盘价"),
//金额=×参考标的期初价格×参考标的名义份额。
spanText(detail) {
var ut = (detail && detail.UnderlyingType) || 0;
if ((ut & 32768) > 0 || (ut & 536870912) > 0) {
return {
priceInit: '参考标的期初净价',
priceCur: '参考标的当前净价',
priceCurShort1: '参考标的当前收盘价',
amountBase: '参考标的期初价格 × 参考标的名义份额'
};
}
return {
priceInit: '期初净价',
priceCur: '当前净价',
priceCurShort1: '当前净价',
amountBase: '期初全价 × 券面总额'
};
},
//4个空追保区间 tier(区间边界/金额比例均存小数,null=未填)
emptySpanTiers() {
var tiers = [];
for (var i = 0; i < 4; i++) tiers.push({ Lower: null, Upper: null, AmountRate: null });
return tiers;
},
//规则15:补齐区间录入结构——SpanConfig 缺省时补空对象,WarnLine/CloseLine 缺键时补 null
//LongSpans/ShortSpans 非数组或为空时初始化为4个空 tier,tier 缺键补齐(全程 Vue.set 保证响应式,v-model 绑定点必须预先存在)
ensureSpanDetail(detail) {
if (!detail.SpanConfig) Vue.set(detail, 'SpanConfig', { BondTerm: '' });
var sc = detail.SpanConfig;
if (sc.WarnLine === undefined) Vue.set(sc, 'WarnLine', null);
if (sc.CloseLine === undefined) Vue.set(sc, 'CloseLine', null);
var that = this;
['LongSpans', 'ShortSpans'].forEach(function (key) {
if (!Array.isArray(sc[key]) || sc[key].length === 0) Vue.set(sc, key, that.emptySpanTiers());
for (var i = 0; i < 4; i++) {
if (!sc[key][i]) Vue.set(sc[key], i, { Lower: null, Upper: null, AmountRate: null });
if (sc[key][i].Lower === undefined) Vue.set(sc[key][i], 'Lower', null);
if (sc[key][i].Upper === undefined) Vue.set(sc[key][i], 'Upper', null);
if (sc[key][i].AmountRate === undefined) Vue.set(sc[key][i], 'AmountRate', null);
}
});
},
sum(items) {
var total = 0;
@@ -238,42 +549,12 @@ $(function () {
$(this).datepicker("setDate", dateVal);
});
//chosen 选中变化通过 jQuery trigger("change") 通知(chosen.jquery.js trigger_form_field_change),
//Vue 的 v-on:change(原生监听)收不到,这里用 jQuery 委托监听规则15区块的资产类型选择器
$(document).on('change', 'select.chosen-select[id^="UnderlyingType"]', function () {
var idx = parseInt(this.id.substring('UnderlyingType'.length));
if (!isNaN(idx)) vue.onUnderlyingTypeChange(idx);
});
SetAceDropDown();
});
var vueDetail = new Vue({
el: "#myModal",
data: {
detail: {},
backupDetail: {},
isAdd: true,
ruleType: 0,
},
created: function () {
},
methods: {
closeModal: function () {
if (!this.isAdd) {
Vue.set(vue.marginTemplate.Details, vue.marginTemplate.Details.indexOf(this.detail), this.backupDetail);
}
$('#myModal').modal('hide');
},
saveModal: function () {
if (this.isAdd) {
if (vue.latestValueDate && vue.latestValueDate >= this.detail.ValueDate) {
main.message("生效日期需要大于已设置生效日");
return;
}
vue.marginTemplate.Details.push(this.detail);
vue.latestValueDate = this.detail.ValueDate;
}
Vue.set(vue.marginTemplate.Details, vue.marginTemplate.Details.indexOf(this.detail), this.detail);
$('#myModal').modal('hide');
}
},
components: {
'vue-datepicker': FastVue.vueDatePicker(),
'vue-number-input': FastVue.vueNumberInput()
}
});
@@ -24,66 +24,171 @@ const MarginRuleTypeEnum = {
const UnderlyingSeperateTypeEnum = {
None: "0",//不区分
CustomStock: "1"//按自定义权益分类
CustomStock: "1",//按自定义权益分类
CustomInstrumentType: "2"//按资产类型分类
};
//允许配置期限档(SpanConfig.BondTerm)的标的资产类型标志位掩码,
//与后端 ConsMarginTerm.TermTierEnabledUnderlyingTypes 保持一致(本期仅 利率债 TBonds=1<<4=16
const TermTierEnabledUnderlyingMask = 16;
//利率债区块固定展开的4个期限档([BondTerm值, 展示文案]),与 docx 原型 4.3.1 一致
const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']];
//区块键自增序号(仅前端分组用,不持久化)
var spanBlockSeq = 0;
//标的资产类型标志位 -> 名称(与明细表格 chosen 选项一致,用于规则15录入区块标题)
const UnderlyingTypeNames = [
[16, "利率债"], [32, "信用债"], [64, "其它债券"], [128, "股票"], [256, "股指"], [512, "股指期货"],
[1024, "商品期货"], [2048, "商品现货"], [4096, "新三板挂牌股票"], [8192, "香港股票"], [16384, "香港股指"],
[32768, "基金及基金专户"], [65536, "黄金期货"], [131072, "国债期货"], [262144, "其他期货"], [524288, "黄金现货"],
[1048576, "其他现货"], [2097152, "境外期货"], [4194304, "境外现货"], [8388608, "境外股票"], [16777216, "境外股指"],
[33554432, "汇率"], [67108864, "Shibor"], [134217728, "银行间回购定盘"], [268435456, "利率收益率"], [536870912, "债券指数"]
];
const vue = new Vue({
el: '#marginTemplateV2Form',
data: {
isAdd: page.isAdd,
marginTemplate: page.marginTemplate,
latestValueDate: page.marginTemplate.LatestValueDate
marginTemplate: page.marginTemplate
},
created: function () {
//区间追保结构:加载时按 资产类型+期限 排序一次,再重建资产类型区块(利率债区块补齐固定4档),
//编辑过程中不再重排,避免 chosen/选择器失序
if (this.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构 && this.marginTemplate.Details) {
var termOrder = ['', '<5y', '5y-10y', '10y-30y', '>30y'];
var that = this;
this.marginTemplate.Details.forEach(function (d) {
//兜底:历史数据可能没有 SpanConfig / 区间录入结构,补齐避免录入区块不渲染
that.ensureSpanDetail(d);
});
this.marginTemplate.Details.sort(function (a, b) {
var at = a.UnderlyingType || 0, bt = b.UnderlyingType || 0;
if (at !== bt) return at - bt;
var ai = termOrder.indexOf((a.SpanConfig && a.SpanConfig.BondTerm) || '');
var bi = termOrder.indexOf((b.SpanConfig && b.SpanConfig.BondTerm) || '');
return (ai < 0 ? termOrder.length : ai) - (bi < 0 ? termOrder.length : bi);
});
this.marginTemplate.Details = this.rebuildSpanBlocks(this.marginTemplate.Details);
}
this.changeRuleType(true);
},
computed: {
//区间追保结构:参数组按资产类型区块展示,相邻且区块键(_bk)相同的行合并(rowspan)。
//_bk 由 加载重建/新增/切换资产类型 时维护:利率债区块含固定4档行,其他区块单行。
ruleRangeDetailRows() {
var details = this.marginTemplate.Details || [];
var keyOf = function (d, i) {
return d._bk || ('idx_' + i);
};
var rows = [];
for (var i = 0; i < details.length; i++) {
var key = keyOf(details[i], i);
if (i > 0 && keyOf(details[i - 1], i - 1) === key) {
rows.push({ detail: details[i], index: i, rowspan: 0 });
continue;
}
var span = 1;
for (var j = i + 1; j < details.length; j++) {
if (keyOf(details[j], j) !== key) break;
span++;
}
rows.push({ detail: details[i], index: i, rowspan: span });
}
return rows;
},
//区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)区块固定展开4个期限档段(每段一条 detail),
//其他区块单段(BondTerm 为空)。段的 detail 缺失时模板侧 v-if 兜底不渲染该段
ruleRangeBlocks() {
if (this.marginTemplate.RuleType != MarginRuleTypeEnum.区间追保结构) return [];
var blocks = [];
var rows = this.ruleRangeDetailRows;
var details = this.marginTemplate.Details || [];
for (var i = 0; i < rows.length; i++) {
if (rows[i].rowspan <= 0) continue;
var head = rows[i];
var ut = head.detail.UnderlyingType || 0;
var isTBond = ut === 16;
var sections = [];
if (isTBond) {
for (var t = 0; t < SpanBondTerms.length; t++) {
var found = null;
for (var j = head.index; j < head.index + head.rowspan; j++) {
var bt = (details[j].SpanConfig && details[j].SpanConfig.BondTerm) || '';
if (bt === SpanBondTerms[t][0]) { found = details[j]; break; }
}
sections.push({ key: SpanBondTerms[t][0], termLabel: SpanBondTerms[t][1], detail: found });
}
} else {
sections.push({ key: 'single', termLabel: '', detail: head.detail });
}
blocks.push({ key: head.detail._bk || ('i' + head.index), no: blocks.length + 1, headIndex: head.index, ut: ut, isTBond: isTBond, sections: sections });
}
return blocks;
}
},
methods: {
changeRuleType(isInitial) {
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
switch (this.marginTemplate.RuleType.toString()) {
case MarginRuleTypeEnum.无预付金:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "预付金占用为零。";
break;
case MarginRuleTypeEnum.按固定利率:
this.marginTemplate.Comments = "预付金占用为成交/实时名义本金的固定比例。";
break;
case MarginRuleTypeEnum.按浮动盈亏:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "预付金占用为成交/实时名义本金的固定比例,加上对手方交易浮动亏损,扣除对手方交易浮动盈利。";
break;
case MarginRuleTypeEnum.类香草预付金规则:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "预付金占用为为成交/实时名义本金的浮动比例,加上对手方交易浮动亏损。浮动比例依据标的涨跌停情况判断。";
break;
case MarginRuleTypeEnum.自动赎回买入预付金规则:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "预付金占用为n+2(≤N)个票息之和,N为合约期内敲出观察日总数,n为当前经过的敲出观察日个数(当n+2>N时,n = N - 2)。";
break;
case MarginRuleTypeEnum.触发条件预付金规则1:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "预付金占用为成交名义本金的固定比例,加标的跌幅的市值(跌幅达到一定条件触发)";
break;
case MarginRuleTypeEnum.触发条件预付金规则2:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "预付金占用为成交/实时名义本金的固定比例,加标的跌幅的市值(跌幅达到一定条件触发)";
break;
case MarginRuleTypeEnum.触发条件预付金规则3:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "预付金占用为成交/实时名义本金的固定比例,加上对手方分段式交易浮动亏损(涨跌幅达到一定条件触发)";
break;
case MarginRuleTypeEnum.保底预付金规则:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "预付金占用为对手方最大亏损金额。";
break;
case MarginRuleTypeEnum.含赔付预付金计算规则:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "预付金占用为对手方最大赔付金额。";
break;
case MarginRuleTypeEnum.交易所基本规则:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "交易所基本规则";
break;
case MarginRuleTypeEnum.标准SPAN:
this.marginTemplate.Comments = "计算标的价格和波动率上涨下跌4个场景下的对手方最大亏损";
break;
case MarginRuleTypeEnum.买入自动赎回规则1:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "Minx*首次票息金额+y*Max(0,Pv),合约期间全部票息金额);\n若勾选了“取与保底金额孰小”,则还要与取较小的一个;\n保底金额 =ABS(执行价格 - 保底价格)* ABS(交易数量)。";
break;
case MarginRuleTypeEnum.卖出自动赎回规则1:
this.marginTemplate.Comments = "名义本金*Maxx,y-0.5*虚值比例)+ Flag*Max(0,PV)\n其中虚值比例 = max(0, P - 执行价)/期初价格,当计算日为成交日时,Flag = 0,P = 期初价格,当计算日不为成交日时,Flag = 1,P = 标的现价;\n若勾选了“取与保底金额孰小”,则取较小的一个;\n保底金额 =ABS(执行价格 - 保底价格)* ABS(交易数量)。";
break;
case MarginRuleTypeEnum.区间追保结构:
this.marginTemplate.Comments = "预付金占用为名义本金的固定比例。按利率债期限(<5y/5y-10y/10y-30y/>30y)分别设置初始预付金率x、维持预付金率y;以期初净价为基准,按(x-y)逐档推算预警线/平仓线,标的价格触及档位线时触发追保或平仓。";
this.marginTemplate.Comments = "预付金占用为名义本金的固定比例。按利率债期限(<5y/5y-10y/10y-30y/>30y)分别设置预警线/平仓线与多空各4层追保价格区间及追保金额比例,支持再按标的资产类型(利率债/信用债/其它债券/股票/股指等全部资产类型)分组;以期初净价为基准,标的价格落入录入的追保区间时按对应比例追保,触及预警线/平仓线时触发预警或平仓。";
break;
case MarginRuleTypeEnum.单独计算规则:
this.marginTemplate.UnderlyingSeperateType = UnderlyingSeperateTypeEnum.None;
this.marginTemplate.Comments = "基于宏源定制的预付金规则,用于计算单笔交易,不参与Span预付金规则的轧差。";
break;
default:
@@ -97,6 +202,14 @@ const vue = new Vue({
return;
}
if (this.marginTemplate.UnderlyingSeperateType == UnderlyingSeperateTypeEnum.None) {
this.marginTemplate.UnderlyingSeperateComments = "无论挂钩标的代码为何,都采用同一套参数";
} else if (this.marginTemplate.UnderlyingSeperateType == UnderlyingSeperateTypeEnum.CustomInstrumentType) {
this.marginTemplate.UnderlyingSeperateComments = "按标的的资产类型分类:在每组参数中选择适用的资产类型(利率债/信用债/其它债券/股票/股指/商品期货等全部资产类型),按标的资产类型匹配对应参数组的区间追保参数。";
} else {
this.marginTemplate.UnderlyingSeperateComments = "把所有标的根据代码所属区间段自动分为4类:\n【1-指数】沪深指数;\n【2-个股/ETF-核准制发行】主板股票、非科创版ETF;\n【3-个股/ETF-非核准制发行】科创版股票、科创版ETF、创业板股票;\n【4-其他】所有不满足以上分类的。";
}
let that = this
new Promise((reslove) => {
that.marginTemplate.Details = [];
@@ -116,7 +229,14 @@ const vue = new Vue({
needDetailRules.push(MarginRuleTypeEnum.单独计算规则1);
if (needDetailRules.indexOf(that.marginTemplate.RuleType.toString()) >= 0) {
new Promise((reslove) => {
that.marginTemplate.Details.push(_.cloneDeep(page.detail));
//规则15:新增的是"资产类型区块"(含区间录入结构与独立区块键);其他规则维持原样
if (that.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构) {
var d = that.newSpanDetail(0, '');
Vue.set(d, '_bk', 'bk_' + (++spanBlockSeq));
that.marginTemplate.Details.push(d);
} else {
that.marginTemplate.Details.push(_.cloneDeep(page.detail));
}
reslove();
}).then(() => {
SetAceDropDown();
@@ -140,6 +260,37 @@ const vue = new Vue({
}
this.marginTemplate.TradeTypes = $("#tradeTypes").val().join(",");
this.marginTemplate.IsDefault = false;
var that = this;
this.marginTemplate.Details.forEach((detail, index) => {
if (that.marginTemplate.UnderlyingSeperateType == 0) {
detail.UnderlyingType = 15;//15 = UnderlyingTypeEnum.All:不区分时的惰性通配值,与后端枚举对齐
}
detail.SpanConfigJson = JSON.stringify(detail.SpanConfig);
});
if (that.marginTemplate.UnderlyingSeperateType != 0) {
if (that.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构) {
//区间追保结构表格:按区块取值——区块首行读选择器,被合并的档位行跟随本区块(区块内资产类型一致)
var groupValue = 0;
that.ruleRangeDetailRows.forEach((row) => {
if (row.rowspan > 0) {
var ut = $("#UnderlyingType" + row.index).val();
groupValue = ut ? that.sum(ut) : (row.detail.UnderlyingType || 0);
//与 onUnderlyingTypeChange 同口径兜底:含利率债(位16)时只能单选利率债
if ((groupValue & TermTierEnabledUnderlyingMask) > 0) groupValue = TermTierEnabledUnderlyingMask;
}
row.detail.UnderlyingType = groupValue;
});
}
else {
that.marginTemplate.Details.forEach((detail, index) => {
//本页仅规则15 表格渲染资产类型选择器;其他规则下元素不存在时保持原值不回写,避免静默清零
var $el = $("#UnderlyingType" + index);
if ($el.length) {
detail.UnderlyingType = that.sum($el.val());
}
});
}
}
main.post("/margin_template_v2/saveMarginTemplateV2", { marginTemplate: this.marginTemplate }).done(function (resp) {
try {
window.parent.reloadmargin_template();
@@ -147,22 +298,223 @@ const vue = new Vue({
} catch (e) { }
});
},
setTemplateDetail(detail) {
vueDetail.backupDetail = _.cloneDeep(detail);
detail.ValueDate = new moment(detail.ValueDate).format("YYYY-MM-DD");
vueDetail.detail = detail;
vueDetail.isAdd = false;
vueDetail.ruleType = vue.marginTemplate.RuleType;
$("#myModal").modal("show");
},
addDetail() {
var detail = _.cloneDeep(page.detail);
detail.UsePositionStockEqvNotional = false;
detail.ValueDate = new moment(detail.ValueDate).format("YYYY-MM-DD");
vueDetail.detail = detail;
vueDetail.isAdd = true;
vueDetail.ruleType = vue.marginTemplate.RuleType;
$("#myModal").modal("show");
let that = this
new Promise((reslove) => {
//规则15:新增参数组=新增一个资产类型区块(默认未选资产类型,单行;选成纯利率债时自动展开4档)
if (that.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构) {
var d = that.newSpanDetail(0, '');
Vue.set(d, '_bk', 'bk_' + (++spanBlockSeq));
that.marginTemplate.Details.push(d);
} else {
that.marginTemplate.Details.push(_.cloneDeep(page.detail));
}
reslove();
}).then(() => {
that.refreshChosen();
})
},
deleteDetail(index) {
//规则15:删除区块=删除该区块全部 detail 行(利率债区块4行、其他区块1行)
if (this.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构) {
var rows = this.ruleRangeDetailRows;
for (var i = 0; i < rows.length; i++) {
if (rows[i].index === index) {
this.marginTemplate.Details.splice(index, Math.max(rows[i].rowspan, 1));
break;
}
}
} else {
this.marginTemplate.Details.splice(index, 1);
}
this.refreshChosen();
},
//资产类型 chosen 变更(区块级):含利率债(位16)时强制单选利率债;
//纯利率债区块改为不含利率债时先弹确认(4档收缩为1条,其余3档录入数据将丢弃);
//反向(非利率债 → 利率债展开4档)保留已有数据,无需确认
onUnderlyingTypeChange(index) {
var ut = this.sum($("#UnderlyingType" + index).val());
if ((ut & TermTierEnabledUnderlyingMask) > 0) ut = TermTierEnabledUnderlyingMask;
var rows = this.ruleRangeDetailRows;
var head = null;
for (var i = 0; i < rows.length; i++) {
if (rows[i].index === index) { head = rows[i]; break; }
}
if (!head) return;
if ((head.detail.UnderlyingType || 0) === TermTierEnabledUnderlyingMask && ut !== TermTierEnabledUnderlyingMask) {
var that = this;
main.confirm("该区块当前为利率债分档配置,改为其他资产类型后其余期限档的录入内容将被清除,确认修改?", function () {
that.applyUnderlyingTypeChange(index, ut);
}, function () {
//取消:Vue 数据未动,翻转一次 UnderlyingType 强制重渲染 select,再由 chosen 重读选中态回退为纯利率债
head.detail.UnderlyingType = 0;
Vue.nextTick(function () {
head.detail.UnderlyingType = TermTierEnabledUnderlyingMask;
that.refreshChosen();
});
});
return;
}
this.applyUnderlyingTypeChange(index, ut);
},
//区块资产类型变更的实际落地(onUnderlyingTypeChange 确认后调用):
//ut=纯利率债 → 区块固定展开4个期限档 detail(保留已有档位行数据,缺档补新行);
//否则 → 区块收敛为单条 detail(BondTerm 置空,保留首行数据)
applyUnderlyingTypeChange(index, ut) {
var rows = this.ruleRangeDetailRows;
var head = null;
for (var i = 0; i < rows.length; i++) {
if (rows[i].index === index) { head = rows[i]; break; }
}
if (!head) return;
var details = this.marginTemplate.Details;
var headDetail = head.detail;
var bk = headDetail._bk;
if (!bk) {
bk = 'bk_' + (++spanBlockSeq);
Vue.set(headDetail, '_bk', bk);
}
var oldCount = Math.max(head.rowspan, 1);
if (ut === TermTierEnabledUnderlyingMask) {
var byTerm = {};
details.slice(index, index + oldCount).forEach(function (r) {
var bt = (r.SpanConfig && r.SpanConfig.BondTerm) || '';
if (!byTerm[bt]) byTerm[bt] = r;
});
var merged = [];
for (var t = 0; t < SpanBondTerms.length; t++) {
var r = byTerm[SpanBondTerms[t][0]] || (t === 0 ? headDetail : null);
if (!r) r = this.newSpanDetail(TermTierEnabledUnderlyingMask, SpanBondTerms[t][0]);
r.UnderlyingType = TermTierEnabledUnderlyingMask;
r.SpanConfig.BondTerm = SpanBondTerms[t][0];
Vue.set(r, '_bk', bk);
merged.push(r);
}
details.splice.apply(details, [index, oldCount].concat(merged));
} else {
headDetail.UnderlyingType = ut;
if (headDetail.SpanConfig) headDetail.SpanConfig.BondTerm = '';
if (oldCount > 1) details.splice(index + 1, oldCount - 1);
}
this.refreshChosen();
},
//行合并重组/删除后重渲染:SetAceDropDown 只增强新出现的 select(已初始化的会跳过,见 MyJs.js:137-138),
//chosen:updated 让被 Vue 复用的 select 按最新 :selected 重读选中态,两者需同时执行
refreshChosen() {
Vue.nextTick(function () {
SetAceDropDown();
$('.chosen-select').trigger('chosen:updated');
});
},
//新建一条规则15 detail:克隆 page.detail 模板行,设置资产类型/期限档,并补齐区间录入结构
newSpanDetail(ut, bondTerm) {
var d = _.cloneDeep(page.detail);
d.UnderlyingType = ut;
this.ensureSpanDetail(d);
d.SpanConfig.BondTerm = bondTerm || '';
return d;
},
//存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行归为一个区块并按固定4档补齐(缺档补空行);
//同档重复/异常档的利率债行、非利率债行均各自独立成块展示,不丢数据
rebuildSpanBlocks(details) {
var termValues = SpanBondTerms.map(function (t) { return t[0]; });
var result = [];
var that = this;
var i = 0;
while (i < details.length) {
var d = details[i];
var ut = d.UnderlyingType || 0;
if (ut === TermTierEnabledUnderlyingMask) {
var rows = [];
while (i < details.length && (details[i].UnderlyingType || 0) === TermTierEnabledUnderlyingMask) {
rows.push(details[i]);
i++;
}
var byTerm = {};
var anomalies = [];
rows.forEach(function (r) {
var bt = (r.SpanConfig && r.SpanConfig.BondTerm) || '';
if (termValues.indexOf(bt) >= 0 && !byTerm[bt]) byTerm[bt] = r; else anomalies.push(r);
});
var bk = 'bk_' + (++spanBlockSeq);
termValues.forEach(function (t) {
var r = byTerm[t];
if (!r) r = that.newSpanDetail(TermTierEnabledUnderlyingMask, t);
r.UnderlyingType = TermTierEnabledUnderlyingMask;
r.SpanConfig.BondTerm = t;
Vue.set(r, '_bk', bk);
result.push(r);
});
anomalies.forEach(function (r) {
Vue.set(r, '_bk', 'bk_' + (++spanBlockSeq));
result.push(r);
});
} else {
Vue.set(d, '_bk', 'bk_' + (++spanBlockSeq));
result.push(d);
i++;
}
}
return result;
},
underlyingTypeNames(mask) {
mask = parseInt(mask) || 0;
if (mask === 0) return "全部";
var names = [];
for (var i = 0; i < UnderlyingTypeNames.length; i++) {
if ((mask & UnderlyingTypeNames[i][0]) > 0) names.push(UnderlyingTypeNames[i][1]);
}
return names.length > 0 ? names.join("、") : "全部";
},
bondTermLabel(detail) {
var v = (detail.SpanConfig && detail.SpanConfig.BondTerm) || '';
var map = { '': '全部', '<5y': '≤5y', '5y-10y': '(5y-10y]', '10y-30y': '(10y-30y]', '>30y': '>30y' };
return map[v] !== undefined ? map[v] : v;
},
//规则15录入区块文案:按 detail 资产类型切换计价口径。
//债券类(利率债16/信用债32/其它债券64)及其他类型(默认债券口径):期初净价/当前净价,金额=×期初全价×券面总额;
//基金及基金专户(32768)/债券指数(536870912):参考标的期初净价/参考标的当前净价(空头第1层用"参考标的当前收盘价"),
//金额=×参考标的期初价格×参考标的名义份额。
spanText(detail) {
var ut = (detail && detail.UnderlyingType) || 0;
if ((ut & 32768) > 0 || (ut & 536870912) > 0) {
return {
priceInit: '参考标的期初净价',
priceCur: '参考标的当前净价',
priceCurShort1: '参考标的当前收盘价',
amountBase: '参考标的期初价格 × 参考标的名义份额'
};
}
return {
priceInit: '期初净价',
priceCur: '当前净价',
priceCurShort1: '当前净价',
amountBase: '期初全价 × 券面总额'
};
},
//4个空追保区间 tier(区间边界/金额比例均存小数,null=未填)
emptySpanTiers() {
var tiers = [];
for (var i = 0; i < 4; i++) tiers.push({ Lower: null, Upper: null, AmountRate: null });
return tiers;
},
//规则15:补齐区间录入结构——SpanConfig 缺省时补空对象,WarnLine/CloseLine 缺键时补 null
//LongSpans/ShortSpans 非数组或为空时初始化为4个空 tier,tier 缺键补齐(全程 Vue.set 保证响应式,v-model 绑定点必须预先存在)
ensureSpanDetail(detail) {
if (!detail.SpanConfig) Vue.set(detail, 'SpanConfig', { BondTerm: '' });
var sc = detail.SpanConfig;
if (sc.WarnLine === undefined) Vue.set(sc, 'WarnLine', null);
if (sc.CloseLine === undefined) Vue.set(sc, 'CloseLine', null);
var that = this;
['LongSpans', 'ShortSpans'].forEach(function (key) {
if (!Array.isArray(sc[key]) || sc[key].length === 0) Vue.set(sc, key, that.emptySpanTiers());
for (var i = 0; i < 4; i++) {
if (!sc[key][i]) Vue.set(sc[key], i, { Lower: null, Upper: null, AmountRate: null });
if (sc[key][i].Lower === undefined) Vue.set(sc[key][i], 'Lower', null);
if (sc[key][i].Upper === undefined) Vue.set(sc[key][i], 'Upper', null);
if (sc[key][i].AmountRate === undefined) Vue.set(sc[key][i], 'AmountRate', null);
}
});
}
},
components: {
@@ -194,42 +546,12 @@ $(function () {
$(this).datepicker("setDate", dateVal);
});
//chosen 选中变化通过 jQuery trigger("change") 通知(chosen.jquery.js trigger_form_field_change),
//Vue 的 v-on:change(原生监听)收不到,这里用 jQuery 委托监听规则15区块的资产类型选择器
$(document).on('change', 'select.chosen-select[id^="UnderlyingType"]', function () {
var idx = parseInt(this.id.substring('UnderlyingType'.length));
if (!isNaN(idx)) vue.onUnderlyingTypeChange(idx);
});
SetAceDropDown();
});
var vueDetail = new Vue({
el: "#myModal",
data: {
detail: {},
backupDetail: {},
isAdd: true,
ruleType: 0,
},
created: function () {
},
methods: {
closeModal: function () {
if (!this.isAdd) {
Vue.set(vue.marginTemplate.Details, vue.marginTemplate.Details.indexOf(this.detail), this.backupDetail);
}
$('#myModal').modal('hide');
},
saveModal: function () {
if (this.isAdd) {
if (vue.latestValueDate && vue.latestValueDate >= this.detail.ValueDate) {
main.message("生效日期需要大于已设置生效日");
return;
}
vue.marginTemplate.Details.push(this.detail);
vue.latestValueDate = this.detail.ValueDate;
}
Vue.set(vue.marginTemplate.Details, vue.marginTemplate.Details.indexOf(this.detail), this.detail);
$('#myModal').modal('hide');
}
},
components: {
'vue-datepicker': FastVue.vueDatePicker(),
'vue-number-input': FastVue.vueNumberInput()
}
});