1909 lines
91 KiB
C#
1909 lines
91 KiB
C#
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=LackOfMoney,controller 按 系统参数"允许交易特批"
|
||
/// 判定后传入,与确认/审批环节 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);
|
||
}
|
||
|
||
}
|
||
}
|