Merge remote-tracking branch 'origin/glms/feature/1.4.2' into glms/feature/1.4.2

This commit is contained in:
张名锐
2026-08-28 09:45:00 +08:00
25 changed files with 693 additions and 7958 deletions
@@ -10,33 +10,15 @@ namespace YLErp.DBModels
/// <summary> /// <summary>
/// 允许配置期限档(SpanConfig.BondTerm)的标的资产类型标志位。 /// 允许配置期限档(SpanConfig.BondTerm)的标的资产类型标志位。
/// 区间追保结构"按资产类型分类"时,仅这些类型允许设置非空期限档; /// 区间追保结构"按资产类型分类"时,仅这些类型允许设置非空期限档;
/// 本期仅含 利率债(TBonds=1&lt;&lt;4=16ETF 子类分档能力见 TermTierEnabledEtfKinds /// 仅含 利率债(TBonds=1&lt;&lt;4=16)。
/// ETF 不分期限档(2026-08-27 业务裁定:ETF 没有期限概念,
/// 此前的 可转债 ETF/科创债 ETF 分档白名单 TermTierEnabledEtfKinds 已移除,如需恢复查 git 历史)。
/// </summary> /// </summary>
public static readonly UnderlyingTypeEnum[] TermTierEnabledUnderlyingTypes = public static readonly UnderlyingTypeEnum[] TermTierEnabledUnderlyingTypes =
{ {
UnderlyingTypeEnum.TBonds UnderlyingTypeEnum.TBonds
}; };
/// <summary>
/// 允许配置期限档的 ETF 子类白名单(来自数据字典"ETF 子类")。
/// 选择这些子类的 ETF 区块按固定4档展开(同利率债);其余子类/不区分的 ETF 区块单套参数、不分档。
/// 取值与 underlying_manager.EtfSubType 字典项一致,由标的维护页维护。
/// 注意:分档 ETF 子类行的 UnderlyingType 仍为 基金(32768),不走 TermTierEnabledUnderlyingTypes 位掩码(§1.2 决策:不再加枚举位)。
/// </summary>
public static readonly List<string> TermTierEnabledEtfKinds = new List<string>
{
"可转债 ETF",
"科创债 ETF"
};
/// <summary>
/// 判断 EtfKind 是否允许分档(在 TermTierEnabledEtfKinds 白名单内)。
/// </summary>
public static bool IsTermTierEnabledEtfKind(string etfKind)
{
return !string.IsNullOrEmpty(etfKind) && TermTierEnabledEtfKinds.Contains(etfKind);
}
/// <summary> /// <summary>
/// 5年以下(同时也是兜底默认期限) /// 5年以下(同时也是兜底默认期限)
/// </summary> /// </summary>
@@ -0,0 +1,144 @@
using YLErp.BLL;
using YLErp.DBModels;
using YLErp.Enums;
using YLErp.Modules.MarginModule;
namespace YLErp.Modules.CalcModules
{
/// <summary>
/// GetRateByTemplate 资产类型先行匹配回归(连 dev 库,2026-08-27 顺序裁定:先品种后期限):
/// 期限档仅利率债允许配置,品种匹配在期限之前——防止非利率债标的被利率债期限档行截胡
/// (交易2567 实证:信用债标的按 "<5y" 精确匹配到利率债行,信用债行 BondTerm 空永远不参与)。
/// 标的代码用库里不存在的代码(GetApplicableMarginTerm 无标的兜底返回 <5y),不依赖真实标的行情数据。
/// 测试数据全部带 "ZZZ-品种先行测试-" 名称前缀,TestInitialize/TestCleanup 双向清理。
/// </summary>
[TestClass]
public class MarginTemplateV2InstrumentFirstMatchTest
{
private const string Marker = "ZZZ-品种先行测试-";
private DateTime EffectiveDate = new DateTime(2000, 1, 1);
private YLContext db;
[TestInitialize]
public void Init()
{
db = new YLContext();
Cleanup();
}
[TestCleanup]
public void CleanupFixture()
{
Cleanup();
db.Dispose();
}
private void Cleanup()
{
var templateIds = db.margin_template_v2.Where(x => x.Name.StartsWith(Marker)).Select(x => x.id).ToList();
if (templateIds.Count > 0)
{
db.margin_template_detail.RemoveRange(db.margin_template_detail.Where(x => templateIds.Contains(x.MarginTemplateId)));
db.margin_template_v2.RemoveRange(db.margin_template_v2.Where(x => templateIds.Contains(x.id)));
db.SaveChanges();
}
}
private margin_template_v2 AddTieredTemplate()
{
var t = new margin_template_v2
{
Name = Marker + "分档",
IsDefault = false,
IsForClient = false,
IsValid = true,
TradeTypes = "收益互换",
RuleType = (int)MarginRuleTypeEnum.,
UnderlyingSeperateType = (int)UnderlyingSeperateTypeEnum.CustomInstrumentType,
ValueDate = EffectiveDate
};
db.margin_template_v2.Add(t);
db.SaveChanges();
return t;
}
private void AddDetail(int templateId, UnderlyingTypeEnum underlyingType, string bondTermJson, double initRate, double maintainRate)
{
db.margin_template_detail.Add(new margin_template_detail
{
MarginTemplateId = templateId,
ValueDate = EffectiveDate,
UnderlyingType = underlyingType,
SpanConfigJson = bondTermJson,
MarginRatio1 = initRate,
MarginRatio2 = maintainRate
});
}
/// <summary>
/// 信用债标的不被利率债期限档行截胡:term 恒为 "<5y"(标的不存在兜底),
/// 旧序会精确命中利率债 <5y 行;新序品种先行应命中信用债行(BondTerm 空)。
/// </summary>
[TestMethod]
public void TI_001_信用债标的_命中信用债行_不被利率债期限档截胡()
{
var tpl = AddTieredTemplate();
AddDetail(tpl.id, UnderlyingTypeEnum.TBonds, "{\"BondTerm\":\"<5y\"}", 0.11, 0.12);
AddDetail(tpl.id, UnderlyingTypeEnum.CreditBonds, null, 0.05, 0.06);
db.SaveChanges();
var rate = MarginTemplateV2RateHelper.GetRateByTemplate(tpl, "ZZZ-NOT-EXIST-CD.IB", "CreditBonds", DateTime.Today, db);
Assert.IsNotNull(rate, "品种先行后信用债行(BondTerm 空)应经期限兜底命中");
Assert.AreEqual(0.05m, rate.InitRate.Value, "应取信用债行的初始预付金率,而非利率债 <5y 行的 0.11");
Assert.AreEqual(0.06m, rate.MaintainRate.Value, "应取信用债行的维持预付金率,而非利率债 <5y 行的 0.12");
}
/// <summary>
/// 利率债标的行为不变:品种命中利率债行后,期限精确档 "<5y" 命中对应期限行(压过 5y-10y 行)。
/// </summary>
[TestMethod]
public void TI_002_利率债标的_品种内期限精确档仍生效()
{
var tpl = AddTieredTemplate();
AddDetail(tpl.id, UnderlyingTypeEnum.TBonds, "{\"BondTerm\":\"<5y\"}", 0.11, 0.12);
AddDetail(tpl.id, UnderlyingTypeEnum.TBonds, "{\"BondTerm\":\"5y-10y\"}", 0.13, 0.14);
db.SaveChanges();
var rate = MarginTemplateV2RateHelper.GetRateByTemplate(tpl, "ZZZ-NOT-EXIST-TB.IB", "TBonds", DateTime.Today, db);
Assert.IsNotNull(rate);
Assert.AreEqual(0.11m, rate.InitRate.Value, "期限兜底 <5y 时应精确命中 <5y 档行");
Assert.AreEqual(0.12m, rate.MaintainRate.Value);
}
/// <summary>
/// 模板未配标的品种时的既有兜底不变:品种行与通配行均无 → 不缩小行集,回落期限匹配(与旧序一致)。
/// </summary>
[TestMethod]
public void TI_003_模板未配品种_回落期限匹配_行为不变()
{
var tpl = AddTieredTemplate();
AddDetail(tpl.id, UnderlyingTypeEnum.TBonds, "{\"BondTerm\":\"<5y\"}", 0.11, 0.12);
db.SaveChanges();
var rate = MarginTemplateV2RateHelper.GetRateByTemplate(tpl, "ZZZ-NOT-EXIST-CF.IB", "CommodityFutures", DateTime.Today, db);
Assert.IsNotNull(rate, "品种落空应回落到期限匹配(旧行为兜底),不应返回 null");
Assert.AreEqual(0.11m, rate.InitRate.Value);
}
/// <summary>
/// 品种行与期限行均无法匹配时返回 null:非利率债标的不再"借用"利率债期限档行,
/// 由调用方按无预付金要求兜底(引擎不产出 trade_span)。
/// </summary>
[TestMethod]
public void TI_004_品种与期限均无匹配行_返回null()
{
var tpl = AddTieredTemplate();
AddDetail(tpl.id, UnderlyingTypeEnum.TBonds, "{\"BondTerm\":\"5y-10y\"}", 0.13, 0.14);
db.SaveChanges();
var rate = MarginTemplateV2RateHelper.GetRateByTemplate(tpl, "ZZZ-NOT-EXIST-CD.IB", "CreditBonds", DateTime.Today, db);
Assert.IsNull(rate, "信用债标的不应命中利率债 5y-10y 期限行");
}
}
}
+1
View File
@@ -171,6 +171,7 @@ namespace YLErp
"yladmin" => _configuration.GetConnectionString("yladmin"), "yladmin" => _configuration.GetConnectionString("yladmin"),
"ylclient" => _configuration.GetConnectionString("ylclient"), "ylclient" => _configuration.GetConnectionString("ylclient"),
"bondoms" => _configuration.GetConnectionString("bondoms"), "bondoms" => _configuration.GetConnectionString("bondoms"),
"glms_bigdata" => _configuration.GetConnectionString("glms_bigdata"),
"apex_oracle"=> _configuration.GetConnectionString("apex_oracle"), "apex_oracle"=> _configuration.GetConnectionString("apex_oracle"),
_ => string.Empty, _ => string.Empty,
}; };
@@ -129,19 +129,39 @@ namespace YLErp.Modules.MarginModule
var latestValueDate = detailQuery.Max(x => x.ValueDate); var latestValueDate = detailQuery.Max(x => x.ValueDate);
var details = detailQuery.Where(x => x.ValueDate == latestValueDate).ToList(); var details = detailQuery.Where(x => x.ValueDate == latestValueDate).ToList();
//4.利率债/分档ETF 期限档匹配:精确档 → "全部"BondTerm 为空)兜底 //4.资产类型先行(2026-08-27 顺序裁定:先品种后期限):按资产类型分档的模板先按标的品种缩小行集——
//品种行 → 通配行(None/All)→ 均无则不缩小(回落到与旧序一致的期限匹配,模板未配该品种的既有兜底不变)。
//期限档仅利率债允许配置(ConsMarginTerm),品种匹配必须在期限之前:期限精确匹配对任何标的恒有 term
//GetApplicableMarginTerm 兜底 <5y),非利率债标的会被利率债期限档行截胡、本品种行(BondTerm 空)永远不参与
//(2026-08-27 交易2567 实证:信用债标的按 "<5y" 命中利率债行多收追保)
var candidates = details;
if (template.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType
&& Enum.TryParse<UnderlyingTypeEnum>(underlyingInstrumentType, out var instrumentFlag))
{
var byInstrument = details.Where(x => (x.UnderlyingType & instrumentFlag) > 0).ToList();
if (!byInstrument.Any())
{
byInstrument = details.Where(x => x.UnderlyingType == UnderlyingTypeEnum.None || x.UnderlyingType == UnderlyingTypeEnum.All).ToList();
}
if (byInstrument.Any())
{
candidates = byInstrument;
}
}
//5.期限档匹配(利率债四档):精确档 → "全部"BondTerm 为空)兜底
var term = UnderlyingHelper.GetApplicableMarginTerm(underlyingCode, valueDate); var term = UnderlyingHelper.GetApplicableMarginTerm(underlyingCode, valueDate);
var matched = details.Where(x => x.SpanConfig != null && x.SpanConfig.BondTerm == term).ToList(); var matched = candidates.Where(x => x.SpanConfig != null && x.SpanConfig.BondTerm == term).ToList();
if (!matched.Any()) if (!matched.Any())
{ {
matched = details.Where(x => x.SpanConfig == null || string.IsNullOrEmpty(x.SpanConfig.BondTerm)).ToList(); matched = candidates.Where(x => x.SpanConfig == null || string.IsNullOrEmpty(x.SpanConfig.BondTerm)).ToList();
} }
if (!matched.Any()) if (!matched.Any())
{ {
return null; return null;
} }
//5.ETF 子类行优先(子类区分度高于期限):标的有 EtfSubType(基金类)时优先取 EtfKind=子类 的行—— //6.ETF 子类行优先(子类区分度高于期限):标的有 EtfSubType(基金类)时优先取 EtfKind=子类 的行——
//期限档匹配未命中子类行时再单独尝试"子类 + BondTerm 空"(子类不分档通配);无子类行维持原 matched(基金通配兜底) //期限档匹配未命中子类行时再单独尝试"子类 + BondTerm 空"(子类不分档通配);无子类行维持原 matched(基金通配兜底)
var underlyingCategory = GetUnderlyingCategory(underlyingCode, underlyingInstrumentType); var underlyingCategory = GetUnderlyingCategory(underlyingCode, underlyingInstrumentType);
if (underlyingCategory != null) if (underlyingCategory != null)
@@ -149,24 +169,6 @@ namespace YLErp.Modules.MarginModule
matched = PreferCategoryRows(matched, details, underlyingCategory); matched = PreferCategoryRows(matched, details, underlyingCategory);
} }
if (template.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType
&& Enum.TryParse<UnderlyingTypeEnum>(underlyingInstrumentType, out var instrumentFlag))
{
var byInstrument = matched.Where(x => (x.UnderlyingType & instrumentFlag) > 0).ToList();
if (byInstrument.Any())
{
matched = byInstrument;
}
else
{
var wildcard = matched.Where(x => x.UnderlyingType == UnderlyingTypeEnum.None || x.UnderlyingType == UnderlyingTypeEnum.All).ToList();
if (wildcard.Any())
{
matched = wildcard;
}
}
}
var detail = matched.First(); var detail = matched.First();
return new MarginRateResult return new MarginRateResult
{ {
@@ -0,0 +1,94 @@
using Dapper;
using MySqlConnector;
using YieldChain.Helpers;
using YLErp.BLL;
namespace YLErp.Modules.UnderlyingModule
{
public enum FundManagerLookupStatus
{
NotFound,
Unique,
Multiple,
Unavailable
}
public sealed class FundManagerLookupResult
{
public FundManagerLookupStatus Status { get; init; }
public string InvestAdvisorName { get; init; }
}
/// <summary>
/// 查询上游基金档案中的基金管理人。上游不可用时返回降级结果,不阻断页面编辑。
/// </summary>
public sealed class FundManagerLookupService
{
private sealed class FundManagerRow
{
public string InvestAdvisorCode { get; set; }
public string InvestAdvisorName { get; set; }
}
private const string LookupSql = @"
SELECT
ia.investadvisorcode AS InvestAdvisorCode,
ia.investadvisorname AS InvestAdvisorName
FROM glms_bigdata.mf_fundarchives AS fa
INNER JOIN glms_bigdata.mf_investadvisoroutline AS ia
ON CONVERT(fa.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
CONVERT(ia.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci
WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci =
CONVERT(TRIM(SUBSTRING_INDEX(@UnderlyingCode, '.', 1)) USING utf8mb4) COLLATE utf8mb4_unicode_ci";
public FundManagerLookupResult Lookup(string underlyingCode)
{
var normalizedCode = NormalizeCode(underlyingCode);
if (string.IsNullOrEmpty(normalizedCode))
{
return new FundManagerLookupResult { Status = FundManagerLookupStatus.NotFound };
}
var connectionString = AppManager.GetConnectionString("glms_bigdata");
if (string.IsNullOrWhiteSpace(connectionString))
{
return new FundManagerLookupResult { Status = FundManagerLookupStatus.Unavailable };
}
try
{
using var connection = new MySqlConnection(connectionString);
var matches = connection.Query<FundManagerRow>(LookupSql, new { UnderlyingCode = normalizedCode }, commandTimeout: 10)
.Where(row => !string.IsNullOrWhiteSpace(row.InvestAdvisorName))
.GroupBy(row => (row.InvestAdvisorCode ?? string.Empty).Trim(), StringComparer.OrdinalIgnoreCase)
.Select(group => group.Select(row => row.InvestAdvisorName.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray())
.Where(names => names.Length > 0)
.ToArray();
return matches.Length switch
{
0 => new FundManagerLookupResult { Status = FundManagerLookupStatus.NotFound },
1 when matches[0].Length == 1 => new FundManagerLookupResult { Status = FundManagerLookupStatus.Unique, InvestAdvisorName = matches[0][0] },
_ => new FundManagerLookupResult { Status = FundManagerLookupStatus.Multiple }
};
}
catch (Exception ex)
{
LogFactory.GetLogger<FundManagerLookupService>().Error("查询基金管理人失败,代码:" + normalizedCode, ex);
return new FundManagerLookupResult { Status = FundManagerLookupStatus.Unavailable };
}
}
public static string NormalizeCode(string underlyingCode)
{
var trimmed = underlyingCode?.Trim();
if (string.IsNullOrEmpty(trimmed))
{
return null;
}
var dotIndex = trimmed.IndexOf('.');
return (dotIndex < 0 ? trimmed : trimmed.Substring(0, dotIndex)).TrimToNull();
}
}
}
-53
View File
@@ -85,54 +85,6 @@ namespace YLErp.Web.Controllers
return View(model); return View(model);
} }
/// <summary>
/// 组合报价
/// </summary>
[MyAuthorize("报价管理-结构化交易定价")]
public ActionResult Structure_DZ()
{
var otcTrade = new OtcOptionTradeFull()
{
TraderId = CurUser.UserId,
TraderName = CurUser.UserName,
BuySell = "卖出",
VolType = "交易",
TradeType = "香草期权",
OptionType = "看涨",
ExerciseMode = "European",
TradeDate = valuedateBLL.ValueDate,
UnderlyingInstrumentType = AppHelper.OtcConfig.StockFirst ? "Stock" : "CommodityFutures",
SettlementType = (int)SettlementTypeEnum.ClosePrice,
NoRiskRate = valuedateBLL.SystemDate.RiskFreeRate / 100,
ParticipationRate = 1,
AnnualizeFactor = 1,
MarginTemplateName = "系统默认",
CouponIncludeStartDate = false,
CouponUsePaymentDate = false
};
var model = new Models.PricingModel(CurUser, UserBLL.IsTradeOfCurrentLogin(CurUser.UserId)) { Trade = otcTrade };
if (model.NumOfSmoothingDaysCfg == "ONE")
{
model.Trade.NumOfSmoothingDays = 1;
}
//获取自定义结构信息
var structureTypes =
new StructureService(CurUser)
.QueryStructureMap(StructureRangeEnum.BALCK_TRADE);
var structureTypeMap = new Dictionary<string, List<Structure_Details>>() {
{ "气囊结构",new List<Structure_Details>() }
};
foreach (var item in structureTypes)
{
structureTypeMap[item.Key] = item.Value;
}
ViewBag.StructureTypeMap = structureTypeMap;
return View(model);
}
/// <summary> /// <summary>
/// 组合报价导入 /// 组合报价导入
/// </summary> /// </summary>
@@ -198,11 +150,6 @@ namespace YLErp.Web.Controllers
ViewBag.ExtendInfoMap[item.Key] = item.Value; ViewBag.ExtendInfoMap[item.Key] = item.Value;
} }
if (PS.Config.Is润和)
{
return View(nameof(Structure_DZ), model);
}
return View(nameof(Structure), model); return View(nameof(Structure), model);
} }
@@ -304,8 +304,8 @@ namespace YLErp.Web.Controllers
} }
} }
//区间追保结构 + 按资产类型分类时,期限档仅允许"允许分期限档的资产类型"(利率债)或"允许分档的 ETF 子类" //区间追保结构 + 按资产类型分类时,期限档仅允许利率债配置(2026-08-27 裁定:ETF 无期限概念,
//可转债 ETF/科创债 ETF,行 UnderlyingType=基金 + EtfKind=子类,不加枚举位)配置,防止非分档类型误配期限档; //可转债 ETF/科创债 ETF 的分档白名单已移除,所有 ETF 子类/基金行一律单套参数不分档),防止非分档类型误配期限档;
//该校验按行生效,与明细行数无关(单行明细同样拦截); //该校验按行生效,与明细行数无关(单行明细同样拦截);
//按严格掩码判定:行标的类型位必须全部落在可分档类型内(混合标志位如 利率债|信用债 配期限档同样拦截,与取数侧整行期限档过滤语义一致); //按严格掩码判定:行标的类型位必须全部落在可分档类型内(混合标志位如 利率债|信用债 配期限档同样拦截,与取数侧整行期限档过滤语义一致);
//UnderlyingType 为空的通配行允许配期限档(取数侧期限档过滤在前、通配匹配在后,语义自洽) //UnderlyingType 为空的通配行允许配期限档(取数侧期限档过滤在前、通配匹配在后,语义自洽)
@@ -313,34 +313,24 @@ namespace YLErp.Web.Controllers
&& marginTemplate.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType) && marginTemplate.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType)
{ {
var enabledMask = ConsMarginTerm.TermTierEnabledUnderlyingTypes.Aggregate(UnderlyingTypeEnum.None, (a, t) => a | t); var enabledMask = ConsMarginTerm.TermTierEnabledUnderlyingTypes.Aggregate(UnderlyingTypeEnum.None, (a, t) => a | t);
var tierEnabledEtfKinds = ConsMarginTerm.TermTierEnabledEtfKinds;
foreach (var detail in marginTemplate.Details) foreach (var detail in marginTemplate.Details)
{ {
var etfKind = detail.SpanConfig?.EtfKind; var etfKind = detail.SpanConfig?.EtfKind;
var bondTerm = detail.SpanConfig?.BondTerm;
//ETF 子类行:仅允许纯基金行(配置页子类选择器也只在纯基金区块出现) //ETF 子类行:仅允许纯基金行(配置页子类选择器也只在纯基金区块出现)
if (!string.IsNullOrEmpty(etfKind) && detail.UnderlyingType != UnderlyingTypeEnum.Fund) if (!string.IsNullOrEmpty(etfKind) && detail.UnderlyingType != UnderlyingTypeEnum.Fund)
{ {
throw new Exception("配置了 ETF 子类(" + etfKind + ")的参数组资产类型必须为 基金及基金专户"); throw new Exception("配置了 ETF 子类(" + etfKind + ")的参数组资产类型必须为 基金及基金专户");
} }
var isTierEnabledEtfKind = tierEnabledEtfKinds.Contains(etfKind ?? ""); //ETF 子类不分期限档(对存量 4 档子类行回存给出精确报错;基金通配行由下面的掩码校验拦截)
if (!string.IsNullOrEmpty(detail.SpanConfig?.BondTerm) if (!string.IsNullOrEmpty(etfKind) && !string.IsNullOrEmpty(bondTerm))
&& (detail.UnderlyingType & ~enabledMask) != UnderlyingTypeEnum.None {
&& !isTierEnabledEtfKind) throw new Exception("ETF 子类 " + etfKind + " 不分期限档,参数行不能配置期限档");
}
if (!string.IsNullOrEmpty(bondTerm) && (detail.UnderlyingType & ~enabledMask) != UnderlyingTypeEnum.None)
{ {
throw new Exception("标的类型不允许配置期限档:" + UnderlyingTypeUtil.GetDesc(detail.UnderlyingType)); throw new Exception("标的类型不允许配置期限档:" + UnderlyingTypeUtil.GetDesc(detail.UnderlyingType));
} }
//分档 ETF 子类必须配期限档(区块固定4档),其余子类行禁配期限档
if (!string.IsNullOrEmpty(etfKind))
{
if (isTierEnabledEtfKind && string.IsNullOrEmpty(detail.SpanConfig?.BondTerm))
{
throw new Exception("ETF 子类 " + etfKind + " 为分期限档类型,参数行必须配置期限档");
}
if (!isTierEnabledEtfKind && !string.IsNullOrEmpty(detail.SpanConfig?.BondTerm))
{
throw new Exception("ETF 子类 " + etfKind + " 不分期限档,参数行不能配置期限档");
}
}
} }
} }
@@ -399,8 +389,8 @@ namespace YLErp.Web.Controllers
{ {
if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.) if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.)
{ {
//区间追保结构:按 (利率债期限档, ETF细分) 元组分组校验(EtfKind 为预留扩展键,本期无 UI 写入,等价于仅期限档分组), //区间追保结构:按 (利率债期限档, ETF细分) 元组分组校验——期限档仅利率债有值(其余恒空),
//同一分组内标的类型不允许重复,不同分组允许相同标的类型; //EtfKind 为基金行的子类键;同一分组内标的类型不允许重复,不同分组允许相同标的类型;
//通配行(空/全部,UnderlyingType=None/All)两两之间位与恒为 0 检不出,须单独拦截(BUG-09)—— //通配行(空/全部,UnderlyingType=None/All)两两之间位与恒为 0 检不出,须单独拦截(BUG-09)——
//否则取数侧通配兜底 matched.First() 命中不确定 //否则取数侧通配兜底 matched.First() 命中不确定
foreach (var termGroup in marginTemplate.Details.GroupBy(x => (x.SpanConfig?.BondTerm ?? "", x.SpanConfig?.EtfKind ?? ""))) foreach (var termGroup in marginTemplate.Details.GroupBy(x => (x.SpanConfig?.BondTerm ?? "", x.SpanConfig?.EtfKind ?? "")))
@@ -525,6 +525,25 @@ namespace YLErp.Web.Controllers
return JsonSuccess("", underlying); return JsonSuccess("", underlying);
} }
/// <summary>
/// 查询上游基金档案中的基金管理人。查询失败或结果不唯一时返回可降级结果。
/// </summary>
[HttpGet]
public JsonResult GetFundManager(string code, string instrumentType)
{
if (!string.Equals(instrumentType, ConsGlobal.InstrumentType.Fund, StringComparison.OrdinalIgnoreCase))
{
return JsonSuccess("", new FundManagerLookupResult { Status = FundManagerLookupStatus.NotFound });
}
var result = new FundManagerLookupService().Lookup(code);
return JsonSuccess("", new
{
result.InvestAdvisorName,
IsUnique = result.Status == FundManagerLookupStatus.Unique
});
}
/// <summary> /// <summary>
/// 预付金参数 /// 预付金参数
/// </summary> /// </summary>
-910
View File
@@ -1,910 +0,0 @@
@using Microsoft.AspNetCore.Html
@using YLErp.QdpModule.Constants
@model PricingModel
@{
ViewBag.Title = "期权定价";
if (Model.IsImport)
{
Layout = "~/Views/Shared/_InfoLayout.cshtml";
}
else
{
Layout = "~/Views/Shared/_MainLayout.cshtml";
}
var assetunits = JsDataModel.GetAssetUnits(CurUser);
var traders = JsDataModel.GetTraders(assetunits);
var pageObj = new
{
assetunits = assetunits,
trade = new trade() { Strike = 0 },
traders = JsDataModel.GetTraders(assetunits),
tradeMarginTemplateItems = new tradeController().GetMarginTemplateItems(),
tradeMarginTemplates = new tradeController().GetMarginTemplates(),
engineNames = new[] { "abc", "xyz" },
structureTypes = ViewBag.StructureTypeMap?.Keys,
PropertyMap = ViewBag.StructureTypeMap,
IsPVIncludePrincipal = PS.Config.ErpElement.IsPVIncludePrincipal
};
var pageData = new
{
showCCR = PS.Config.Company == CompanyEnum.国海,
is厦门象屿 = PS.Config.Company == CompanyEnum.厦门象屿,
};
}
@section CSS{
<link href="~/Style/Css/pricing.structure.css?v=1" rel="stylesheet" />
@switch (PS.Config.Company)
{
case CompanyEnum.光大光子:
<link href="~/Style/GDGZ/pricing.structure.css" rel="stylesheet" />
break;
case CompanyEnum.国泰君安:
<link href="~/Style/GTJA/pricing.structure.css" rel="stylesheet" />
break;
}
<style>
.pitem-cash:not(.pitem) {
background: #f0f8ff
}
.customPanel {
background: #CDE0E6;
height: 100%;
display: table;
width: 100%;
}
.autocomplete-suggestions {
width: auto !important;
min-width: 11%;
}
.pricing-item {
transform: none !important;
}
.empty {
border-top: none !important;
background: none !important;
}
.bottomborder {
border-bottom: 1px solid #ddd !important;
}
.topborder {
border-top: 1px solid #ddd !important;
}
</style>
}
@section JS{
<script src="~/Statics/libs/_utils/dragscroll.js?v=@(HtmlUtil.JsVersion)"></script>
<script src="~/Statics/libs/_utils/FileSaver.js?v=@(HtmlUtil.JsVersion)"></script>
<script src="~/Statics/libs/_utils/dom-to-image.min.js?v=@(HtmlUtil.JsVersion)"></script>
<script src="~/Statics/libs/sortable/Sortable.min.js"></script>
<script src="~/front/calendar?v=@(HtmlUtil.JsVersion)"></script>
<script src="~/Scripts/app/tradeHelper.js?v=@HtmlUtil.JsVersion"></script>
<script src="~/Scripts/app/trade/percentColumnText.js?v=@HtmlUtil.JsVersion"></script>
<script src="@HtmlUtil.BasicDataJs("品种", "客户")"></script>
<script>
const pageObj = @Json.Serialize(pageObj);
const pageData = @Json.Serialize(pageData);
const pageVue = @Json.Serialize(Model);
const optionTradeTypes = Object.freeze([{ value: "香草期权", pinyin: 'XCQQ' },
{ value: "障碍期权", pinyin: 'ZAQQ' }, { value: "二元期权", pinyin: 'EYQQ' },
{ value: "亚式期权", pinyin: 'YSQQ' }, { value: "凤凰期权", pinyin: 'FHQQ' },
{ value: "雪球期权", pinyin: 'XQQQ' }, { value: "结构化产品", pinyin: 'JGHCP' },
]);
pageVue.GetTotalMargin = function (trades, structureType) {
return _.reduce(trades, (acc, cur) => acc += parseFloat(cur.InitialMargin) || 0, 0);
};
pageVue.GetTotalTradePrice = function (trades) {
return _.reduce(trades, (acc, cur) => acc += parseFloat(cur.TradePrice) || 0, 0);
};
pageVue.GetTotalDay1Pnl = function (trades) {
return _.reduce(trades, (acc, cur) => acc += parseFloat(cur.Day1Pnl) || 0, 0);
};
pageVue.CalcVersion = "@(Context.Request.Query["version"])";
ylotc.trade = pageObj.trade;
ylotc.traders = pageObj.traders;
ylotc.assetunits = pageObj.assetunits;
ylotc.tradeMarginTemplateItems = pageObj.tradeMarginTemplateItems;
ylotc.tradeMarginTemplates = pageObj.tradeMarginTemplates;
ylotc.engineNames = pageObj.engineNames;
ylotc.options = pageObj.options;
ylotc.structureTypes = pageObj.structureTypes;
</script>
@if (PS.Config.Company == CompanyEnum.伴兴)
{
<script>
pageVue.GetTotalMargin = function (trades, structureType) {
if (trades.length === 1) return parseFloat(trades[0].InitialMargin) || 0;
if (!structureType || structureType === "结构化交易") {
return _.reduce(trades, (acc, cur) => acc += parseFloat(cur.InitialMargin) || 0, 0);
}
if (structureType.indexOf("跨式") >= 0) {
return _.max(_.map(trades, x => Math.abs(x.InitialMargin)));
}
return _.reduce(trades, (acc, cur) => acc += cur.BuySell === '卖出' ? 0 : parseFloat(cur.InitialMargin) || 0, 0);
};
</script>
}
else if (PS.Config.Company == CompanyEnum.茂川资本)
{
<script>
pageVue.GetTotalMargin = function (trades, structureType) {
if (structureType && structureType.indexOf("跨式") >= 0) {
return _.max(_.map(trades, x => Math.abs(x.InitialMargin)));
}
return _.reduce(trades, (acc, cur) => acc += parseFloat(cur.InitialMargin) || 0, 0);
};
</script>
}
else if (PS.Config.Company == CompanyEnum.弘业)
{
<script>
pageVue.GetTotalMargin = function (trades, structureType) {
if (structureType && structureType.indexOf("跨式") >= 0) {
return _.max(_.map(trades, x => Math.abs(x.InitialMargin)));
}
return _.reduce(trades, (acc, cur) => acc += parseFloat(cur.InitialMargin) || 0, 0);
};
</script>
}
<script src="~/Scripts/app/pricing/tradePricing_dz.js?v=@(HtmlUtil.JsVersion)"></script>
<script src="~/Scripts/app/pricing/structure_dz.js?v=@(HtmlUtil.JsVersion)"></script>
}
@await Html.PartialAsync("_CouponDayCount")
<div id="listdiv" class="@(Model.IsImport ? "sr-only" : "")">
<!--定价顶部-->
<div class="card pricing-top" id="pricing-top">
<div class="card-body">
<div class="row no-gutters">
<label class="col-auto mr-2">波动率类型</label>
<div class="col-auto mr-2">
<select v-model="viewState.VolType" v-on:change="pricingVue.changeVolType()">
@foreach (var item in ConsVolInfos.VolTypes)
{
<option value="@item">@item</option>
}
</select>
</div>
<div class="col" style="min-width:350px">
<div class="dropdown d-inline-block">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">新增单腿</button>
<div class="dropdown-menu" style="width:200px;">
<a href="javascript:;" class="dropdown-item" v-for="item in optionTradeTypes" v-on:click="pricingVue.addTrade(null,item.value)">{{item.value}}</a>
<div class="dropdown-divider"></div>
<div class="form-group ml-2">
<div class="input-group">
<input type="text" class="form-control" id="importTradeNumber" placeholder="输入交易编号导入" />
<span class="input-group-append">
<a href="javascript:;" class="pl-2 pr-2" onclick="topVue.importTrade()"><i class="fa fa-copy"></i></a>
</span>
</div>
</div>
</div>
</div>
<div class="dropdown d-inline-block">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">新增组合</button>
<div class="dropdown-menu">
<a v-if="!simpleMode" href="javascript:;" class="dropdown-item" onclick="pricingVue.combine()">自由组合</a>
@foreach (var item in StructureOption_Code.StructureOptionsCn)
{
if (!new List<string>() { "复制标的资产", "比例价差", "箱式价差" }.Contains(item.Text))
{
<a class="dropdown-item" href="javascript:;" v-on:click="addStructure('@item.Value','@item.Text')">@item.Text</a>
}
}
</div>
</div>
<button type="button" class="btn btn-primary" onclick="pricingVue.calcPrice()">定价计算</button>
@if (CurUser.交易管理_交易新增)
{
<button type="button" class="btn btn-primary" onclick="pricingVue.saveTrades()">录入交易</button>
}
<div class="dropdown d-inline-block">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">图形分析</button>
<div class="dropdown-menu">
@*<a class="dropdown-item" href="javascript:;" v-on:click="pricingVue.showPayoffLineChart()">到期收益曲线</a>*@
@*<a class="dropdown-item" href="javascript:;" v-on:click="pricingVue.showPvChart()">组合价值曲线</a>*@
<a class="dropdown-item" href="javascript:;" v-on:click="pricingVue.showLifeLineChart()">Pv变化曲线</a>
<a class="dropdown-item" href="javascript:;" v-on:click="pricingVue.showGreeksChart()">时间变化曲线</a>
</div>
</div>
<div class="btn-group">
<button type="button" class="btn btn-danger" onclick="templateVue.showList()">模板</button>
<button type="button" class="btn btn-danger" onclick="templateVue.showSave()" title="保存为模板">保存</button>
</div>
<div class="dropdown d-inline-block">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">选项</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="javascript:;" v-on:click="toggleSimpleMode" title="是否隐藏不常用的字段">
<span class="fa mr-2" v-bind:class="simpleMode?'fa-check':''"></span>
<span>精简模式</span>
</a>
@if (Model.ShowInitialMargin)
{
<a class="dropdown-item" href="javascript:;" v-on:click="toggleCalcMargin" title="是否计算初始预付金">
<span class="fa mr-2" v-bind:class="calcMargin?'fa-check':''"></span>
<span>计算预付金</span>
</a>
}
<a class="dropdown-item" href="javascript:;" v-on:click="toggleCalcAutocallGreeks" title="是否计算凤凰和雪球的所有Greeks">
<span class="fa mr-2" v-bind:class="calcAutocallGreeks?'fa-check':''"></span>
<span>计算凤凰和雪球的所有Greeks</span>
</a>
</div>
</div>
<button type="button" class="btn btn-primary" onclick="pricingVue.clear()" title="清空legs交易">清空</button>
@if (CurUser.交易管理_分组设置)
{
<button type="button" class="btn btn-primary" onclick="pricingVue.saveGroupTrades()">加入分组</button>
}
</div>
</div>
</div>
</div>
<div class="yc-panel">
<!--定价合计-->
<div class="pricing-summary" id="pricing-summary">
<table class="table table-bordered">
<colgroup>
<col span="1" width="200" />
<col span="1" width="150" />
</colgroup>
<thead>
<tr>
<th>对冲手数</th>
@*<th>组合成交金额</th>*@
<th v-if="topVue.calcMargin">组合预付金</th>
<th>Day1Pnl</th>
<th>PV</th>
<th>Delta</th>
<th>GammaCash</th>
<th>Theta</th>
<th>Vega</th>
<th>Rho</th>
</tr>
</thead>
<tbody v-cloak>
<tr>
<td style="color:red;font-weight: bold;">
<span>{{DeltaHands}}</span>
@if (Model.HedgingOrder && Model.IsTrader)
{
<a href="javascript:;" class="ml-1" onclick="pricingVue.hedgingOrder()" title="对冲下单"><i class="fa fa-external-link"></i></a>
}
</td>
@*<td>{{summary.TotalTradePrice| FixNumber}}</td>*@
<td v-if="topVue.calcMargin">{{summary.TotalMargin| FixNumber}}</td>
<td>{{summary.TotalDay1Pnl| FixNumber}}</td>
<td>{{summary.Pv| FixNumber}}</td>
<td>{{summary.Delta| FixNumber}}</td>
<td>{{summary.GammaCash| FixNumber}}</td>
<td>{{summary.Theta| FixNumber}}</td>
<td>{{summary.Vega| FixNumber}}</td>
<td>{{summary.Rho| FixNumber}}</td>
</tr>
</tbody>
</table>
</div>
<div class="clearfix"></div>
<!--定价列表-->
<div class="pricing-main">
<div class="d-flex">
<div class="pricing-titles-cont">
<div class="singleprice" id="singleprice">@(Model.CompanyName) 付 0.000</div>
<div class="pricing-titles">
<div>组合单价</div>
</div>
<div id="pricing-titles"></div>
</div>
<div class="pricing-items-cont">
<div class="zhanwei"></div>
<div class="d-flex" id="pricing-items"></div>
</div>
</div>
</div>
</div>
</div>
<!--定价组件模板框架-->
<template id="pricingItems_tpl">
<div class="pricing-item">
<div class="pricing-index screenshot-hide" v-if="!pageVue.IsImport">
<span class="structure-type">{{structureType}}</span>
<a href="javascript:;" title="拖动位置" class="pricing-index-drag" v-show="!floating"><i class="fa fa-arrows"></i></a>
<a href="javascript:;" v-on:click="remove" title="移除此项" v-show="!floating"><i class="fa fa-remove"></i></a>
<a href="javascript:void(0)" class="pricing-index-text">{{index}}</a>
<div class="pricing-index-icons" v-show="!floating">
<label class="check-label">
<input type="checkbox" v-model="isSelected" v-on:change="resetSummary(true)">
</label>
<a href="javascript:;" v-on:click="saveTrade" title="录入交易"><i class="fa fa-save"></i></a>
<a href="javascript:;" v-show="showFloatingIcon" v-on:click="showFloatVue" title="浮窗编辑">
<i class="fa fa-external-link"></i>
</a>
</div>
<div class="pricing-index-right" v-show="!floating">
<div class="dropdown">
<a href="javascript:;" class="pricing-index-right-toggle" data-toggle="dropdown">
<i class="fa fa-ellipsis-v"></i>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a href="javascript:;" class="dropdown-item" v-on:click="copyThis">复制此项</a>
<a href="javascript:;" class="dropdown-item" v-on:click="screenshot">生成截图</a>
<a href="javascript:;" class="dropdown-item" v-show="datas.length>1" v-on:click="uncouple">取消组合</a>
</div>
</div>
</div>
</div>
<div class="pricing-trades">
<template v-for="(data,index) in datas">
<vue-trade2 v-if="data.trade.TradeType==='现金流交易'" v-on:reset-summary="resetSummary" v-on:change-client="changeClient" :floating="floating" :trade="data.trade" :view-state="data.viewState" :calc-result="data.calcResult"></vue-trade2>
<vue-trade v-else v-on:reset-summary="resetSummary" v-on:change-client="changeClient" v-on:synch-trade="synchTrade" v-on:sum-total="sumTotal" :floating="floating" :trade="data.trade" :view-state="data.viewState" :calc-result="data.calcResult"></vue-trade>
</template>
</div>
<div class="border-0 text-center" v-show="!floating">
<label class="w-100 m-0">
<input type="checkbox" class="checkbox" v-model="isSelected" title="是否录入交易" v-on:change="resetSummary(true)" />
</label>
</div>
</div>
</template>
<!--定价组件模板内部-->
@await Html.PartialAsync("_PricingItemTpl_dz")
<!--定价浮窗-->
<div id="floatModal" class="modal modal-fullscreen @(Model.IsImport?"show":"")" tabindex="-1" data-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="pricing-float-topbar">
<div class="btn-group screenshot-hide">
<button type="button" class="btn btn-primary" onclick="floatVue.screenshot()">截图</button>
<button type="button" class="btn btn-primary" onclick="floatVue.calcPrice()">定价计算</button>
<button type="button" class="btn btn-primary" onclick="floatVue.saveTrade()">录入交易</button>
<button type="button" class="btn btn-primary" onclick="floatVue.hide()">关闭</button>
</div>
</div>
<div class="pricing-float-main d-flex" id="for-screenshot">
<div class="ml-auto pricing-titles-cont">
<div class="singleprice screenshot-hide" id="singleprice2">@(Model.CompanyName) 付 0.000</div>
<div class="pricing-titles screenshot-hide">
<div>组合单价</div>
</div>
<div id="pricing-titles2"></div>
</div>
<div class="mr-auto pricing-items-cont">
<div class="zhanwei screenshot-hide"></div>
<div class="pricing-float-items" id="pricing-items2"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!--截图内容-->
<div id="screenshot" class="text-center screenshot-wrap" style="display:none;">
<div class="img-wrap">
<img id="screenshot-img" />
</div>
<div class="toolbar">
<button type="button" class="btn btn-primary" onclick="screenshoter.download()">保存截图</button>
<button type="button" class="btn btn-primary" onclick="screenshoter.closeShow()">关闭</button>
</div>
</div>
<!--标的下拉选择显示模板-->
<script type="text/html" id="underlyingSuggestionTpl">
<div class="row no-gutters" style="padding:3px 10px;">
<div class="col text-left">${Code}</div>
<% if (!!Name) { %>
<div class="col-auto text-right text-truncate" style="width:70px;">${Name}</div>
<% } %>
</div>
</script>
<!--输入错误信息提示-->
<div class="modal" id="checkErrorModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">错误提示</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<table id="checkErrorTable" class="table table-bordered table-hover">
<colgroup>
<col span="1" width="100" />
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>错误信息</th>
</tr>
</thead>
<tbody>
<tr>
<th>1</th>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!--交易录入-->
<div class="modal" tabindex="-1" id="modalTradeSave" data-backdrop="static">
<div class="modal-dialog">
<div class="modal-content" style="width:520px;">
<div class="modal-header">
<h5 class="modal-title">交易保存</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="mx-auto form-layout form-layout-save p-0 m-0 border-0" style="width:500px;border-radius:0;box-shadow:none">
<div class="form-group">
<label class="form-label">簿记账户</label>
<input type="text" class="form-input" id="AssetId" />
</div>
<div class="form-group">
<label class="form-label">交易员</label>
<input type="text" class="form-input" @(Model.CanSelectTrader ? "" : "readonly") id="TraderId" value="@(Model.Trade.TraderName)" data-id="@(Model.Trade.TraderId)" />
</div>
@if (!Model.ClientUsedForCalc)
{
<div class="form-group">
<label class="form-label">客户名称</label>
<input type="text" class="form-input" id="ClientId" />
</div>
}
<div id="salesCommissionCtrl"></div>
<div class="form-group">
<label class="form-label">交易编号</label>
<input type="text" class="form-input @(Model.UpperTradeNumber?"text-uppercase":"")" id="TradeNumber" maxlength="50" autocomplete="off" placeholder="不填则自动生成" />
</div>
@if (PS.Config.ErpElement.SecuritiesEnvironment)
{
<div class="form-group" hidden>
<label class="form-label">交易场所</label>
<select class="form-input" name="TradingPlace" id="TradingPlace">
@{
HtmlString html1 = new HtmlString("");
foreach (var item in YLErp.DBModels.Consts.ConsReport.TradingPlaceMap)
{
var str = "";
if (Model.Trade.MetaDic.ContainsKey("交易场所") && Model.Trade.MetaDic["交易场所"] == item.Key)
{
str = "selected='selected'";
}
<option @str>@item.Key</option>
}
}
</select>
</div>
<div class="form-group" hidden>
<label class="form-label">清算机构</label>
<select class="form-input" name="ClearingAgency" id="ClearingAgency">
@{
html1 = new HtmlString("");
foreach (var item in YLErp.DBModels.Consts.ConsReport.ClearingAgencyMap)
{
if (item.Key == "甲方" || item.Key == "乙方")
{
continue;
}
<option>@item.Key</option>
}
}
</select>
</div>
if (!Model.ClientUsedForCalc)
{
<div class="form-group">
<label class="form-label">主协议编号</label>
<select class="form-input" name="MainProtocolCode" id="MainProtocolCode">
</select>
</div>
<div class="form-group">
<label class="formlabel">补充协议编号</label>
<select class="form-input" name="SupProtocolCode" id="SupProtocolCode">
</select>
</div>
}
}
else
{
<div class="form-group">
<label class="formlabel">中央对手方清算</label>
<select id="IsCentralClearing" class="form-input" name="IsCentralClearing">
<option value="N">否</option>
<option value="Y">是</option>
<option value="I">计划中央对手方清算</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">中央清算平台</label>
<select id="CentralClearingPaltform" class="form-input" name="CentralClearingPaltform">
@foreach (var item in YLErp.BLL.DictionaryBLL.GetList("中央清算平台", true,"",true))
{
<option value="@item.Value">@item.Text</option>
}
</select>
</div>
<div class="form-group">
<label class="formlabel">交易平台</label>
<select id="TradingPaltform" class="form-input" name="TradingPaltform">
@foreach (var item in YLErp.BLL.DictionaryBLL.GetList("交易平台", true, "", true))
{
<option value="@item.Value">@item.Text</option>
}
</select>
</div>
}
<div class="form-group" hidden>
<label class="form-label">交易对手方角色</label>
<select class="form-input" name="OpponentRole" id="OpponentRole">
<option>甲方</option>
<option selected>乙方</option>
</select>
</div>
@if (PS.Config.Company == CompanyEnum.东方财富)
{
<div class="form-group">
<label class="formlabel">初始预付金率</label>
<input type="number" class="form-input" id="InitialAdvance" />
<span>% </span>
</div>
<div class="form-group">
<label class="formlabel">期间预付金率</label>
<input type="number" class="form-input" id="PeriodAdvance" />
<span>% </span>
</div>
<div class="form-group">
<label class="formlabel">前端收益费率</label>
<input type="number" class="form-input" id="FontEarning" />
<span>% </span>
</div>
<div class="form-group">
<label class="formlabel">追保线</label>
<input type="number" class="form-input" id="ConfirmedLine" />
<span>% </span>
</div>
<div class="form-group">
<label class="formlabel">追保比例下限</label>
<input type="number" class="form-input" id="ConfirmedFloor" />
<span>% </span>
</div>
}
</form>
</div>
<div class="modal-footer">
<div class="mx-auto">
<button type="button" id="btnSave" class="btn btn-primary btn-save">录入交易</button>
<button type="button" class="btn btn-primary btn-close" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
</div>
<!--加入组合-->
<div class="modal" tabindex="-1" id="modalGroupTradeSave" data-backdrop="static">
<div class="modal-dialog">
<div class="modal-content" style="width:1020px;">
<div class="modal-header">
<h5 class="modal-title">组合交易保存</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="mx-auto form-layout form-layout-save p-0 m-0 border-0" style="width:990px;border-radius:0;box-shadow:none">
<div style="background-color: #F2F2F2; height: 15px; margin: unset;"></div>
<div style="background-color: #F2F2F2; height: 225px; margin: unset;">
<div class="form-group col-6">
<label class="formlabel">交易日期</label>
<vue-datepicker class="text-box form-input" v-model="trade.TradeDate" disabled />
</div>
<div class="form-group col-6">
<label class="formlabel">到期日期</label>
<vue-datepicker class="text-box form-input" v-model="trade.ExerciseDate" />
</div>
<div class="form-group col-6">
<label class="formlabel">买卖方向</label>
<select class="form-input" v-model="trade.BuySell" v-on:change="changeBuySell()">
<option>卖出</option>
<option>买入</option>
</select>
</div>
<div class="form-group col-6">
<label class="formlabel">成交方式</label>
<select class="form-input" v-model="trade.IsUsePremiumRate" v-on:change="changeIsUsePremiumRate()">
<option :value="true">名义本金</option>
<option :value="false">成交数量</option>
</select>
</div>
<div class="form-group col-6">
<label class="formlabel">期初价格</label>
<vue-number-input class="form-input" v-model="trade.SpotPrice" v-bind:format="inputFormatSpot" disabled></vue-number-input>
</div>
<div class="form-group col-6">
<label class="formlabel">成交数量</label>
<vue-number-input class="form-input" v-model="trade.TradeAmount" v-on:input="changeTradeAmount()" v-bind:format="inputFormatTradeAmount" v-bind:disabled="trade.IsUsePremiumRate"></vue-number-input>
</div>
<div class="form-group col-6">
<label class="formlabel">名义本金</label>
<vue-number-input class="form-input" v-model="trade.StockEqvNotional" v-on:input="changeStockEqvNotional()" v-bind:format="inputFormatEqvNotional" v-bind:disabled="!trade.IsUsePremiumRate"></vue-number-input>
</div>
<div class="form-group col-6">
<label class="formlabel">权利金</label>
<template v-if="trade.IsUsePremiumRate">
<vue-number-input class="form-input" v-model="trade.PremiumRate" v-bind:format="inputFormatPremiumRate" disabled></vue-number-input>
</template>
<template v-else>
<vue-number-input class="form-input" v-model="trade.TradeSinglePrice" v-bind:format="inputFormatSinglePriceOnly" disabled></vue-number-input>
</template>
</div>
<div class="form-group col-6">
<label class="formlabel">交易总额</label>
<vue-number-input class="form-input" v-model="trade.TradePrice" disabled></vue-number-input>
</div>
<div class="form-group col-6">
<label class="formlabel">相对行权价</label>
<select class="form-input" v-model="trade.IsMoneynessOption">
<option>是</option>
<option>否</option>
</select>
</div>
</div>
<div style="background-color: #CDE0E6; height: 15px; margin: unset;"></div>
<div class="customPanel">
<div class="form-group col-6">
<label class="form-label">结构类型</label>
<select class="form-input" v-model="trade.StructureType" v-on:change="changeStrucTureType()">
<option v-for="structureType in structureTypes">
{{ structureType }}
</option>
</select>
</div>
<template v-if="trade.StructureType == '气囊结构'">
<div class="form-group col-6">
<label class="formlabel">看涨看跌</label>
<select class="form-input" v-model="trade.OptionType">
<option>看涨</option>
<option>看跌</option>
</select>
</div>
<div class="form-group col-6">
<label class="formlabel">敲入价格</label>
<vue-number-input class="form-input" v-model="trade.trade_airbag.Barrier" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="form-group col-6">
<label class="formlabel">低执行价格</label>
<vue-number-input class="form-input" v-model="trade.Strike" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="form-group col-6">
<label class="formlabel">高执行价格</label>
<vue-number-input class="form-input" v-model="trade.trade_airbag.HighStrike" v-bind:format="inputFormatStrike" v-bind:disabled="!trade.trade_airbag.HasPayoffLimit"></vue-number-input>
<input type="checkbox" class="mr-1" v-model="trade.trade_airbag.HasPayoffLimit">
</div>
<div class="form-group col-6">
<label class="formlabel">未敲入参与率</label>
<vue-number-input class="form-input" v-model="trade.trade_airbag.NotKIParticipationRate" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="form-group col-6">
<label class="formlabel">上涨参与率</label>
<vue-number-input class="form-input" v-model="trade.trade_airbag.KIParticipationRate" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<template v-else>
<template v-for="(value,key,index) in PropertyMap">
<template v-if="key != '气囊结构'">
<div v-show="trade.StructureType == key">
<template v-for="(item,index) in value">
<div class="form-group col-6">
<template v-if="item.isNew">
<input type="text" class="form-input" style="width: 120px;" v-model="item.ColumnName" v-on:change="changeOption()" />
</template>
<template v-else>
<label class="formlabel">{{item.ColumnName}}</label>
</template>
<template v-if="item.ColumnType == @((int)YLErp.Enums.StructureColumnTypeEnum.NUMBER)">
<vue-number-input type="text" class="form-input" v-model="item.ColumnDefaultValue" v-bind:format="customNumberFormat"></vue-number-input>
</template>
<template v-else-if="item.ColumnType == @((int)YLErp.Enums.StructureColumnTypeEnum.NUMBERP)">
<vue-number-input type="text" class="form-input" v-model="item.ColumnDefaultValue" v-bind:format="customNumberPercentFormat"></vue-number-input>
</template>
<template v-else-if="item.ColumnType == @((int)YLErp.Enums.StructureColumnTypeEnum.DATE)">
<vue-datepicker class="text-box form-input" v-model="item.ColumnDefaultValue" />
</template>
<template v-else-if="item.ColumnType == @((int)YLErp.Enums.StructureColumnTypeEnum.COMBO_BOX_SINGLE)">
<select class="form-input" v-model="item.ColumnDefaultValue" autocomplete="off">
<option v-for="opt in item.ColumnOptions.split(',')" :value="opt" :key="opt">
{{opt}}
</option>
</select>
</template>
<template v-else>
@*YLErp.Enums.StructureColumnTypeEnum.TEXT*@
<input type="text" class="form-input" v-model="item.ColumnDefaultValue" />
</template>
<a class="fa fa-minus" style="margin-left: 10px;" v-on:click="deleteProperty(index)"></a>
</div>
</template>
</div>
</template>
</template>
<div class="form-group col-6" style="text-align: center;">
<button type="button" class="btn btn-primary btn-save" v-on:click="addNewProperty()">新增</button>
</div>
</template>
</div>
<div style="background-color: #F2F2F2; height: 15px; margin: unset;"></div>
<div style="background-color: #F2F2F2; height: 180px;">
<div style="margin-left: 15px;">
<label class='formlabel'>交易备注</label>
<textarea rows='3' v-model="trade.Comments" class="text-box h-auto text-left" style="width: 80%"></textarea>
</div>
<div class="form-group col-6">
<label class="form-label">簿记账户</label>
<input type="text" class="form-input" id="GroupAssetId" />
</div>
<div class="form-group col-6">
<label class="form-label">交易员</label>
<input type="text" class="form-input" id="GroupTraderId" />
</div>
<div class="form-group col-6">
<label class="form-label">交易对手方</label>
<input type="text" class="form-input" id="GroupClientId" />
</div>
<div class="form-group col-6">
<label class="form-label">交易编号</label>
<input type="text" class="form-input" v-model="trade.TradeNumber" />
</div>
</div>
@*@if (Model.SecuritiesEnvironment)
{
<div class="form-group">
<label class="form-label">交易场所</label>
<select class="form-input" v-model="trade.TradingPlace">
@foreach (var item in YLErp.DBModels.Consts.ConsReport.TradingPlaceMap)
{
<option>@item.Key</option>
}
</select>
</div>
<div class="form-group">
<label class="form-label">清算机构</label>
<select class="form-input" v-model="trade.ClearingAgency">
@foreach (var item in YLErp.DBModels.Consts.ConsReport.ClearingAgencyMap)
{
if (item.Key == "甲方" || item.Key == "乙方")
{
continue;
}
<option>@item.Key</option>
}
</select>
</div>
if (!Model.ClientUsedForCalc)
{
<div class="form-group">
<label class="form-label">主协议编号</label>
<select class="form-input" name="MainProtocolCode" id="MainProtocolCode">
</select>
</div>
<div class="form-group">
<label class="formlabel">补充协议编号</label>
<select class="form-input" name="SupProtocolCode" id="SupProtocolCode">
</select>
</div>
}
}*@
</form>
</div>
<div class="modal-footer">
<div class="mx-auto">
<button type="button" class="btn btn-primary btn-save" data-dismiss="modal" v-on:click="saveGroupTrade()">录入组合交易</button>
<button type="button" class="btn btn-primary btn-close" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
</div>
<!--对冲下单-->
<form id="orderForm" action="/RiskHedging/HedgingOrder" method="post" target="_blank" class="sr-only">
<input type="hidden" name="InstType" value="" />
</form>
@await Html.PartialAsync("_SyntheticPrice")
@await Html.PartialAsync("/Views/trade/_part/SalesCommission.cshtml", new SalesCommissionModel() { Disabled = false, ViewType = "期权" })
<!--自定义模板-->
<div class="modal" tabindex="-1" id="modalTemplate">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">自定义模板</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<template v-if="saveMode">
<form id="formTemplateSave" onsubmit="return false;">
<div class="form-group">
<label>模板名称</label>
<input type="text" class="form-control text-left" style="width:100%;" maxlength="50" v-model="saveData.name">
</div>
<div class="form-group">
<div><label><input type="checkbox" v-model="saveData._override" checked="" class="mr-1">覆盖已存在的模板</label></div>
<div>
<label><input type="checkbox" v-model="saveData._CommonTemplate" checked="" class="mr-1">保存为公共模板</label>
</div>
</div>
<div class="form-group text-center">
<button type="button" class="btn btn-primary" v-on:click="saveTemplate">保存模板</button>
</div>
</form>
</template>
<template v-else>
<div class="row">
<div class="col">
<div class="yt-input-group d-inline-block">
<input type="text" class="search" v-model="searchText" placeholder="搜索" v-on:input="searchList" />
<a href="javascript:void(0)" class="yt-input-group-append show" v-on:click="searchList('reset')" style="padding-top:4px;" title="清除搜索"><i class="fa fa-remove"></i></a>
</div>
</div>
<div class="col text-right">
<a href="javascript:void(0)" v-on:click="refreshList" title="重新加载列表" style="line-height:30px;"><i class="fa fa-refresh"></i></a>
</div>
</div>
<hr />
<ul class="list-group list-group-flush yt-template-group border">
<li class="list-group-item yt-template-item" v-for="item in listData" v-show="item.show">
<a href="javascript:void(0)" title="删除" class="yt-template-remove" v-on:click="removeTemplate(item.id)"><i class="fa fa-remove"></i></a>
<a href="javascript:void(0)" v-on:click="loadTemplate" class="yt-template-link">{{item.name}}</a><span>{{item.ConfigType==3?"公共模板":"个人模板"}}</span>
</li>
</ul>
</template>
</div>
</div>
</div>
</div>
@@ -1,905 +0,0 @@
@*定价模板*@
@model PricingModel
@{ Layout = null;}
<template id="pricingItem_tpl">
<div v-bind:class="isTitle?'pricing-titles':'pricing-trade'">
<div class="ptitle screenshot-hide" v-if="!pageVue.IsImport">交易序号</div>
<template v-if="pageVue.ClientUsedForCalc">
<div class="ptitle">客户名称</div>
<div class="pitem pitem-cash" v-show="!trade.hideCommen" v-bind:class="trade.hideCommen ? 'topborder' : ''">
<vue-client v-model="trade.ClientId" v-on:input="changeClient" :update="updateKey.client" />
</div>
<div class="pitem empty" v-show="trade.hideCommen" v-bind:class="trade.hideCommen ? 'topborder' : ''"></div>
</template>
<div class="ptitle">期权类型</div>
<div class="pitem" v-show="!trade.hideCommen">
<vue-tradetype v-model="trade.TradeType" v-on:input="changeTradeType" v-bind:disabled="!!trade.StructureType" />
</div>
<div class="pitem empty" v-show="trade.hideCommen" v-bind:class="!pageVue.ClientUsedForCalc && trade.hideCommen ? 'topborder' : ''"></div>
<div class="pitem-cash">
<span>现金流</span>
</div>
<template v-if="!topVue.simpleMode" style="display:none">
<div class="ptitle screenshot-hide">标的类型</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen">
<vue-niceselect name="UnderlyingInstrumentType" :items="tradeHelper.InstrumentTypes" v-model="trade.UnderlyingInstrumentType" v-on:input="changeInstrumentType" />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
</template>
<template v-if="!topVue.simpleMode">
<div class="ptitle screenshot-hide">标的品种</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen">
<vue-variety v-bind:variety="viewState.variety" v-on:change-variety="changeVariety" />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
</template>
<div class="ptitle">标的代码</div>
<div class="pitem" v-show="!trade.hideCommen">
<vue-underlying v-bind:underlying="viewState.underlying" v-on:change-underlying="changeUnderlying" />
</div>
<div class="pitem empty" v-show="trade.hideCommen"></div>
<div class="pitem-cash"></div>
<div class="ptitle">成交数量</div>
<div class="pitem" v-show="!trade.hideCommen || trade.StructureType == '蝶式组合'">
<vue-number-input v-model="trade.TradeAmountV" v-on:input="changeTradeAmountV" v-bind:format="inputFormatTradeAmountV" v-bind:disabled="!!trade.IsUsePremiumRate"></vue-number-input>
</div>
<div class="pitem empty" v-show="trade.hideCommen && trade.StructureType != '蝶式组合'"></div>
<div class="pitem-cash"></div>
<template v-if="!topVue.simpleMode">
<div class="ptitle screenshot-hide">有效成交数量</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen || trade.StructureType == '蝶式组合'">
<vue-number-input v-model="trade.TradeAmount" v-on:input="changeTradeAmount" v-bind:format="inputFormatTradeAmount" v-bind:disabled="!!trade.IsUsePremiumRate"></vue-number-input>
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen && trade.StructureType != '蝶式组合'"></div>
<div class="pitem-cash screenshot-hide"></div>
<div class="ptitle screenshot-hide">名义本金</div>
<div class="pitem pitem-cash screenshot-hide" v-show="!trade.hideCommen || trade.StructureType == '蝶式组合'">
<input type="number" v-bind:disabled="!trade.IsUsePremiumRate" v-model="trade.StockEqvNotional" v-on:change="changeStockEqvNotional" />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen && trade.StructureType != '蝶式组合'"></div>
<div class="ptitle screenshot-hide">有效名义本金</div>
<div class="pitem pitem-cash screenshot-hide" v-show="!trade.hideCommen || trade.StructureType == '蝶式组合'">
<input type="number" v-bind:disabled="!trade.IsUsePremiumRate" v-model="trade.StockEqvNotionalReal" v-on:change="changeStockEqvNotionalReal" />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen && trade.StructureType != '蝶式组合'"></div>
</template>
<template v-if="!floating||fieldState.ExerciseMode">
<div class="ptitle">行权方式</div>
<div class="pitem" v-show="!trade.hideCommen">
<vue-niceselect name="ExerciseMode" :items="tradeHelper.ExerciseModes" v-model="trade.ExerciseMode" v-on:input="changeExerciseMode" v-if="showExerciseMode" v-bind:disabled="showExerciseMode===1" />
</div>
<div class="pitem empty" v-bind:class="trade.StructureType == '蝶式组合' ? 'topborder' : ''" v-show="trade.hideCommen"></div>
<div class="pitem-cash"></div>
</template>
<div class="ptitle">交易日期</div>
<div class="pitem pitem-cash" v-show="!trade.hideCommen">
<vue-datepicker :maxdate="trade.ExerciseDate" v-model="trade.TradeDate" :noholiday="fieldState.Cashflow" v-on:input="changeTradeDate" />
</div>
<div class="pitem empty" v-show="trade.hideCommen"></div>
<div class="ptitle">到期日期</div>
<div class="pitem pitem-cash" v-show="!trade.hideCommen || trade.StructureType == '日历价差'">
<vue-datepicker :maxdate="trade.MaturityDate" v-model="trade.ExerciseDate" :noholiday="fieldState.Cashflow" v-on:input="changeExerciseDate" />
</div>
<div class="pitem empty" v-show="trade.hideCommen && trade.StructureType != '日历价差'"></div>
<template v-if="!topVue.simpleMode">
<div class="ptitle screenshot-hide">结算日期</div>
<div class="pitem pitem-cash screenshot-hide" v-show="!trade.hideCommen || trade.StructureType == '日历价差'">
<vue-datepicker v-model="trade.SettlementDate" :noholiday="fieldState.Cashflow" v-on:input="changeSettlementDate" />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen && trade.StructureType != '日历价差'"></div>
</template>
<div class="ptitle">期初标的价格</div>
<div class="pitem seal" v-show="!trade.hideCommen">
<div class="yt-input-group" v-bind:class="viewState.SpotPrice">
<input type="number" class="has-popover-syn" v-model="trade.SpotPrice" v-on:input="changeSpotPrice(true)" v-on:blur="blurSpotPrice" v-bind:step="getPriceTick()" v-bind:readonly="!!viewState.synthetic" v-bind:data-calcid="trade.CalcId" max="999999" min="-999999" />
<a href="javascript:;" v-on:click="getSpotPrice" title="获取系统的期初标的价格" class="yt-input-group-append s1" tabindex="-1">
<i class="fa fa-refresh"></i>
</a>
</div>
</div>
<div class="pitem empty" v-bind:class="trade.StructureType == '日历价差' ? 'topborder' : ''" v-show="trade.hideCommen"></div>
<div class="pitem-cash seal"><span class="sr-only">期初标的价格</span></div>
<template v-if="!topVue.simpleMode">
<div class="ptitle screenshot-hide">标的价格</div>
<div class="pitem seal screenshot-hide" v-show="!trade.hideCommen">
<div class="yt-input-group">
<input type="number" v-model="trade.UnderlyingPrice" v-on:input="synchTrade" max="999999" min="-999999" step="0.01" />
<a href="javascript:;" v-on:click="getUnderlyingPrice" title="获取系统的标的价格" class="yt-input-group-append s1" tabindex="-1">
<i class="fa fa-refresh"></i>
</a>
</div>
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash seal screenshot-hide"><span class="sr-only">标的价格</span></div>
</template>
<div class="ptitle" v-show="!floating||!fieldState.AutoCall&&!fieldState.SnowBall">执行价格</div>
<div class="pitem seal" v-show="!floating||!fieldState.AutoCall&&!fieldState.SnowBall">
<div class="yt-input-group" v-if="!fieldState.AutoCall&&!fieldState.SnowBall&&trade.StrikeType!=='Floating'">
<vue-number-input name="Strike" v-model="trade.Strike" v-on:input="changeStrike" v-bind:format="inputFormatStrike"></vue-number-input>
<span class="yt-input-group-append" tabindex="-1">
<a href="javascript:;" v-if="trade.IsMoneynessOption==='是'" v-on:click="showAbsStrike" title="点击后切换成绝对值执行价格">%</a>
<a href="javascript:;" v-else v-on:click="showPercentStrike" title="点击后切换成标的价格百分比">¥</a>
</span>
</div>
</div>
<div class="pitem-cash"><span class="sr-only">执行价格</span></div>
<template v-if="!floating||fieldState.OptionType">
<div class="ptitle">看涨看跌</div>
<div class="pitem">
<vue-niceselect name="OptionType" :items="tradeHelper.OptionTypes" v-model="trade.OptionType" v-if="showOptionType" />
</div>
<div class="pitem-cash"></div>
</template>
<div class="ptitle">交易方向</div>
<div class="pitem pitem-cash">
<vue-niceselect name="BuySell" :items="consBuysellTypes" v-model="trade.BuySell" v-on:input="changeBuySell" />
</div>
<div class="ptitle">权利金</div>
<div class="pitem yt-input-group">
<template v-if="trade.IsUsePremiumRate">
<vue-number-input name="PremiumRate" v-model="trade.PremiumRate" v-on:input="changePremiumRate" v-bind:format="inputFormatPremiumRate"></vue-number-input>
<a href="javascript:;" title="点击后切换成绝对值权利金" v-on:click="showAbsPrice" class="yt-input-group-append" tabindex="-1">%</a>
</template>
<template v-else>
<vue-number-input name="TradeSinglePrice" v-model="trade.TradeSinglePrice" v-on:input="changeTradeSinglePrice" v-bind:format="inputFormatSinglePriceOnly"></vue-number-input>
<a href="javascript:;" title="点击后切换成标的价格百分比" v-on:click="showPercentPrice" class="yt-input-group-append" tabindex="-1">¥</a>
</template>
</div>
<div class="pitem-cash"></div>
<!--简单模式-->
<template v-if="!topVue.simpleMode">
<div class="ptitle screenshot-hide">参与率</div>
<div class="pitem screenshot-hide">
<vue-number-input v-model="trade.ParticipationRate" v-on:input="changeEffectRatio" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="pitem-cash screenshot-hide"></div>
<div class="ptitle screenshot-hide">保底收益</div>
<div class="pitem screenshot-hide">
<template v-if="trade.IsUsePremiumRate">
<vue-number-input name="PrincipalRateWrite" v-model="trade.PrincipalRateWrite" v-on:input="changeEffectRatio" v-bind:format="inputFormatPremiumRate"></vue-number-input>
</template>
<template v-else>
<vue-number-input name="SinglePrincipalWrite" v-model="trade.SinglePrincipalWrite" v-on:input="changeEffectRatio" v-bind:format="inputFormatSinglePrincipalOnly"></vue-number-input>
</template>
</div>
<div class="pitem-cash screenshot-hide"></div>
<div class="ptitle screenshot-hide">保底收益总额</div>
<div class="pitem yt-input-group screenshot-hide">
<vue-number-input name="OriginalPrincipalSum" v-model="trade.OriginalPrincipalSum" v-bind:format="inputFormatDouble2" v-on:input="changePrincipalSum"></vue-number-input>
<a href="javascript:;" v-on:click="changeEffectRatio" title="计算保底收益总额" class="yt-input-group-append s1" tabindex="-1">
<i class="fa fa-refresh"></i>
</a>
</div>
</template>
<div class="ptitle">成交金额</div>
<div class="pitem pitem-cash">
<vue-number-input v-model="trade.TradePrice" v-bind:disabled="!fieldState.Cashflow" v-bind:format="inputFormatTradePrice"></vue-number-input>
</div>
<div class="pitem-cash"></div>
<!--初始预付金-->
@if (Model.ShowInitialMargin)
{
//组合交易初始预付金不需要展示,只需要展示组合预付金
<template v-if="topVue.calcMargin && !fieldState.ShowTotal">
<div class="ptitle">初始预付金</div>
<div class="pitem">
<div class="yt-input-group" v-bind:class="viewState.InitialMargin">
<vue-number-input v-on:change="changeInitialMargin" v-model="trade.InitialMargin" v-bind:format="inputFormatTradePrice" step="0.1"></vue-number-input>
<a href="javascript:;" v-on:click="getInitialMargin" title="使用系统计算的初始预付金" class="yt-input-group-append s1">
<i class="fa fa-refresh"></i>
</a>
</div>
</div>
<div class="pitem-cash"></div>
</template>
}
<template v-if="fieldState.ShowTotal">
<div class="ptitle">组合价格</div>
<div class="pitem pitem-cash" v-show="!trade.hideCommen">
<vue-number-input v-model="trade.TotalTradePrice" v-bind:format="inputFormatTradePrice" disabled></vue-number-input>
</div>
<div class="pitem empty topborder" v-show="trade.hideCommen"></div>
<div class="pitem-cash"></div>
</template>
@if (Model.ShowInitialMargin)
{
<template v-if="topVue.calcMargin && fieldState.ShowTotal">
<div class="ptitle">组合预付金</div>
<div class="pitem" v-show="!trade.hideCommen">
<div class="yt-input-group" v-bind:class="viewState.InitialMargin">
<vue-number-input v-model="trade.TotalInitialMargin" v-bind:format="inputFormatTradePrice" disabled></vue-number-input>
</div>
</div>
<div class="pitem empty" v-show="trade.hideCommen"></div>
<div class="pitem-cash"></div>
</template>
}
<!--奇异期权-->
<template v-if="floating">
<!--------------二元期权---------------->
<template v-if="fieldState.BinaryOption">
<div class="ptitle">二元类型</div>
<div class="pitem">
<vue-niceselect name="PayoffType" :items="binaryPayoffTypes" v-model="trade.PayoffType" v-on:input="changePayoffType" />
</div>
<div class="ptitle">补偿金额</div>
<div class="pitem">
<vue-number-input v-if="trade.IsUsePremiumRate" v-model="trade.CashOrNothingAmountRate" v-bind:format="inputFormatPremiumRate" v-bind:disabled="trade.PayoffType==='AssetOrNothing'"></vue-number-input>
<vue-number-input v-else v-model="trade.CashOrNothingAmount" v-bind:format="inputFormatSinglePrice" v-bind:disabled="trade.PayoffType==='AssetOrNothing'"></vue-number-input>
</div>
<template v-if="fieldState.ExerciseMode>10">
<template v-if="fieldState.PayoffTypeShow">
<div class="ptitle">高障碍价格</div>
<div class="pitem">
<vue-number-input v-model="trade.BarrierHigh" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">高障碍补偿金额</div>
<div class="pitem">
<div>
<vue-number-input v-if="trade.IsUsePremiumRate" v-model="trade.CashOrNothingAmountHighRate" v-bind:format="inputFormatPremiumRate" v-bind:disabled="trade.PayoffType=='DoubleNoTouch'&&trade.ExerciseMode=='American'"></vue-number-input>
<vue-number-input v-else v-model="trade.CashOrNothingAmountHigh" v-bind:format="inputFormatSinglePrice" v-bind:disabled="trade.PayoffType=='DoubleNoTouch'&&trade.ExerciseMode=='American'"></vue-number-input>
</div>
</div>
</template>
<div class="ptitle">观察方式</div>
<div class="pitem">
<vue-niceselect name="MonitorType" :items="consMonitorTypes" v-model="trade.MonitorType" />
</div>
<div class="ptitle">补偿支付</div>
<div class="pitem">
<vue-niceselect name="RebateType" :items="binaryRebateTypes" v-model="trade.RebateType" />
</div>
</template>
<div class="ptitle">补偿按敲出日年化</div>
<div class="pitem">
<vue-niceselect name="RebateAnnualizedAtKO" :items="consFalseAsNo" v-model="trade.RebateAnnualizedAtKO" v-bind:disabled="trade.ExerciseMode === 'European' || this.trade.PayoffType === 'UpNoTouch' || this.trade.PayoffType === 'DownNoTouch' || this.trade.PayoffType === 'DoubleNoTouch'" />
</div>
<div class="ptitle">补偿计息规则</div>
<div class="pitem">
<vue-daycount v-if="trade.RebateAnnualizedAtKO" name="RebateDayCount" v-model="trade.RebateDayCount" />
</div>
</template>
<!--------------障碍期权---------------->
<template v-if="fieldState.BarrierOption">
<div class="ptitle">障碍类型</div>
<div class="pitem">
<vue-niceselect name="BarrierType" :items="consBarrierTypes" v-model="trade.BarrierType" v-on:input="changeFieldState" />
</div>
<div class="ptitle">障碍价格</div>
<div class="pitem">
<vue-number-input v-model="trade.BarrierLow" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">高障碍价格</div>
<div class="pitem">
<vue-number-input v-if="(trade.BarrierType || '').startsWith('双')" v-model="trade.BarrierHigh" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">补偿金额</div>
<div class="pitem">
<vue-number-input v-if="trade.IsUsePremiumRate" v-model="trade.RebateRate" v-bind:format="inputFormatPremiumRate"></vue-number-input>
<vue-number-input v-else v-model="trade.Rebate" v-bind:format="inputFormatSinglePrice"></vue-number-input>
</div>
<div class="ptitle" v-show="fieldState.IsBarrierType">高障碍补偿金额</div>
<div class="pitem" v-show="fieldState.IsBarrierType">
<vue-number-input v-if="trade.IsUsePremiumRate" v-model="trade.RebateHighRate" v-bind:format="inputFormatPremiumRate"></vue-number-input>
<vue-number-input v-else v-model="trade.RebateHigh" v-bind:format="inputFormatSinglePrice"></vue-number-input>
</div>
<div class="ptitle">障碍偏移</div>
<div class="pitem">
<input type="number" v-model="trade.BarrierShift" />
</div>
<div class="ptitle">观察方式</div>
<div class="pitem">
<vue-niceselect name="MonitorType" :items="consMonitorTypes" v-model="trade.MonitorType" />
</div>
<div class="ptitle">补偿支付</div>
<div class="pitem">
<vue-niceselect name="RebateType" :items="consRebateTypes" v-model="trade.RebateType" />
</div>
<div class="ptitle">补偿按敲出日年化</div>
<div class="pitem">
<vue-niceselect v-if="(trade.BarrierType || '').endsWith('敲出')" name="RebateAnnualizedAtKO" :items="consFalseAsNo" v-model="trade.RebateAnnualizedAtKO" />
</div>
<div class="ptitle">补偿计息规则</div>
<div class="pitem">
<vue-daycount v-if="trade.RebateAnnualizedAtKO" name="RebateDayCount" v-model="trade.RebateDayCount" />
</div>
</template>
<!--------------双鲨期权---------------->
<template v-if="fieldState.DBSharkOption">
<div class="ptitle">障碍价格</div>
<div class="pitem">
<vue-number-input v-model="trade.BarrierLow" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">高障碍价格</div>
<div class="pitem">
<vue-number-input v-model="trade.BarrierHigh" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">高行权价</div>
<div class="pitem">
<vue-number-input v-model="trade.StrikeHigh" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">补偿金额</div>
<div class="pitem">
<vue-number-input v-if="trade.IsUsePremiumRate" v-model="trade.RebateRate" v-bind:format="inputFormatPremiumRate"></vue-number-input>
<vue-number-input v-else v-model="trade.Rebate" v-bind:format="inputFormatSinglePrice"></vue-number-input>
</div>
<div class="ptitle">高障碍补偿金额</div>
<div class="pitem">
<vue-number-input v-if="trade.IsUsePremiumRate" v-model="trade.RebateHighRate" v-bind:format="inputFormatPremiumRate"></vue-number-input>
<vue-number-input v-else v-model="trade.RebateHigh" v-bind:format="inputFormatSinglePrice"></vue-number-input>
</div>
<div class="ptitle">高参与率</div>
<div class="pitem">
<vue-number-input v-model="trade.CallParticipationRate" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="ptitle">低参与率</div>
<div class="pitem">
<vue-number-input v-model="trade.PutParticipationRate" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="ptitle">观察方式</div>
<div class="pitem">
<vue-niceselect name="MonitorType" :items="consMonitorTypes" v-model="trade.MonitorType" />
</div>
<div class="ptitle">补偿支付</div>
<div class="pitem">
<vue-niceselect name="RebateType" :items="consRebateTypes" v-model="trade.RebateType" />
</div>
</template>
<!--------------亚式期权---------------->
<template v-if="fieldState.AsiaOption">
<div class="ptitle">均价起算日</div>
<div class="pitem">
<vue-datepicker v-model="trade.AveragingPeriodStartDate" />
</div>
<div class="ptitle">均价计算</div>
<div class="pitem">
<vue-niceselect name="PayoffType" :items="consAsiaPayoffType" v-model="trade.PayoffType" v-on:input="changePayOffType" />
</div>
<div class="ptitle" id="EnhancedPriceTitle" v-show="trade.PayoffType=='EnhancedArithmeticAverage'&&trade.StrikeType!='Floating'">增强价格</div>
<div class="pitem" v-show="trade.PayoffType=='EnhancedArithmeticAverage'&&trade.StrikeType!='Floating'">
<vue-number-input v-model="trade.EnhancedPrice" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">行权价类型</div>
<div class="pitem">
<vue-niceselect name="StrikeType" :items="consAsiaStrikeType" v-model="trade.StrikeType" v-on:input="changeStrikeType" />
</div>
<div class="ptitle">杠杆率</div>
<div class="pitem">
<vue-number-input v-model="trade.StrikeGearingFactor" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<!----------------雪球期权---------------->
<template v-if="fieldState.SnowBall">
<!---雪球期权敲出--->
<div class="ptitle">敲出障碍价格</div>
<div class="pitem">
<div class="yt-input-group">
<vue-number-input v-model="trade.KOBarrier" v-on:input="changeKOBarrier" v-bind:format="inputFormatStrike"></vue-number-input>
<span class="yt-input-group-append">
<a href="javascript:;" v-if="trade.IsMoneynessOption==='是'" v-on:click="showAbsStrike" title="点击后切换成绝对值执行价格" tabindex="-1">%</a>
<a href="javascript:;" v-else v-on:click="showPercentStrike" title="点击后切换成标的价格百分比" tabindex="-1">¥</a>
</span>
</div>
</div>
<div class="ptitle">敲出赔付类别</div>
<div class="pitem">
<vue-niceselect name="KOPayoffType" :items="snowballKOPayoffTypeEnums" v-model="trade.KOPayoffType" v-on:input="changeFieldState" />
</div>
<div class="ptitle" v-show="fieldState.IsFixedCoupon">票息年化</div>
<div class="pitem" v-show="fieldState.IsFixedCoupon">
<vue-niceselect name="IsFixedCoupon" :items="consFalseAsYes" v-model="trade.IsFixedCoupon" />
</div>
<div class="ptitle" v-show="fieldState.KORebate">票息率</div>
<div class="pitem yt-input-group" v-show="fieldState.KORebate">
<div><vue-number-input v-model="trade.KORebate" v-on:input="changeRebate" v-bind:format="inputFormatPremiumRate"></vue-number-input></div>
<a href="javascript:;" v-on:click="calcKORebate()" class="yt-input-group-append" tabindex="-1">
<span title="根据权利金反算票息" class="fa fa-refresh"></span>
</a>
</div>
<div class="ptitle" v-show="fieldState.IsFixedCoupon">票息日历规则</div>
<div class="pitem" v-show="fieldState.IsFixedCoupon">
<vue-daycount name="CouponDayCount" v-model="trade.CouponDayCount" />
</div>
<div class="ptitle" v-show="fieldState.KORebate">票息包含首日</div>
<div class="pitem" v-show="fieldState.KORebate">
<vue-niceselect name="CouponIncludeStartDate" :items="consFalseAsNo" v-model="trade.CouponIncludeStartDate" v-once />
</div>
<div class="ptitle" v-show="fieldState.KORebate">使用支付日计息</div>
<div class="pitem" v-show="fieldState.KORebate">
<vue-niceselect name="CouponUsePaymentDate" :items="consFalseAsNo" v-model="trade.CouponUsePaymentDate" />
</div>
<div class="ptitle" v-show="fieldState.AnnualizedPremiumRate">年化期权费率</div>
<div class="pitem yt-input-group " v-show="fieldState.AnnualizedPremiumRate">
<div><vue-number-input v-model="trade.AnnualizedPremiumRate" v-bind:format="inputFormatPremiumRate"></vue-number-input></div>
<a href="javascript:;" v-on:click="calcSnowballAnnualPremium()" class="yt-input-group-append" tabindex="-1">
<span title="保本雪球计算年化期权费率" class="fa fa-refresh"></span>
</a>
</div>
<div class="ptitle" v-show="fieldState.SpreadStrikeAtKO1">敲出行权价1</div>
<div class="pitem" v-show="fieldState.SpreadStrikeAtKO1">
<vue-number-input v-model="trade.SpreadStrikeAtKO1" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle" v-show="fieldState.SpreadStrikeAtKO">敲出行权价2</div>
<div class="pitem" v-show="fieldState.SpreadStrikeAtKO">
<vue-number-input v-model="trade.SpreadStrikeAtKO" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">敲出支付方式</div>
<div class="pitem">
<vue-niceselect name="KORebateType" :items="consKORebateTypes" v-model="trade.KORebateType" />
</div>
<div class="ptitle screenshot-hide">敲出观察频率</div>
<div class="pitem screenshot-hide">
<div class="yt-input-group">
<input type="text" v-model="koObservationRate" readonly />
<a href="javascript:;" class="yt-input-group-append show" title="设置观察日" v-on:click="setObservationDates('#KO')" tabindex="-1">
<i class="fa fa-pencil-square"></i>
</a>
</div>
</div>
<!---雪球期权敲入--->
<div class="ptitle screenshot-hide">敲入观察频率</div>
<div class="pitem screenshot-hide">
<div class="yt-input-group">
<input type="text" v-model="observationRate" readonly />
<a href="javascript:;" class="yt-input-group-append show" title="设置观察日" v-on:click="setObservationDates('#KI')" tabindex="-1">
<i class="fa fa-pencil-square"></i>
</a>
</div>
</div>
<div class="ptitle">敲入障碍价格</div>
<div class="pitem">
<div class="yt-input-group">
<vue-number-input v-model="trade.KIBarrier" v-bind:format="inputFormatStrike"></vue-number-input>
<span class="yt-input-group-append">
<a href="javascript:;" v-if="trade.IsMoneynessOption==='是'" v-on:click="showAbsStrike" title="点击后切换成绝对值执行价格" tabindex="-1">%</a>
<a href="javascript:;" v-else v-on:click="showPercentStrike" title="点击后切换成标的价格百分比" tabindex="-1">¥</a>
</span>
</div>
</div>
<div class="ptitle">敲入到期支付类别</div>
<div class="pitem">
<vue-niceselect name="KIPayoffType" :items="snowballKIPayoffTypeEnums" v-model="trade.KIPayoffType" v-on:input="changeFieldState" :key="updateKey.KIPayoffType" />
</div>
<div class="ptitle" v-show="fieldState.SpreadStrike1">敲入行权价</div>
<div class="pitem" v-show="fieldState.SpreadStrike1">
<vue-number-input v-model="trade.SpreadStrike1" v-bind:format="inputFormatStrike" v-on:input="changeSpreadStrike1"></vue-number-input>
</div>
<div class="ptitle" v-show="fieldState.SpreadStrike">封底/封顶行权价</div>
<div class="pitem" v-show="fieldState.SpreadStrike">
<vue-number-input v-model="trade.SpreadStrike" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">红利票息</div>
<div class="pitem">
<vue-number-input v-model="trade.Coupon" v-bind:format="inputFormatPremiumRate"></vue-number-input>
</div>
</template>
<!--------------凤凰期权---------------->
<template v-if="fieldState.AutoCall">
<div class="ptitle">票息年化</div>
<div class="pitem">
<vue-niceselect name="IsFixedCoupon" :items="consFalseAsYes" v-model="trade.IsFixedCoupon" />
</div>
<div class="ptitle">票息率</div>
<div class="pitem yt-input-group">
<vue-number-input name="Coupon" v-model="trade.Coupon" v-on:input="changeRebate" v-bind:format="inputFormatPremiumRate"></vue-number-input>
<a href="javascript:;" v-on:click="calcPhoenixCouponRate()" class="yt-input-group-append" tabindex="-1">
<span title="根据权利金反算票息" class="fa fa-refresh"></span>
</a>
</div>
<div class="ptitle">票息障碍价格</div>
<div class="pitem">
<div class="yt-input-group">
<vue-number-input v-model="trade.CouponBarrier" v-bind:format="inputFormatStrike"></vue-number-input>
<span class="yt-input-group-append">
<a href="javascript:;" v-if="trade.IsMoneynessOption==='是'" v-on:click="showAbsStrike" title="点击后切换成绝对值执行价格" tabindex="-1">%</a>
<a href="javascript:;" v-else v-on:click="showPercentStrike" title="点击后切换成标的价格百分比" tabindex="-1">¥</a>
</span>
</div>
</div>
<div class="ptitle">票息日历规则</div>
<div class="pitem">
<vue-daycount name="CouponDayCount" v-model="trade.CouponDayCount" />
</div>
<div class="ptitle">票息包含首日</div>
<div class="pitem">
<vue-niceselect name="CouponIncludeStartDate" :items="consFalseAsNo" v-model="trade.CouponIncludeStartDate" v-once />
</div>
<div class="ptitle">票息结算方式</div>
<div class="pitem">
<vue-niceselect name="CouponPayType" :items="consCouponPayTypes" v-model="trade.CouponPayType" v-on:input="changeCouponPayType" />
</div>
<div class="ptitle">敲出障碍价格</div>
<div class="pitem">
<div class="yt-input-group">
<vue-number-input v-model="trade.KOBarrier" v-on:input="changeKOBarrier" v-bind:format="inputFormatStrike"></vue-number-input>
<span class="yt-input-group-append">
<a href="javascript:;" v-if="trade.IsMoneynessOption==='是'" v-on:click="showAbsStrike" title="点击后切换成绝对值执行价格" tabindex="-1">%</a>
<a href="javascript:;" v-else v-on:click="showPercentStrike" title="点击后切换成标的价格百分比" tabindex="-1">¥</a>
</span>
</div>
</div>
<div class="ptitle screenshot-hide">敲出观察频率</div>
<div class="pitem screenshot-hide">
<div class="yt-input-group">
<input type="text" v-model="koObservationRate" readonly />
<a href="javascript:;" class="yt-input-group-append show" title="设置观察日" v-on:click="setObservationDates('#KO')" tabindex="-1">
<i class="fa fa-pencil-square"></i>
</a>
</div>
</div>
<div class="ptitle screenshot-hide">敲入观察频率</div>
<div class="pitem screenshot-hide">
<div class="yt-input-group">
<input type="text" v-model="observationRate" readonly />
<a href="javascript:;" class="yt-input-group-append show" title="设置观察日" v-on:click="setObservationDates('#KI')" tabindex="-1">
<i class="fa fa-pencil-square"></i>
</a>
</div>
</div>
<div class="ptitle">敲入障碍价格</div>
<div class="pitem">
<div class="yt-input-group">
<vue-number-input v-model="trade.KIBarrier" v-bind:format="inputFormatStrike"></vue-number-input>
<span class="yt-input-group-append">
<a href="javascript:;" v-if="trade.IsMoneynessOption==='是'" v-on:click="showAbsStrike" title="点击后切换成绝对值执行价格" tabindex="-1">%</a>
<a href="javascript:;" v-else v-on:click="showPercentStrike" title="点击后切换成标的价格百分比" tabindex="-1">¥</a>
</span>
</div>
</div>
<div class="ptitle">敲入到期是否支付票息</div>
<div class="pitem">
<div v-if="trade.CouponPayType===2">
<vue-niceselect name="IncludeCouponAfterKI" :items="consTrueAsYes" v-model="trade.IncludeCouponAfterKI" />
</div>
<div v-else>是</div>
</div>
<div class="ptitle">敲入到期支付类别</div>
<div class="pitem">
<vue-niceselect name="KIPayoffType" :items="autocallKIPayoffTypeEnums" v-model="trade.KIPayoffType" v-on:input="changeFieldState" :key="updateKey.KIPayoffType" />
</div>
<div class="ptitle" v-show="fieldState.SpreadStrike1">敲入行权价</div>
<div class="pitem" v-show="fieldState.SpreadStrike1">
<vue-number-input v-model="trade.SpreadStrike1" v-bind:format="inputFormatStrike" v-on:input="changeSpreadStrike1"></vue-number-input>
</div>
<div class="ptitle" v-show="fieldState.SpreadStrike">封顶/封底行权价</div>
<div class="pitem" v-show="fieldState.SpreadStrike">
<vue-number-input v-model="trade.SpreadStrike" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
</template>
<!--------------区间累计---------------->
<template v-if="fieldState.RangeAcc">
<div class="ptitle">区间下限</div>
<div class="pitem">
<vue-number-input v-model="trade.LowerRange" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">区间上限</div>
<div class="pitem">
<vue-number-input v-model="trade.UpperRange" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">区间收益</div>
<div class="pitem">
<vue-number-input v-model="trade.BonusRate" v-bind:format="inputFormatPercent4"></vue-number-input>
</div>
</template>
<!--------------气囊结构---------------->
<template v-if="fieldState.AirBag">
<div class="ptitle">障碍价格</div>
<div class="pitem">
<vue-number-input v-model="trade.BarrierLow" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
<div class="ptitle">观察方式</div>
<div class="pitem">
<vue-niceselect name="IsDiscreteMonitored" :items="consIsDiscreteMonitored" v-model="trade.IsDiscreteMonitored" />
</div>
<div class="ptitle">敲入参与率</div>
<div class="pitem">
<vue-number-input v-model="trade.KIParticipationRate" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
<div class="ptitle">收益封顶</div>
<div class="pitem">
<vue-niceselect name="HasPayoffLimit" :items="consTrueAsYes" v-model="trade.HasPayoffLimit" />
</div>
<div class="ptitle">收益封顶价格</div>
<div class="pitem">
<vue-number-input v-model="trade.HighStrike" v-bind:format="inputFormatStrike"></vue-number-input>
</div>
</template>
<!--------------收益增强结构---------------->
<template v-if="fieldState.UnEnhance">
<div class="ptitle">年化增强收益</div>
<div class="pitem">
<vue-number-input v-model="trade.AnnualizedEnhanceRate" v-bind:format="inputFormatPercent"></vue-number-input>
</div>
</template>
<!--------------现金流交易-------------->
<template v-if="fieldState.Cashflow">
<div class="ptitle">利率</div>
<div class="pitem-cash">
<vue-number-input name="ProfitRate" v-model="trade.ProfitRate" v-bind:format="inputFormatPremiumRate"></vue-number-input>
</div>
<div class="ptitle">资金类型</div>
<div class="pitem-cash">
<vue-niceselect name="DepositType" :items="consDepositType" v-model="trade.DepositType" v-on:input="changeDepositType" />
</div>
<div class="ptitle">利率类型</div>
<div class="pitem-cash">
<vue-niceselect name="RateType" :items="consRateType" v-model="trade.RateType" />
</div>
<div class="ptitle">计算日历规则</div>
<div class="pitem-cash">
<vue-daycount name="CouponDayCount" v-model="trade.CouponDayCount" />
</div>
<div class="ptitle">预付返还比例</div>
<div class="pitem-cash">
<vue-number-input v-show="trade.DepositType=='0'" name="PrepayRatio" v-model="trade.PrepayRatio" v-bind:format="inputFormatPremiumRate"></vue-number-input>
</div>
</template>
<!--------------观察频率---------------->
<template v-if="fieldState.ObservationDatesShow">
<div class="ptitle screenshot-hide">观察频率</div>
<div class="pitem screenshot-hide">
<div class="yt-input-group" v-if="trade.TradeType!=='二元期权'||(trade.TradeType==='二元期权'&&trade.ExerciseMode === 'American')">
<input type="text" v-model="observationRate" readonly />
<a href="javascript:;" class="yt-input-group-append show" title="设置观察日" v-on:click="setObservationDates('#KI')">
<i class="fa fa-pencil-square"></i>
</a>
</div>
</div>
</template>
</template>
<div class="ptitle screenshot-hide">成交波动率</div>
<div class="pitem screenshot-hide yt-input-group" v-bind:class="viewState.VolState">
<vue-number-input v-model="trade.Vol" v-bind:format="inputFormatVolPercent" v-on:input="changeOpenVolatility"></vue-number-input>
<a href="javascript:;" v-on:click="getTradeOpenVolatility" title="更新波动率" class="yt-input-group-append" tabindex="-1">
<i class="fa fa-refresh"></i>
</a>
<a href="javascript:;" v-on:click="getTradeImpliedVol" title="计算隐含波动率" class="yt-input-group-append s1" tabindex="-1" v-show="trade.TradeType==='香草期权'">
<i class="fa fa-calculator"></i>
</a>
</div>
<div class="pitem-cash screenshot-hide"></div>
<div class="ptitle screenshot-hide">Mid Vol</div>
<div class="pitem screenshot-hide yt-input-group" v-bind:class="viewState.MidVolState">
<vue-number-input v-model="trade.MidVol" v-bind:format="inputFormatVolPercent" v-on:input="changeOpenMidVolatility"></vue-number-input>
<a href="javascript:;" v-on:click="getTradeMidVolatility" title="更新Mid Vol" class="yt-input-group-append" tabindex="-1">
<i class="fa fa-refresh"></i>
</a>
</div>
<div class="pitem-cash screenshot-hide"></div>
<div class="ptitle screenshot-hide">Day1 Pnl</div>
<div class="pitem pitem-cash screenshot-hide">
<input type="number" v-model="trade.Day1Pnl" v-bind:format="inputFormatVolPercent" disabled />
</div>
<!--收益结算-->
<template v-if="!topVue.simpleMode">
<div class="ptitle screenshot-hide">收益结算</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen">
<vue-niceselect name="SettlementType" :items="tradeHelper.SettlementTypes" v-model="trade.SettlementType" v-on:input="synchTrade" />
</div>
<div class="pitem empty topborder screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
</template>
<template v-if="!topVue.simpleMode">
<div class="ptitle screenshot-hide">无风险利率</div>
<div class="pitem pitem-cash yt-input-group screenshot-hide" v-bind:class="viewState.NoRiskRate" v-show="!trade.hideCommen">
<vue-number-input v-model="trade.NoRiskRate" v-on:input="changeNoRiskRate" v-bind:format="inputFormatPercent"></vue-number-input>
<a href="javascript:;" v-on:click="getNoRiskRate" class="yt-input-group-append" tabindex="-1">
<span title="获取系统设定的无风险利率" class="fa fa-refresh"></span>
</a>
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<template v-if="!floating&&fieldState.DividendRate">
<div class="ptitle screenshot-hide">分红率</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen">
<div class="yt-input-group" v-bind:class="viewState.DividendRate" v-if="trade.UnderlyingInstrumentType==='Stock'||trade.UnderlyingInstrumentType==='StockIndex'">
<vue-number-input v-model="trade.DividendRate" v-on:input="changeDividendRate" v-bind:format="inputFormatPercentN"></vue-number-input>
<a href="javascript:;" v-on:click="getDividendRate" class="yt-input-group-append" tabindex="-1">
<span title="获取系统设定的分红率" class="fa fa-refresh"></span>
</a>
</div>
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
</template>
</template>
<div class="ptitle screenshot-hide">TTM(Days)</div>
<div class="pitem yt-input-group screenshot-hide" v-bind:class="viewState.TTMDays" v-show="!trade.hideCommen">
<input type="number" min="0" v-on:change="changeTTM" v-model="trade.TTMDays" />
<a href="javascript:;" v-on:click="getTTM(true)" class="yt-input-group-append s1" tabindex="-1">
<span title="获取交易日期和到期日期之间工作日的长度" class="fa fa-refresh"></span>
</a>
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
<!--------------结构化产品-------------->
<template v-if="floating">
<template v-if="fieldState.Structure">
<div class="ptitle screenshot-hide">结算类型</div>
<div class="pitem screenshot-hide">
<vue-niceselect :items="consCashAsPhysical" v-model="viewState.CashOrPhysical" v-on:input="setcashOrPhysical" />
</div>
<div class="ptitle screenshot-hide">结构要素</div>
<div class="pitem screenshot-hide">
<div class="yt-input-group">
<input type="text" v-model="structure" />
<a href="javascript:;" class="yt-input-group-append show" title="结构要素" v-on:click="setStructureList()" tabindex="-1">
<i class="fa fa-pencil-square"></i>
</a>
</div>
</div>
<div class="ptitle screenshot-hide">观察日</div>
<div class="pitem screenshot-hide">
<div class="yt-input-group">
<input type="text" v-model="observationRate" readonly />
<a href="javascript:;" class="yt-input-group-append show" title="设置观察日" v-on:click="setObservationDates('#ST')" tabindex="-1">
<i class="fa fa-pencil-square"></i>
</a>
</div>
</div>
</template>
</template>
<div class="ptitle screenshot-hide">工作日</div>
<div class="pitem pitem-cash screenshot-hide" v-show="!trade.hideCommen">
<input type="number" v-model="trade.Weekdays" disabled />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="ptitle screenshot-hide">交易日</div>
<div class="pitem pitem-cash screenshot-hide" v-show="!trade.hideCommen">
<input type="number" v-model="trade.TradingDays" disabled />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="ptitle screenshot-hide">公共假日</div>
<div class="pitem pitem-cash screenshot-hide" v-show="!trade.hideCommen">
<input type="number" v-model="trade.PublicHolidays" disabled />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<!--年化系数-->
<template v-if="!topVue.simpleMode">
<template v-if="fieldState.SnowBall||fieldState.AutoCall">
<div class="ptitle screenshot-hide">是否年化</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen">
<vue-niceselect name="IsAnnualized2" :items="consFalseAsNo" v-model="trade.IsAnnualized2" v-on:input="changeIsAnnualized" />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
<div class="ptitle screenshot-hide">年化系数</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen">
<div class="row no-gutters" v-if="trade.IsAnnualized2">
<div class="col">
<vue-number-input name="AnnualizeFactor2" v-model="AnnualizeFactor2.ttmDays" v-bind:format="inputFormatDouble2" v-on:input="changeAnnualizeFactor" class="text-right pr-2"></vue-number-input>
</div>
<div class="col-auto" style="width:1rem">/</div>
<div class="col">
<vue-number-input name="AnnualizeFactor2" v-model="AnnualizeFactor2.daysInYear" v-bind:format="inputFormatDouble2" v-on:input="changeAnnualizeFactor" class="text-left pl-2"></vue-number-input>
</div>
</div>
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
</template>
<template v-else>
<div class="ptitle screenshot-hide">是否年化</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen">
<vue-niceselect name="IsAnnualized" :items="consFalseAsNo" v-model="trade.IsAnnualized" v-on:input="changeIsAnnualized" />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
<div class="ptitle screenshot-hide">年化系数</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen">
<div class="row no-gutters" v-if="trade.IsAnnualized">
<div class="col">
<vue-number-input v-model="AnnualizeFactor.ttmDays" v-bind:format="inputFormatDouble2" v-on:input="changeAnnualizeFactor" class="text-right pr-2"></vue-number-input>
</div>
<div class="col-auto" style="width:1rem">/</div>
<div class="col">
<vue-number-input v-model="AnnualizeFactor.daysInYear" v-bind:format="inputFormatDouble2" v-on:input="changeAnnualizeFactor" class="text-left pl-2"></vue-number-input>
</div>
</div>
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
</template>
</template>
<!--定价模型-->
<template v-if="!topVue.simpleMode">
<div class="ptitle screenshot-hide">定价模型</div>
<div class="pitem screenshot-hide" v-show="!trade.hideCommen">
<vue-niceselect name="EngineName" :items="engineNames" v-model="trade.EngineName" :disabled="!canEditEngineName" />
</div>
<div class="pitem empty screenshot-hide" v-show="trade.hideCommen"></div>
<div class="pitem-cash screenshot-hide"></div>
</template>
<!--单笔计算结果-->
<div class="ptitle screenshot-hide" style="height:130px;background: #fff;border:0;">
<div class="ptitle-calc-result" style="border-bottom:1px solid #ddd;">单笔计算结果</div>
<div style="height: 54px;" v-show="!floating">
<div>
<button type="button" class="btn btn-primary mt-3" onclick="pricingVue.calcPrice()">定价计算</button>
</div>
<div v-show="!topVue.simpleMode">
<button type="button" class="btn btn-primary mt-3" onclick="pricingVue.combine()">自由组合</button>
</div>
</div>
</div>
<div class="pitem pitem-cash border-0 screenshot-hide" style="height:130px">
<table class="table table-bordered table-calc-result-1">
<tbody>
<tr>
<th style="width:50%">PV</th>
<th style="width:50%" title="每报价单位Delta*手数">Delta</th>
</tr>
<tr>
<td style="width:50%">{{calcResult.Pv}}</td>
<td style="width:50%">{{calcResult.Delta}}</td>
</tr>
<tr>
<th title="每报价单位GammaCash*手数">GammaCash</th>
<th title="每报价单位Vega*交易份额">Vega</th>
</tr>
<tr>
<td>{{calcResult.GammaCash}}</td>
<td>{{calcResult.Vega}}</td>
</tr>
<tr>
<th title="每报价单位Theta*交易份额">Theta</th>
<th title="每报价单位Rho*交易份额*100">Rho</th>
</tr>
<tr>
<td>{{calcResult.Theta}}</td>
<td>{{calcResult.Rho}}</td>
</tr>
</tbody>
</table>
</div>
<div class="pitem screenshot-fill"></div>
</div>
</template>
@@ -34,7 +34,7 @@
<form id="marginTemplateV2Form" method="post" onsubmit="return false;"> <form id="marginTemplateV2Form" method="post" onsubmit="return false;">
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col form-layout" style="height: 720px; overflow-y: auto;"> <div class="col form-layout tpl-base" style="height: calc(100vh - 120px); overflow-y: auto;">
<div class="border"> <div class="border">
<p>新模板信息</p> <p>新模板信息</p>
<div class="form-group"> <div class="form-group">
@@ -142,7 +142,7 @@
<div class="form-group"> <div class="form-group">
<label class="formlabel">规则描述</label> <label class="formlabel">规则描述</label>
<textarea class="text-box" rows="3" style="width: 320px; text-align:left;" v-model="marginTemplate.Comments" v-bind:title="marginTemplate.Comments" disabled> <textarea class="text-box" rows="3" style="text-align:left;" v-model="marginTemplate.Comments" v-bind:title="marginTemplate.Comments" disabled>
</textarea> </textarea>
</div> </div>
@@ -158,7 +158,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="formlabel"></label> <label class="formlabel"></label>
<textarea class="text-box" rows="5" style="width: 460px; text-align:left;" v-model="marginTemplate.UnderlyingSeperateComments" v-bind:title="marginTemplate.UnderlyingSeperateComments" disabled> <textarea class="text-box" rows="5" style="text-align:left;" v-model="marginTemplate.UnderlyingSeperateComments" v-bind:title="marginTemplate.UnderlyingSeperateComments" disabled>
</textarea> </textarea>
</div> </div>
@@ -167,15 +167,15 @@
</div> </div>
</div> </div>
<div class="col form-layout" style="height: 720px; overflow-y:auto;"> <div class="col form-layout" style="height: calc(100vh - 120px); overflow-y:auto;">
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)"> <template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="border detail"> <div class="border detail">
<table class="table table-bordered" style="margin-bottom:0;"> <table class="table table-bordered" style="margin-bottom:0;">
<thead> <thead>
<tr> <tr>
<th v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">标的资产类型</th> <th v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">标的资产类型</th>
<th style="width:120px;">期限档位</th> <th style="width:220px;">期限档位</th>
<th style="width:80px;">操作</th> <th style="width:110px;">操作</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -214,7 +214,7 @@
</td> </td>
@*期限档由区块结构固定承载(纯利率债区块固定4档、其他区块为空=全部),只读展示不再下拉选择*@ @*期限档由区块结构固定承载(纯利率债区块固定4档、其他区块为空=全部),只读展示不再下拉选择*@
<td>{{bondTermLabel(row.detail)}}</td> <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> <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> </tr>
</tbody> </tbody>
</table> </table>
@@ -225,10 +225,10 @@
参数组{{blk.no}}<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span> 参数组{{blk.no}}<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span>
@*ETF 子类(仅纯基金区块展示):选 可转债ETF/科创债ETF 分期限档(展开4档),其他子类/不区分不分档*@ @*ETF 子类(仅纯基金区块展示):选 可转债ETF/科创债ETF 分期限档(展开4档),其他子类/不区分不分档*@
<span v-if="blk.isFundBlock"> ETF 子类:<select v-model="blk.sections[0].detail.SpanConfig.EtfKind" v-on:change="onEtfKindChange(blk)"><option value="">不区分</option><option v-for="it in etfSubtypeItems" v-bind:value="it">{{it}}</option></select></span> <span v-if="blk.isFundBlock"> ETF 子类:<select v-model="blk.sections[0].detail.SpanConfig.EtfKind" v-on:change="onEtfKindChange(blk)"><option value="">不区分</option><option v-for="it in etfSubtypeItems" v-bind:value="it">{{it}}</option></select></span>
<span v-if="blk.isTBond || blk.isTieredEtf">(按期限分档:≤5y、(5y-10y]、(10y-30y]、&gt;30y,每个档位独立设置)</span> <span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、&gt;30y,每个档位独立设置)</span>
</p> </p>
<div v-for="sec in blk.sections" v-bind:key="sec.key"> <div v-for="sec in blk.sections" v-bind:key="sec.key">
<p v-if="blk.isTBond || blk.isTieredEtf" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p> <p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
<template v-if="sec.detail && sec.detail.SpanConfig"> <template v-if="sec.detail && sec.detail.SpanConfig">
<p style="margin:4px 0;"> <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.WarnLine" v-bind:format="inputFormatPercent"></vue-number-input>
@@ -507,7 +507,7 @@
</template> </template>
</div> </div>
<div class="col-auto" v-show="index + 1 == marginTemplate.Details.length"> <div class="col-auto" v-show="index + 1 == marginTemplate.Details.length">
<button class="delBtn" v-show="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomStock) || marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType) || marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)" v-on:click="deleteDetail(index)">- 除</button> <button class="delBtn" v-show="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomStock) || marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType) || marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)" v-on:click="deleteDetail(index)">- 除</button>
</div> </div>
</div> </div>
</div> </div>
@@ -34,7 +34,7 @@
<form id="marginTemplateV2Form" method="post" onsubmit="return false;"> <form id="marginTemplateV2Form" method="post" onsubmit="return false;">
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col form-layout" style="height: 720px; overflow-y: auto;"> <div class="col form-layout tpl-base" style="height: calc(100vh - 120px); overflow-y: auto;">
<div class="border"> <div class="border">
<P>新模板信息</P> <P>新模板信息</P>
<div class="form-group"> <div class="form-group">
@@ -149,7 +149,7 @@
<div class="form-group"> <div class="form-group">
<label class="formlabel">规则描述</label> <label class="formlabel">规则描述</label>
<textarea class="text-box" rows="3" style="width: 460px; text-align:left;" v-model="marginTemplate.Comments" v-bind:title="marginTemplate.Comments" disabled> <textarea class="text-box" rows="3" style="text-align:left;" v-model="marginTemplate.Comments" v-bind:title="marginTemplate.Comments" disabled>
</textarea> </textarea>
</div> </div>
@@ -163,20 +163,20 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="formlabel"></label> <label class="formlabel"></label>
<textarea class="text-box" rows="5" style="width: 460px; text-align:left;" v-model="marginTemplate.UnderlyingSeperateComments" v-bind:title="marginTemplate.UnderlyingSeperateComments" disabled> <textarea class="text-box" rows="5" style="text-align:left;" v-model="marginTemplate.UnderlyingSeperateComments" v-bind:title="marginTemplate.UnderlyingSeperateComments" disabled>
</textarea> </textarea>
</div> </div>
</div> </div>
</div> </div>
<div class="col form-layout" style="height: 720px; overflow-y:auto;"> <div class="col form-layout" style="height: calc(100vh - 120px); overflow-y:auto;">
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)"> <template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)">
<div class="border detail"> <div class="border detail">
<table class="table table-bordered" style="margin-bottom:0;"> <table class="table table-bordered" style="margin-bottom:0;">
<thead> <thead>
<tr> <tr>
<th v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">标的资产类型</th> <th v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">标的资产类型</th>
<th style="width:120px;">期限档位</th> <th style="width:220px;">期限档位</th>
<th style="width:80px;">操作</th> <th style="width:110px;">操作</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -215,7 +215,7 @@
</td> </td>
@*期限档由区块结构固定承载(纯利率债区块固定4档、其他区块为空=全部),只读展示不再下拉选择*@ @*期限档由区块结构固定承载(纯利率债区块固定4档、其他区块为空=全部),只读展示不再下拉选择*@
<td>{{bondTermLabel(row.detail)}}</td> <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> <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> </tr>
</tbody> </tbody>
</table> </table>
@@ -226,10 +226,10 @@
参数组{{blk.no}}<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span> 参数组{{blk.no}}<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span>
@*ETF 子类(仅纯基金区块展示):选 可转债ETF/科创债ETF 分期限档(展开4档),其他子类/不区分不分档*@ @*ETF 子类(仅纯基金区块展示):选 可转债ETF/科创债ETF 分期限档(展开4档),其他子类/不区分不分档*@
<span v-if="blk.isFundBlock"> ETF 子类:<select v-model="blk.sections[0].detail.SpanConfig.EtfKind" v-on:change="onEtfKindChange(blk)"><option value="">不区分</option><option v-for="it in etfSubtypeItems" v-bind:value="it">{{it}}</option></select></span> <span v-if="blk.isFundBlock"> ETF 子类:<select v-model="blk.sections[0].detail.SpanConfig.EtfKind" v-on:change="onEtfKindChange(blk)"><option value="">不区分</option><option v-for="it in etfSubtypeItems" v-bind:value="it">{{it}}</option></select></span>
<span v-if="blk.isTBond || blk.isTieredEtf">(按期限分档:≤5y、(5y-10y]、(10y-30y]、&gt;30y,每个档位独立设置)</span> <span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、&gt;30y,每个档位独立设置)</span>
</p> </p>
<div v-for="sec in blk.sections" v-bind:key="sec.key"> <div v-for="sec in blk.sections" v-bind:key="sec.key">
<p v-if="blk.isTBond || blk.isTieredEtf" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p> <p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
<template v-if="sec.detail && sec.detail.SpanConfig"> <template v-if="sec.detail && sec.detail.SpanConfig">
<p style="margin:4px 0;"> <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.WarnLine" v-bind:format="inputFormatPercent"></vue-number-input>
@@ -487,7 +487,7 @@
</template> </template>
</div> </div>
<div class="col-auto" v-show="index + 1 == marginTemplate.Details.length"> <div class="col-auto" v-show="index + 1 == marginTemplate.Details.length">
<button class="delBtn" v-show="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomStock) || marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType) || marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)" v-on:click="deleteDetail(index)">- 除</button> <button class="delBtn" v-show="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomStock) || marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType) || marginTemplate.RuleType == @((int)MarginRuleTypeEnum.区间追保结构)" v-on:click="deleteDetail(index)">- 除</button>
</div> </div>
</div> </div>
</div> </div>
@@ -32,7 +32,7 @@
<form id="marginTemplateV2Form" method="post" onsubmit="return false;"> <form id="marginTemplateV2Form" method="post" onsubmit="return false;">
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col form-layout" style="height: calc(100vh - 120px); overflow-y: auto;"> <div class="col form-layout tpl-base" style="height: calc(100vh - 120px); overflow-y: auto;">
<div class="border"> <div class="border">
<P>新模板信息</P> <P>新模板信息</P>
<div class="form-group"> <div class="form-group">
@@ -115,7 +115,7 @@
<div class="form-group"> <div class="form-group">
<label class="formlabel">规则描述</label> <label class="formlabel">规则描述</label>
<textarea class="text-box" rows="3" style="width: 320px; text-align:left;" v-model="marginTemplate.Comments" v-bind:title="marginTemplate.Comments" disabled> <textarea class="text-box" rows="3" style="text-align:left;" v-model="marginTemplate.Comments" v-bind:title="marginTemplate.Comments" disabled>
</textarea> </textarea>
</div> </div>
@@ -128,7 +128,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="formlabel"></label> <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 class="text-box" rows="5" style="text-align:left;" v-model="marginTemplate.UnderlyingSeperateComments" v-bind:title="marginTemplate.UnderlyingSeperateComments" disabled>
</textarea> </textarea>
</div> </div>
</div> </div>
@@ -140,8 +140,8 @@
<thead> <thead>
<tr> <tr>
<th v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">标的资产类型</th> <th v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">标的资产类型</th>
<th style="width:120px;">期限档位</th> <th style="width:220px;">期限档位</th>
<th style="width:80px;">操作</th> <th style="width:110px;">操作</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -180,7 +180,7 @@
</td> </td>
@*期限档由区块结构固定承载(纯利率债区块固定4档、其他区块为空=全部),只读展示不再下拉选择*@ @*期限档由区块结构固定承载(纯利率债区块固定4档、其他区块为空=全部),只读展示不再下拉选择*@
<td>{{bondTermLabel(row.detail)}}</td> <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> <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> </tr>
</tbody> </tbody>
</table> </table>
@@ -191,10 +191,10 @@
参数组{{blk.no}}<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span> 参数组{{blk.no}}<span v-if="marginTemplate.UnderlyingSeperateType == @((int)UnderlyingSeperateTypeEnum.CustomInstrumentType)">资产类型:{{underlyingTypeNames(blk.ut)}}</span>
@*ETF 子类(仅纯基金区块展示):选 可转债ETF/科创债ETF 分期限档(展开4档),其他子类/不区分不分档*@ @*ETF 子类(仅纯基金区块展示):选 可转债ETF/科创债ETF 分期限档(展开4档),其他子类/不区分不分档*@
<span v-if="blk.isFundBlock"> ETF 子类:<select v-model="blk.sections[0].detail.SpanConfig.EtfKind" v-on:change="onEtfKindChange(blk)"><option value="">不区分</option><option v-for="it in etfSubtypeItems" v-bind:value="it">{{it}}</option></select></span> <span v-if="blk.isFundBlock"> ETF 子类:<select v-model="blk.sections[0].detail.SpanConfig.EtfKind" v-on:change="onEtfKindChange(blk)"><option value="">不区分</option><option v-for="it in etfSubtypeItems" v-bind:value="it">{{it}}</option></select></span>
<span v-if="blk.isTBond || blk.isTieredEtf">(按期限分档:≤5y、(5y-10y]、(10y-30y]、&gt;30y,每个档位独立设置)</span> <span v-if="blk.isTBond">(按期限分档:≤5y、(5y-10y]、(10y-30y]、&gt;30y,每个档位独立设置)</span>
</p> </p>
<div v-for="sec in blk.sections" v-bind:key="sec.key"> <div v-for="sec in blk.sections" v-bind:key="sec.key">
<p v-if="blk.isTBond || blk.isTieredEtf" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p> <p v-if="blk.isTBond" style="margin:8px 0 2px; font-weight:bold;">期限档位:{{sec.termLabel}}</p>
<template v-if="sec.detail && sec.detail.SpanConfig"> <template v-if="sec.detail && sec.detail.SpanConfig">
<p style="margin:4px 0;"> <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.WarnLine" v-bind:format="inputFormatPercent"></vue-number-input>
+1 -8
View File
@@ -93,14 +93,7 @@
<script src="~/Scripts/app/tradeHelper.js?v=@HtmlUtil.JsVersion"></script> <script src="~/Scripts/app/tradeHelper.js?v=@HtmlUtil.JsVersion"></script>
<script src="~/Scripts/app/trade/percentColumnText.js?v=@HtmlUtil.JsVersion"></script> <script src="~/Scripts/app/trade/percentColumnText.js?v=@HtmlUtil.JsVersion"></script>
@if (PS.Config.Is润和) <script src="~/Scripts/app/pricing/tradePricing.js?v=@HtmlUtil.JsVersion"></script>
{
<script src="~/Scripts/app/pricing/tradePricing_dz.js?v=@HtmlUtil.JsVersion"></script>
}
else
{
<script src="~/Scripts/app/pricing/tradePricing.js?v=@HtmlUtil.JsVersion"></script>
}
<script src="~/Scripts/app/trade/tradeEditV2.js?v=@HtmlUtil.JsVersion"></script> <script src="~/Scripts/app/trade/tradeEditV2.js?v=@HtmlUtil.JsVersion"></script>
<script src="~/Scripts/app/trade/settag.js?v=@HtmlUtil.JsVersion" type="text/javascript"></script> <script src="~/Scripts/app/trade/settag.js?v=@HtmlUtil.JsVersion" type="text/javascript"></script>
@@ -1,9 +0,0 @@
$(function () {
var autoClientCtrl = FastVue.autocomplete(document.getElementById('ClientId'), {
lookup: ylotc.clients, nameField: 'Name', valueField: 'id', searchField: ['Name', 'Code', 'PinYin'],
onSelect: function (data) {
console.log(data);
console.log('#ClientId Value:' + $('#ClientId').val());
}
});
});
@@ -47,10 +47,6 @@ const TermTierEnabledUnderlyingMask = 16;
//基金及基金专户标志位(ETF 子类行的 UnderlyingType,不加新枚举位) //基金及基金专户标志位(ETF 子类行的 UnderlyingType,不加新枚举位)
const FundTypeMask = 32768; const FundTypeMask = 32768;
//允许配置期限档的 ETF 子类(SpanConfig.EtfKind 值,取值=数据字典"ETF 子类"),
//与后端 ConsMarginTerm.TermTierEnabledEtfKinds 保持一致;其余子类与"不区分"的基金行不分档
const TermTierEnabledEtfKinds = ['可转债 ETF', '科创债 ETF'];
//利率债区块固定展开的4个期限档([BondTerm值, 展示文案]),与 docx 原型 4.3.1 一致 //利率债区块固定展开的4个期限档([BondTerm值, 展示文案]),与 docx 原型 4.3.1 一致
const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']]; const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']];
@@ -74,6 +70,10 @@ const vue = new Vue({
etfSubtypeItems: page.etfSubtypeItems || [] etfSubtypeItems: page.etfSubtypeItems || []
}, },
created: function () { created: function () {
//留档进入页面时的生效状态:v-model 改写的就是 page.marginTemplate 同一对象(data 浅拷贝的是引用),
//原始值须在用户交互前记下,供保存时判断"生效中规则被改为否"弹失效确认;
//select 的 v-model 取值是字符串,统一 String 化比较
this.originalIsValid = String(this.marginTemplate.IsValid);
//区间追保结构:加载时按 资产类型+期限 排序一次,再重建资产类型区块(利率债区块补齐固定4档), //区间追保结构:加载时按 资产类型+期限 排序一次,再重建资产类型区块(利率债区块补齐固定4档),
//编辑过程中不再重排,避免 chosen/选择器失序 //编辑过程中不再重排,避免 chosen/选择器失序
if (this.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构 && this.marginTemplate.Details) { if (this.marginTemplate.RuleType == MarginRuleTypeEnum.区间追保结构 && this.marginTemplate.Details) {
@@ -118,8 +118,8 @@ const vue = new Vue({
} }
return rows; return rows;
}, },
//区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)与分档 ETF 子类 //区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)区块固定展开4个期限档段(每段一条 detail),
//ut=基金 + EtfKind∈可转债/科创债 ETF)区块固定展开4个期限档段(每段一条 detail),其他区块单段(BondTerm 为空)。 //其他区块(含全部 ETF 子类,2026-08-27 裁定 ETF 无期限概念不再分档)单段(BondTerm 为空)。
//段的 detail 缺失时模板侧 v-if 兜底不渲染该段 //段的 detail 缺失时模板侧 v-if 兜底不渲染该段
ruleRangeBlocks() { ruleRangeBlocks() {
if (this.marginTemplate.RuleType != MarginRuleTypeEnum.区间追保结构) return []; if (this.marginTemplate.RuleType != MarginRuleTypeEnum.区间追保结构) return [];
@@ -132,10 +132,8 @@ const vue = new Vue({
var ut = head.detail.UnderlyingType || 0; var ut = head.detail.UnderlyingType || 0;
var kind = (head.detail.SpanConfig && head.detail.SpanConfig.EtfKind) || ''; var kind = (head.detail.SpanConfig && head.detail.SpanConfig.EtfKind) || '';
var isTBond = ut === 16; var isTBond = ut === 16;
var isTieredEtf = ut === FundTypeMask && TermTierEnabledEtfKinds.indexOf(kind) >= 0;
var isTiered = isTBond || isTieredEtf;
var sections = []; var sections = [];
if (isTiered) { if (isTBond) {
for (var t = 0; t < SpanBondTerms.length; t++) { for (var t = 0; t < SpanBondTerms.length; t++) {
var found = null; var found = null;
for (var j = head.index; j < head.index + head.rowspan; j++) { for (var j = head.index; j < head.index + head.rowspan; j++) {
@@ -147,7 +145,7 @@ const vue = new Vue({
} else { } else {
sections.push({ key: 'single', termLabel: '', detail: head.detail }); 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, etfKind: kind, isTBond: isTBond, isTieredEtf: isTieredEtf, isFundBlock: ut === FundTypeMask, sections: sections }); blocks.push({ key: head.detail._bk || ('i' + head.index), no: blocks.length + 1, headIndex: head.index, ut: ut, etfKind: kind, isTBond: isTBond, isFundBlock: ut === FundTypeMask, sections: sections });
} }
return blocks; return blocks;
} }
@@ -268,6 +266,18 @@ const vue = new Vue({
}) })
}, },
saveMarginTemplateV2(isForClient) { saveMarginTemplateV2(isForClient) {
//失效确认:编辑中把"生效中"规则改为否时先弹窗提示降级影响,确认后才真正提交;
//新增规则尚未生效、生效状态未翻转,不弹。自定义规则编辑页无"是否生效"开关,不经过此逻辑
if (!this.isAdd && this.originalIsValid === 'true' && String(this.marginTemplate.IsValid) === 'false') {
var that = this;
main.confirm("该规则失效后,适用范围内的持仓将降级匹配下一优先级规则(客户→全局),确认继续?", function () {
that.doSaveMarginTemplateV2(isForClient);
});
return;
}
this.doSaveMarginTemplateV2(isForClient);
},
doSaveMarginTemplateV2(isForClient) {
if (!this.marginTemplate.Name) { if (!this.marginTemplate.Name) {
main.message("模板名称不能为空"); main.message("模板名称不能为空");
return; return;
@@ -434,64 +444,35 @@ const vue = new Vue({
d.SpanConfig.EtfKind = etfKind || null; d.SpanConfig.EtfKind = etfKind || null;
return d; return d;
}, },
//ETF 子类选择器变更(仅纯基金区块渲染):选可转债/科创债 ETF → 区块展开固定4档(缺档补行、异常档行删除); //ETF 子类选择器变更(仅纯基金区块渲染):子类仅用于行区分(取数侧子类行优先),
//选其他子类/不区分 → 收敛为单行(BondTerm 置空,多余档位行删除,首行承载 EtfKind //ETF 无期限概念不分档——任何子类切换后区块始终收敛为单行(BondTerm 置空,多余档位行删除,首行承载 EtfKind
//存量 4 档子类数据在这里被收敛时仅保留首行录入内容)
onEtfKindChange(blk) { onEtfKindChange(blk) {
var head = blk.sections[0].detail; var head = blk.sections[0].detail;
var kind = (head.SpanConfig && head.SpanConfig.EtfKind) || ''; var kind = (head.SpanConfig && head.SpanConfig.EtfKind) || '';
var tiered = TermTierEnabledEtfKinds.indexOf(kind) >= 0;
var details = this.marginTemplate.Details; var details = this.marginTemplate.Details;
var bk = head._bk; var bk = head._bk;
var rows = details.filter(function (d) { return d._bk === bk; }); var rows = details.filter(function (d) { return d._bk === bk; });
if (tiered) { rows.forEach(function (r) {
var byTerm = {}; if (r !== head) {
rows.forEach(function (r) {
var bt = (r.SpanConfig && r.SpanConfig.BondTerm) || '';
if (byTerm[bt] === undefined) byTerm[bt] = r;
});
var that = this;
var merged = [];
//首行未被4个合法档位命中时(单段行 BondTerm 为空)复用为首个档位行,保留已录入内容;
//否则首行会以"EtfKind 已选、BondTerm 为空"的残留行留在明细里,保存时被服务端"分期限档类型必须配置期限档"拦截
var headMatched = SpanBondTerms.some(function (t) { return byTerm[t[0]] === head; });
SpanBondTerms.forEach(function (t, ti) {
var r = byTerm[t[0]] || (ti === 0 && !headMatched ? head : null);
if (!r) r = that.newSpanDetail(FundTypeMask, t[0], kind);
r.UnderlyingType = FundTypeMask;
r.SpanConfig.EtfKind = kind;
r.SpanConfig.BondTerm = t[0];
Vue.set(r, '_bk', bk);
merged.push(r);
var idx = details.indexOf(r); var idx = details.indexOf(r);
if (idx >= 0 && r !== head) details.splice(idx, 1); if (idx >= 0) details.splice(idx, 1);
}); }
//异常档/重复行全部移除后按档序回插到区块首行位置 });
var headIdx = details.indexOf(head); head.UnderlyingType = FundTypeMask;
if (headIdx < 0) headIdx = details.length; head.SpanConfig.BondTerm = '';
details.splice.apply(details, [headIdx + 1, 0].concat(merged.filter(function (r) { return r !== head; }))); head.SpanConfig.EtfKind = kind || null;
} else {
rows.forEach(function (r) {
if (r !== head) {
var idx = details.indexOf(r);
if (idx >= 0) details.splice(idx, 1);
}
});
head.UnderlyingType = FundTypeMask;
head.SpanConfig.BondTerm = '';
head.SpanConfig.EtfKind = kind || null;
}
this.refreshChosen(); this.refreshChosen();
}, },
//分档行判定与分档键:纯利率债 → 'tbond';基金 + 分档 ETF 子类 → 'etf:子类'(同一子类的连续行归一个区块) //分档行判定与分档键:纯利率债 → 'tbond'(连续行归一个区块按固定4档补齐);ETF 子类不分档,基金行一律独立成块
spanTierKey(d) { spanTierKey(d) {
var ut = d.UnderlyingType || 0; var ut = d.UnderlyingType || 0;
var kind = (d.SpanConfig && d.SpanConfig.EtfKind) || '';
if (ut === TermTierEnabledUnderlyingMask) return 'tbond'; if (ut === TermTierEnabledUnderlyingMask) return 'tbond';
if (ut === FundTypeMask && TermTierEnabledEtfKinds.indexOf(kind) >= 0) return 'etf:' + kind;
return null; return null;
}, },
//存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行、基金+分档ETF子类行各自归为区块并按固定4档补齐(缺档补空行); //存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行归为区块并按固定4档补齐(缺档补空行);
//同档重复/异常档的分档行、非分档行(含基金+不分档子类、基金通配行)均各自独立成块展示,不丢数据 //同档重复/异常档的分档行、其余全部行(含基金+子类、基金通配行)均各自独立成块展示,不丢数据——
//存量 ETF 子类 4 档行会以多个单行区块出现,保存时由服务端"ETF 子类不分期限档"校验拦截提示清理
rebuildSpanBlocks(details) { rebuildSpanBlocks(details) {
var termValues = SpanBondTerms.map(function (t) { return t[0]; }); var termValues = SpanBondTerms.map(function (t) { return t[0]; });
var result = []; var result = [];
@@ -513,13 +494,10 @@ const vue = new Vue({
if (termValues.indexOf(bt) >= 0 && !byTerm[bt]) byTerm[bt] = r; else anomalies.push(r); if (termValues.indexOf(bt) >= 0 && !byTerm[bt]) byTerm[bt] = r; else anomalies.push(r);
}); });
var bk = 'bk_' + (++spanBlockSeq); var bk = 'bk_' + (++spanBlockSeq);
var utv = tk === 'tbond' ? TermTierEnabledUnderlyingMask : FundTypeMask;
var kind = tk === 'tbond' ? '' : tk.substring(4);
termValues.forEach(function (t) { termValues.forEach(function (t) {
var r = byTerm[t]; var r = byTerm[t];
if (!r) r = that.newSpanDetail(utv, t, kind); if (!r) r = that.newSpanDetail(TermTierEnabledUnderlyingMask, t);
r.UnderlyingType = utv; r.UnderlyingType = TermTierEnabledUnderlyingMask;
if (kind) r.SpanConfig.EtfKind = kind;
r.SpanConfig.BondTerm = t; r.SpanConfig.BondTerm = t;
Vue.set(r, '_bk', bk); Vue.set(r, '_bk', bk);
result.push(r); result.push(r);
@@ -551,15 +529,19 @@ const vue = new Vue({
return map[v] !== undefined ? map[v] : v; return map[v] !== undefined ? map[v] : v;
}, },
//规则15录入区块文案:按 detail 资产类型切换计价口径。 //规则15录入区块文案:按 detail 资产类型切换计价口径。
//债券类(利率债16/信用债32/其它债券64)及其他类型(默认债券口径):期初净价/当前净价,金额=×期初全价×券面总额; //比较价格口径与计算侧对齐(MarginCalculationBase.CalcSwapSpanMaintenanceMarginIsBond→中债估值净价,其余→收盘价):
//基金及基金专户(32768)/债券指数(536870912):参考标的期初净价/参考标的当前收盘价(空头各层统一收盘价,BUG-11 修正), //纯债券类(利率债16/信用债32/其它债券64,可组合):期初净价/当前净价,金额=×期初全价×券面总额;
//其余全部类型(基金/债券指数/股票/股指等):参考标的期初净价/参考标的当前收盘价(多空各层统一收盘价),
//金额=×参考标的期初价格×参考标的名义份额。 //金额=×参考标的期初价格×参考标的名义份额。
spanText(detail) { spanText(detail) {
var ut = (detail && detail.UnderlyingType) || 0; var ut = (detail && detail.UnderlyingType) || 0;
if ((ut & 32768) > 0 || (ut & 536870912) > 0) { //非空且标志位全部落在债券三类内才按债券口径;混合行(债券|非债券)按收盘价口径显示,与取数侧标的实际类型判定方向一致
var bondMask = 16 | 32 | 64;
var isBond = ut !== 0 && (ut & ~bondMask) === 0;
if (!isBond) {
return { return {
priceInit: '参考标的期初净价', priceInit: '参考标的期初净价',
priceCur: '参考标的当前价', priceCur: '参考标的当前收盘价',
priceCurShort1: '参考标的当前收盘价', priceCurShort1: '参考标的当前收盘价',
priceCurShort: '参考标的当前收盘价', priceCurShort: '参考标的当前收盘价',
amountBase: '参考标的期初价格 × 参考标的名义份额' amountBase: '参考标的期初价格 × 参考标的名义份额'
@@ -649,5 +631,23 @@ $(function () {
if (!isNaN(idx)) vue.onUnderlyingTypeChange(idx); if (!isNaN(idx)) vue.onUnderlyingTypeChange(idx);
}); });
//多选 chosen:已有选中项后隐藏搜索框——空框没有输入内容却占一行,放不下时还会换行留白
//(本页多选均为点选场景,无需键盘过滤);全部取消后恢复,显示"请选择…"占位。
//勾选变化会触发原 select 的 jQuery changetrigger_form_field_change),
//加载重建/增删区块后的 refreshChosen 会触发 chosen:updated,两处都走这里的委托统一刷新
function refreshChosenSearchField(select) {
var $sel = $(select);
var $container = $sel.next('.chosen-container-multi');
if ($container.length === 0) return;
var noneSelected = $sel.find('option:selected').length === 0;
$container.find('li.search-field').toggle(noneSelected);
}
$(document).on('change chosen:updated', 'select.chosen-select[multiple]', function () {
refreshChosenSearchField(this);
});
SetAceDropDown(); SetAceDropDown();
$('select.chosen-select[multiple]').each(function () {
refreshChosenSearchField(this);
});
}); });
@@ -35,10 +35,6 @@ const TermTierEnabledUnderlyingMask = 16;
//基金及基金专户标志位(ETF 子类行的 UnderlyingType,不加新枚举位) //基金及基金专户标志位(ETF 子类行的 UnderlyingType,不加新枚举位)
const FundTypeMask = 32768; const FundTypeMask = 32768;
//允许配置期限档的 ETF 子类(SpanConfig.EtfKind 值,取值=数据字典"ETF 子类"),
//与后端 ConsMarginTerm.TermTierEnabledEtfKinds 保持一致;其余子类与"不区分"的基金行不分档
const TermTierEnabledEtfKinds = ['可转债 ETF', '科创债 ETF'];
//利率债区块固定展开的4个期限档([BondTerm值, 展示文案]),与 docx 原型 4.3.1 一致 //利率债区块固定展开的4个期限档([BondTerm值, 展示文案]),与 docx 原型 4.3.1 一致
const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']]; const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']];
@@ -106,8 +102,8 @@ const vue = new Vue({
} }
return rows; return rows;
}, },
//区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)与分档 ETF 子类 //区间追保结构录入区块:每个资产类型区块一个;纯利率债(ut=16)区块固定展开4个期限档段(每段一条 detail),
//ut=基金 + EtfKind∈可转债/科创债 ETF)区块固定展开4个期限档段(每段一条 detail),其他区块单段(BondTerm 为空)。 //其他区块(含全部 ETF 子类,2026-08-27 裁定 ETF 无期限概念不再分档)单段(BondTerm 为空)。
//段的 detail 缺失时模板侧 v-if 兜底不渲染该段 //段的 detail 缺失时模板侧 v-if 兜底不渲染该段
ruleRangeBlocks() { ruleRangeBlocks() {
if (this.marginTemplate.RuleType != MarginRuleTypeEnum.区间追保结构) return []; if (this.marginTemplate.RuleType != MarginRuleTypeEnum.区间追保结构) return [];
@@ -120,10 +116,8 @@ const vue = new Vue({
var ut = head.detail.UnderlyingType || 0; var ut = head.detail.UnderlyingType || 0;
var kind = (head.detail.SpanConfig && head.detail.SpanConfig.EtfKind) || ''; var kind = (head.detail.SpanConfig && head.detail.SpanConfig.EtfKind) || '';
var isTBond = ut === 16; var isTBond = ut === 16;
var isTieredEtf = ut === FundTypeMask && TermTierEnabledEtfKinds.indexOf(kind) >= 0;
var isTiered = isTBond || isTieredEtf;
var sections = []; var sections = [];
if (isTiered) { if (isTBond) {
for (var t = 0; t < SpanBondTerms.length; t++) { for (var t = 0; t < SpanBondTerms.length; t++) {
var found = null; var found = null;
for (var j = head.index; j < head.index + head.rowspan; j++) { for (var j = head.index; j < head.index + head.rowspan; j++) {
@@ -135,7 +129,7 @@ const vue = new Vue({
} else { } else {
sections.push({ key: 'single', termLabel: '', detail: head.detail }); 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, etfKind: kind, isTBond: isTBond, isTieredEtf: isTieredEtf, isFundBlock: ut === FundTypeMask, sections: sections }); blocks.push({ key: head.detail._bk || ('i' + head.index), no: blocks.length + 1, headIndex: head.index, ut: ut, etfKind: kind, isTBond: isTBond, isFundBlock: ut === FundTypeMask, sections: sections });
} }
return blocks; return blocks;
} }
@@ -432,64 +426,35 @@ const vue = new Vue({
d.SpanConfig.EtfKind = etfKind || null; d.SpanConfig.EtfKind = etfKind || null;
return d; return d;
}, },
//ETF 子类选择器变更(仅纯基金区块渲染):选可转债/科创债 ETF → 区块展开固定4档(缺档补行、异常档行删除); //ETF 子类选择器变更(仅纯基金区块渲染):子类仅用于行区分(取数侧子类行优先),
//选其他子类/不区分 → 收敛为单行(BondTerm 置空,多余档位行删除,首行承载 EtfKind //ETF 无期限概念不分档——任何子类切换后区块始终收敛为单行(BondTerm 置空,多余档位行删除,首行承载 EtfKind
//存量 4 档子类数据在这里被收敛时仅保留首行录入内容)
onEtfKindChange(blk) { onEtfKindChange(blk) {
var head = blk.sections[0].detail; var head = blk.sections[0].detail;
var kind = (head.SpanConfig && head.SpanConfig.EtfKind) || ''; var kind = (head.SpanConfig && head.SpanConfig.EtfKind) || '';
var tiered = TermTierEnabledEtfKinds.indexOf(kind) >= 0;
var details = this.marginTemplate.Details; var details = this.marginTemplate.Details;
var bk = head._bk; var bk = head._bk;
var rows = details.filter(function (d) { return d._bk === bk; }); var rows = details.filter(function (d) { return d._bk === bk; });
if (tiered) { rows.forEach(function (r) {
var byTerm = {}; if (r !== head) {
rows.forEach(function (r) {
var bt = (r.SpanConfig && r.SpanConfig.BondTerm) || '';
if (byTerm[bt] === undefined) byTerm[bt] = r;
});
var that = this;
var merged = [];
//首行未被4个合法档位命中时(单段行 BondTerm 为空)复用为首个档位行,保留已录入内容;
//否则首行会以"EtfKind 已选、BondTerm 为空"的残留行留在明细里,保存时被服务端"分期限档类型必须配置期限档"拦截
var headMatched = SpanBondTerms.some(function (t) { return byTerm[t[0]] === head; });
SpanBondTerms.forEach(function (t, ti) {
var r = byTerm[t[0]] || (ti === 0 && !headMatched ? head : null);
if (!r) r = that.newSpanDetail(FundTypeMask, t[0], kind);
r.UnderlyingType = FundTypeMask;
r.SpanConfig.EtfKind = kind;
r.SpanConfig.BondTerm = t[0];
Vue.set(r, '_bk', bk);
merged.push(r);
var idx = details.indexOf(r); var idx = details.indexOf(r);
if (idx >= 0 && r !== head) details.splice(idx, 1); if (idx >= 0) details.splice(idx, 1);
}); }
//异常档/重复行全部移除后按档序回插到区块首行位置 });
var headIdx = details.indexOf(head); head.UnderlyingType = FundTypeMask;
if (headIdx < 0) headIdx = details.length; head.SpanConfig.BondTerm = '';
details.splice.apply(details, [headIdx + 1, 0].concat(merged.filter(function (r) { return r !== head; }))); head.SpanConfig.EtfKind = kind || null;
} else {
rows.forEach(function (r) {
if (r !== head) {
var idx = details.indexOf(r);
if (idx >= 0) details.splice(idx, 1);
}
});
head.UnderlyingType = FundTypeMask;
head.SpanConfig.BondTerm = '';
head.SpanConfig.EtfKind = kind || null;
}
this.refreshChosen(); this.refreshChosen();
}, },
//分档行判定与分档键:纯利率债 → 'tbond';基金 + 分档 ETF 子类 → 'etf:子类'(同一子类的连续行归一个区块) //分档行判定与分档键:纯利率债 → 'tbond'(连续行归一个区块按固定4档补齐);ETF 子类不分档,基金行一律独立成块
spanTierKey(d) { spanTierKey(d) {
var ut = d.UnderlyingType || 0; var ut = d.UnderlyingType || 0;
var kind = (d.SpanConfig && d.SpanConfig.EtfKind) || '';
if (ut === TermTierEnabledUnderlyingMask) return 'tbond'; if (ut === TermTierEnabledUnderlyingMask) return 'tbond';
if (ut === FundTypeMask && TermTierEnabledEtfKinds.indexOf(kind) >= 0) return 'etf:' + kind;
return null; return null;
}, },
//存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行、基金+分档ETF子类行各自归为区块并按固定4档补齐(缺档补空行); //存量回显重建资产类型区块(分配区块键 _bk):连续的纯利率债行归为区块并按固定4档补齐(缺档补空行);
//同档重复/异常档的分档行、非分档行(含基金+不分档子类、基金通配行)均各自独立成块展示,不丢数据 //同档重复/异常档的分档行、其余全部行(含基金+子类、基金通配行)均各自独立成块展示,不丢数据——
//存量 ETF 子类 4 档行会以多个单行区块出现,保存时由服务端"ETF 子类不分期限档"校验拦截提示清理
rebuildSpanBlocks(details) { rebuildSpanBlocks(details) {
var termValues = SpanBondTerms.map(function (t) { return t[0]; }); var termValues = SpanBondTerms.map(function (t) { return t[0]; });
var result = []; var result = [];
@@ -511,13 +476,10 @@ const vue = new Vue({
if (termValues.indexOf(bt) >= 0 && !byTerm[bt]) byTerm[bt] = r; else anomalies.push(r); if (termValues.indexOf(bt) >= 0 && !byTerm[bt]) byTerm[bt] = r; else anomalies.push(r);
}); });
var bk = 'bk_' + (++spanBlockSeq); var bk = 'bk_' + (++spanBlockSeq);
var utv = tk === 'tbond' ? TermTierEnabledUnderlyingMask : FundTypeMask;
var kind = tk === 'tbond' ? '' : tk.substring(4);
termValues.forEach(function (t) { termValues.forEach(function (t) {
var r = byTerm[t]; var r = byTerm[t];
if (!r) r = that.newSpanDetail(utv, t, kind); if (!r) r = that.newSpanDetail(TermTierEnabledUnderlyingMask, t);
r.UnderlyingType = utv; r.UnderlyingType = TermTierEnabledUnderlyingMask;
if (kind) r.SpanConfig.EtfKind = kind;
r.SpanConfig.BondTerm = t; r.SpanConfig.BondTerm = t;
Vue.set(r, '_bk', bk); Vue.set(r, '_bk', bk);
result.push(r); result.push(r);
@@ -549,15 +511,19 @@ const vue = new Vue({
return map[v] !== undefined ? map[v] : v; return map[v] !== undefined ? map[v] : v;
}, },
//规则15录入区块文案:按 detail 资产类型切换计价口径。 //规则15录入区块文案:按 detail 资产类型切换计价口径。
//债券类(利率债16/信用债32/其它债券64)及其他类型(默认债券口径):期初净价/当前净价,金额=×期初全价×券面总额; //比较价格口径与计算侧对齐(MarginCalculationBase.CalcSwapSpanMaintenanceMarginIsBond→中债估值净价,其余→收盘价):
//基金及基金专户(32768)/债券指数(536870912):参考标的期初净价/参考标的当前收盘价(空头各层统一收盘价,BUG-11 修正), //纯债券类(利率债16/信用债32/其它债券64,可组合):期初净价/当前净价,金额=×期初全价×券面总额;
//其余全部类型(基金/债券指数/股票/股指等):参考标的期初净价/参考标的当前收盘价(多空各层统一收盘价),
//金额=×参考标的期初价格×参考标的名义份额。 //金额=×参考标的期初价格×参考标的名义份额。
spanText(detail) { spanText(detail) {
var ut = (detail && detail.UnderlyingType) || 0; var ut = (detail && detail.UnderlyingType) || 0;
if ((ut & 32768) > 0 || (ut & 536870912) > 0) { //非空且标志位全部落在债券三类内才按债券口径;混合行(债券|非债券)按收盘价口径显示,与取数侧标的实际类型判定方向一致
var bondMask = 16 | 32 | 64;
var isBond = ut !== 0 && (ut & ~bondMask) === 0;
if (!isBond) {
return { return {
priceInit: '参考标的期初净价', priceInit: '参考标的期初净价',
priceCur: '参考标的当前价', priceCur: '参考标的当前收盘价',
priceCurShort1: '参考标的当前收盘价', priceCurShort1: '参考标的当前收盘价',
priceCurShort: '参考标的当前收盘价', priceCurShort: '参考标的当前收盘价',
amountBase: '参考标的期初价格 × 参考标的名义份额' amountBase: '参考标的期初价格 × 参考标的名义份额'
@@ -633,5 +599,23 @@ $(function () {
if (!isNaN(idx)) vue.onUnderlyingTypeChange(idx); if (!isNaN(idx)) vue.onUnderlyingTypeChange(idx);
}); });
//多选 chosen:已有选中项后隐藏搜索框——空框没有输入内容却占一行,放不下时还会换行留白
//(本页多选均为点选场景,无需键盘过滤);全部取消后恢复,显示"请选择…"占位。
//勾选变化会触发原 select 的 jQuery changetrigger_form_field_change),
//加载重建/增删区块后的 refreshChosen 会触发 chosen:updated,两处都走这里的委托统一刷新
function refreshChosenSearchField(select) {
var $sel = $(select);
var $container = $sel.next('.chosen-container-multi');
if ($container.length === 0) return;
var noneSelected = $sel.find('option:selected').length === 0;
$container.find('li.search-field').toggle(noneSelected);
}
$(document).on('change chosen:updated', 'select.chosen-select[multiple]', function () {
refreshChosenSearchField(this);
});
SetAceDropDown(); SetAceDropDown();
$('select.chosen-select[multiple]').each(function () {
refreshChosenSearchField(this);
});
}); });
File diff suppressed because it is too large Load Diff
@@ -1,585 +0,0 @@
const consVarieties = ylotc.varieties;
const pricingFormat = otcformat.trading;
const consUnderlyingFlag = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
return unSelFlag.UseForTrading | unSelFlag.IncludeMatured | unSelFlag.UsePinYinFilter;
}());
var autoVariety, autoUnderlying;
function __init(vue) {
//标的品种
autoVariety = FastVue.autocomplete(document.getElementById('VarietyId'), {
lookup: consVarieties, nameField: 'Name', valueField: 'id', searchField: ['Name', 'Code', 'PinYin'],
onSelect: function (data) { vue.changeVariety(data, 'select'); }
});
//标的资产
autoUnderlying = tradeHelper.UnderlyingAutoComplete('UnderlyingCode').setFlag(consUnderlyingFlag);
autoUnderlying.onSelect(vue.changeunderlying);
//组合标的控件
synthenticPriceCtrl.init({
getSynthetic(input) {
return vue.viewState.synthetic;
},
setSynthetic(input, synthetic) {
vue.viewState.synthetic = synthetic;
vue.Model.SpotPrice = synthetic.Price;
}
});
//标的控件初始化
if (vue.Model.UnderlyingCode) {
autoUnderlying.selectByCode(vue.Model.UnderlyingCode);
}
else {
autoUnderlying.selectFirst();
}
}
var vue = new Vue({
el: "#listdiv",
data: {
Model: Model,
IsMoneynessOption: false
},
mounted: function () {
var thisObj = this;
//设置默认值补丁
thisObj.Model.ExerciseMode = "European";
thisObj.Model.TradeDate = moment(thisObj.Model.TradeDate).format("YYYY-MM-DD");
thisObj.Model.UnderlyingInstrumentType = "CommodityFutures";
$("#selUnderlyingInstrumentType").val("CommodityFutures");
__init(thisObj)
thisObj.setUnderlyingInstrumentType();
},
computed: {
inputFormatStrike: function () {
let fmt = {};
if (this.Model.UnderlyingInstrumentType === 'Stock') {
fmt.append = '%';
fmt.negative = false;
fmt.precision = pricingFormat.premiumRateP.precision;
} else {
fmt.append = '';
fmt.negative = true;
fmt.precision = pricingFormat.umprice.precision;
}
return fmt;
}
},
filters: {
ShowUnit: function (m) {
return m && m.Variety ? m.Variety.QuoteUnitSingle : "";
}
},
methods: {
ExerciseDateChange: function (data) {
var thisObj = this;
thisObj.Model.ExerciseDate = data;
},
ExerciseDateChange2: function (data) {
var thisObj = this;
thisObj.Model.ExerciseDate2 = data;
},
TradeDateChange: function (data) {
var thisObj = this;
thisObj.Model.TradeDate = data;
},
changeUnderlyingPrice: function () {
this.Model.UnderlyingPriceType = "User";
if (this.Model.UnderlyingInstrumentType !== "Stock") {
this.Model.StockEqvNotional = otcformat.trading.StockEqvNotional(this.Model.Notional * this.Model.Price);
this.Model.Price = otcformat.trading.umprice(this.Model.Price);
}
else {
this.Model.Notional = otcformat.trading.notional(this.Model.StockEqvNotional / this.Model.Price);
var CountRatio = this.Model.Variety.CountRatio || 1;
this.Model.TradeAmount = otcformat.trading.notional(this.Model.Notional / CountRatio);
}
},
refreshUnderlyingPrice: function () {
var thisObj = this;
main.post("/underlying_manager/underlyingGetById", { id: thisObj.Model.UnderlyingId }).done(function (res) {
thisObj.Model.Price = otcformat.trading.umprice(res.underlying_manager.Price);
if (thisObj.Model.UnderlyingInstrumentType !== "Stock") {
thisObj.Model.StockEqvNotional = otcformat.trading.StockEqvNotional(thisObj.Model.Notional * thisObj.Model.Price);
}
else {
thisObj.Model.Notional = otcformat.trading.notional(thisObj.Model.StockEqvNotional / thisObj.Model.Price);
var CountRatio = thisObj.Model.Variety.CountRatio || 1;
thisObj.Model.TradeAmount = otcformat.trading.notional(thisObj.Model.Notional / CountRatio);
}
thisObj.$forceUpdate();
});
},
setUnderlyingInstrumentType: function () {
var Underlying = $("#selUnderlyingInstrumentType").val();
autoUnderlying.selectFirst({ InstrumentTypes: Underlying });
},
changeunderlying: function (data) {
var thisObj = this;
thisObj.Model.VarietyId = data.VarietyId;
thisObj.Model.UnderlyingId = data.id;
thisObj.Model.UnderlyingCode = data.Code;
thisObj.Model.Variety = data;
thisObj.Model.Price = data.Price;
if (!data.Code) return;
thisObj.Model.UnderlyingInstrumentType = data.InstrumentType;
main.post("/Pricing/AjaxGetExerciseDate", { underlyingMaturityDate: data.MaturityDate, tradeDate: thisObj.Model.TradeDate }).done(function (res) {
thisObj.Model.ExerciseDate = new moment(res.obj.ExerciseDate).format("YYYY-MM-DD");
});
$.each(thisObj.Model.trades, function (i, d) {
d.MaturityDate = data.MaturityDate;
d.UnderlyingPrice = data.Price;
});
autoUnderlying.setVarietyId(data.VarietyId);
thisObj.changeVariety(data.VarietyId ? ylotc.varieties.find(x => x.id === data.VarietyId) : null);
thisObj.Model.Price = otcformat.trading.umprice(data.Price);
main.setTradeDatePicker("", "#ModelTradeDate", thisObj.Model.TradeDate, thisObj.TradeDateChange);
main.setTradeDatePicker("", "#ModelExerciseDate", thisObj.Model.ExerciseDate, thisObj.ExerciseDateChange);
main.setTradeDatePicker("", "#ModelExerciseDate2", thisObj.Model.ExerciseDate, thisObj.ExerciseDateChange2);
thisObj.IsMoneynessOption = thisObj.Model.UnderlyingInstrumentType === "Stock";
if (thisObj.IsMoneynessOption) {
thisObj.Model.StockEqvNotional = 1e6;
thisObj.changeStockEqvNotional();
} else {
thisObj.changeAmount();
}
},
setStrike: function (strikeType) {
if (this.Model.Name === "Condor") {
if (this.Model.Strike2 && this.Model.Strike3) {
if (this.Model.Strike2 >= this.Model.Strike3) {
if (strikeType === "Strike2") {
main.message("执行价格2需要小于执行价格3");
this.Model.Strike2 = "";
} else if (strikeType === "Strike3") {
main.message("执行价格3需要大于执行价格2");
this.Model.Strike3 = "";
}
return;
}
var diff = this.Model.Strike3 - this.Model.Strike2;
this.Model.Strike = this.Model.Strike2 - diff;
this.Model.Strike4 = parseFloat(this.Model.Strike3) + diff;
}
}
this.setStrike1();
},
setStrike1: function () {
this.Model.Strike = otcformat.trading.umprice(this.Model.Strike);
this.Model.Strike2 = otcformat.trading.umprice(this.Model.Strike2);
this.Model.Strike3 = otcformat.trading.umprice(this.Model.Strike3);
this.Model.Strike4 = otcformat.trading.umprice(this.Model.Strike4);
},
submitstructure: function () {
var thisObj = this;
if (thisObj.checkTrades()) {
thisObj.setModelTrades();
window.parent.vue.setOption(thisObj.Model.trades);
window.parent.layer.closeAll();
}
},
close: function () {
window.parent.layer.closeAll();
},
checkTrades: function () {
var thisObj = this;
var pass = true;
var checkModel = { Strike12: true, Strike12Empty: true, CheckExerciseDate: true };
switch (thisObj.Model.Name) {
case "Butterfly":
checkModel.Strike12 = false;
checkModel.Strike12Empty = false;
thisObj.Model.MidStrike = thisObj.Model.Price;
if (!thisObj.Model.StrikeGap || !thisObj.Model.MidStrike) {
main.message("执行价格间距必填!"); pass = false;
}
if (parseFloat(thisObj.Model.MidStrike) <= parseFloat(thisObj.Model.StrikeGap)) {
main.message("标的价格必须大于行权价间隔!"); pass = false; //中间行权价 即 标的价格
}
break;
case "Condor":
checkModel.Strike12 = false;
checkModel.Strike12Empty = false;
//验证行权价是否相同,从小到大排序,中间两个行权价允许相同,其他行权价不能相同
if (!thisObj.Model.Strike3 || !thisObj.Model.Strike4) {
main.message("行权价34必需输入!"); pass = false;
}
var strikes = [thisObj.Model.Strike, thisObj.Model.Strike2, thisObj.Model.Strike3, thisObj.Model.Strike4];
strikes.sort();
if (strikes[0] == strikes[1] || strikes[3] == strikes[2]) {
main.message("中间两个行权价允许相同,其他行权价不能相同!"); pass = false;
}
break;
//case "Preplicating Underlying":
case "Straddle":
//跨式组合的 执行价格 永远和标的价格一致
if (thisObj.Model.UnderlyingInstrumentType === "Stock") {
thisObj.Model.Strike = 100;
}
else {
thisObj.Model.Strike = thisObj.Model.Price;
}
checkModel.Strike12 = false;
checkModel.Strike12Empty = false;
if (!thisObj.Model.Strike) {
main.message("行权价必需输入!"); pass = false;
}
break;
case "Ratio Spread":
break;
case "Calender Spread"://ExerciseDate2
checkModel.Strike12 = false;
checkModel.Strike12Empty = false;
if (!thisObj.Model.ExerciseDate || !thisObj.Model.ExerciseDate2) {
main.message("两个到期日必需输入!"); pass = false;
}
if ((new Date(thisObj.Model.ExerciseDate)).getTime() <= (new Date(thisObj.Model.ExerciseDate2)).getTime()) {
main.message("到期日期2须早于到期日期1!"); pass = false;
}
if (!thisObj.Model.Strike) { main.message("请输入行权价!"); pass = false; }
break;
case "Collar":
checkModel.Strike12 = false;
checkModel.Strike12Empty = false;
if (!thisObj.Model.Strike || !thisObj.Model.Strike2 || !thisObj.Model.Strike3) {
main.message("执行价格1,2,3必需输入!"); pass = false;
}
if ((thisObj.Model.Strike == thisObj.Model.Strike2) || thisObj.Model.Strike == thisObj.Model.Strike3 || thisObj.Model.Strike3 == thisObj.Model.Strike2) {
main.message("执行价格1,2,3两两不能相等!"); pass = false;
}
break;
}
if (!thisObj.Model.TradeAmount) {
main.message("请输入交易数量!"); pass = false;
}
if (thisObj.Model.TradeAmount < 0) {
main.message("交易数量不能小于0!"); pass = false;
}
if (checkModel.CheckExerciseDate) {
if (!main.isDate(thisObj.Model.ExerciseDate)) {
main.message("请输入正确的到期日!"); pass = false;
}
}
if (checkModel.Strike12Empty && (!thisObj.Model.Strike || !thisObj.Model.Strike2)) { // Butterfly Condor 不必
main.message("执行价格12必需输入!"); pass = false;
}
return pass;
},
setModelTrades: function () {
var thisObj = this;
$.each(thisObj.Model.trades, function (i, d) {
d.VarietyId = thisObj.Model.VarietyId;
d.UnderlyingId = thisObj.Model.UnderlyingId;
d.UnderlyingCode = thisObj.Model.UnderlyingCode;
d.UnderlyingInstrumentType = thisObj.Model.UnderlyingInstrumentType;
d.Notional = thisObj.Model.Notional;
d.TradeAmount = otcformat.trading.notional(thisObj.Model.TradeAmount);
d.StockEqvNotional = thisObj.Model.StockEqvNotional;
d.ExerciseMode = thisObj.Model.ExerciseMode;
d.SettlementType = thisObj.Model.SettlementType;
if (thisObj.Model.OptionType)
d.OptionType = thisObj.Model.OptionType;
d.ExerciseDate = thisObj.Model.ExerciseDate;
d.TradeDate = thisObj.Model.TradeDate;
d.UnderlyingPrice = d.SpotPrice = thisObj.Model.Price;
d.NoRiskRateType = "System";
});
switch (thisObj.Model.Name) {
case "Bull Spread":
if (thisObj.Model.Strike < thisObj.Model.Strike2) {
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "卖出";
} else {
thisObj.Model.trades[0].BuySell = "卖出";
thisObj.Model.trades[1].BuySell = "买入";
}
thisObj.Model.trades[0].Strike = thisObj.Model.Strike;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike2;
break;
case "Bear Spread":
if (thisObj.Model.Strike < thisObj.Model.Strike2) {
thisObj.Model.trades[0].BuySell = "卖出";
thisObj.Model.trades[1].BuySell = "买入";
} else {
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "卖出";
}
thisObj.Model.trades[0].Strike = thisObj.Model.Strike;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike2;
thisObj.Model.Strike = thisObj.Model.Price;
break;
case "Straddle":
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "买入";
if (thisObj.Model.UnderlyingInstrumentType === "Stock") {
thisObj.Model.Strike = 100;
thisObj.Model.trades[0].Strike = 100;
thisObj.Model.trades[1].Strike = 100;
}
else {
thisObj.Model.Strike = thisObj.Model.Price;
thisObj.Model.trades[0].Strike = thisObj.Model.Strike;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike;
}
break;
case "Preplicating Underlying":
//交易方向“买入” 0看涨,1看跌
//两条leg 分别是: 买入执行价格高的看涨和卖出执行价格低看跌;
//交易方向为“卖出”:
//两条leg分别是:买入执行价格低的看跌和卖出执行价格高的看涨
//debugger;
thisObj.Model.trades[0].Strike = thisObj.Model.Strike;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike2;
if (thisObj.Model.BuySell === "买入") {
if (thisObj.Model.trades[0].Strike > thisObj.Model.trades[1].Strike) {
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[0].OptionType = "看涨";
thisObj.Model.trades[1].BuySell = "卖出";
thisObj.Model.trades[1].OptionType = "看跌";
}
else {
thisObj.Model.trades[1].BuySell = "买入";//买入执行价格高的看涨
thisObj.Model.trades[1].OptionType = "看涨";
thisObj.Model.trades[0].BuySell = "卖出"; //看跌??
thisObj.Model.trades[0].OptionType = "看跌";
}
} else {
if (thisObj.Model.trades[0].Strike > thisObj.Model.trades[1].Strike) {
thisObj.Model.trades[0].BuySell = "买入"; //相对客户说??
thisObj.Model.trades[0].OptionType = "看涨";
thisObj.Model.trades[1].BuySell = "卖出";
thisObj.Model.trades[1].OptionType = "看跌";
} else {
thisObj.Model.trades[1].BuySell = "买入";
thisObj.Model.trades[1].OptionType = "看涨";
thisObj.Model.trades[0].BuySell = "卖出";
thisObj.Model.trades[0].OptionType = "看跌";
}
}
thisObj.Model.Strike = thisObj.Model.Price;
break;
case "Strangle":
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "买入";
//thisObj.Model.Strike = thisObj.Model.Price;
thisObj.Model.trades[0].Strike = thisObj.Model.Strike;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike2;
if (thisObj.Model.Strike >= thisObj.Model.Strike2) {
thisObj.Model.trades[0].OptionType = "看涨";
thisObj.Model.trades[1].OptionType = "看跌";
} else {
thisObj.Model.trades[0].OptionType = "看跌";
thisObj.Model.trades[1].OptionType = "看涨";
}
break;
case "Butterfly":
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "卖出";
thisObj.Model.trades[2].BuySell = "买入";
thisObj.Model.trades[0].Notional = thisObj.Model.Notional;
var CountRatio = thisObj.Model.Variety.CountRatio || 1;
thisObj.Model.trades[0].TradeAmount = otcformat.trading.notional(thisObj.Model.trades[0].Notional / CountRatio);
thisObj.Model.trades[1].Notional = thisObj.Model.Notional * 2;
thisObj.Model.trades[1].TradeAmount = otcformat.trading.notional(thisObj.Model.trades[1].Notional / CountRatio);
thisObj.Model.trades[2].Notional = thisObj.Model.Notional;
thisObj.Model.trades[2].TradeAmount = otcformat.trading.notional(thisObj.Model.trades[2].Notional / CountRatio);
//中间行权价-行权价间隔,中间行权价,中间行权价+行权价间隔
thisObj.Model.trades[0].Strike = thisObj.Model.MidStrike - thisObj.Model.StrikeGap;
thisObj.Model.trades[1].Strike = thisObj.Model.MidStrike;
thisObj.Model.trades[2].Strike = parseFloat(thisObj.Model.MidStrike) + parseFloat(thisObj.Model.StrikeGap);
break;
case "Condor":
thisObj.Model.trades[0].Strike = thisObj.Model.Strike;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike2;
thisObj.Model.trades[2].Strike = thisObj.Model.Strike3;
thisObj.Model.trades[3].Strike = thisObj.Model.Strike4;
thisObj.Model.trades[0].OptionType = thisObj.Model.OptionType;
thisObj.Model.trades[1].OptionType = thisObj.Model.OptionType;
thisObj.Model.trades[2].OptionType = thisObj.Model.OptionType;
thisObj.Model.trades[3].OptionType = thisObj.Model.OptionType;
thisObj.Model.trades.sort(thisObj.strikeSort);
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "卖出";
thisObj.Model.trades[2].BuySell = "卖出";
thisObj.Model.trades[3].BuySell = "买入";
break;
case "Ratio Spread":
if (thisObj.Model.OptionType === "看涨") {
if (thisObj.Model.Strike < thisObj.Model.Strike2) {
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "卖出";
} else {
thisObj.Model.trades[0].BuySell = "卖出";
thisObj.Model.trades[1].BuySell = "买入";
}
} else if (thisObj.Model.OptionType === "看跌") {
if (thisObj.Model.Strike < thisObj.Model.Strike2) {
thisObj.Model.trades[0].BuySell = "卖出";
thisObj.Model.trades[1].BuySell = "买入";
} else {
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "卖出";
}
}
thisObj.Model.trades[0].Notional = thisObj.Model.Notional;
var CountRatio = thisObj.Model.Variety.CountRatio || 1;
thisObj.Model.trades[0].TradeAmount = otcformat.trading.notional(thisObj.Model.trades[0].Notional / CountRatio);
thisObj.Model.trades[1].Notional = thisObj.Model.Notional2;
thisObj.Model.trades[1].TradeAmount = otcformat.trading.notional(thisObj.Model.trades[1].Notional / CountRatio);
thisObj.Model.trades[0].Strike = thisObj.Model.Strike;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike2;
break;
case "Calender Spread":
if (thisObj.Model.ExerciseDate < thisObj.Model.ExerciseDate2) {
thisObj.Model.trades[0].BuySell = "卖出";
thisObj.Model.trades[1].BuySell = "买入";
} else {
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "卖出";
}
thisObj.Model.trades[0].ExerciseDate = thisObj.Model.ExerciseDate;
thisObj.Model.trades[1].ExerciseDate = thisObj.Model.ExerciseDate2;
thisObj.Model.trades[0].Strike = thisObj.Model.Strike;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike;
break;
case "Box Spread":
thisObj.Model.trades[0].BuySell = "买入";
thisObj.Model.trades[1].BuySell = "卖出";
thisObj.Model.trades[2].BuySell = "买入";
thisObj.Model.trades[3].BuySell = "卖出";
thisObj.Model.trades[0].OptionType = "看涨";
thisObj.Model.trades[1].OptionType = "看跌";
thisObj.Model.trades[2].OptionType = "看跌";
thisObj.Model.trades[3].OptionType = "看涨";
if (thisObj.Model.Strike < thisObj.Model.Strike2) {
thisObj.Model.trades[0].Strike = thisObj.Model.Strike;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike;
thisObj.Model.trades[2].Strike = thisObj.Model.Strike2;
thisObj.Model.trades[3].Strike = thisObj.Model.Strike2;
} else {
thisObj.Model.trades[0].Strike = thisObj.Model.Strike2;
thisObj.Model.trades[1].Strike = thisObj.Model.Strike2;
thisObj.Model.trades[2].Strike = thisObj.Model.Strike;
thisObj.Model.trades[3].Strike = thisObj.Model.Strike;
}
break;
case "Risk Reversal":
thisObj.Model.trades[0].Strike = Math.max(thisObj.Model.Strike, thisObj.Model.Strike2);
thisObj.Model.trades[1].Strike = Math.min(thisObj.Model.Strike, thisObj.Model.Strike2);
break;
case "Collar":
thisObj.Model.trades[0].OptionType = "看跌";
thisObj.Model.trades[1].OptionType = "看跌";
thisObj.Model.trades[2].OptionType = "看涨";
if (thisObj.Model.SeagullType === "Bullish") {
thisObj.Model.trades[1].OptionType = "看涨";
}
var strikes = [parseFloat(thisObj.Model.Strike), parseFloat(thisObj.Model.Strike2), parseFloat(thisObj.Model.Strike3)];
strikes.sort((x1, x2) => x1 - x2)
thisObj.Model.trades[0].Strike = strikes[0];
thisObj.Model.trades[1].Strike = strikes[1];
thisObj.Model.trades[2].Strike = strikes[2];
break;
}
//设置初始化信息
$.each(thisObj.Model.trades, function (i, d) {
if (d.UnderlyingInstrumentType === "Stock") {
d.IsMoneynessOption = "是";
d.TradeSinglePriceType = "%";
d.IsUsePremiumRate = true;
}
d.Strike = otcformat.trading.umprice(d.Strike);
});
thisObj.buySellCalc();
},
strikeSort: function (tr1, tr2) {
return tr1.Strike - tr2.Strike;
},
buySellCalc: function () {
var thisObj = this;
if (thisObj.Model.BuySell === "卖出") {
//相反
$.each(thisObj.Model.trades, function (i, d) {
d.BuySell = d.BuySell === "卖出" ? "买入" : "卖出";
});
}
},
addStructureOption: function () {
//增加交易策略
var thisObj = this;
main.open("/trade/GetStructureOption", thisObj.Model.Option).done(function (res) { });
},
changeAmount: function () { //交易数量= 份额 / 比率
var thisObj = this;
thisObj.Model.TradeAmount = otcformat.trading.notional(thisObj.Model.TradeAmount);
thisObj.Model.Notional = thisObj.Model.TradeAmount * (thisObj.Model.Variety.CountRatio || 1);
thisObj.Model.StockEqvNotional = otcformat.trading.StockEqvNotional(thisObj.Model.Notional * thisObj.Model.Price);
},
changeVariety: function (variety, flag) {
if (flag === 'select') {
if (this.Model.VarietyId === variety.id) return;
autoUnderlying.setVarietyId(variety.id);
autoUnderlying.selectFirst(variety.id);
} else {
autoVariety.setData(variety);
}
},
changeStockEqvNotional: function () {
var thisObj = this;
thisObj.Model.Notional = otcformat.trading.notional(thisObj.Model.StockEqvNotional / thisObj.Model.Price);
if (thisObj.Model.Notional) {
thisObj.Model.TradeAmount = otcformat.trading.notional(thisObj.Model.Notional / (thisObj.Model.Variety.CountRatio || 1));
} else {
thisObj.Model.TradeAmount = "";
}
},
//绝对值执行价格
showAbsStrike() {
this.IsMoneynessOption = false;
this.moneynessSwitch(this.trade);
},
//百分比执行价格
showPercentStrike() {
this.IsMoneynessOption = true;
this.moneynessSwitch(this.trade);
},
moneynessSwitch(trade) {
let spotPrice = parseFloat(this.Model.Price) || 0;
let isSpotZero = Math.abs(spotPrice) < 1e-4;
let strikes = ["Strike", "Strike2", "Strike3", "Strike4"];
for (var key of strikes) {
if (this.IsMoneynessOption) {
this.Model[key] = pricingFormat.premiumRate(isSpotZero ? 1 : this.Model[key] / spotPrice);
} else {
this.Model[key] = pricingFormat.umprice(this.Model[key] * spotPrice);
}
}
}
},
components: {
'vue-number-input': FastVue.vueNumberInput()
}
});
File diff suppressed because it is too large Load Diff
@@ -1,24 +0,0 @@
$(function () {
var table = $("#infoTable")[0];
pageObj.SacInfoList.forEach(Obj => {
formatHtml(Obj, table, 1);
})
})
function formatHtml(obj, table, rowIndex) {
if (obj.FieldName !== "Root" && obj.FieldName !== "Header" && obj.FieldName !== "Body") {
var html = '<td title="{0}">{1}</td><td>{2}</td><td>{3}</td>'.template(obj.FieldName, obj.FieldDescribe, obj.FieldValue, obj.ShowMessage);
rowIndex = addRow(table, rowIndex, html);
}
if (obj.SubInfos && obj.SubInfos.length > 0) {
obj.SubInfos.forEach(function (item) {
rowIndex = formatHtml(item, table, rowIndex);
});
}
return rowIndex;
}
function addRow(table, rowIndex, htmlStr) {
var row = table.insertRow(rowIndex);
row.innerHTML = htmlStr;
rowIndex += 1;
return rowIndex;
}
@@ -1,105 +0,0 @@
//roleFunctionEdit.cshtml
function autocheck(a) {
$("input[id={0}]".template(a.id)).prop("checked", a.checked);
linkage(a);
}
function linkage(a) {
var obj = $(a);
var parentName = obj.attr('data-parent');
var status = $(a).is(":checked");
var typeName = obj.attr('data-type');
var fname = obj.attr('lang');
if (parentName === "-") {
$(obj).next().css('display', 'none');
$('input[type="checkbox"][data-parent="{0}"][data-type="{1}"]'.template(fname, typeName)).prop("checked", status);
return;
}
var arr = $("input[type='checkbox'][data-parent='{0}'][data-type='{1}']".template(parentName, typeName));
var parSelector = 'input[type="checkbox"][lang="{0}"][data-type="{1}"]'.template(parentName, typeName);
$(parSelector).next().css('display', 'none');
var continueState = false;
$(arr).each(function (i, obj) {
if ($(obj).is(":checked") != status) {
$(parSelector).prop("checked", true);
$(parSelector).next().css('display', 'inline-block');
continueState = true;
return false;
}
});
if (!continueState) {
$(parSelector).prop("checked", status);
}
}
function GoBack() {
if (document.all) { //ie
if (window.history.length > 0) {
window.history.back();
return;
}
} else {
if (window.history.length > 1) {
window.history.back();
} else {
window.opener = null;
window.close();
}
}
window.close();
}
function showOrHide(res) {
if ($(res).text() === "-") {
$(res).parent().next().next().hide();
$(res).html("+");
} else {
$(res).parent().next().next().show();
$(res).html("-");
}
}
function modulePermissions() {
$(".tab-1").parent().addClass("active");
$(".tab-link").parent().removeClass("active");
$(".tab-content").removeClass("tab-none");
$(".tab-content2").addClass("tab-none");
$(".tab-content2").removeClass("tab-block");
}
function operationPeemissions() {
$(".tab-link").parent().addClass("active");
$(".tab-1").parent().removeClass("active");
$(".tab-content").addClass("tab-none");
$(".tab-content2").addClass("tab-block");
}
$(".spanleft").parent().addClass("spanleft-w");
$(document).ready(function () {
var parentArr = $("input[type='checkbox'][data-parent='-']");
$(parentArr).each(function (i, obj) {
var dataType = $(obj).attr("data-type")
var allCount = $('input[type="checkbox"][data-parent="{0}"][data-type="{1}"]'.template(obj.lang, dataType)).length;
var selectCount = $('input[type="checkbox"][data-parent="{0}"][data-type="{1}"]:checked'.template(obj.lang, dataType)).length;
if (allCount != selectCount && selectCount > 0) {
$(obj).next().css('display', 'inline-block');
}
});
modulePermissions();
main.form({
el: '#roleFunctionEditForm',
submit: {
url: '/system/roleFunctionEditFormJson',
after(res) {
window.location.href = "/system/RoleView?id=" + res.obj.Id;
try {
window.parent && window.parent.SearchClick();
}
catch (e) {
//
}
}
}
});
});
@@ -17,6 +17,10 @@
const consSelect = ['MarketCode', 'UnderlyingInstrumentType', 'UnderlyingState', 'UnderlyingStatus', 'UpDownLimitType', 'EtfSubType']; const consSelect = ['MarketCode', 'UnderlyingInstrumentType', 'UnderlyingState', 'UnderlyingStatus', 'UpDownLimitType', 'EtfSubType'];
var autoUpDownLimit, autoVariety; var autoUpDownLimit, autoVariety;
var fundManagerLookupSeq = 0;
var fundManagerLookupTimer = null;
var fundManagerLookupXhr = null;
var fundManagerManualEdit = false;
$(function () { $(function () {
@@ -46,6 +50,12 @@ $(function () {
lookup: ylotc.varieties lookup: ylotc.varieties
}); });
$('#InvestAdvisorName').on('input', function () {
fundManagerManualEdit = true;
});
$('#UnderlyingCode').on('input', refreshFundManagerLookup);
for (var i = 1; i <= 5; i++) { for (var i = 1; i <= 5; i++) {
let datas = ylotc.underlyingBlocks.filter(x => x.Group === i); let datas = ylotc.underlyingBlocks.filter(x => x.Group === i);
let autoBlock = FastVue.autocomplete(document.getElementById('inputBlock' + i), { let autoBlock = FastVue.autocomplete(document.getElementById('inputBlock' + i), {
@@ -112,9 +122,57 @@ $(function () {
break; break;
} }
$('#editForm').removeClass("form-None form-Stock form-CommodityFutures form-CommoditySpot form-Bonds form-Fund").addClass("form-" + classType); $('#editForm').removeClass("form-None form-Stock form-CommodityFutures form-CommoditySpot form-Bonds form-Fund").addClass("form-" + classType);
refreshFundManagerLookup();
}).trigger('change'); }).trigger('change');
}); });
function refreshFundManagerLookup() {
var requestSeq = ++fundManagerLookupSeq;
if (fundManagerLookupTimer) {
clearTimeout(fundManagerLookupTimer);
fundManagerLookupTimer = null;
}
if (fundManagerLookupXhr) {
fundManagerLookupXhr.abort();
fundManagerLookupXhr = null;
}
var codeInput = $('#UnderlyingCode');
var typeInput = $('#UnderlyingInstrumentType');
var managerInput = $('#InvestAdvisorName');
if (page.Model.id > 0 || !codeInput.length || !typeInput.length ||
typeInput.val() !== 'Fund' || !codeInput.val() || !managerInput.length) {
return;
}
var codeAtRequest = codeInput.val();
var managerAtRequest = managerInput.val() || '';
var manualAtRequest = fundManagerManualEdit;
fundManagerLookupTimer = setTimeout(function () {
fundManagerLookupTimer = null;
fundManagerLookupXhr = $.ajax({
url: '/underlying_manager/GetFundManager',
method: 'GET',
data: { code: codeAtRequest, instrumentType: 'Fund' }
}).done(function (resp) {
if (requestSeq !== fundManagerLookupSeq || page.Model.id > 0 ||
$('#UnderlyingCode').val() !== codeAtRequest ||
$('#UnderlyingInstrumentType').val() !== 'Fund' ||
manualAtRequest || fundManagerManualEdit) {
return;
}
var result = resp && resp.obj;
if (result && result.IsUnique && result.InvestAdvisorName) {
managerInput.val(result.InvestAdvisorName);
fundManagerManualEdit = false;
}
}).always(function () {
fundManagerLookupXhr = null;
});
}, 200);
}
function saveData() { function saveData() {
var data = $('#editForm').serializeObject(); var data = $('#editForm').serializeObject();
@@ -62,9 +62,9 @@
padding: 5px; padding: 5px;
} }
.chosen-container-multi .chosen-choices li.search-choice { /*多选 chosen 的 search-choice 已去掉原固定 130px 宽(改由 chosen 默认自适应):
width: 130px; 固定宽会让短标签(买入/收益互换等)右侧大片留白,并把隐藏的搜索输入框挤到下一行、撑出一截空白高度。
} 下方 span:first-child 120px 是单选 chosen 选中项的旧规则,与多选无关*/
.form-layout .chosen-choices > li > span:first-child, .searchdiv .chosen-choices .search-choice > span:first-child { .form-layout .chosen-choices > li > span:first-child, .searchdiv .chosen-choices .search-choice > span:first-child {
width: 120px; width: 120px;
@@ -89,14 +89,83 @@ p {
padding-bottom: unset; padding-bottom: unset;
} }
/*区间追保结构区块:单元格里多个数字输入与文字并列,全局 input[type=text] 152px 会把单元格内容挤成两行,调窄保证单行*/ /*区间追保结构区块:单元格里多个数字输入与文字并列,全局 input[type=text] 152px 会把单元格内容挤成两行
64px 保证 100.00% 这类两位小数百分比完整显示且不换行*/
.spanBlk table input[type=text] { .spanBlk table input[type=text] {
width: 50px; width: 64px;
padding-left: 2px; padding-left: 2px;
padding-right: 2px; padding-right: 2px;
} }
/*同区块表格单元格边距:bootstrap .table 默认 0.75rem,压缩使表格更紧凑*/ /*同区块表格单元格边距:bootstrap .table 默认 0.75rem,压缩使表格更紧凑;垂直居中使公式文字与输入框对齐*/
.spanBlk table th, .spanBlk table td { .spanBlk table th, .spanBlk table td {
padding: 2px 4px; padding: 2px 4px;
vertical-align: middle;
}
/*左栏基础信息列(tpl-base 标记在三个编辑页共用的左侧 col 上):
控件统一 240px 宽、右缘对齐;多选 chosen 初始化取 select 宽度(SetAceDropDown),随之统一。
说明类 textarea 跟随列宽(减去 160px 标签 + 8px 间距 + 4px 余量)*/
.tpl-base {
/*左右两栏约 1:2:左栏基础信息收窄,右侧追保区间表格更宽。
不用 col-md-5/7form-layout 自带 1rem 外边距,两栏 basis 相加已满 100% 会折行;
左栏定宽(不缩不放)+ 右栏 .col(flex:1) 吸收剩余宽度,无折行风险*/
flex: 0 0 34%;
max-width: 34%;
}
.tpl-base input.text-box,
.tpl-base select {
width: 240px;
}
.tpl-base textarea.text-box {
width: calc(100% - 172px);
}
/*只读说明(规则描述/分类说明):禁用输入框的灰底边框观感突兀,弱化为说明文字块;
保留滚动(规则描述较长),滚动条细化*/
.tpl-base textarea.text-box:disabled {
background: #f8f8f8;
border: none;
border-radius: 3px;
color: #777;
resize: none;
}
.tpl-base textarea.text-box:disabled::-webkit-scrollbar {
width: 4px;
}
/*多选 chosen:有选中项后由页面 JS 隐藏搜索框(见 marginTemplateV2*Edit.js 的 refreshChosenSearchField),
空态时搜索框独占控件全宽,负责展示"请选择…"占位*/
/*期限档位表操作列:delBtn 的 20px 左边距在 fixed 布局的窄列里会把"- 删除区块"挤成竖排*/
.border.detail td .delBtn {
margin-left: 0;
white-space: nowrap;
}
/*区块标题(新模板信息/选择模板规则/参数组N):左色条 + 加粗,与表单正文分层*/
.border > p:first-child {
font-weight: bold;
border-left: 3px solid #e33333;
padding-left: 6px;
}
/*参数组区块外边界加深:区块内表格线是浅灰,外框同步太浅导致分组不明显*/
.spanBlk.border {
border-color: #9e9e9e !important;
}
/*期限档位区块表:固定布局让"标的资产类型"列吃剩余宽度,期限档位/操作按内容定宽,
避免两列均分 100% 时操作列被撑得过宽、与内容不成比例*/
.border.detail table {
table-layout: fixed;
}
/*标的资产类型多选(期限档位表单元格内):chosen 初始化取原生 select 的固有宽度(约 150px),
窄容器放不下第二个已选标签,导致标签逐行竖排;单元格空间充足,容器撑满即可横向排列*/
.border.detail .chosen-container-multi {
width: 100% !important;
} }
@@ -0,0 +1,125 @@
# 旧保证金链路存亡分析与下线重构方案
- 日期:2026-08-28
- 分析基线:`glms/feature/1.4.2` @ `cd615a61`
- 背景:2026-08-26 `4adfbabe`(配套 `134e07c6`/`1969e292`)将互换预付金 EOD 计算收口为本端 .NET 引擎(`EodWorstClientPayableCalc` + `MarginTemplateV2RateHelper`,读 `margin_template_v2`),不再经 bond-oms Java 按旧 `marginrate` 数据算盯市。本文回答:**山证 v2.3.0 拷贝带过来的旧保证金相关代码(DMA 与否),在新引擎上线后还剩多少活口、如何分阶段下线。**
## 一、结论总览
| # | 链路 | 判定 | 一句话依据 |
|---|------|------|-----------|
| 1 | bond-oms HTTP 保证金接口(`/marginAlgorithm/realTimeMarginCalc``CalcDMAMargin`) | **死,可删** | 全仓(含 YLWinSer/前端/配置)零调用方;`git log -S` 全历史自拷贝日起从未被调用 |
| 2 | `margin_template` V1 模板管理链 | **部分可删** | 计算链已全部 V2 化;但 5 个 Razor 页面的模板下拉仍喂 V1 表 |
| 3 | `client_marginrate` 旧预付金率链(MarginRateSwap) | **半死** | EOD 盯市消费方已随收口消失;剩 2 条活读链(流水导入落快照、录入页取率回显) |
| 4 | `MarginRate`(不带 Swap)+ 远期保证金链 | **远期结算确定死;其余需业务确认** | `EodForwardMarginSettlement` 2025-04-16 已从 EOD 调度摘除,零调用方 |
| 5 | DMA 分类(`Client.SwapTradeType`) | **活,但仅 2 处真实分支** | 其余全是透传展示与命名误导;`CalcDMAMargin` 死代码不影响判定 |
## 二、分链详解
### 2.1 bond-oms HTTP 保证金接口(死,可删)
接口本体在 bond-oms Java 服务(`BondOmsInterface.BaseUrl = trs_hub_api`),本仓是唯一已知调用方,而调用方本身是死的:
| 层 | 位置 | 状态 |
|---|---|---|
| 调用点 | `YLErpDAL/BLL/EodSettlement/RealtimePnlCalc.cs:722` `CalcDMAMargin()` | 全仓无任何调用方(全文件类型 grep) |
| 方法内 URL | `RealtimePnlCalc.cs:725` 硬编码 | 随方法一起死 |
| 配置键 | `YLWinSer/RealTimeCalcPositionService/appsettings.json:48` `CalculateDMAMarginUrl` | 无任何代码读取(URL 是硬编码的,配置键是摆设) |
| DTO | `YLErpDAL/Model/CalculateMarginRequest.cs` | 仅被 `CalcDMAMargin` 使用,可连带删 |
排除项:YLWinSer 常驻任务(ClientPosiTask/Worker/ClientNoDMABalanceTask)不调它;YLErpWeb 无 `marginAlgorithm` 路由/代理,前端(含 Vue3 仓)架构上不可达;无反射/字符串调度。
**历史证据**:`git log -S "CalcDMAMargin()" --all` 仅两条 —— `f9d8a256`(山证拷贝,方法进仓)与 `4adfbabe`(只加注释)。即该方法从进仓第一天起就从未被调用,山证原版的 DMA 任务未随拷贝进来(本仓只有 `ClientNoDMABalanceTask`)。
**注意**:`EodCheckMonitoredTrade.cs:186` 注释(4adfbabe 加)把 `CalcDMAMargin` 描述为"迁移方案阶段三待切项"——与事实不符(无流量可切),删码时必须同步修正,否则迁移计划继续被带偏。
Java 端点能否删需在 bond-oms 仓自查内部调度,本仓证据只能证明"本仓侧调用链已死"。
### 2.2 margin_template V1 模板链(部分可删)
**计算链已全部 V2 化,无 V1 回退**:
- 核心预付金:`MarginCalculationBase.cs:392,420``MarginTemplateV2RateHelper`(纯 V2 三级解析)
- 追保/span:`SwapAdditionalMarginService.cs:115``SwapSpanBalanceQueryService.cs:91` 均走 V2 helper
- 交易保存:`SwapTradeService.cs:162-209` 只查 `margin_template_v2` 并写 `trade_margin_template`(存 V2 id);`TradeSaveService.cs:654-697``TradeQueryService.cs:324``SwapMarginTemplateConfigService.cs:17`
- V1 管理页入口已死:`Menus.txt` 仅剩指向 V2;`margin_templateController.cs:9` 引用的 FunctionRight 权限已不存在
**V1 表仅剩 2 类活读点**:
1. `tradeController.cs:8861/8873`(`GetMarginTemplates`/`GetMarginTemplateItems`,直接 `db.margin_template.ToList()`),被 5 个在用页面 Razor 服务端调用做模板名下拉:
`Views/SwapTrade2/TradeEdit.cshtml:17,33``Views/SwapTrade/tradeEdit.cshtml:15-16``Views/trade/TradeEditV2.cshtml:20-21`(远期编辑)、`Views/Pricing/Structure_DZ.cshtml:22-23``Views/Pricing/structure.cshtml`
2. `ForwardTradeImportService.cs:318-352,872` 远期导入消费 V1 比率(远期业务本身存亡见 2.4)
**不可删(易误伤)**:`client_margin_template` 已 V2 化,是 V2 引擎第二级数据源(`MarginTemplateV2RateHelper.cs:256,341``ClientBalanceUtility.cs:631`);`margin_template_detail``trade_margin_template` 是 V2 的表,与 V1 同名前缀但归属 V2。
### 2.3 client_marginrate 旧预付金率链(半死)
无独立 margin_rate_swap 表,整条链落在 `client_marginrate` 表(`Framework/YLErp.Core/DBModels/client_marginrate.cs:8`)。
- **写入/维护链**:菜单入口已于 `ab907122`(2026-08-12,EQD-6947)注释下线(Menus.txt:81-82"由预付金模板V2替代");`MarginRateSwapController` 的 CRUD/导入 action 仍可直接 URL 访问,FunctionRight 权限残留。
- **活读链(仅剩 2 条)**:
1. 流水导入:`SwapTradeFlowImportService.cs:351-354` 按 客户+品种+日期 取 `client_marginrate``:374` `InitialMargin = marginRate * StockEqvNotional``:393``trade_swap.GetMarginRate` 快照(TradeSwapService 8 处调用)
2. 录入取率回显:`SwapTradeController.cs:1152` `GetInitMarginRate``swapTradeEdit.js`(互换录入页实时回显)
- **死读点**:`RealTimeClientBanlanceService.cs:1469` 加载后从未使用;`ClientBalanceUtility.cs:1517``ConfirmationGenerateContext.cs:1913``ITradeDocGeneratorContext.cs:443` 全仓零调用;`SwapTradeValidator` 只看 `trade_swap.GetMarginRate` 快照,`:24` 的调用已注释。
- **与新引擎零交叉**:已逐一确认 `EodWorstClientPayableCalc``MarginTemplateV2RateHelper``RealtimePnlCalc``SwapSpanBalanceCalc``SwapAdditionalMarginService` 均不读 `client_marginrate`;无 SQL/Dapper/存储过程读点;YLWinSer 无相关任务。
### 2.4 MarginRate(不带 Swap)+ 远期链
- **`EodForwardMarginSettlement` 确定死**:`8e61f23e`(2025-04-16)已将其从 EOD 调度摘除,现全仓零调用方;`eod_forward_margin` 表唯一写入点随之失活,读方(`EodPositionSettleService.cs:870,967``RealtimePnlCalc.cs:1009``TradeForwardUnwindService.cs:96`)全部空转。`SettlementConfig.CalcForwradMargin` 零消费。
- **Forward 模块整体**:拷贝后零提交改动;录入页/导入/11 个 Views/API 均在,菜单在 DB `sys_menu`(仓内无法证明挂没挂);`tradeController.cs:6295` 仅渤海/广发商贸分支才查远期。**需业务确认**(菜单是否还挂、trade 表有无远期存量)。
- **`MarginRate` CRUD/导入**:UI 自闭环;表读方仅 `SwapTradeFlowImportService.cs:337`(且被 `PS.Config.Company==中金` 门控,本部署非中金)与 `VarietyDalService.cs:426`(防删校验);`GetMarginRate`(`MarginRateService.cs:210`)零调用。
- **`MarginParamProvider` 不可删**:`MarginCalcHelper.cs:30`(活引擎 `RunMarginCalculation` 内)与 `TradeDelaySettlementService.cs:72,163` 在用其涨跌幅/波动率;其预付金率读法(`:149-160`)才是死的。
### 2.5 DMA 概念:字段活,概念基本只剩命名
字段 = `Client.SwapTradeType`(`Client.cs:1149`,1=DMA/MDA,0=非DMA)。**真实分支仅 2 处**:
1. `QuotaMonitorService.cs:5591` `CheckFund`:`SwapTradeType==0` 非 DMA 不做资金校验直接过(经 `RunQuotaTrial``tradeController.cs:8502` 限额试算,活)
2. `YLErpWeb/App/KafkaTask/ClientBalanceTask.cs:62`:DMA 客户 `lastBalanceDate=valuedate`(起算日改为当日),活
其余全部是透传/展示/命名:`ClientSettleBalance.ClientTypeStr``ClientBalanceUtility``RealTimeClientBanlanceService`、监控/报表 js、客户编辑下拉等;`SwapFlowCombookingHub.cs:79` "DMA合成持仓" 只是 region 名(DMA 过滤已注释,与 DMA 无关,Hub 本身有前端连接方,活);`ClientNoDMABalanceTask` 的 "NoDMA" 纯任务名,`GetBalances()` 取全部客户无 DMA 过滤。
## 三、下线重构方案(分阶段)
### 阶段 0:立即可删(零调用方,已实证)
| 删除项 | 前置条件 |
|---|---|
| `RealtimePnlCalc.CalcDMAMargin()` + `CalculateMarginRequest.cs` + appsettings `CalculateDMAMarginUrl` 键 | 无 |
| `EodCheckMonitoredTrade.cs:186` 注释修正(去掉"阶段三待切项"误导) | 随上条同提交 |
| `EodForwardMarginSettlement.cs` + `SettlementConfig.CalcForwradMargin` | 无(可选:同步清 `eod_forward_margin` 读方空转代码) |
### 阶段 1:小改造后可删(V1 模板链)
1. `tradeController.GetMarginTemplates/GetMarginTemplateItems` 改为从 `margin_template_v2` 取数(或确认 5 个页面的 V1 下拉已无业务意义直接去掉下拉)。
2. 改造完成后删:`margin_templateController.cs``Views/margin_template/*``MarginTemplateService.cs``margin_templateReq.cs``DBModels/margin_template.cs``YLContext.cs` 中对应 DbSet。
3. 远期导入的 V1 比率消费随阶段 2 远期业务结论一并处理。
### 阶段 2:需先迁移读链(client_marginrate / 远期)
1. 流水导入落快照(`SwapTradeFlowImportService.cs:351-393`)与录入取率回显(`GetInitMarginRate`)两条链迁 V2 引擎取率。
2. 迁完后整体下线 `MarginRateSwapController` + `MarginRateSwapService` + `client_marginrate` 表链,并清理 FunctionRight 残留权限。
3. 远期业务经业务确认后:无存量则 Forward 模块 + `MarginRate` 链整体清理;有存量则查询/平仓/对账页暂留、仅清结算死链。
### 需确认清单(删除前必须逐项闭环)
| 项 | 确认方式 | 风险 |
|---|---|---|
| Vue3 前端仓是否调用 `/trade/GetMarginTemplates`、ForwardTrade、MarginRateSwap 系列 action | 前端仓 grep(本仓不可见) | action 被直连调用 |
| trade 表有无远期存量数据、`sys_menu` 是否还挂远期菜单 | DB 查询 | 存量交易无法查询/平仓 |
| `client_margin_template` 历史行 `MarginTemplateId` 是否残留 V1 id | DB 查询 | join 不上会静默落到第三级默认 |
| `client_marginrate` 表是否有 V2 之外的人工维护依赖(运营流程) | 业务确认 | 导入链迁 V2 后仍有人改旧表 |
| bond-oms 内部是否有 `/marginAlgorithm/realTimeMarginCalc` 的其他触发方 | bond-oms 仓确认 | Java 端点下线 |
### 不可删清单(防误伤)
`client_margin_template` / `margin_template_detail` / `trade_margin_template`(V2 数据源)、`MarginParamProvider`(涨跌幅/波动率在用)、`Client.SwapTradeType` 及其 2 处分支、`ClientBalanceTask``SwapFlowCombookingHub`
### 顺手项(命名去毒,不动逻辑)
- `ClientNoDMABalanceTask` 任务名、`SwapFlowCombookingHub` "DMA合成持仓" region 名、`ClientBalanceForTrsResponse.ClientType` 注释口径,均与实际行为不符,可在触碰时改名。
## 四、验证纪律(执行删除时)
沿用 2026-08 死代码清理的教训(见 `多租户死代码清理执行计划.md`):
1. 每个删除项独立小提交,删前全文件类型 grep(不止 *.cs,含 js/cshtml/xml/json/配置),删后 Release + Debug 双构建;
2. `git rm` 目录混批后必须重新 find 核对(笔误会静默回滚整批);
3. 涉及 DBModel/列的删除,列残留留给 DBA,不在应用层迁移;
4. action 删除必须先过"前端仓确认"关卡。