767 lines
38 KiB
C#
767 lines
38 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Web.Mvc;
|
||
using YLErp.BLL;
|
||
using YLErp.Configuration;
|
||
using YLErp.DBModels;
|
||
using YLErp.Enums;
|
||
using YLErp.Modules.MarginModule;
|
||
|
||
namespace YLErp.Web.Controllers
|
||
{
|
||
public class margin_template_v2Controller : BaseController
|
||
{
|
||
[MyAuthorize("基础参数管理-预付金模板V2")]
|
||
public ActionResult margin_template_v2List()
|
||
{
|
||
if (PS.Config.Company != CompanyEnum.广发商贸)
|
||
{
|
||
return View();
|
||
}
|
||
return Redirect("margin_template_v2ClientList");
|
||
}
|
||
|
||
[MyAuthorize("基础参数管理-预付金模板V2")]
|
||
public ActionResult margin_template_v2DefaultList()
|
||
{
|
||
return View();
|
||
}
|
||
|
||
[MyAuthorize("基础参数管理-预付金模板V2")]
|
||
public ActionResult margin_template_v2ClientList()
|
||
{
|
||
return View();
|
||
}
|
||
|
||
[HttpPost]
|
||
public JsonResult margin_template_v2GroupQuery(margin_template_v2Req req)
|
||
{
|
||
req.IsDefault = false;
|
||
req.IsForClient = false;
|
||
var sList = new MarginTemplateService(CurUser).SearchMarginTemplateV2List(req);
|
||
return Json(sList);
|
||
}
|
||
|
||
[HttpPost]
|
||
public JsonResult margin_template_v2GroupDefaultQuery(margin_template_v2Req req)
|
||
{
|
||
req.IsDefault = true;
|
||
req.IsForClient = false;
|
||
var sList = new MarginTemplateService(CurUser).SearchMarginTemplateV2List(req);
|
||
return Json(sList);
|
||
}
|
||
|
||
[HttpPost]
|
||
public JsonResult margin_template_v2GroupClientQuery(margin_template_v2Req req)
|
||
{
|
||
req.IsDefault = true;
|
||
req.IsForClient = true;
|
||
var sList = new MarginTemplateService(CurUser).SearchMarginTemplateV2List(req);
|
||
return Json(sList);
|
||
}
|
||
|
||
public ActionResult margin_template_v2DefaultEdit(string enid)
|
||
{
|
||
if (string.IsNullOrEmpty(enid) || enid == "0")
|
||
{
|
||
return View(new margin_template_v2() { ValueDate = valuedateBLL.ValueDate, RuleType = (int)MarginRuleTypeEnum.无预付金, Comments = "预付金占用为零。", IsValid = true });
|
||
}
|
||
|
||
var intid = DecryptInt(enid);
|
||
var r = yldb.margin_template_v2.AsNoTracking().FirstOrDefault(n => n.id == intid);
|
||
if (r == null)
|
||
{
|
||
return ShowError("数据不存在");
|
||
}
|
||
|
||
r.Details = yldb.margin_template_detail.Where(x => x.MarginTemplateId == r.id).OrderBy(x => x.ValueDate).ToList();
|
||
if (!r.Details.Any())
|
||
{
|
||
r.Details.Add(new margin_template_detail() { ValueDate = new DateTime(2020, 1, 1) });
|
||
}
|
||
r.LatestValueDate = r.Details.LastOrDefault()?.ValueDate;
|
||
return View(r);
|
||
}
|
||
|
||
public ActionResult margin_template_v2DefaultCopy(string enid)
|
||
{
|
||
var intid = DecryptInt(enid);
|
||
var r = yldb.margin_template_v2.AsNoTracking().FirstOrDefault(n => n.id == intid);
|
||
if (r == null)
|
||
{
|
||
return ShowError("数据不存在");
|
||
}
|
||
|
||
r.Details = yldb.margin_template_detail.Where(x => x.MarginTemplateId == r.id).OrderBy(x => x.ValueDate).ToList();
|
||
r.id = 0;
|
||
if (!r.Details.Any())
|
||
{
|
||
r.Details.Add(new margin_template_detail() { ValueDate = new DateTime(2020, 1, 1) });
|
||
}
|
||
else
|
||
{
|
||
r.Details.ForEach(x =>
|
||
{
|
||
x.MarginTemplateId = 0;
|
||
x.id = 0;
|
||
});
|
||
}
|
||
r.LatestValueDate = r.Details.LastOrDefault()?.ValueDate;
|
||
return View("margin_template_v2DefaultEdit", r);
|
||
}
|
||
|
||
public ActionResult margin_template_v2ClientEdit(string enid)
|
||
{
|
||
if (string.IsNullOrEmpty(enid) || enid == "0")
|
||
{
|
||
if (PS.Config.Company == CompanyEnum.广发商贸)
|
||
{
|
||
return View(new margin_template_v2() { ValueDate = new DateTime(2020, 1, 1), RuleType = (int)MarginRuleTypeEnum.默认预付金规则, Comments = "考虑了14种场景下的Span算法与Delta算法比较的通用预付金规则,该规则可以调整Span涨跌幅度", IsValid = true });
|
||
}
|
||
//预付金模板V2迁移:本部署(TRS)可选规则只有 无预付金/区间追保结构,新模板默认无预付金。
|
||
//原始代码如下(如需恢复,用注释行整行替换下面的 return):
|
||
//return View(new margin_template_v2() { ValueDate = new DateTime(2020, 1, 1), RuleType = (int)MarginRuleTypeEnum.触发条件预付金规则1, Comments = "预付金占用为成交名义本金的固定比例,加标的跌幅的市值(跌幅达到一定条件触发)", IsValid = true });
|
||
return View(new margin_template_v2() { ValueDate = new DateTime(2020, 1, 1), RuleType = (int)MarginRuleTypeEnum.无预付金, Comments = "预付金占用为零。", IsValid = true });
|
||
|
||
}
|
||
|
||
var intid = DecryptInt(enid);
|
||
var r = yldb.margin_template_v2.AsNoTracking().FirstOrDefault(n => n.id == intid);
|
||
if (r == null)
|
||
{
|
||
return ShowError("数据不存在");
|
||
}
|
||
|
||
r.Details = yldb.margin_template_detail.Where(x => x.MarginTemplateId == r.id).OrderBy(x => x.ValueDate).ToList();
|
||
if (!r.Details.Any())
|
||
{
|
||
r.Details.Add(new margin_template_detail() { ValueDate = new DateTime(2020, 1, 1) });
|
||
}
|
||
r.LatestValueDate = r.Details.LastOrDefault()?.ValueDate;
|
||
return View(r);
|
||
}
|
||
|
||
public ActionResult margin_template_v2ClientCopy(string enid)
|
||
{
|
||
var intid = DecryptInt(enid);
|
||
var r = yldb.margin_template_v2.AsNoTracking().FirstOrDefault(n => n.id == intid);
|
||
if (r == null)
|
||
{
|
||
return ShowError("数据不存在");
|
||
}
|
||
|
||
r.Details = yldb.margin_template_detail.Where(x => x.MarginTemplateId == r.id).OrderBy(x => x.ValueDate).ToList();
|
||
r.id = 0;
|
||
if (!r.Details.Any())
|
||
{
|
||
r.Details.Add(new margin_template_detail() { ValueDate = new DateTime(2020, 1, 1) });
|
||
}
|
||
else
|
||
{
|
||
r.Details.ForEach(x =>
|
||
{
|
||
x.MarginTemplateId = 0;
|
||
x.id = 0;
|
||
});
|
||
}
|
||
r.LatestValueDate = r.Details.LastOrDefault()?.ValueDate;
|
||
return View("margin_template_v2ClientEdit", r);
|
||
}
|
||
|
||
public ActionResult margin_template_v2Edit(string enid)
|
||
{
|
||
if (string.IsNullOrEmpty(enid) || enid == "0")
|
||
{
|
||
|
||
return View(new margin_template_v2() { ValueDate = new DateTime(2020, 1, 1), RuleType = (int)MarginRuleTypeEnum.无预付金, Comments = "预付金占用为零。", IsValid = true });
|
||
|
||
}
|
||
|
||
var intid = DecryptInt(enid);
|
||
var r = yldb.margin_template_v2.AsNoTracking().FirstOrDefault(n => n.id == intid);
|
||
if (r == null)
|
||
{
|
||
return ShowError("数据不存在");
|
||
}
|
||
|
||
r.Details = yldb.margin_template_detail.Where(x => x.MarginTemplateId == r.id).OrderBy(x => x.ValueDate).ToList();
|
||
if (!r.Details.Any())
|
||
{
|
||
r.Details.Add(new margin_template_detail() { ValueDate = new DateTime(2020, 1, 1) });
|
||
}
|
||
r.LatestValueDate = r.Details.LastOrDefault()?.ValueDate;
|
||
return View(r);
|
||
}
|
||
|
||
public ActionResult margin_template_v2Copy(string enid)
|
||
{
|
||
var intid = DecryptInt(enid);
|
||
var r = yldb.margin_template_v2.AsNoTracking().FirstOrDefault(n => n.id == intid);
|
||
if (r == null)
|
||
{
|
||
return ShowError("数据不存在");
|
||
}
|
||
|
||
r.Details = yldb.margin_template_detail.Where(x => x.MarginTemplateId == r.id).OrderBy(x => x.ValueDate).ToList();
|
||
r.id = 0;
|
||
if (!r.Details.Any())
|
||
{
|
||
r.Details.Add(new margin_template_detail() { ValueDate = new DateTime(2020, 1, 1) });
|
||
}
|
||
else
|
||
{
|
||
r.Details.ForEach(x =>
|
||
{
|
||
x.MarginTemplateId = 0;
|
||
x.id = 0;
|
||
});
|
||
}
|
||
r.LatestValueDate = r.Details.LastOrDefault()?.ValueDate;
|
||
return View("margin_template_v2Edit", r);
|
||
}
|
||
|
||
public JsonResult margin_template_v2Delete(string enid)
|
||
{
|
||
var intid = DecryptInt(enid);
|
||
var tradeMarginTemplates = yldb.trade_margin_template.Where(x => x.MarginTemplateId == intid);
|
||
if (tradeMarginTemplates.Any())
|
||
{
|
||
throw new Exception("该自定义预付金规则已经有交易使用了,不可被删除");
|
||
}
|
||
else
|
||
{
|
||
var marginTemplate = yldb.margin_template_v2.Find(intid);
|
||
if (marginTemplate != null)
|
||
{
|
||
yldb.margin_template_v2.Remove(marginTemplate);
|
||
yldb.SaveChanges();
|
||
}
|
||
}
|
||
|
||
return JsonSuccess("删除成功");
|
||
}
|
||
|
||
[HttpPost]
|
||
public JsonResult saveMarginTemplateV2(margin_template_v2 marginTemplate)
|
||
{
|
||
try
|
||
{
|
||
marginTemplate.BookIds = margin_template_v2.NormalizeBookIds(marginTemplate.BookIds);
|
||
ValidateBookIds(marginTemplate.BookIds);
|
||
|
||
if (string.IsNullOrEmpty(marginTemplate.TradeTypes))
|
||
{
|
||
throw new Exception("默认规则的适用结构不能为空");
|
||
}
|
||
|
||
if (yldb.margin_template_v2.Any(x => x.id != marginTemplate.id && x.Name == marginTemplate.Name && x.IsDefault == marginTemplate.IsDefault && x.IsForClient == marginTemplate.IsForClient))
|
||
{
|
||
throw new Exception("不能取重复的预付金名称");
|
||
}
|
||
|
||
if (marginTemplate.IsDefault && !marginTemplate.IsForClient && marginTemplate.IsValid)
|
||
{
|
||
//一套规则多套默认参数的互斥验证
|
||
if (marginTemplate.DefaultParameterType != 0)
|
||
{
|
||
//验证不同规则是否已经存在该交易类型
|
||
var marginTemplateDefault = yldb.margin_template_v2
|
||
.Where(x => x.id != marginTemplate.id && x.IsDefault && x.IsValid && !x.IsForClient && x.RuleType != marginTemplate.RuleType && x.ValueDate == marginTemplate.ValueDate)
|
||
.ToList()
|
||
.Where(x => margin_template_v2.AreBookScopesOverlapping(x.BookIds, marginTemplate.BookIds));
|
||
var tradeTypes = margin_template_v2.GetOverlappingTradeTypes(marginTemplateDefault, marginTemplate.BookIds).ToList();
|
||
|
||
//验证相同规则相同的默认参数类型,是否包含相同的适用结构
|
||
var sameRuleParameterRecords = yldb.margin_template_v2
|
||
.Where(x => x.id != marginTemplate.id && x.IsDefault && x.IsValid && !x.IsForClient && x.RuleType == marginTemplate.RuleType && x.DefaultParameterType == marginTemplate.DefaultParameterType && (x.BuySellType & marginTemplate.BuySellType) > 0 && (x.MarginScenarios & marginTemplate.MarginScenarios) > 0 && x.ValueDate == marginTemplate.ValueDate)
|
||
.ToList()
|
||
.Where(x => margin_template_v2.AreBookScopesOverlapping(x.BookIds, marginTemplate.BookIds));
|
||
tradeTypes.AddRange(margin_template_v2.GetOverlappingTradeTypes(sameRuleParameterRecords, marginTemplate.BookIds));
|
||
|
||
tradeTypes.ForEach(x =>
|
||
{
|
||
if (marginTemplate.TradeTypes.Contains(x))
|
||
{
|
||
throw new Exception("默认规则同一天不能有重复的适用结构:" + x);
|
||
}
|
||
});
|
||
}
|
||
else
|
||
{
|
||
var marginTemplateDefault = yldb.margin_template_v2
|
||
.Where(x => x.id != marginTemplate.id && x.IsDefault && x.IsValid && !x.IsForClient && (x.BuySellType & marginTemplate.BuySellType) > 0 && (x.MarginScenarios & marginTemplate.MarginScenarios) > 0 && x.ValueDate == marginTemplate.ValueDate)
|
||
.ToList()
|
||
.Where(x => margin_template_v2.AreBookScopesOverlapping(x.BookIds, marginTemplate.BookIds));
|
||
var tradeTypes = margin_template_v2.GetOverlappingTradeTypes(marginTemplateDefault, marginTemplate.BookIds).ToList();
|
||
tradeTypes.ForEach(x =>
|
||
{
|
||
if (marginTemplate.TradeTypes.Contains(x))
|
||
{
|
||
throw new Exception("默认规则不能有相同的适用结构:" + x);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
//区间追保结构 + 按资产类型分类时,期限档仅允许利率债配置(2026-08-27 裁定:ETF 无期限概念,
|
||
//可转债 ETF/科创债 ETF 的分档白名单已移除,所有 ETF 子类/基金行一律单套参数不分档),防止非分档类型误配期限档;
|
||
//该校验按行生效,与明细行数无关(单行明细同样拦截);
|
||
//按严格掩码判定:行标的类型位必须全部落在可分档类型内(混合标志位如 利率债|信用债 配期限档同样拦截,与取数侧整行期限档过滤语义一致);
|
||
//UnderlyingType 为空的通配行允许配期限档(取数侧期限档过滤在前、通配匹配在后,语义自洽)
|
||
if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.区间追保结构
|
||
&& marginTemplate.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType)
|
||
{
|
||
var enabledMask = ConsMarginTerm.TermTierEnabledUnderlyingTypes.Aggregate(UnderlyingTypeEnum.None, (a, t) => a | t);
|
||
foreach (var detail in marginTemplate.Details)
|
||
{
|
||
var etfKind = detail.SpanConfig?.EtfKind;
|
||
var bondTerm = detail.SpanConfig?.BondTerm;
|
||
//ETF 子类行:仅允许纯基金行(配置页子类选择器也只在纯基金区块出现)
|
||
if (!string.IsNullOrEmpty(etfKind) && detail.UnderlyingType != UnderlyingTypeEnum.Fund)
|
||
{
|
||
throw new Exception("配置了 ETF 子类(" + etfKind + ")的参数组资产类型必须为 基金及基金专户");
|
||
}
|
||
//ETF 子类不分期限档(对存量 4 档子类行回存给出精确报错;基金通配行由下面的掩码校验拦截)
|
||
if (!string.IsNullOrEmpty(etfKind) && !string.IsNullOrEmpty(bondTerm))
|
||
{
|
||
throw new Exception("ETF 子类 " + etfKind + " 不分期限档,参数行不能配置期限档");
|
||
}
|
||
if (!string.IsNullOrEmpty(bondTerm) && (detail.UnderlyingType & ~enabledMask) != UnderlyingTypeEnum.None)
|
||
{
|
||
throw new Exception("标的类型不允许配置期限档:" + UnderlyingTypeUtil.GetDesc(detail.UnderlyingType));
|
||
}
|
||
}
|
||
}
|
||
|
||
//BUG-13:期限档值须为合法四档(页面为固定区块正常到不了这里,接口直传非法档值会成取数侧永远匹配不到的死配置)
|
||
if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.区间追保结构)
|
||
{
|
||
foreach (var detail in marginTemplate.Details)
|
||
{
|
||
var bondTerm = detail.SpanConfig?.BondTerm;
|
||
if (!string.IsNullOrEmpty(bondTerm) && !ConsMarginTerm.IsValidTerm(bondTerm))
|
||
{
|
||
throw new Exception("非法的利率债期限档:" + bondTerm + "(允许值:" + string.Join(" / ", ConsMarginTerm.AllowedTerms) + ")");
|
||
}
|
||
}
|
||
}
|
||
|
||
//区间追保结构:客户原型全量手工录入的预警线/平仓线与多空各4层追保区间校验。
|
||
//预警线/平仓线/两层区间全部为空视为未配置(兼容存量 x/y 过渡),任一值存在即按规则校验:
|
||
//数值非负;同层上下边界都有时上边界>下边界;单层部分填写报错提示完整填写
|
||
//(第1层边界特殊:多头第1层只需下边界Lower、空头第1层只需上边界Upper);
|
||
//多头各层下边界逐层严格递减、空头各层上边界逐层严格递增(填了才比)。
|
||
if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.区间追保结构)
|
||
{
|
||
for (var i = 0; i < marginTemplate.Details.Count; i++)
|
||
{
|
||
var cfg = marginTemplate.Details[i].SpanConfig;
|
||
if (cfg == null)
|
||
{
|
||
continue;
|
||
}
|
||
var longSpans = cfg.LongSpans ?? new List<SpanTierConfig>();
|
||
var shortSpans = cfg.ShortSpans ?? new List<SpanTierConfig>();
|
||
var hasAnyValue = cfg.WarnLine.HasValue || cfg.CloseLine.HasValue
|
||
|| longSpans.Any(t => t != null && (t.Lower.HasValue || t.Upper.HasValue || t.AmountRate.HasValue))
|
||
|| shortSpans.Any(t => t != null && (t.Lower.HasValue || t.Upper.HasValue || t.AmountRate.HasValue));
|
||
if (!hasAnyValue)
|
||
{
|
||
continue;
|
||
}
|
||
var label = "参数组" + (i + 1);
|
||
//区块化结构下利率债区块占连续4行(固定4档),报错带上期限档便于定位
|
||
if (!string.IsNullOrEmpty(cfg.BondTerm))
|
||
{
|
||
label += "(利率债期限档 " + cfg.BondTerm + ")";
|
||
}
|
||
if (cfg.WarnLine < 0 || cfg.CloseLine < 0)
|
||
{
|
||
throw new Exception(label + ":预警线/平仓线不能为负数");
|
||
}
|
||
ValidateSpanTiers(longSpans, true, label);
|
||
ValidateSpanTiers(shortSpans, false, label);
|
||
}
|
||
}
|
||
|
||
if (marginTemplate.Details.Count() > 1)
|
||
{
|
||
if (marginTemplate.RuleType == (int)MarginRuleTypeEnum.区间追保结构)
|
||
{
|
||
//区间追保结构:按 (利率债期限档, ETF细分) 元组分组校验——期限档仅利率债有值(其余恒空),
|
||
//EtfKind 为基金行的子类键;同一分组内标的类型不允许重复,不同分组允许相同标的类型;
|
||
//通配行(空/全部,UnderlyingType=None/All)两两之间位与恒为 0 检不出,须单独拦截(BUG-09)——
|
||
//否则取数侧通配兜底 matched.First() 命中不确定
|
||
foreach (var termGroup in marginTemplate.Details.GroupBy(x => (x.SpanConfig?.BondTerm ?? "", x.SpanConfig?.EtfKind ?? "")))
|
||
{
|
||
var termUnderlyingType = UnderlyingTypeEnum.None;
|
||
var wildcardSeen = false;
|
||
foreach (var detail in termGroup)
|
||
{
|
||
var isWildcard = detail.UnderlyingType == UnderlyingTypeEnum.None || detail.UnderlyingType == UnderlyingTypeEnum.All;
|
||
if (isWildcard && wildcardSeen)
|
||
{
|
||
if (marginTemplate.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.None)
|
||
{
|
||
throw new Exception("同一利率债期限档下存在重复的参数组");
|
||
}
|
||
throw new Exception("同一分组下存在重复的兜底参数组(标的类型为空或全部)");
|
||
}
|
||
if (isWildcard)
|
||
{
|
||
wildcardSeen = true;
|
||
}
|
||
if ((detail.UnderlyingType & termUnderlyingType) > 0)
|
||
{
|
||
if (marginTemplate.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.None)
|
||
{
|
||
throw new Exception("同一利率债期限档下存在重复的参数组");
|
||
}
|
||
throw new Exception("存在重复的标的类型:" + UnderlyingTypeUtil.GetDesc(detail.UnderlyingType & termUnderlyingType));
|
||
}
|
||
|
||
termUnderlyingType |= detail.UnderlyingType;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var underlyingType = UnderlyingTypeEnum.None;
|
||
foreach (var detail in marginTemplate.Details)
|
||
{
|
||
if (detail.UnderlyingType == UnderlyingTypeEnum.None)
|
||
{
|
||
throw new Exception("标的类型不能为空");
|
||
}
|
||
if ((detail.UnderlyingType & underlyingType) > 0)
|
||
{
|
||
throw new Exception("存在重复的标的类型:" + UnderlyingTypeUtil.GetDesc(detail.UnderlyingType & underlyingType));
|
||
}
|
||
|
||
underlyingType |= detail.UnderlyingType;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (PS.Config.Company == CompanyEnum.广发商贸)
|
||
{
|
||
if (marginTemplate.Details.Count() > 0)
|
||
{
|
||
if (marginTemplate.Details[0].SpanConfig.InitialMarginFactor < 0 || marginTemplate.Details[0].SpanConfig.CoefficientFactor < 0)
|
||
{
|
||
throw new Exception("只能填入大于等于0的数字");
|
||
}
|
||
}
|
||
}
|
||
|
||
var isAddNew = marginTemplate.id == 0;
|
||
if (isAddNew)
|
||
{
|
||
marginTemplate.OptDate = DateTime.Now;
|
||
marginTemplate.OptId = CurUser.UserId;
|
||
marginTemplate.OptName = CurUser.UserName;
|
||
yldb.margin_template_v2.Add(marginTemplate);
|
||
}
|
||
else
|
||
{
|
||
var r = yldb.margin_template_v2.Find(marginTemplate.id);
|
||
r.BuySellType = marginTemplate.BuySellType;
|
||
r.MarginScenarios = marginTemplate.MarginScenarios;
|
||
r.UnderlyingSeperateType = marginTemplate.UnderlyingSeperateType;
|
||
r.UnderlyingSeperateComments = marginTemplate.UnderlyingSeperateComments;
|
||
r.IsValid = marginTemplate.IsValid;
|
||
r.ValueDate = marginTemplate.ValueDate;
|
||
r.MarginType = marginTemplate.MarginType;
|
||
r.Name = marginTemplate.Name;
|
||
r.IsDefault = marginTemplate.IsDefault;
|
||
r.IsForClient = marginTemplate.IsForClient;
|
||
r.BookIds = marginTemplate.BookIds;
|
||
r.Comments = marginTemplate.Comments;
|
||
r.RuleType = marginTemplate.RuleType;
|
||
r.TradeTypes = marginTemplate.TradeTypes;
|
||
r.OptDate = DateTime.Now;
|
||
r.OptId = CurUser.UserId;
|
||
r.OptName = CurUser.UserName;
|
||
}
|
||
|
||
var detailIds = marginTemplate.Details.Select(x => x.id).ToArray();
|
||
var details = yldb.margin_template_detail.Where(x => !detailIds.Contains(x.id) && x.MarginTemplateId == marginTemplate.id);
|
||
yldb.margin_template_detail.RemoveRange(details);
|
||
|
||
yldb.SaveChanges();
|
||
|
||
//BUG-14:明细生效日期回写——前端新增行只带占位日期(2020-01-01 前后),此前不回写使新行(含模板拷贝)
|
||
//落库 0001-01-01,被取数侧 Max(ValueDate) 分组静默丢弃;占位日期对齐本次提交行的最新生效日期,无有效行时用模板生效日期
|
||
//明细生效日期跟主表走:主表有真实生效日期时全部行强制对齐(含复制带入的源模板行日期、行上残留的旧日期),
|
||
//否则只改主表日期会出现"模板已生效但明细未生效"的取数断档;主表仍为占位日期时保持原兜底
|
||
var placeholderDate = new DateTime(2020, 1, 1);
|
||
var detailValueDate = marginTemplate.ValueDate > placeholderDate
|
||
? marginTemplate.ValueDate
|
||
: marginTemplate.Details
|
||
.Where(d => d.ValueDate > placeholderDate)
|
||
.Select(d => d.ValueDate)
|
||
.DefaultIfEmpty(placeholderDate)
|
||
.Max();
|
||
|
||
marginTemplate.Details.ForEach(x =>
|
||
{
|
||
var detail = yldb.margin_template_detail.Find(x.id);
|
||
|
||
if (detail == null)
|
||
{
|
||
detail = new margin_template_detail();
|
||
yldb.margin_template_detail.Add(detail);
|
||
}
|
||
detail.ValueDate = detailValueDate;
|
||
detail.MarginTemplateId = marginTemplate.id;
|
||
detail.GroupRatio = x.GroupRatio;
|
||
detail.MarginRatio1 = x.MarginRatio1;
|
||
detail.MarginRatio2 = x.MarginRatio2;
|
||
detail.MarginRatio3 = x.MarginRatio3;
|
||
detail.MarginRatio4 = x.MarginRatio4;
|
||
detail.IsIntrinsicValue = x.IsIntrinsicValue;
|
||
detail.PriceLimitType = x.PriceLimitType;
|
||
detail.UsePositionStockEqvNotional = x.UsePositionStockEqvNotional;
|
||
detail.PositionUnderlyingAmount = x.PositionUnderlyingAmount;
|
||
detail.RedeemPriceLimit = x.RedeemPriceLimit;
|
||
detail.RedeemPriceMax = x.RedeemPriceMax;
|
||
detail.IsUseStrike = x.IsUseStrike;
|
||
detail.IsNeedCompare = x.IsNeedCompare;
|
||
detail.IsUsePriceLimit = x.IsUsePriceLimit;
|
||
detail.SingleStockEqvNotional = x.SingleStockEqvNotional;
|
||
detail.StockEqvNotional = x.StockEqvNotional;
|
||
detail.UnderlyingType = x.UnderlyingType;
|
||
detail.ComparedInitialMarginRate = x.ComparedInitialMarginRate;
|
||
detail.OptDate = DateTime.Now;
|
||
detail.OptId = CurUser.UserId;
|
||
detail.OptName = CurUser.UserName;
|
||
detail.SpanConfigJson = JsonHelper.Serialize(x.SpanConfig);
|
||
|
||
yldb.SaveChanges();
|
||
});
|
||
|
||
|
||
return JsonSuccess("更新成功");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return JsonError(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 规则15 区间追保结构单方向(多头/空头)4层追保区间校验,报错文案风格与"存在重复的标的类型:xxx"一致。
|
||
/// isLong=true 多头:第1层只需下边界 Lower(上不封顶),各层 Lower 逐层严格递减;
|
||
/// isLong=false 空头:第1层只需上边界 Upper(下不设限),各层 Upper 逐层严格递增。
|
||
/// </summary>
|
||
private static void ValidateSpanTiers(List<SpanTierConfig> tiers, bool isLong, string label)
|
||
{
|
||
var direction = isLong ? "多头方向" : "空头方向";
|
||
double? prevBoundary = null;
|
||
for (var n = 0; n < tiers.Count; n++)
|
||
{
|
||
var tier = tiers[n];
|
||
if (tier == null)
|
||
{
|
||
continue;
|
||
}
|
||
var tierLabel = label + ":" + direction + "第" + (n + 1) + "层";
|
||
if (tier.Lower < 0 || tier.Upper < 0 || tier.AmountRate < 0)
|
||
{
|
||
throw new Exception(tierLabel + "追保区间不能为负数");
|
||
}
|
||
var hasAny = tier.Lower.HasValue || tier.Upper.HasValue || tier.AmountRate.HasValue;
|
||
if (!hasAny)
|
||
{
|
||
continue;
|
||
}
|
||
if (tier.Lower.HasValue && tier.Upper.HasValue && tier.Upper <= tier.Lower)
|
||
{
|
||
throw new Exception(tierLabel + "追保区间上边界必须大于下边界");
|
||
}
|
||
//第1层边界特殊:多头只需 Lower、空头只需 Upper;其余层 Lower/Upper/AmountRate 需完整填写
|
||
if (n == 0)
|
||
{
|
||
if ((isLong && !tier.Lower.HasValue) || (!isLong && !tier.Upper.HasValue) || !tier.AmountRate.HasValue)
|
||
{
|
||
throw new Exception(tierLabel + "请完整填写追保价格区间与追保金额");
|
||
}
|
||
}
|
||
else if (!tier.Lower.HasValue || !tier.Upper.HasValue || !tier.AmountRate.HasValue)
|
||
{
|
||
throw new Exception(tierLabel + "请完整填写追保价格区间与追保金额");
|
||
}
|
||
//逐层单调性:多头 Lower 逐层严格递减、空头 Upper 逐层严格递增(填了才比)
|
||
var boundary = isLong ? tier.Lower : tier.Upper;
|
||
if (boundary.HasValue)
|
||
{
|
||
if (prevBoundary.HasValue && (isLong ? boundary >= prevBoundary : boundary <= prevBoundary))
|
||
{
|
||
throw new Exception(tierLabel + (isLong ? "追保区间下边界须小于上一层的下边界" : "追保区间上边界须大于上一层的上边界"));
|
||
}
|
||
prevBoundary = boundary;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ValidateBookIds(string bookIds)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(bookIds))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var ids = margin_template_v2.ParseBookIds(bookIds)
|
||
.Select(x => int.Parse(x))
|
||
.ToList();
|
||
var existingIds = yldb.assetunit
|
||
.Where(x => ids.Contains(x.id))
|
||
.Select(x => x.id)
|
||
.ToList();
|
||
var missingIds = ids.Except(existingIds).ToList();
|
||
if (missingIds.Any())
|
||
{
|
||
throw new Exception("适用簿记账户组包含不存在的簿记账户ID:" + string.Join(",", missingIds));
|
||
}
|
||
}
|
||
|
||
[HttpPost]
|
||
public JsonResult deleteEditmargin_template_v2(string enid)
|
||
{
|
||
var id = DataProtectHelper.DecryptInt(enid);
|
||
var r = yldb.margin_template_v2.Find(id);
|
||
|
||
if (r == null)
|
||
{
|
||
return JsonError("删除失败,找不到该预付金规则");
|
||
}
|
||
|
||
yldb.margin_template_v2.Remove(r);
|
||
var details = yldb.margin_template_detail.Where(x => x.MarginTemplateId == r.id);
|
||
if (details.Any())
|
||
{
|
||
yldb.margin_template_detail.RemoveRange(details);
|
||
}
|
||
|
||
yldb.SaveChanges();
|
||
|
||
return JsonSuccess("删除成功");
|
||
}
|
||
|
||
public static List<SelectListItem> GetAllRuleType()
|
||
{
|
||
var ruleTypeList = new List<SelectListItem>();
|
||
if (PS.Config.Is厦门象屿)
|
||
{
|
||
ruleTypeList.Add(new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.无预付金.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.无预付金).ToString()
|
||
});
|
||
ruleTypeList.Add(new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.交易所基本规则.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.交易所基本规则).ToString()
|
||
});
|
||
|
||
}
|
||
else
|
||
{
|
||
//预付金模板V2迁移:本部署(TRS)预付金规则只开放 无预付金 和 区间追保结构 两种(2026-08 按业务要求)。
|
||
//原"其他公司显示完整规则列表"的代码以块注释形式保留在下方,如需恢复取消注释即可。
|
||
ruleTypeList.Add(new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.无预付金.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.无预付金).ToString()
|
||
});
|
||
ruleTypeList.Add(new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.区间追保结构.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.区间追保结构).ToString()
|
||
});
|
||
/* 原完整规则列表(保留备查):
|
||
ruleTypeList.AddRange(new List<SelectListItem>(){
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.无预付金.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.无预付金).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.按固定利率.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.按固定利率).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.按浮动盈亏.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.按浮动盈亏).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.类香草预付金规则.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.类香草预付金规则).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.自动赎回买入预付金规则.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.自动赎回买入预付金规则).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.触发条件预付金规则1.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.触发条件预付金规则1).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.触发条件预付金规则2.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.触发条件预付金规则2).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.保底预付金规则.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.保底预付金规则).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.含赔付预付金计算规则.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.含赔付预付金计算规则).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.触发条件预付金规则3.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.触发条件预付金规则3).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.标准SPAN.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.标准SPAN).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.买入自动赎回规则1.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.买入自动赎回规则1).ToString()
|
||
},
|
||
new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.卖出自动赎回规则1.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.卖出自动赎回规则1).ToString()
|
||
}
|
||
});
|
||
*/
|
||
}
|
||
if (PS.Config.Company == CompanyEnum.宏源)
|
||
{
|
||
ruleTypeList.Add(new SelectListItem()
|
||
{
|
||
Text = MarginRuleTypeEnum.单独计算规则.ToString(),
|
||
Value = ((int)MarginRuleTypeEnum.单独计算规则).ToString()
|
||
});
|
||
}
|
||
return ruleTypeList;
|
||
}
|
||
|
||
|
||
}
|
||
}
|