Files
zszq-trs/YLErpDAL/Modules/SwapModule/SwapTradeService.cs
T

1909 lines
91 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using BaseOUDAL;
using ClosedXML.Report.Options;
using Confluent.Kafka;
using CsvHelper;
using MoreLinq;
using Newtonsoft.Json;
using NPOI.POIFS.NIO;
using Org.BouncyCastle.Ocsp;
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos;
using Qdp.Foundation.Implementations;
using Qdp.Pricing.Ecosystem.Trade.FixedIncome;
using Qdp.Pricing.Library.Base.Utilities;
using Qdp.Pricing.Library.Common.Products.Rates;
using System;
using System.Data;
using System.Linq.Expressions;
using System.Text;
using YLErp.BLL;
using YLErp.BLL.Eod;
using YLErp.Configuration;
using YLErp.Configuration.Enums;
using YLErp.CustomizedBizLogic;
using YLErp.DBModels;
using YLErp.DBModels.Enums;
using YLErp.Enums;
using YLErp.Helpers;
using YLErp.Model;
using YLErp.Model.Enum;
using YLErp.Models;
using YLErp.Modules.DataProviderModule;
using YLErp.Modules.EodModule;
using YLErp.Modules.RiskModule;
using YLErp.Modules.SalesModule;
using YLErp.Modules.TradeModule;
using YLErp.Modules.TradeModule.DealModule;
using YLErp.Modules.TradeModule.DocGenerateModule;
using YLErp.Modules.TradeModule.QueryModule;
using YLErp.Modules.UnderlyingModule;
using YLErp.QdpModule;
using static YLErp.ConsGlobal;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// 新版互换交易服务
/// </summary>
public class SwapTradeService : SwapTradeBaseService
{
public SwapTradeService(OptUserInfo optUser) : base(optUser)
{
}
public SwapTradeService(YLBaseService baseService) : base(baseService)
{
}
private static decimal? RoundSwapBondNetPriceAndYtm(decimal? value)
{
return value.HasValue
? Math.Round(value.Value, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero)
: null;
}
#region 互换交易保存
/// <summary>
/// 新版收益互换预付金校验
/// </summary>
public string ValidateMargin(trade req)
{
var errorMsg = string.Empty;
var client = DataCacheProvider.GetClientDataSource().GetData(req.ClientId);
if (!client.DerivativesInvestmentVarieties.Contains((int)DerivativesInvestmentVarietiesEnum.场外互换 + ""))
{
return $"客户:{client.Name}未设置交易种类“场外互换”,无法生成互换交易!";
}
//收益互换初始预付金校验
errorMsg = checkMarginRate(req);
return errorMsg;
}
/// <summary>
/// 互换交易保存
/// ignoreMoneyCheck=true:资金不足特批放行(additionalProcessing=LackOfMoneycontroller 按 系统参数"允许交易特批"
/// 判定后传入,与确认/审批环节 ignoreMoneyCheck 同口径);确认成交时资金校验独立再跑,特批链路在那边仍有兜底。
/// </summary>
/// <param name="td"></param>
/// <param name="ignoreMoneyCheck"></param>
/// <returns></returns>
public trade SaveTrade(trade req, bool ignoreMoneyCheck = false, bool allowMarginCreditSplit = false)
{
//资金来源必填(现金/授信,默认现金):保存前归一,兜住 DMA 自动建仓等绕过录入页的链路
if (req.TradeType == "收益互换" && string.IsNullOrWhiteSpace(req.MarginFundSource))
{
req.MarginFundSource = ConsFundTag.Cash;
}
// §2.3 保存前授信拆单(2026-08-26 业务确认):保存检查授信→不足拦截(UI 确认)→拆完再保存。
// 特批(ignoreMoneyCheck)语义为全现金不占授信,跳过拆单
if (!ignoreMoneyCheck && req.TradeType == "收益互换")
{
new SwapFundTagService(this).PreSplitMarginLegsByCredit(req, allowMarginCreditSplit);
}
var um = checkUnderlying(req);
trade dbTrade = new trade();
//交易保存处理(PrepareInitialMargin 在此把 trade_Initial_Margin 折算进 req.InitialMargin
//资金校验须在其后取值,否则互换表单不填平铺 InitialMargin 时校验会按 0 放行)
var tradeNumberGenerated = PrepareTrade(req, TradeSourceEnum.系统交易, um);
// R4 簿记资金校验:可用资金(现金结存+授信−已使用授信,口径见 RealtimePnlCalc.TradeCanBeConfirm
// 需覆盖 应付预付金+成交金额;不足时抛 TradeLackOfMoneyException——controller 依"允许交易特批"开关
// 走 AdditionalProcessing/LackOfMoney 特批协议(与确认/审批环节一致)或按保存失败拦截。
// ExerciseDate 为空(异常数据)时跳过该校验,避免 TradeCanBeConfirm 内部解引用抛错。
if (!ignoreMoneyCheck && req.ExerciseDate.HasValue && !RealtimePnlCalc.TradeCanBeConfirm(req.ClientId, req, out var fundErrorMsg))
{
throw new TradeLackOfMoneyException(fundErrorMsg);
}
var trans = DbContext.Database.BeginTransaction();
try
{
var isAddNew = req.id == 0;
dbTrade = isAddNew ? InnerSaveNewTrade(req, tradeNumberGenerated) : InnerSaveEditTrade(req, out var changeConfirmStatus);
if (req.SalesCommission != null)
{
req.SalesCommission.ClientId = dbTrade.ClientId;
}
if (PS.Config.ErpElement.SalesCommissionCalculation == "默认" || PS.Config.ErpElement.SalesCommissionCalculation == "" || PS.Config.ErpElement.SalesCommissionCalculation == null)
{
new SalesCommissionDataService(this).SaveTradeCommission(dbTrade.id, req.SalesCommission, false);
}
else if (PS.Config.ErpElement.SalesCommissionCalculation == "公式1")
{
if (req.SalesCommission?.SalesIds != null && req.SalesCommission.SalesIds.Any())
{
new SalesCommissionDetailDataService(this).SaveTradeCommissionDetail(dbTrade.id, req.SalesCommission.ClientId, req.SalesCommission.Commission, req.SalesCommission.CommissionFixed, req.SalesCommission.SalesIds, false);
}
else
{
new SalesCommissionDetailDataService(this).RemoveTradeCommissionDetail(dbTrade.id);
}
}
//保证金模板V2迁移:按交易页选择的保证金模板(MarginTemplateName 存模板V2名称)维护交易模板绑定
SyncTradeMarginTemplate(dbTrade);
trans.Commit();
}
finally
{
trans.Dispose();
}
return dbTrade;
}
/// <summary>
/// 保证金模板V2迁移:按交易上选择的保证金模板(MarginTemplateName 存预付金模板V2的名称)维护 trade_margin_template 绑定。
/// 未选择或名称匹配不到有效自定义模板时仅清除旧绑定,不新增(保持无预付金占用的现状)。
/// </summary>
private void SyncTradeMarginTemplate(trade dbTrade)
{
var olds = DbContext.trade_margin_template.Where(x => x.TradeId == dbTrade.id).ToList();
DbContext.trade_margin_template.RemoveRange(olds);
if (!string.IsNullOrWhiteSpace(dbTrade.MarginTemplateName))
{
//匹配自定义+全局默认模板(重名时优先自定义),模板"适用结构"需包含 收益互换
var template = DbContext.margin_template_v2
.Where(x => x.Name == dbTrade.MarginTemplateName && !x.IsForClient && x.IsValid && x.TradeTypes.Contains("收益互换"))
.OrderBy(x => x.IsDefault)
.FirstOrDefault();
if (template != null)
{
DbContext.trade_margin_template.Add(new trade_margin_template()
{
TradeId = dbTrade.id,
ValueDate = new DateTime(2000, 1, 1),
MarginTemplateId = template.id,
IsLatest = true,
OptId = UserId,
OptName = UserName,
OptDate = DateTime.Now
});
}
}
DbContext.SaveChanges();
}
/// <summary>
/// 单标的生成开仓事件
/// </summary>
/// <param name="td"></param>
public void AddPositionEvent(trade td, string optLog)
{
var initials = DbContext.swap_position.Where(x => x.SwapTradeId == td.id && x.IsInitial && !x.Invalid).ToList();
if (td.StructureType == "多空组合")
{
initials = initials.Where(x => x.PosiDirection == 0).ToList();
}
new SwapFlowEventService(this).InitEvent(initials, td, optLog);
}
/// <summary>
/// 删除互换交易
/// </summary>
/// <param name="id"></param>
public void deleteTrade(int id, bool addLog = true)
{
var dbTrade = DbContext.trade.Find(id);
if (dbTrade == null)
{
throw new ServiceException("找不到交易信息");
}
dbTrade.OptId = UserId;
dbTrade.OptName = UserName;
dbTrade.OptDate = OptDate;
dbTrade.ValidState = ConsGlobal.InValid;
var tradeNumber = dbTrade.TradeNumber;
if (!string.IsNullOrWhiteSpace(dbTrade.TradeNumber))
{
dbTrade.TradeNumber = dbTrade.TradeNumber.Insert(0, "XX");
var contractBll = new TradeContractBLL(UserId, UserName);
contractBll.DeleteConfirmBook(dbTrade.id);
}
var swapPositions = DbContext.swap_position.Where(x => x.SwapTradeId == id).ToList();
swapPositions.ForEach(x =>
{
x.Invalid = true;
x.OptTime = OptDate;
});
new SwapEventService(this).AddSwapEventDate(DateTime.Now.Date, id, (int)SwapEventTypeEnum.删除, string.Empty, 0, false);
DeleteTradeDetials(id);
DeleteTradeCashInCashOut(id);
DbContext.SaveChanges();
}
private void DeleteTradeDetials(int tradeId)
{
var eodSwapPosiSql = $"{nameof(eod_swap_position.SwapTradeId)}={tradeId}";
var eodSwapSql = $"{nameof(eod_swap.SwapTradeId)}={tradeId}";
DbContext.BulkDelete<eod_swap_position>(eodSwapPosiSql);
DbContext.BulkDelete<eod_swap>(eodSwapSql);
}
public void DeleteTradeCashInCashOut(int tradeId)
{
var predicate_cashIncashOut = PredicateBuilder.Create<ClientCashInCashOut>(x => x.TradeId == tradeId);
var delCashInCashOutArr = DbContext.ClientCashInCashOut.Where(predicate_cashIncashOut);
DbContext.ClientCashInCashOut.RemoveRange(delCashInCashOutArr);
// R4:授信出入记录与资金记录同生命周期,随资金记录一并清理(回退到开仓/删除交易),
// 重新确认时按最新标签与额度重写,避免授信占用悬挂
new ClientCreditInoutService(this).RemoveByTrade(tradeId);
}
/// <summary>
/// 单标的初始化实时持仓
/// </summary>
/// <param name="tradeId"></param>
public void InitialPosition(trade td, bool save = false)
{
var swapPostions = DbContext.swap_position.Where(x => x.SwapTradeId == td.id);
var initials = swapPostions.Where(x => x.IsInitial);
var positions = swapPostions.Where(x => !x.IsInitial);
if (td.StructureType == "多空组合")
{
initials = initials.Where(x => x.PosiDirection == 0);
positions = swapPostions.Where(x => !x.IsInitial && x.PosiDirection == 0);
}
List<swap_position> _Positions = new List<swap_position>(positions);
DbContext.swap_position.RemoveRange(positions);
foreach (var item in initials)
{
if (string.IsNullOrEmpty(item.PosiNumber))
{
item.PosiNumber = $"{td.TradeNumber}-{item.id}";
}
if (td.StructureType == "多空组合")
{
var position = _Positions.FirstOrDefault(x => x.PositionId == item.id);
if (position == null)
{
position = item.Clone();
position.id = 0;
position.PositionId = item.id;
position.IsInitial = false;
DbContext.swap_position.Add(position);
}
}
else
{
var position = item.Clone();
position.id = 0;
position.PositionId = item.id;
position.IsInitial = false;
DbContext.swap_position.Add(position);
}
}
if (save)
{
DbContext.SaveChanges();
}
}
//新增交易保存
private trade InnerSaveNewTrade(trade req, bool tradeNumberGenerated)
{
var dbTrade = req.Clone();
// 新增场景同样从 swap_positions 取 InitYtm,无则默认 0,避免数据库 NOT NULL 约束报错
dbTrade.InitYtm = req.swap_positions.FirstOrDefault(p => p.InitYtm != null)?.InitYtm ?? 0;
DbContext.trade.Add(dbTrade);
InnerSaveTrade(true, dbTrade, "");
return dbTrade;
}
/// <summary>
/// 新增互换交易并确认开仓
/// </summary>
/// <param name="flowMerge"></param>
/// <param name="matuirityDate"></param>
/// <param name="client"></param>
/// <param name="asset"></param>
/// <param name="underlying"></param>
/// <param name="swapFloatRate"></param>
/// <returns></returns>
public trade NewSwapTrade(swap_flow_merge flowMerge,
Client client,
AssetUnit asset,
underlying_manager underlying,
SwapFloatRate swapFloatRate,
string clearingAgency,
string structureType = "普通债券类收益互换",
bool cashNeedAfter = false)
{
trade td;
using (var trans = BeginTransaction())
{
int SwapEndDays = UnderlyingHelper.GetApplicableMarginRate(client.id,underlying.UnderlyingCode,flowMerge.OccurTime)?.swap_days??14;
td = PrepareTrade(flowMerge, client, asset, underlying, SwapEndDays, structureType);
PrepareTradeExtend(flowMerge, td, underlying, swapFloatRate);
if (!GuolianContractNoGenerator.IsGuolianSwapTrade(td))
{
td.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(td, DbContext);
}
PrepareSwapTrade(td, TradeSourceEnum.系统交易, underlying);
DbContext.trade.Add(td);
DbContext.SaveChanges();
if (GuolianContractNoGenerator.TryGenerateTradeNumberAfterSave(DbContext, td))
{
DbContext.SaveChanges();
}
flowMerge.SwapTradeNo = td.TradeNumber;
td.trade_Initial_Margin = new trade_initial_margin()
{
TradeId = td.id,
MarginType = 1,
Direction = (int)SwapDirectionEnum.收取,
MarginValue = 0,
};
AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.MainProtocolCode, client.MainProtocolCode);
AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.SupProtocolCode, client.SupProtocolCode);
AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.TradingPlace, "柜台市场");
if (!string.IsNullOrEmpty(clearingAgency))
{
AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.ClearingAgency, clearingAgency);
}
TradeExtendJson tradeExtendJson = new TradeExtendJson()
{
FlowBookMode = (int)FlowBookModeEnum.先进先出,
FloatingPnlAnnualized = false,
NeedOpenFee = true,
OpenFeeType = 1,
Direction = 1,
InterestCalcMode = "10",
SettlementRules = cashNeedAfter ? 1 : 0,
};
if (structureType != "普通债券类收益互换")
{
tradeExtendJson.FlowBookMode = (int)FlowBookModeEnum.加权平均;
}
td.trade_extend = new trade_extend()
{
TradeId = td.id,
ExtendJson = JsonHelper.Serialize(tradeExtendJson)
};
DbContext.trade_initial_margin.Add(td.trade_Initial_Margin);
DbContext.trade_extend.Add(td.trade_extend);
foreach (var item in td.swap_positions)
{
item.SwapTradeId = td.id;
DbContext.swap_position.Add(item);
}
DbContext.SaveChanges();
trans.Commit();
}
new TradeConfirmService(UserInfo).SwapTradeConfirm(td, "流水自动簿记确认交易", true, flowMerge.OccurTime, "流水自动");
return td;
}
/// <summary>
/// 准备框架合约数据
/// </summary>
/// <param name="flowMerge"></param>
/// <param name="client"></param>
/// <param name="asset"></param>
/// <param name="underlying"></param>
/// <returns></returns>
private trade PrepareTrade(swap_flow_merge flowMerge, Client client, AssetUnit asset, underlying_manager underlying,int swapEndDays, string structureType = "普通债券类收益互换")
{
trade td = new trade()
{
TradeType = "收益互换",
BuySell = "卖出",
StartDate = flowMerge.OccurTime,
TradeDate = flowMerge.OccurTime,
TraderId = asset.TraderIdsInt.FirstOrDefault(),
TraderName = asset.TraderNamesList.FirstOrDefault(),
MarginTemplateName = null,
OpponentRole = "乙方",
StructureType = structureType,
InitialMargin = 0,
ClientId = client.id,
ClientNumber = client.Number,
ClientName = client.Name,
AssetId = asset.id,
AssetBookName = asset.Name,
Notional = Convert.ToDouble(flowMerge.TradingQtyAbs),
TradeAmount = Convert.ToDouble(flowMerge.TradingQtyAbs),
StockEqvNotional = Math.Round(Convert.ToDouble(flowMerge.TradingAmount), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero),
IsAutoGenerate = true,
};
if (flowMerge.SettleDate.HasValue)
{
td.StartDate = flowMerge.SettleDate.Value;
}
td.ExerciseDate = QdpCalendarHelper.GetNonHolidayDefore(td.StartDate.Value.AddDays(swapEndDays));
PrepareUnderlying(td, underlying);
PrepareSwapTradeClient(td, underlying);
td.TradeOldStatus = ConsTrade.新增待确认;
td.OptId = UserId;
td.OptName = UserName;
td.OptDate = OptDate;
td.CreateDate = OptDate;
td.ValidState = "Valid";
td.TradeSource = "系统交易";
td.TradeStatus = ConsTrade.确认成交;
td.InitYtm = RoundSwapBondNetPriceAndYtm(flowMerge.InitYtm) ?? 0;
return td;
}
/// <summary>
/// 准备框架合约明细数据
/// </summary>
/// <param name="flowMerge"></param>
/// <param name="td"></param>
/// <param name="underlying"></param>
/// <param name="swapFloatRate"></param>
private void PrepareTradeExtend(swap_flow_merge flowMerge, trade td, underlying_manager underlying, SwapFloatRate swapFloatRate)
{
swap_position floatPosition = new swap_position()
{
PosiDirection = (int)SwapDirectionEnum.支付,
PositionType = flowMerge.BsType,
UnderlyingCode = flowMerge.UnderlyingCode,
CountRatio = underlying.CountRatio,
ContractSize = Convert.ToDecimal(underlying.ContractSize),
PosiNetPrice = flowMerge.TradingAmountFeeAvgAbs,
PosiGrossPrice = Math.Round(
flowMerge.TradingAmountAvg,
underlying.IsBond() ? ConsGlobal.PriceRound : ConsGlobal.SwapDeliveryPriceRound,
MidpointRounding.AwayFromZero),
PosiNetFeePrice = flowMerge.TradingAmountNetFeeAvg ?? 0,
PosiNetNoFeePrice = flowMerge.TradingAmountNetAvg ?? 0,
PosiQuantity = flowMerge.TradingQtyAbs,
PosiNotionalValue = Math.Round(flowMerge.TradingAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero),
PosiTradingFeePending = flowMerge.TradingFeePending,
PosiTradingFee = 0,
PosiTradingFeeUnit = 0,
SwapTradeId = td.id,
PosiMatuirityDate = td.ExerciseDate.Value,
PosiStartDate = td.StartDate.Value,
IsInitial = true,
OptTime = DateTime.Now,
OptId = UserInfo.UserId,
OptName = UserInfo.UserName,
UnderlyingInstrumentType = underlying.UnderlyingInstrumentType,
InitYtm = RoundSwapBondNetPriceAndYtm(flowMerge.InitYtm)
};
td.swap_positions.Add(floatPosition);
swap_position interestPosition = new swap_position()
{
InterestDirection = flowMerge.BsType == (int)PositionTypeFlag.Long ? (int)SwapDirectionEnum.收取 : (int)SwapDirectionEnum.支付,
InterestRateDefault = 0,
InterestMode = (int)InterestModeEnum.标的期初全价,
InterestPrincipalFix = 0,
InterestType = 1,
IsAnnualized = true,
SwapTradeId = td.id,
PosiMatuirityDate = td.ExerciseDate.Value,
PosiStartDate = td.StartDate.Value,
IsInitial = true,
interest_rest_days = 7,
OptTime = DateTime.Now,
OptId = UserInfo.UserId,
OptName = UserInfo.UserName,
};
if (swapFloatRate != null)
{
interestPosition.InterestRateDefault = flowMerge.BsType == (int)PositionTypeFlag.Long ? (swapFloatRate.LongPricePoint ?? 0) : (swapFloatRate.ShortPricePoint ?? 0) * -1;
interestPosition.InterestRateDefault = interestPosition.InterestRateDefault * 0.0001m;
interestPosition.FloatRateUnderlyingCode = swapFloatRate.BaseUnderlyingCode;
interestPosition.interest_rule = swapFloatRate.interest_rule;
interestPosition.interest_rest_days = swapFloatRate.interest_rest_days;
interestPosition.IsAnnualized = swapFloatRate.is_annualized ?? false;
interestPosition.InterestType = swapFloatRate.interest_type??0;
}
var interval = new IntervalModel()
{
Date = td.ExerciseDate.Value,
Rate = interestPosition.InterestRateDefault,
Settlement = 0
};
List<IntervalModel> intervalModels = new List<IntervalModel>() { interval };
interestPosition.InterestSwapInterval = JsonConvert.SerializeObject(intervalModels);
td.swap_positions.Add(interestPosition);
}
//修改交易保存
private trade InnerSaveEditTrade(trade req, out bool changeConfirmStatus)
{
var dbTrade = DbContext.trade.Find(req.id);
if (dbTrade == null)
{
throw new ServiceException("保存失败,数据不存在");
}
dbTrade.SalesCommission = new SalesCommissionDataService(OptUser).GetTradeCommissionInfo(dbTrade.id);
if (dbTrade.SalesCommission?.Commission == null && req.SalesCommission == null)
{
dbTrade.SalesCommission = null;
}
if (!ConsGlobal.IsValid(dbTrade.ValidState))
{
throw new NotSupportedException("不支持对已无效的交易进行修改:" + dbTrade.TradeNumber);
}
if (valuedateBLL.SystemDate.EditTradeNeedRemark)
{
req.MetaDic.TryGetValue("remark", out var remark);
req.MetaDic.Remove("remark");
}
//使用明确的IsUsePremiumRate布尔值
if (!req.IsUsePremiumRate.HasValue && dbTrade.IsUsePremiumRate.HasValue)
{
req.IsUsePremiumRate = false;
}
req.CalcFlag = dbTrade.CalcFlag;
var oldClientId = dbTrade.ClientId;
var oldTradeStatus = dbTrade.TradeStatus;
var tradeDateChanged = dbTrade.TradeDate != req.TradeDate;
//移除价格观察
if (dbTrade.UnderlyingId != req.UnderlyingId)
{
RemoveEntities<manually_trade_observation_price>(x => x.TradeId == req.id);
}
//交易编号调整
var canGenerateTradeNumber = false;
if (req.ValidState == "InValid")
{
if (!string.IsNullOrWhiteSpace(dbTrade.TradeNumber))
{
new TradeDocumentDataService(this).DeleteConfirmBook(dbTrade.id);
if (dbTrade.TradeNumber.StartsWith("CW"))
{
req.TradeNumber = dbTrade.TradeNumber.Replace("CW", "XX");
}
else // if (PS.Config.Is光大光子)
{
req.TradeNumber = dbTrade.TradeNumber.Insert(0, "XX");
}
}
}
else
{
canGenerateTradeNumber = string.IsNullOrWhiteSpace(req.TradeNumber);
if (canGenerateTradeNumber)
{
req.TradeNumber = dbTrade.TradeNumber;
}
}
req.ContractVersion = dbTrade.ContractVersion;
req.PairTrade = dbTrade.PairTrade;
req.CreateDate = dbTrade.CreateDate;
//确认成交状态的交易修改后变为原始标记
req.DividendDate = new DateTime(2000, 1, 1);
//记录交易变更信息
var changsStr = string.Empty;
changeConfirmStatus = true;
var changes = DataChangeHelper.GetDataChanges(dbTrade, req);
var dbTradeMeta = DbContext.TradeMeta.Where(a => a.TradeId == dbTrade.id).ToDictionary(a => a.MetaKey, b => b.MetaValue);
var changMetas = new List<List<string>>();
var keys = new string[] { "交易场所", "清算机构", "主协议编号", "补充协议编号" };
foreach (var key in keys)
{
bool isdbMeta = dbTradeMeta.TryGetValue(key, out string oldVal);
bool isreqMeta = req.MetaDic.TryGetValue(key, out string newVal);
if (!isdbMeta && !isreqMeta || oldVal == newVal || (string.IsNullOrWhiteSpace(oldVal) && string.IsNullOrWhiteSpace(newVal)))
{
continue;
}
var changeMeta = new List<string>() { "", key, oldVal, newVal };
changMetas.Add(changeMeta);
}
if (changMetas.Any())
{
changes.AddRange(changMetas);
}
changsStr = changes.ToJson();
if (dbTrade.TradeStatus != ConsTrade.新增待确认)
{
//修改备注和簿记不需要提交审核和审批
changeConfirmStatus = !changes.All(n => string.IsNullOrEmpty(n[0]) || n[0] == nameof(trade.Comments)
|| n[0] == nameof(trade.AssetId) || n[0] == nameof(trade.AssetBookName));
}
//更新数据库实体
UpdateEntity(dbTrade, req);
//交易状态处理
if (dbTrade.CheckTradeUpdate == Convert.ToInt32(TradeCheckEnum.StatusOfNew) || dbTrade.TradeStatus == ConsTrade.确认成交)
{
if (changeConfirmStatus)
{
dbTrade.TradeStatus = ConsTrade.修改待确认;
dbTrade.CheckTradeUpdate = Convert.ToInt32(TradeCheckEnum.StatusOfOld);
dbTrade.CheckStatus = null;
}
}
else if (dbTrade.TradeStatus != ConsTrade.修改待确认)
{
dbTrade.TradeStatus = ConsTrade.新增待确认;
dbTrade.CheckTradeUpdate = Convert.ToInt32(TradeCheckEnum.StatusOfOld);
dbTrade.CheckStatus = null;
//如果是新增待确认并且修改了客户,需要重新生成交易编号
if (oldClientId != dbTrade.ClientId
&& canGenerateTradeNumber
&& !GuolianContractNoGenerator.IsGuolianSwapTrade(dbTrade))
{
dbTrade.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(dbTrade, DbContext);
}
}
dbTrade.TradeOldStatus = oldTradeStatus;
//重置历史数据(20210928:如果是当天修改也支持重置)
var resetHisData = dbTrade.TradeStatus == "新增待确认" || tradeDateChanged || valuedateBLL.ValueDate == dbTrade.TradeDate;
if (resetHisData)
{
dbTrade.TradeSavedVol = PS.Config.Is厦门象屿 ? dbTrade.TradeCloseVolatility : dbTrade.TradeOpenVolatility;
if (PS.Config.ErpElement.VolMode == Configuration.VolModeEnum.VolSurface)
{
dbTrade.TradeSavedVol = dbTrade.Vol;
}
}
dbTrade.trade_Initial_Margin = req.trade_Initial_Margin;
dbTrade.trade_extend = req.trade_extend;
dbTrade.swap_positions = req.swap_positions;
dbTrade.MetaDic = req.MetaDic;
dbTrade.InitYtm = req.swap_positions.FirstOrDefault(p => p.InitYtm != null)?.InitYtm;
InnerSaveTrade(false, dbTrade, changsStr, changeConfirmStatus);
return dbTrade;
}
//用于交易(不能是结构化主交易)入库后续处理
//changeConfirmStatus:保存交易修改时需要
private void InnerSaveTrade(bool isAddNew, trade dbTrade, string changsStr, bool changeConfirmStatus = false)
{
//保存修改
DbContext.SaveChanges();
if (isAddNew && GuolianContractNoGenerator.TryGenerateTradeNumberAfterSave(DbContext, dbTrade))
{
DbContext.SaveChanges();
}
SaveTradeExend(dbTrade);
SaveTradeMargin(dbTrade);
SaveSwapPositions(dbTrade.swap_positions, dbTrade);
ClearSwapPositions(dbTrade);
//交易元数据
if (dbTrade.MetaDic.Any())
{
foreach (var kv in dbTrade.MetaDic)
{
if (isAddNew)
{
if (!string.IsNullOrEmpty(kv.Value))
{
AddTradeMeta(false, dbTrade.id, kv.Key, kv.Value);
}
}
else
{
AddOrUpdateTradeMeta(false, dbTrade.id, kv.Key, kv.Value);
}
}
}
if (changeConfirmStatus)
{
if (GuolianSwapConfirmBookScope.IsPreApprovalTrade(dbTrade))
{
new GuolianSwapConfirmBookCleanupService(this).InvalidateAfterEdit(dbTrade);
}
else
{
//删除交易确认书 TODO
new TradeDocumentDataService(this).DeleteBooksAfterEditTrade(dbTrade);
}
}
int eventType = isAddNew ? (int)SwapEventTypeEnum.新增交易 : (int)SwapEventTypeEnum.修改交易;
new SwapEventService(UserInfo).AddSwapEventDate(dbTrade.TradeDate.Value, dbTrade.id, eventType, "", 0, true, "", true);
DbContext.SaveChanges();
}
/// <summary>
/// 添加交易元数据
/// </summary>
private void AddTradeMeta(bool saveChanges, int tradeId, string metaKey, string metaValue)
{
DbContext.TradeMeta.Add(new TradeMeta
{
TradeId = tradeId,
MetaKey = metaKey,
MetaValue = metaValue,
CreateTime = DateTime.Now
});
if (saveChanges)
{
DbContext.SaveChanges();
}
}
/// <summary>
/// 添加或更新交易元数据
/// </summary>
private void AddOrUpdateTradeMeta(bool saveChanges, int tradeId, string metaKey, string metaValue)
{
new TradeMetaService(this).AddTradeMeta(tradeId, metaKey, metaValue, saveChanges);
}
/// <summary>
/// 保存交易扩展信息
/// </summary>
/// <param name="dbTrade"></param>
private void SaveTradeExend(trade dbTrade)
{
RemoveEntities<trade_extend>(x => x.TradeId == dbTrade.id);
var trade_extend = dbTrade.trade_extend.Clone();
trade_extend.TradeId = dbTrade.id;
DbContext.trade_extend.Add(trade_extend);
}
/// <summary>
/// 保存交易预付金信息
/// </summary>
public void SaveTradeMargin(trade dbTrade)
{
RemoveEntities<trade_initial_margin>(x => x.TradeId == dbTrade.id);
var trade_Initial_Margin = dbTrade.trade_Initial_Margin.Clone();
trade_Initial_Margin.TradeId = dbTrade.id;
DbContext.trade_initial_margin.Add(trade_Initial_Margin);
}
private bool PrepareTrade(trade req, TradeSourceEnum dataSource, underlying_manager um)
{
bool tradeNumberGenerated = false;
PrepareInitialMargin(req);
var isAddNew = req.id == 0;
if (isAddNew)
{
req.TradeStatus = ConsTrade.新增待确认;
req.TradeSource = dataSource.ToString();
if (string.IsNullOrWhiteSpace(req.TradeNumber) || (PS.Config.Company == CompanyEnum.光大光子 && (req.TradeNumber == "STG" || req.TradeNumber == "STD" || req.TradeNumber == "FWD" || req.TradeNumber == "SPD")))
{
tradeNumberGenerated = true;
if (req.UnderlyingCode != null && req.UnderlyingCode != "")
{
PrepareUnderlying(req, um);
}
if (!GuolianContractNoGenerator.IsGuolianSwapTrade(req))
{
req.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(req, DbContext);
}
}
else if (DbContext.trade.Any(n => n.TradeNumber == req.TradeNumber))
{
throw new ServiceException($"交易编号'{req.TradeNumber}'已经存在");
}
else if (PS.Config.ErpElement.UpperTradeNumber)
{
req.TradeNumber = req.TradeNumber.ToUpperInvariant();
}
}
else if (!string.IsNullOrWhiteSpace(req.TradeNumber))
{
if (DbContext.trade.Any(n => n.id != req.id && n.TradeNumber == req.TradeNumber))
{
throw new ServiceException($"交易编号'{req.TradeNumber}'已经存在");
}
if (PS.Config.ErpElement.UpperTradeNumber)
{
req.TradeNumber = req.TradeNumber.ToUpperInvariant();
}
}
PrepareSingleTrade(req, dataSource, isAddNew, um);
req.PrincipalRateWrite = req.PrincipalRateWrite.IsNormalize() ? req.PrincipalRateWrite : null;
return tradeNumberGenerated;
}
private static void PrepareInitialMargin(trade req)
{
// 初始预付金依赖最终入库的名义本金,须先统一金额精度,避免两者无法勾稽。
req.StockEqvNotional = Math.Round(req.StockEqvNotional, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
req.InitialMargin = Convert.ToDouble(req.trade_Initial_Margin.MarginValue);
if (req.trade_Initial_Margin.MarginType == 0)
{
req.InitialMargin = req.StockEqvNotional == 0 ? 0 : Convert.ToDouble(req.trade_Initial_Margin.MarginValue) * req.StockEqvNotional;
}
}
//准备单个交易
private trade PrepareSingleTrade(trade req, TradeSourceEnum dataSource, bool isAddNew, underlying_manager um)
{
//准备交易基础信息
PrepareBaseInfo(req, um);
PrepareSwapTrade(req, dataSource, um);
req.OptId = UserId;
req.OptName = UserName;
req.OptDate = OptDate;
if (isAddNew)
{
req.ValidState = ConsGlobal.Valid;
req.CreateDate = DateTime.Now;
req.TradeSavedVol = PS.Config.Is厦门象屿 ? req.TradeCloseVolatility : req.TradeOpenVolatility;
if (PS.Config.ErpElement.VolMode == Configuration.VolModeEnum.VolSurface)
{
req.TradeSavedVol = req.Vol;
}
}
return req;
}
//准备互换交易
private void PrepareSwapTrade(trade req, TradeSourceEnum dataSource, underlying_manager um)
{
PrepareSwapTradeClient(req, um);
req.OriginalNotional = req.Notional;
req.BuySell = "买入";
if (req.StructureType == ClientMarginTypeEnum.多空组合.ToString())
{
req.BuySell = req.trade_extend.ExtendObj.Direction == 1 ? "买入" : "卖出";
}
else
{
var swapPosition = req.swap_positions.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode));
req.BuySell = swapPosition.PosiDirection == 1 ? "买入" : "卖出";
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(req.QuoteCurrency, req.SettlementCurrency, req.TradeDate.Value
, seekPreday: dataSource == TradeSourceEnum.系统交易);
var tradingFees = req.swap_positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).Sum(x => x.PosiTradingFee * (x.PosiDirection == 1 ? -1 : 1));
req.TradePrice = Convert.ToDouble(tradingFees) * currencyRate;
req.SpotPrice = Convert.ToDouble(swapPosition.PosiNetPrice);
}
req.Strike = null;
req.StockEqvNotional = Math.Round(req.StockEqvNotional, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
req.OriginalStockEqvNotional = req.StockEqvNotional;
req.StockEqvNotionalReal = req.StockEqvNotional;
}
//互换交易
private void PrepareSwapTradeClient(trade req, underlying_manager _underlying)
{
var client = PrepareBaseTradeClient(req);
if (client.EvaluateExpireDate < req.TradeDate)
{
throw new ServiceException("适当性评估已经过期,只有在适当性有效期内才可以新开仓");
}
req.ClientName = client.Name;
req.SettlementCurrency = client.SettlementCurrency;
}
//客户信息
private Client PrepareBaseTradeClient(trade req)
{
if (req.ClientId < 1)
{
throw new ServiceException("客户信息不存在");
}
var client = DbContextFactory.GetClientDbContext(OptUser).client.FirstOrDefault(n => n.id == req.ClientId);
if (client == null)
{
throw new ServiceException("客户信息不存在");
}
if (client.ProcessStatus == "已休眠" || client.ProcessStatus == "已销户")
{
if (!client.ProcessOptDate.HasValue || client.ProcessOptDate.Value < req.TradeDate.Value)
{
var tradeDateStr = req.TradeDate.Value.ToString("yyyy-MM-dd");
var processDateStr = client.ProcessOptDate?.ToString("yyyy-MM-dd HH:mm:ss");
throw new ServiceException($"客户'{client.Name}'{client.ProcessStatus}并且处理日期{processDateStr}早于交易日期'{tradeDateStr}',无法保存交易!");
}
}
else if (client.ProcessStatus != "已开户")
{
throw new ServiceException($"客户'{client.Name}'尚未完成开户!");
}
return client;
}
//准备交易基础信息(标的、簿记、交易日期)
private void PrepareBaseInfo(trade req, underlying_manager um)
{
if (!req.TradeDate.HasValue)
{
throw new ServiceException("交易日期 必须填写");
}
if (req.TradeDate.Value.Year < 2000)
{
throw new ServiceException("交易日期 填写错误:" + req.TradeDate.Value.ToString("yyyy-MM-dd"));
}
if (QdpCalendarHelper.IsHoliday(req.TradeDate.Value) && req.TradeType != "现金流交易")
{
throw new ServiceException("交易日期不能是假日:" + req.TradeDate.Value.ToString("yyyy-MM-dd"));
}
//如果起始日没有值,默认赋值交易日
if (!req.StartDate.HasValue)
{
req.StartDate = req.TradeDate;
}
//--------------------------------------
// 标的信息
//--------------------------------------
if (req.StructureType != ClientMarginTypeEnum.多空组合.ToString())
{
PrepareUnderlying(req, um);
}
//--------------------------------------
// 簿记帐户
//--------------------------------------
if (req.AssetId < 1)
{
throw new ServiceException("请选择簿记账户");
}
var _assetUnit = DbContext.assetunit.Where(n => n.id == req.AssetId)
.Select(n => new AssetUnitDto { id = n.id, Name = n.Name, BookCategory = n.BookCategory, TraderIds = n.TraderIds, UserGroup = n.UserGroup }).FirstOrDefault();
if (_assetUnit == null)
{
throw new ServiceException("簿记账户不存在");
}
req.AssetBookName = _assetUnit.Name;
//--------------------------------------
// 交易员信息(如果没有交易员,查找簿记账户中的交易员)
//--------------------------------------
if (string.IsNullOrWhiteSpace(req.TraderName))
{
//有交易员id,通过id查名字
if (req.TraderId > 0)
{
req.TraderName = UserBLL.GetNameById(req.TraderId);
}
else
{
//当前登录用户作为交易员
req.TraderId = UserId;
req.TraderName = UserName;
}
}
req.TradeAmount = Convert.ToDouble(req.swap_positions.Sum(s => s.PosiQuantity));
}
//准备交易标的信息
public underlying_manager PrepareUnderlying(trade req, underlying_manager _underlying)
{
var underlyingCode = req.UnderlyingCode ?? string.Empty;
if (_underlying == null)
{
_underlying = UnderlyingDataProvider.GetUnderlying(underlyingCode);
}
if (_underlying == null)
{
throw new ServiceException("标的信息不存在:" + req.UnderlyingCode);
}
if (_underlying.RequiresMaturityDate() && _underlying.MaturityDate == null)
{
throw new ServiceException("标的到期日不存在:" + req.UnderlyingCode);
}
req.UnderlyingId = _underlying.id;
req.UnderlyingCode = _underlying.UnderlyingCode;
req.VarietyId = _underlying.UnderlyingTypeId;
req.UnderlyingAssetClass = _underlying.UnderlyingType;
req.UnderlyingAssetName = _underlying.UnderlyingName;
req.MaturityDate = _underlying.RequiresMaturityDate() ? _underlying.MaturityDate : null;
req.UnderlyingInstrumentType = _underlying.GetMainType();
req.CountRatio = _underlying.CountRatio;
//权益类标的关联的交易如果没有设置分红率则取标的上的分红率,如果标的分红率为空则取0
if (!req.DividendRate.HasValue && _underlying.CalcTypeIsStock())
{
req.DividendRate = _underlying.DividendRate ?? 0;
}
return _underlying;
}
/// <summary>
/// 收益互换初始预付金校验
/// </summary>
/// <param name="trade"></param>
/// <returns></returns>
private string checkMarginRate(trade trade)
{
var errorMsg = "";
var marginValue = trade.trade_Initial_Margin.MarginValue;
if (trade.trade_Initial_Margin.MarginType == (int)TradeMarginTypeEnum.固定金额)
{
marginValue = trade.StockEqvNotional == 0 ? 0 : trade.trade_Initial_Margin.MarginValue / Convert.ToDecimal(trade.StockEqvNotional);
}
//多空组合
if (ClientMarginTypeEnum.多空组合.ToString().Equals(trade.StructureType))
{
if (marginValue < 0.25m)
{
errorMsg = $"根据监管要求权益类多空组合型收益互换预付金不得低于25%,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!";
}
}
else
{
//获取标的品种
var underlingData = new UnderlyingDataProvider();
var swapposition = trade.swap_positions.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode));
trade.UnderlyingCode = swapposition == null ? "" : swapposition.UnderlyingCode;
var underlying = underlingData.GetUnderlying(trade.UnderlyingCode);
var variety = underlingData.GetVariety(underlying.UnderlyingTypeId);
if (variety == null)
{
errorMsg = $"所设置标的未对应品种,请设置对应品种后再进行交易!";
}
else
{
//股票、窄基股票指数及其产品、信用债 初始预付金比率 必须大于等于100%
if ((variety.AssetType == "个股" || (variety.AssetType == "指数" && "窄基指数".Equals(variety.IndexType))) && marginValue < 1)
{
errorMsg = $"所交易标的为股票或窄基指数及其产品、信用债,初始预付金比例不得低于100%!,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!";
}
else if ((variety.AssetType == "指数" && "宽基指数".Equals(variety.IndexType)) && Convert.ToDecimal(marginValue) < 0.5m)
{
//挂钩宽基股票指数及其产品,无对应期货品种的 向单一交易对手方收取的预付金比例不得低于50%
errorMsg = $"所交易标的挂钩宽基股票指数及其产品,无对应期货品种的,初始预付金比例不得低于50%!,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!";
}
else if (marginValue < Convert.ToDecimal(variety.Margin ?? 0.0))
{
//有对应期货或集中交易品种的 不得低于品种设置的最低预付金比例
errorMsg = $"所交易标的对应期货或集中交易品种的,初始预付金比例不得低于品种[{variety.VarietyName}]所设置最低预付金比例{variety.Margin * 100}%!,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!";
}
}
}
return errorMsg;
}
/// <summary>
/// 标的校验
/// </summary>
/// <param name="trade"></param>
/// <exception cref="ServiceException"></exception>
private underlying_manager checkUnderlying(trade trade)
{
if (trade.StructureType == ClientMarginTypeEnum.多空组合.ToString())
{
return null;
}
var underlingData = new UnderlyingDataProvider();
var posi = trade.swap_positions.Where(x => x.PosiDirection > 0).FirstOrDefault();
if (posi == null)
{
throw new ServiceException("缺少标的信息");
}
var underlying = underlingData.GetUnderlying(posi.UnderlyingCode);
// 标的代码
if (underlying == null)
{
throw new ServiceException("标的代码 必须在系统中存在");
}
if (underlying.UnderlyingInstrumentType == "Stock" && !underlying.IsCombined())
{
var limit = LimitRangeEnum.Swap;
var limitStatus = new StockBlackWhiteService(UserInfo).GetStockBlackWhiteList(limit, out var Codes);
if (Codes != null)
{
if (limitStatus == Codes.Contains(underlying.UnderlyingCode))
{
if (limitStatus)
{
throw new ServiceException("标的代码 填写错误,存在于黑名单中,请求值:" + underlying.UnderlyingCode);
}
else
{
throw new ServiceException("标的代码 填写错误,不存在于白名单中,请求值:" + underlying.UnderlyingCode);
}
}
}
}
trade.UnderlyingCode = underlying.UnderlyingCode;
trade.UnderlyingId = underlying.id;
trade.UnderlyingAssetClass = underlying.UnderlyingType;
trade.UnderlyingInstrumentType = underlying.UnderlyingInstrumentType;
trade.UnderlyingAssetName = underlying.UnderlyingName;
return underlying;
}
#endregion
#region 互换交易查询
/// <summary>
/// 互换交易列表查询
/// </summary>
/// <param name="req"></param>
/// <param name="gsum"></param>
/// <returns></returns>
public SearchListResult<trade> SearchList(SwapTradeQueryRequest req, out tradeGridSum gsum)
{
var query = CreateTradeQuery(req);
var retListResult = new SearchListResult<trade>();
if (req.ValueDate != null)
{
var dateStr = req.ValueDate?.ToString("yyyy-MM-dd");
var _query = from t in query
join sp in DbContext.swap_position on t.id equals sp.SwapTradeId
where sp.InterestSwapInterval.Contains(dateStr) && !sp.Invalid
select t;
retListResult = _query.ToSearchList(req);
}
else
{
retListResult = query.ToSearchList(req);
}
gsum = new tradeGridSum();
if (query.Any())
{
gsum.TradePriceSum = query.Sum(q => q.TradePrice);
}
var tradeids = retListResult.rows.Select(r => r.id).ToList();
var list = DbContext.trade_contract_r.Where(O => tradeids.Contains(O.TradeId) && O.IsValid && O.Type == "交易确认书").AsEnumerable();
var contractCodeDict = list.GroupBy(O => O.TradeId).ToDictionary(K => K.Key, V => V.LastOrDefault().ContractCode);
//获取销售提成信息
foreach (var item in retListResult.rows)
{
item.ContractCode = contractCodeDict.TryGetValue(item.id, out var code) ? code : "";
item.SalesCommission = new SalesModule.SalesCommissionDataService(OptUser).GetTradeCommissionInfo(item.id);
}
retListResult.Sum = gsum;
return retListResult;
}
private Expression<Func<trade, bool>> BuildTradeQuery(TradeReq req)
{
var predicate = PredicateBuilder.Create<trade>(n => n.ValidState != "InValid" && n.TradeType == "收益互换");
if (req.UserAssets != null && req.UserClients != null)
{
predicate = predicate.And(n => req.UserAssets.Contains(n.AssetId) || req.UserClients.Contains(n.ClientId));
}
if (req.AssetIdList.Any())
{
predicate = predicate.And(d => req.AssetIdList.Contains(d.AssetId));
}
if (!string.IsNullOrEmpty(req.TraderNames))
{
predicate = predicate.And(d => req.TraderNamesList.Contains(d.TraderId));
}
if (!string.IsNullOrEmpty(req.TradeNumber))
{
predicate = predicate.And(d => d.TradeNumber.Contains(req.TradeNumber));
}
if (req.TradeStatusList != null)
{
predicate = predicate.And(d => req.TradeStatusList.Contains(d.TradeStatus));
}
if (req.AssetIdList.Any())
{
predicate = predicate.And(d => req.AssetIdList.Contains(d.AssetId));
}
if (req.ClientIdsInt.Any())
{
predicate = predicate.And(d => req.ClientIdsInt.Contains(d.ClientId));
}
if (req.StartDateStart != DateTime.MinValue)
{
predicate = predicate.And(d => d.StartDate >= req.StartDateStart);
}
if (req.StartDateEnd != DateTime.MinValue)
{
var StartDateTemp = req.StartDateEnd.AddDays(1);
predicate = predicate.And(d => d.StartDate < StartDateTemp);
}
if (req.ExerciseDateEnd == null)
{
req.ExerciseDateEnd = DateTime.MaxValue;
}
if (req.ExerciseDateStart == null)
{
req.ExerciseDateStart = DateTime.MinValue;
}
predicate = predicate.And(d => d.ExerciseDate >= req.ExerciseDateStart && d.ExerciseDate <= req.ExerciseDateEnd);
return predicate;
}
/// <summary>
/// 获取互换交易详情
/// </summary>
/// <param name="intid"></param>
/// <returns></returns>
public trade GetSwapTrade(int intid)
{
var tradeObj = DbContext.trade.Find(intid);
if (tradeObj == null)
{
return null;
}
//需要审批或者复核的交易都会显示行权审核提交按钮
tradeObj.MetaDic = new TradeMetaService(UserInfo).GetTradeMeta(tradeObj.id);
tradeObj.trade_Initial_Margin = DbContext.trade_initial_margin.FirstOrDefault(x => x.TradeId == intid);
if (tradeObj.trade_Initial_Margin == null)
{
tradeObj.trade_Initial_Margin = new trade_initial_margin();
}
tradeObj.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == intid);
tradeObj.swap_positions = DbContext.swap_position.ActiveByTrade(intid).ToList();
tradeObj.swap_positions = tradeObj.swap_positions.Where(x => x.PosiQuantity > 0 || x.InterestDirection > 0).ToList();
var intervalPositions = tradeObj.swap_positions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial).ToList();
var intervalPositionIds = intervalPositions.Select(s => s.id).ToList();
var tradeObervations = DbContext.trade_obervation.Where(x => intervalPositionIds.Contains(x.PositionId));
foreach (var position in intervalPositions)
{
position.Obervation = tradeObervations.FirstOrDefault(s => s.PositionId == position.id);
}
var positionIds = tradeObj.swap_positions.Select(x => x.id);
tradeObj.ClientCashInCashOutList = DbContext.ClientCashInCashOut.Where(x => x.ValidState != ConsGlobal.InValid && x.TradeId == intid).OrderBy(o => o.HappenDate).ToList();
List<int> eventTyps = new List<int>() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
var allSwapEvents = GetSwapEvents(intid, eventTyps).OrderBy(o => o.id).ToList();//历史平仓/互换记录
tradeObj.swap_Events.AddRange(allSwapEvents);
if (tradeObj.swap_Events.Count > 0)
{
tradeObj.eod_swaps = DbContext.eod_swap.Where(x => x.SwapTradeId == intid).ToList();
tradeObj.eod_swaps.ForEach(x =>
{
var eod = tradeObj.eod_swaps.Where(t => t.ValueDate < x.ValueDate && t.id != x.id).OrderBy(o => o.ValueDate).FirstOrDefault();
if (eod != null)
{
x.CloseStartDate = eod.ValueDate;
}
else
{
x.CloseStartDate = x.ValueDate;
}
});
}
foreach (var item in tradeObj.swap_positions.Where(x => x.IsInitial))
{
if (!string.IsNullOrEmpty(item.UnderlyingCode))
{
item.underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(item.UnderlyingCode);
if (item.underlying != null && item.underlying.IsBond() && !string.IsNullOrEmpty(item.underlying.ExJson))
{
var bond = JsonHelper.Deserialize<UnderlyingBond>(item.underlying.ExJson);
item.underlying.UnderlyingFullName = bond.UnderlyingFullName;
item.underlying.UnderlyingIssuer = bond.UnderlyingIssuer;
item.underlying.IssueSize = bond.IssueSize;
}
}
}
return tradeObj;
}
/// <summary>
/// 撤回审批
/// </summary>
/// <param name="intid"></param>
public void TradeRevoke(int intid)
{
var tradeObj = DbContext.trade.Find(intid);
if (tradeObj == null)
{
return;
}
if (!new TradeApprovalOAService(this).ArchiveActiveForTrade(tradeObj, out var errorMessage))
{
throw new ServiceException(errorMessage);
}
var eventTypes = new List<int>() { (int)SwapEventTypeEnum.修改交易, (int)SwapEventTypeEnum.新增交易, (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换 };
var lastEvent = DbContext.swap_event.Where(x => x.SwapTradeId == intid && eventTypes.Contains(x.EventType) && !x.Invalid).OrderByDescending(o => o.OptTime).FirstOrDefault();
if (tradeObj.TradeStatus == ConsTrade.平仓待复核 || tradeObj.TradeStatus == ConsTrade.互换待复核)
{
tradeObj.TradeStatus = ConsTrade.确认成交;
}
else
{
if (lastEvent != null && lastEvent.EventType == (int)SwapEventTypeEnum.修改交易)
{
tradeObj.TradeStatus = ConsTrade.修改待确认;
}
else
{
tradeObj.TradeStatus = ConsTrade.新增待确认;
}
}
tradeObj.CheckStatus = null;
tradeObj.ProcessOrderId = 0;
tradeObj.IsApproval = false;
if (lastEvent != null)
{
lastEvent.Invalid = true;
}
DbContext.SaveChanges();
return;
}
private IQueryable<trade> CreateTradeQuery(SwapTradeQueryRequest req)
{
var predicate = PredicateBuilder.Create<trade>(n => n.ValidState != "InValid" && n.TradeType == "收益互换");
var query = DbContext.trade.Where(predicate);
if (req.ClientIds != null && req.ClientIds.Count > 0)
{
query = query.Where(d => req.ClientIds.Contains(d.ClientId));
}
if (req.TradeIds != null && req.TradeIds.Count > 0)
{
query = query.Where(d => req.TradeIds.Contains(d.TraderId));
}
if (req.AssetIds != null && req.AssetIds.Count > 0)
{
query = query.Where(d => req.AssetIds.Contains(d.AssetId));
}
if (req.TradeDateStart.HasValue)
{
query = query.Where(d => d.TradeDate >= req.TradeDateStart);
}
if (req.TradeDateEnd.HasValue)
{
var TradeDateTemp = req.TradeDateEnd.Value.AddDays(1);
query = query.Where(d => d.TradeDate < TradeDateTemp);
}
if (req.ExerciseDateEnd.HasValue)
{
query = query.Where(d => d.ExerciseDate <= req.ExerciseDateEnd);
}
if (req.ExerciseDateStart.HasValue)
{
query = query.Where(d => d.ExerciseDate >= req.ExerciseDateStart);
}
if (!string.IsNullOrEmpty(req.TradeNumber))
{
query = query.Where(d => d.TradeNumber.Contains(req.TradeNumber));
}
if (req.TradeStatus != null && req.TradeStatus.Count > 0)
{
query = query.Where(d => req.TradeStatus.Contains(d.TradeStatus));
}
if (string.IsNullOrEmpty(req.sidx))
{
req.sidx = "id";
req.sord = "desc";
};
query = query.OrderByDescending(s => s.OptDate);
return query;
}
#endregion
/// <summary>
/// 获取互换交易对手方 交易编码
/// </summary>
/// <param name="clientId"></param>
/// <returns></returns>
public List<SelectItem> GetSwapTradeNumbers(int clientId)
{
var tradeNumbers = DbContext.trade.Where(t => t.ClientId == clientId
&& t.TradeType == "收益互换"
&& t.TradeStatus == ConsTrade.确认成交
&& t.StructureType == ClientMarginTypeEnum.多空组合.ToString()
&& t.ValidState != "InValid").Select(s => new SelectItem { Text = s.TradeNumber, Value = s.id.ToString() }).ToList();
return tradeNumbers;
}
/// <summary>
/// 保存交易腿信息
/// </summary>
/// <param name="swap_positions">持仓腿信息</param>
/// <param name="tradeId">交易id</param>
/// <param name="startDate">成交日期</param>
public void SaveSwapPositions(List<swap_position> swap_positions, trade td)
{
var swapPositionList = DbContext.swap_position.Where(x => x.SwapTradeId == td.id);
var swapPositionIds = swap_positions.Select(s => s.id).ToList();
var intervalPositionIds = swapPositionList.Where(x => string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial).Select(s => s.id);
var obervations = DbContext.trade_obervation.Where(o => intervalPositionIds.Contains(o.PositionId));
var originalSwapPositions = swapPositionList.Where(x => x.IsInitial);
var realTimeSwapPositions = swapPositionList.Where(x => !x.IsInitial);
var deleteSwapPositions = swapPositionList.Where(x => !swapPositionIds.Contains(x.id));
if (td.StructureType == "多空组合")
{
deleteSwapPositions = swapPositionList.Where(x => !swapPositionIds.Contains(x.id) && x.PosiDirection == 0);
realTimeSwapPositions = swapPositionList.Where(x => !swapPositionIds.Contains(x.id) && x.PosiDirection == 0 && !x.IsInitial);
var positions = swapPositionList.Where(x => x.PosiDirection > 0);
var direction = td.trade_extend.ExtendObj.Direction;
foreach (var item in positions)
{
item.PosiDirection = direction;
}
}
DbContext.swap_position.RemoveRange(realTimeSwapPositions);
DbContext.swap_position.RemoveRange(deleteSwapPositions);
DbContext.trade_obervation.RemoveRange(obervations);
foreach (var swap in swap_positions)
{
var position = originalSwapPositions.FirstOrDefault(x => x.id == swap.id);
if (position == null)
{
position = new swap_position();
}
position.ContractSize = swap.ContractSize;
position.CountRatio = swap.CountRatio;
var ratio = swap.PositionType == (int)PositionTypeFlag.Short ? -1 : 1;
position.id = swap.id;
position.PositionType = swap.PositionType;
position.PosiTradingFee = swap.PosiTradingFee;
position.PosiTradingFee=Math.Round(position.PosiTradingFee, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
position.PosiTradingFeeUnit = swap.PosiTradingFeeUnit;
position.PosiFeeType = swap.PosiFeeType;
position.PosiTradingFeePending = swap.PosiTradingFeePending;
position.PosiTradingFeePending = Math.Round(position.PosiTradingFeePending, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
position.UnderlyingCode = swap.UnderlyingCode;
position.UnderlyingInstrumentType = swap.UnderlyingInstrumentType;
position.PosiDirection = swap.PosiDirection;
position.PosiGrossPrice = swap.PosiGrossPrice;
position.PosiNetPrice = swap.PosiQuantity == 0 ? 0 : (position.PosiGrossPrice + (position.PosiTradingFeePending / swap.PosiQuantity) * ratio);
position.PosiNetPrice = Math.Round(position.PosiNetPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
position.PosiNetNoFeePrice = swap.PosiNetNoFeePrice;
position.PosiNetFeePrice = swap.PosiQuantity == 0 ? 0 : (swap.PosiNetNoFeePrice + (position.PosiTradingFeePending / swap.PosiQuantity) * ratio);
position.PosiNetFeePrice = Math.Round(position.PosiNetFeePrice??0, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
position.PosiNotionalValue = Math.Round(swap.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
position.PosiQuantity = swap.PosiQuantity;
position.InterestDirection = swap.InterestDirection;
position.InterestMode = swap.InterestMode;
position.InterestRateDefault = swap.InterestRateDefault;
position.InterestPrincipalFix = Math.Round(swap.InterestPrincipalFix, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
position.InterestSwapInterval = swap.InterestSwapInterval;
position.PosiStartDate = td.StartDate.Value;
position.PosiMatuirityDate = td.ExerciseDate.Value;
position.SwapTradeId = td.id;
position.IsInitial = true;
position.OptTime = DateTime.Now;
position.OptId = UserInfo.UserId;
position.OptName = UserInfo.UserName;
position.IsAnnualized = swap.IsAnnualized;
position.HappenDate = swap.HappenDate;
position.Currency = swap.Currency;
position.InterestType = swap.InterestType;
position.FloatRate = swap.FloatRate;
position.FloatRateUnderlyingCode = swap.FloatRateUnderlyingCode;
position.interest_rest_days = swap.interest_rest_days;
position.interest_rule = swap.interest_rule;
position.category_tag = string.IsNullOrEmpty(swap.category_tag) ? "互换利率" : swap.category_tag;
// R4:资金标签(fund_tag 单列)随录入保存用户逐腿选择,确认成交时系统在同列定稿
position.FundTag = string.IsNullOrWhiteSpace(swap.FundTag) ? null : swap.FundTag;
position.InitYtm = swap.InitYtm;
if (position.InitYtm != null && position.InitYtm > 0)
{
td.InitYtm = position.InitYtm;
}
if (position.id == 0)
{
DbContext.swap_position.Add(position);
DbContext.SaveChanges();
position.PosiNumber = $"{td.TradeNumber}-{position.id}";
}
if (swap.Obervation != null)
{
swap.Obervation.OptTime = DateTime.Now;
swap.Obervation.OptId = UserInfo.UserId;
swap.Obervation.OptName = UserInfo.UserName;
swap.Obervation.PositionId = position.id;
DbContext.trade_obervation.Add(swap.Obervation);
}
}
}
/// <summary>
/// 修改清除互换持仓所有信息
/// </summary>
/// <param name="tradeId"></param>
public void ClearSwapPositions(trade td)
{
var tradeId = td.id;
var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.DataState != (int)SwapFlowDateStateEnum.废弃);
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId);
var eodSwaps = DbContext.eod_swap.Where(x => x.SwapTradeId == tradeId);
var clientcashinouts = DbContext.ClientCashInCashOut.Where(x => x.TradeId == tradeId && x.Action == ClientCashInCashOut.系统操作_应付预付金);
// R4:修改清除 应付预付金 资金记录时同步清理对应授信出入记录,重确认时按最新标签重写;
// 追加保证金资金记录(阶段四 EOD 产生)不在本方法删除范围,其授信占用一并保留
new ClientCreditInoutService(this).RemoveByTrade(tradeId, keepAdditionalMargin: true);
swapFlowEvents.ForEach(x =>
{
x.DataState = (int)SwapFlowDateStateEnum.废弃;
});
//DbContext.swap_flow_event.RemoveRange(swapFlowEvents);
DbContext.eod_swap_position.RemoveRange(eodSwapPositions);
DbContext.eod_swap.RemoveRange(eodSwaps);
DbContext.ClientCashInCashOut.RemoveRange(clientcashinouts);
}
/// <summary>
/// 修改展期信息,互换观察日到期日期修改
/// </summary>
/// <param name="tradeId">互换交易id</param>
/// <param name="oldExerciseDate">旧到期日</param>
/// <param name="exerciseDate">新到期日</param>
public void UpdateObservationDay(int tradeId, DateTime oldExerciseDate, DateTime exerciseDate)
{
var dateStr = exerciseDate.ToString("yyyy-MM-dd");
var oldDateStr = oldExerciseDate.ToString("yyyy-MM-dd");
var interestPostions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && string.IsNullOrEmpty(x.UnderlyingCode) && !x.Invalid).ToList();
interestPostions.ForEach(x =>
{
if (x.SwapIntervalList.Count == 1 && x.SwapIntervalList.Any(t => t.Date == oldExerciseDate))
{
x.InterestSwapInterval = x.InterestSwapInterval.Replace(oldDateStr, dateStr);
}
});
}
/// <summary>
/// 交易回退
/// </summary>
/// <param name="tradeId"></param>
/// <returns></returns>
public trade GetTrade(int tradeId)
{
var td = DbContext.trade.Find(tradeId);
return td;
}
/// <summary>
/// 互换交易回退
/// </summary>
/// <param name="tradeId">交易id</param>
/// <param name="valueDate">回退日期</param>
/// <param name="backToBegin">回到开仓</param>
public void TradeBack(int tradeId, DateTime valueDate, bool del = false)
{
var td = DbContext.trade.Find(tradeId);
if (td == null)
{
throw new ServiceException("交易不存在");
}
bool backToBegin = td.TradeDate == valueDate;
var swapPositions = DbContext.swap_position.ActiveByTrade(tradeId).ToList();
td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id);
//展期
var swapEvent = DbContext.swap_event.Where(x => x.SwapTradeId == tradeId && x.EventType == (int)SwapEventTypeEnum.展期 && !x.Invalid && x.ValueDate <= valueDate).OrderByDescending(o => o.ValueDate).FirstOrDefault();
var trans = DbContext.Database.BeginTransaction();
try
{
var swapEventService = new SwapEventService(this);
td.TradeStatus = "确认成交";
td.ProcessOrderId = 0;
td.ProcessStatus = null;
if (backToBegin)
{
td.StockEqvNotional = Math.Round(td.OriginalStockEqvNotional ?? 0, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
td.UnWindDate = null;
td.HasPartialUnWind = null;
SingleTradeBackToBegin(td, swapPositions);
}
else
{
// 按日期回退时只恢复 valueDate 之前最近实际收盘的快照;当天及以后数据会在
// InvalidTradeOptionDatasByDate 中清理。这样回退到除权日 D 会回到 D-1 的
// 1000 份/100 基线并让重收盘重新应用公司行为;回退到 D+1 则保留 D 的 2000 份/50。
TradeBackByDate(td, valueDate, swapPositions);
}
if (swapEvent != null)//展期
{
swapEventService.DeleteExtensionTime(swapEvent.id);
}
var corporateActionEvents = DbContext.swap_event
.Where(x => !x.Invalid
&& x.SwapTradeId == tradeId
&& x.EventType == (int)SwapEventTypeEnum.公司行为
&& x.ValueDate >= valueDate)
.OrderByDescending(x => x.id)
.ToList();
InvalidTradeOptionDatasByDate(tradeId, valueDate, backToBegin);
var rollbackEvent = swapEventService.AddSwapEventDate(
valueDate,
tradeId,
(int)SwapEventTypeEnum.回退,
string.Empty,
0,
false,
$"交易回退至{valueDate:yyyy年MM月dd日}");
// 公司行为原事件保持有效作为不可篡改审计;回退事件通过 BackId 指向本次
// 回退影响的最新公司行为事件,后续重收盘会追加新的公司行为事件。
rollbackEvent.BackId = corporateActionEvents.FirstOrDefault()?.id ?? 0;
DbContext.SaveChanges();
if (del)
{
new TradeRiskCheckLogService(UserInfo).DeleteLog(td.TradeNumber, valueDate);//重置风控记录
}
if (backToBegin) //重置开仓记录
{
DeleteTradeCashInCashOut(tradeId);
DeleteTradeDetials(tradeId);
DbContext.SaveChanges();
if (del)
{
swapPositions.ForEach(x =>
{
x.Invalid = true;
x.OptTime = OptDate;
});
td.ValidState = "InValid";
var tradeNumber = td.TradeNumber;
td.TradeNumber = td.TradeNumber.Insert(0, "XX");
td.OptDate = OptDate;
swapEventService.AddSwapEventDate(valueDate, tradeId, (int)SwapEventTypeEnum.删除, string.Empty, 0, false);
DbContext.SaveChanges();
new TradeDocumentDataService(this).InnerDeleteConfirmBook(tradeId);
trans.Commit();
return;
}
//new SwapEodPositionService(this).InitSaveEodSwapPosition(td);
var happenDate = td.TradeDate.Value;
if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee)
{
happenDate = td.ExerciseDate.Value;
}
ResetMarginAmount(td, happenDate, swapPositions);
}
//new SwapFlowService(this).DeleteSwapFlow(tradeId, valueDate, backToBegin);
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
throw ex;
}
finally
{
trans.Dispose();
}
}
/// <summary>
/// 重新补录预付金记录(回退到开仓后重补,与 SwapTradeConfirm 同口径)
/// </summary>
/// <param name="td"></param>
/// <param name="happenDate"></param>
/// <param name="swapPositions"></param>
private void ResetMarginAmount(trade td, DateTime happenDate, List<swap_position> swapPositions)
{
var cashSvc = new ClientCashInCashOutService(this);
cashSvc.SaveSwapTradeClientCash(td, td.TradePrice ?? 0, happenDate, 0);
// R4:重补同样走标签分配(授信占用/拆单/现金流水);
// 特批标志在回退重补场景不可得,按当前剩余额度重新分配(回退即重新簿记)
var fundTagSvc = new SwapFundTagService(this);
var generateMarginLegs = new List<swap_position>();
foreach (var marginPositions in swapPositions.Where(x => x.HappenDate != null && x.IsInitial && x.InterestMode != (int)InterestModeEnum.追加预付金).GroupBy(g => g.HappenDate))
{
var marginHappenDate = marginPositions.Key;
foreach (var marginPosition in marginPositions)
{
marginPosition.Obervation = DbContext.trade_obervation.FirstOrDefault(x => x.PositionId == marginPosition.id);
bool generateMargin = true;
if (marginPosition.Obervation != null)
{
if (!marginPosition.Obervation.IsDeductPrincipal)
{
generateMargin = false;
}
}
if (generateMargin)
{
generateMarginLegs.Add(marginPosition);
}
}
}
fundTagSvc.ApplyMarginFundTags(td, generateMarginLegs, cashSvc, false);
//标签定稿(含可能的拆单)后重克隆实时持仓:TradeBack 的克隆先于定稿生成,
//重克隆使实时腿继承定稿标签、拆单新拆出的现金腿也获得克隆(平仓返还分流查的是实时腿标签)
InitialPosition(td);
// 合约维度盯市+无预付金腿:重建交易级(positionId=0)初始预付金记录(与 SwapTradeConfirm 一致,回退重补场景)。
// 有预付金腿的互换由上面按腿重建,不在此重复生成。
// R4:资金来源是预付金腿上的录入项,无腿即无从选择授信,本交易级记录恒为现金。
var resetWatchClient = DataCacheProvider.GetClientDataSource().GetData(td.ClientId);
var hasPrepayLeg = swapPositions != null && swapPositions.Any(x => x.InterestMode == (int)InterestModeEnum.初始预付金 || x.InterestMode == (int)InterestModeEnum.追加预付金);
if (resetWatchClient != null && resetWatchClient.MarginWatchRule == 0 && !hasPrepayLeg)
{
var initMargin = cashSvc.GetInitialMarginAmount(td);
if (initMargin > 0)
{
cashSvc.SaveSwapTradeClientCash(td, initMargin, happenDate, 0, ClientCashInCashOut.系统操作_应付预付金);
}
}
}
/// <summary>
/// 获取持仓腿信息
/// </summary>
/// <param name="tradeIds">叫一起集合</param>
/// <param name="_underlyingCodeFilterSet">标的代码</param>
/// <returns></returns>
public IEnumerable<swap_position> GetSwapPositions(List<int> tradeIds, HashSet<string> _underlyingCodeFilterSet)
{
Expression<Func<swap_position, bool>> expression = x => tradeIds.Contains(x.SwapTradeId) && !string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial && !x.Invalid;
if (_underlyingCodeFilterSet != null)
{
expression = expression.And(x => _underlyingCodeFilterSet.Contains(x.UnderlyingCode));
}
return DbContext.swap_position.Where(expression);
}
/// <summary>
/// 单标的回退到初始
/// </summary>
/// <param name="td"></param>
/// <param name="swapPositions"></param>
private void SingleTradeBackToBegin(trade td, List<swap_position> swapPositions)
{
var positions = swapPositions.Where(x => !x.IsInitial).ToList();
var marginPositions = swapPositions.Where(x => x.IsInitial && x.InterestMode == (int)InterestModeEnum.追加预付金).ToList();
var orignPositions = swapPositions.Where(x => x.IsInitial && x.InterestMode != (int)InterestModeEnum.追加预付金).ToList();
DbContext.swap_position.RemoveRange(positions);
DbContext.swap_position.RemoveRange(marginPositions);
td.TradeAmount = 0;
foreach (var item in orignPositions)
{
var interest = item.Clone();
interest.id = 0;
interest.PositionId = item.id;
interest.IsInitial = false;
// R4:回到开仓保留 fund_tag(用户录入的选择),重新确认时按当前额度在同列定稿;
// 授信出入记录已随资金记录清理,重确认时重写
td.TradeAmount = td.TradeAmount + Convert.ToDouble(interest.PosiQuantity);
DbContext.swap_position.Add(interest);
}
}
/// <summary>
/// 回到某个事件之前
/// </summary>
/// <param name="td"></param>
/// <param name="swapEvent"></param>
private void TradeBackByDate(trade td, DateTime valueDate, List<swap_position> swapPositions)
{
SwapEodPositionService eodPositionService = new SwapEodPositionService(this);
SwapDealService swapDealService = new SwapDealService(this);
// 回退基线必须是 valueDate 之前最近一个实际 EOD,而不是 valueDate-1 自然日。
// 例如周一/节假日后的 valueDate 没有周日 EOD 时,AddDays(-1) 会得到空集合,
// 实时腿仍保留除权后的数量/价格。回退到除权日 D 选择 D 前基线并由下面的
// InvalidTradeOptionDatasByDate 删除 D 及以后 EOD;回退到 D+1 则会选择 D,
// 保留 D 已生效的公司行为。ex_dividend_info 本身不删除,重收盘 D 会再应用一次。
var eodSwapPositionList = eodPositionService.GetLatestEodPositionsBefore(td.id, valueDate);
// 没有 valueDate 之前的有效 EOD 时,eodSwapPositionList 为空;这是“没有可证明基线”的情况,
// 下面不会伪造数量/价格或重算公司行为,只保留当前实时持仓并继续清理回退日之后的数据。
var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == td.id && x.EventDate >= valueDate && x.DataState > (int)SwapFlowDateStateEnum.废弃).ToList();
var positions = swapPositions.Where(x => x.PosiDirection > 0 && !x.IsInitial).ToList();
foreach (var posi in positions)
{
var eodPosi = eodSwapPositionList.FirstOrDefault(x => x.PositionId == posi.PositionId);
if (eodPosi != null)
{
posi.InterestAmount = eodPosi.RealizedInterest;
posi.InterestFeePending = eodPosi.InterestFeePending;
posi.InterestPrincipalFix = Math.Round(eodPosi.InterestPrincipalFix, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
posi.PosiTradingFeePending = eodPosi.PosiFeePending;
posi.PosiDividendIncome = eodPosi.PosiDividendSum;
posi.PosiQuantity = eodPosi.PosiQuantity;
posi.PosiNetPrice = eodPosi.PosiNetPrice;
posi.PosiGrossPrice = eodPosi.PosiGrossPrice;
posi.PosiNetFeePrice = eodPosi.PosiNetFeePrice;
posi.PosiNetNoFeePrice = eodPosi.PosiNetNoFeePrice;
posi.PosiNotionalValue = Math.Round(eodPosi.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
if (posi.PosiDirection > 0)
{
td.StockEqvNotional = Math.Round(Convert.ToDouble(posi.PosiNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
td.TradeAmount = Convert.ToDouble(posi.PosiQuantity);
}
}
// eodPosi 为空时刻意不改 posi:回退只能使用已落库的历史快照,不能把缺失数据
// 猜成 0 或交易初始值,否则会把未验证的 Fund 除权数量带入后续收盘。
}
td.UnWindDate = null;
td.UnWindNotional = null;
//DbContext.swap_position.RemoveRange(positions);
//swapDealService.UpdateInitalPosition(valueDate,td, swapPositions, swapFlowEvents);
DbContext.swap_flow_event.RemoveRange(swapFlowEvents);
var clientCashs = DbContext.ClientCashInCashOut.Where(x => x.TradeId == td.id && x.HappenDate >= valueDate).ToList();
DbContext.ClientCashInCashOut.RemoveRange(clientCashs);
DbContext.SaveChanges();
}
/// <summary>
/// 多空组合回到初始
/// </summary>
/// <param name="swapPositions"></param>
private void LongshortTradeBackToBegin(List<swap_position> swapPositions)
{
var positions = swapPositions.Where(x => !x.IsInitial || x.InterestDirection == 0).ToList();
var orignPositions = swapPositions.Where(x => x.IsInitial && x.InterestDirection > 0).ToList();
foreach (var item in positions)
{
item.Invalid = true;
}
//DbContext.swap_position.RemoveRange(positions);
foreach (var item in orignPositions)
{
var interest = item.Clone();
interest.id = 0;
interest.PositionId = item.id;
interest.IsInitial = false;
DbContext.swap_position.Add(interest);
}
}
/// <summary>
/// 废弃或删除回退日期之后的数据
/// </summary>
/// <param name="tradeId"></param>
/// <param name="valueDate"></param>
private void InvalidTradeOptionDatasByDate(int tradeId, DateTime valueDate, bool backToBegin)
{
// 所有清理条件都采用闭区间起点 [valueDate, +∞):回退到除权日 D 要删除 D 当天
// 已应用的 EOD/流水,随后重收盘 D 才会从 D-1 快照重新套一次系数;回退到 D+1
// 不会删除 D,因而保留 D 已生效的 2000 份/50。valueDate 之前的快照始终保留,
// 作为 TradeBackByDate 的唯一可验证基线。
var swapEvents = DbContext.swap_event.Where(x => !x.Invalid && x.SwapTradeId == tradeId && x.ValueDate >= valueDate).ToList();
var swapEodPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId && x.ValueDate >= valueDate);
var swapEods = DbContext.eod_swap.Where(x => x.SwapTradeId == tradeId && x.ValueDate >= valueDate);
var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.EventDate >= valueDate);
var firstConfirm = false;
swapEvents.ForEach(x =>
{
// 公司行为事件是不可篡改审计日志。回退只追加回退事件,不把原始公司
// 行为事件置无效;否则无法追溯交易曾经经历过的调整。
if (x.EventType == (int)SwapEventTypeEnum.公司行为)
{
return;
}
if (backToBegin && !firstConfirm && x.EventType == (int)SwapEventTypeEnum.确认交易)
{
firstConfirm = true;
}
else
{
x.Invalid = true;
}
});
swapFlowEvents.ForEach(x =>
{
if (backToBegin && x.EventType == (int)SwapFlowEventTypeEnum.开仓)
{
}
else
{
x.DataState = (int)SwapFlowDateStateEnum.废弃;
}
});
//DbContext.swap_flow_event.RemoveRange(swapFlowEvents);
DbContext.eod_swap_position.RemoveRange(swapEodPositions);
DbContext.eod_swap.RemoveRange(swapEods);
}
}
}