Files
zszq-trs/YLErpDAL/Modules/TradeModule/StructureTradeImportService.cs
T
2024-05-09 14:06:26 +08:00

727 lines
26 KiB
C#

using BaseOUDAL;
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;
using YieldChain.Helpers;
using YLErp.BLL;
using YLErp.Commons;
using YLErp.CustomizedBizLogic;
using YLErp.DBModels.Consts;
using YLErp.DBModels.Helpers;
using YLErp.Models;
using YLErp.Modules.ClientModule;
using YLErp.Modules.DataCacheModule;
using YLErp.Modules.TradeModule;
using YLErp.Modules.TradeModule.DealModule;
namespace YLErp.Modules.TradeDalModule
{
/// <summary>
/// 结构化交易导入服务
/// </summary>
public class StructureTradeImportService : YLBaseService
{
public StructureTradeImportService(YLBaseService baseService) : base(baseService)
{
}
public StructureTradeImportService(OptUserInfo userInfo) : base(userInfo)
{
}
/// <summary>
///
/// </summary>
public HandleResult ImportFromCsv(Stream stream)
{
IEnumerable<ImportModel> importModels;
var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
//规避空数据行
ShouldSkipRecord = n => n.Row.Parser.Record == null || n.Row.Parser.Record.All(m => string.IsNullOrWhiteSpace(m))
};
//当前编码支持ansi和utf with bom
using (var sr = new StreamReader(stream, Encoding.Default))
using (var csv = new CsvReader(sr, csvConfig))
{
csv.Context.TypeConverterCache.AddConverter<string>(Helpers.CsvTypeConverts.StringConverter.Required);
csv.Context.TypeConverterCache.AddConverter<double>(Helpers.CsvTypeConverts.DoubleConverter.Required);
csv.Context.TypeConverterCache.AddConverter<short>(Helpers.CsvTypeConverts.Int16Converter.Required);
csv.Context.RegisterClassMap<ImportModel.ImportMap>();
importModels = csv.GetRecords<ImportModel>().ToArray();
}
if (!importModels.Any())
{
return "没有可导入的数据";
}
//检查导入的数据
var set = new HashSet<string>();
foreach (var item in importModels)
{
item.StartDate = item.StartDate.Date;
item.SettlementDate = item.SettlementDate.Date;
item.ExpirationDate = item.ExpirationDate.Date;
if (!set.Add(item.ContractNumber))
{
return "合同编号重复:" + item.ContractNumber;
}
if (item.StartDate > DateTime.Today)
{
return "起始日不能大于今日";
}
if (item.StartDate > item.SettlementDate)
{
return "起始日不能大于结算日";
}
if (item.StartDate > item.ExpirationDate)
{
return "起始日不能大于到期日";
}
}
//检查是否数据库中已存在合同编号
var existsContractNos = new TradeContractGenerateService(this).GetExistsContractCode(set);
if (existsContractNos.Any())
{
return "合同编号已存在:" + string.Join(",", existsContractNos);
}
return ImportDatas(importModels);
}
/// <summary>
/// 导入数据
/// </summary>
private HandleResult ImportDatas(IEnumerable<ImportModel> importModels)
{
//
foreach (var model in importModels)
{
using (var transaction = BeginTransaction())
{
SaveData(model);
transaction.Commit();
}
}
return HandleResult.Success;
}
/// <summary>
/// 保存到数据库
/// </summary>
private void SaveData(ImportModel model)
{
//创建结构化交易
var result = CreateStructureTrade(model, out var struInfo);
if (!result.IsSuccess)
{
throw new ServiceException(result.Message);
}
var parentTrade = struInfo.parentTrade;
//保存主交易
SetDBModelCreator(parentTrade);
DbContext.trade.Add(parentTrade);
DbContext.SaveChanges();
//修正子交易数据
var index = 0;
foreach (var subTrade in struInfo.subTrades)
{
index++;
subTrade.ParentTradeId = parentTrade.id;
subTrade.StructureType = parentTrade.StructureType;
SetDBModelCreator(subTrade);
}
//添加
DbContext.trade.AddRange(struInfo.subTrades);
DbContext.SaveChanges();
//应收预付金
var client = struInfo.client;
DbContext.ClientCashInCashOut.Add(new ClientCashInCashOut
{
Direction = ClientCashInCashOut.应收,
ClientId = client.id,
ClientName = client.Name,
ClientNumber = client.Number,
Number = UniqueTimeId.GetStr(),
TradeId = parentTrade.id,
Action = ClientCashInCashOut.人工操作_预付金,
Money = model.Margin,
HappenDate = DateTime.Now,
State = "已执行",
OptId = parentTrade.OptId,
OptName = parentTrade.OptName,
OptDate = DateTime.Now,
CreatorId = parentTrade.OptId,
CreatorName = parentTrade.OptName,
CreateDate = DateTime.Now,
ValidState = "Valid",
Comments = model.ContractNumber,
TradeNumber = parentTrade.TradeNumber
});
///保存添加交易时开仓波动率,持仓波动率,平滑天数
var addTradesVolatility = new List<TradeVolatility>();
addTradesVolatility.Add(new TradeVolatility()
{
TradeId = parentTrade.id,
NumOfSmoothingDays = parentTrade.NumOfSmoothingDays,
OptId = UserId,
OptName = UserName,
OptDate = DateTime.Now,
TradeCloseVolatility = parentTrade.TradeCloseVolatility,
TradePositionVolatility = parentTrade.TradeOpenVolatility,
ValueDate = parentTrade.TradeDate ?? DateTime.Today,
IsFromTradeAdd = true
});
struInfo.subTrades.ToList().ForEach(x =>
{
var tradeV = new TradeVolatility()
{
TradeId = x.id,
NumOfSmoothingDays = x.NumOfSmoothingDays,
OptId = UserId,
OptName = UserName,
OptDate = DateTime.Now,
TradeCloseVolatility = x.TradeCloseVolatility,
TradePositionVolatility = x.TradeOpenVolatility,
ValueDate = x.TradeDate ?? DateTime.Today,
IsFromTradeAdd = true
};
addTradesVolatility.Add(tradeV);
});
DbContext.TradeVolatility.AddRange(addTradesVolatility);
DbContext.SaveChanges();
//保存对冲波动率
new TradeHisDataService(this).SaveHedgeVol(parentTrade.id, valuedateBLL.ValueDate, parentTrade.TradeSavedVol ?? 0);
var tradeMetas = new List<TradeMeta>();
//添加交易确认书合约关联
if (!string.IsNullOrWhiteSpace(model.ContractNumber))
{
tradeMetas.Add(new TradeMeta()
{
TradeId = parentTrade.id,
MetaKey = ConsTradeMetaKey.ContractCode,
MetaValue = model.ContractNumber
});
}
tradeMetas.Add(new TradeMeta()
{
TradeId = parentTrade.id,
MetaKey = ConsTradeMetaKey.ReleaseAnnualPremiumRate,
MetaValue = model.ReleaseAnnualPremiumRate.ToString()
});
new TradeMetaService(this).AddTradeMetas(tradeMetas);
DbContext.SaveChanges();
}
/// <summary>
/// 根据传进来的合约代码过滤出已存在的合约代码
/// </summary>
private IEnumerable<string> GetExistsContractCode(IEnumerable<string> contractCodes)
{
if (contractCodes == null)
{
throw new ArgumentNullException(nameof(contractCodes));
}
if (!contractCodes.Any())
{
return Enumerable.Empty<string>();
}
var query = (from a in DbContext.trade_contract_r.AsNoTracking()
join b in DbContext.trade.AsNoTracking() on a.TradeId equals b.id
where contractCodes.Contains(a.ContractCode) && b.ValidState != "InValid" && a.IsValid
select a.ContractCode).Union
(from tm in DbContext.TradeMeta.AsNoTracking()
join t in DbContext.trade.AsNoTracking() on tm.TradeId equals t.id
where tm.MetaKey == ConsTradeMetaKey.ContractCode && t.ValidState != "InValid"
&& contractCodes.Contains(tm.MetaValue)
select tm.MetaValue).Union
(from t in DbContext.trade.AsNoTracking()
where t.ValidState != "InValid" && contractCodes.Contains(t.TradeNumber)
select t.TradeNumber);
return query.ToArray();
}
/// <summary>
/// 生成结构化交易数据
/// </summary>
private HandleResult CreateStructureTrade(ImportModel model, out StructureTradeInfo structureTradeInfo)
{
structureTradeInfo = null;
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
var trader = UserBLL.FirstOrDefault(O => O.Name == (model.TraderName ?? ""));
if (trader == null)
{
return "交易员名称不存在:" + model.TraderName;
}
var assetUnit = DataCacheManager.GetAssetUnitDataSource().AsQueryable()
.FirstOrDefault(n => n.Name == (model.AssetBookName ?? ""));
if (assetUnit == null)
{
return "簿记账户名称不存在:" + model.AssetBookName;
}
if (!assetUnit.TraderIdsInt.Contains(trader.Id))
{
return $"当前交易员: {trader.Name} 无法访问簿记账户: {assetUnit.Name}";
}
if (model.SpotPrice < 0.01)
{
return "期初价格不符合要求:" + model.SpotPrice;
}
//交易客户
var client = ClientDataQueryService.GetClient(model.ClientName);
if (client == null)
{
return "客户名称不存在:" + model.ClientName;
}
//交易标的
var underlyingCode = NormalizeUnderlyingCode(model.UnderlyingCode);
var underlying = DataCacheManager.GetUnderlyingDataSource().GetData(underlyingCode);
if (underlying == null)
{
return $"挂钩标的不存在,原值:{model.UnderlyingCode},转换后:{underlyingCode}";
}
//交易品种
var variety = DataCacheManager.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId);
if (variety == null)
{
return "未找到对应的交易品种,标的代码:" + underlying.UnderlyingCode;
}
//创建交易
var parentTrade = CreateParentTrade(model, underlying, client, trader, assetUnit);
var subTrades = new[] { parentTrade.Clone(), parentTrade.Clone() };
//以下处理各价差类型
var strikes = new[] { model.StrikePrice1Percent, model.StrikePrice2Percent }.OrderBy(n => n).ToArray();
parentTrade.Strike = strikes[1];
if (model.OptionStructurType.Equals("bull call spread"))
{
parentTrade.StructureType = "牛市价差";
//低行权价交易和主交易保持一致
subTrades[0].Strike = strikes[0];
//低行权价子交易分腿编号为-1;
subTrades[0].TradeNumber = BizLogicSingleton.Instance.GenerateSubTradeNumberBeforeConfirm(subTrades[0], parentTrade, 1, DbContext);
subTrades[1].TradeNumber = BizLogicSingleton.Instance.GenerateSubTradeNumberBeforeConfirm(subTrades[0], parentTrade, 2, DbContext);
subTrades[1].Strike = strikes[1];
subTrades[1].TradePrice = 0;
subTrades[1].TradeSinglePrice = 0;
subTrades[1].InitialMargin = 0;
subTrades[1].BuySell = model.BuySell == "买入" ? "卖出" : "买入";
}
else if (model.OptionStructurType.Equals("bear call spread"))
{
parentTrade.StructureType = "熊市价差";
subTrades[0].Strike = strikes[1];
subTrades[0].TradePrice = 0;
subTrades[0].TradeSinglePrice = 0;
subTrades[0].InitialMargin = 0;
subTrades[0].BuySell = model.BuySell == "买入" ? "卖出" : "买入";
//高行权价交易和主交易保持一致
subTrades[1].Strike = strikes[0];
//高行权价子交易分腿编号为-1;
subTrades[0].TradeNumber = BizLogicSingleton.Instance.GenerateSubTradeNumberBeforeConfirm(subTrades[0], parentTrade, 1, DbContext);
subTrades[1].TradeNumber = BizLogicSingleton.Instance.GenerateSubTradeNumberBeforeConfirm(subTrades[0], parentTrade, 2, DbContext);
}
else
{
return "未能辨识的期权类型:" + model.OptionStructurType;
}
foreach (var subTrade in subTrades)
{
subTrade.TradeType = "香草期权";
subTrade.MaturityDate = parentTrade.MaturityDate ?? underlying.MaturityDate;
}
structureTradeInfo = new StructureTradeInfo
{
parentTrade = parentTrade,
subTrades = subTrades,
client = client
};
return HandleResult.Success;
}
/// <summary>
/// 生成父交易
/// </summary>
private trade CreateParentTrade(ImportModel model, underlying_manager underlying, ClientMainInfo client, SystemUser trader, AssetUnit assetUnit)
{
var tradeOb = new trade
{
VolType = "交易",
IsMoneynessOption = "是",
TradeStatus = "新增待确认",
ValidState = "Valid",
OptionType = "看涨",
TradeType = "结构化交易",
TradeSource = "系统交易",
UnderlyingInstrumentType = "CommodityFutures",
IsUsePremiumRate = true,
Comments = model.Remark,
BuySell = model.BuySell,
DurationDays = model.DurationDays,
TradeSavedVol = 0,
SettlementType = 0,
ProcessOrderId = 0,
//HasGeneratedConfirmBook = 0,
NumOfSmoothingDays = 1,
//ContractNo = model.ContractNumber,
NoRiskRate = valuedateBLL.RiskFreeRate * 0.01,
TradeDate = model.StartDate,
UnderlyingId = underlying.id,
UnderlyingCode = underlying.UnderlyingCode,
UnderlyingAssetClass = underlying.UnderlyingType,
UnderlyingAssetName = underlying.UnderlyingName,
ClientId = client.id,
ClientName = client.Name,
AssetId = assetUnit.id,
AssetBookName = assetUnit.Name,
TraderId = trader?.Id ?? 0,
TraderName = trader?.Name,
ExerciseMode = "European",
ExerciseDate = model.ExpirationDate,
SpotPrice = model.SpotPrice,
StartDate = model.StartDate,
StockEqvNotional = model.NotionalPrincipal,
OriginalStockEqvNotional = model.NotionalPrincipal,
Notional = 0,
OriginalNotional = 0,
OptDate = DateTime.Now,
OptId = UserId,
OptName = UserName,
CreateDate = DateTime.Now,
TradeAmount = 0,
Strike = null,
TradePrice = null,
TradeSinglePrice = null,
InitialMargin = model.Margin,
TradeOpenVolatility = model.Vol,
TradeCloseVolatility = model.Vol,
ParticipationRate = model.ParticipationRate,
CalcFlag = model.CalcFlag,
PremiumRate = model.InitialAnnualPremiumRate,
SettlementDate = model.SettlementDate,
IsAnnualized = model.IsAnnualized == "是",
AnnualizeFactor = model.AnnualizeFactor
};
tradeOb.Notional = model.NotionalPrincipal / model.SpotPrice;
tradeOb.TradeAmount = tradeOb.Notional / underlying.CountRatio;
tradeOb.OriginalNotional = tradeOb.Notional;
tradeOb.Lots = tradeOb.Notional / underlying.ContractSize;
tradeOb.StockEqvNotionalReal = TradeHelper.GetStockEqvNotionalReal(tradeOb.OriginalStockEqvNotional, tradeOb.ParticipationRate, tradeOb.AnnualizeFactor);
tradeOb.TradePrice = model.Premium;
tradeOb.TradeSinglePrice = TradeHelper.GetTradeSinglePriceByTradePrice(model.Premium, tradeOb.Notional, tradeOb.OriginalPrincipalSum, tradeOb.BuySell, tradeOb.TradeType, true);
#region 设置对冲波动率
tradeOb.TradeSavedVol = tradeOb.TradeOpenVolatility;
if (PS.Config.ErpElement.VolMode == Configuration.VolModeEnum.VolSurface)
{
tradeOb.TradeSavedVol = tradeOb.Vol;
}
#endregion
tradeOb.TradeNumber = model.ContractNumber;//BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(tradeOb);
return tradeOb;
}
/// <summary>
/// 标准化标的代码
/// </summary>
private string NormalizeUnderlyingCode(string code)
{
if (string.IsNullOrEmpty(code))
{
return code;
}
var index = code.LastIndexOf('.');
if (index >= 0)
{
code = code.Substring(0, index);
}
if (code.All(n => (n >= 'a' && n <= 'z') || (n >= 'A' && n <= 'Z')))
{
return code + "00";
}
return code;
}
/// <summary>
/// 结构化交易信息类
/// </summary>
class StructureTradeInfo
{
public trade parentTrade;
public trade[] subTrades;
public ClientMainInfo client;
}
/// <summary>
/// 获取导入模板
/// </summary>
public static string GetCsvTemplate()
{
return new ImportModel.ImportMap().GenTemplate();
}
}
class ImportModel
{
/// <summary>
/// 合同编号
/// </summary>
public string ContractNumber { get; set; }
/// <summary>
/// 对手方(客户名称)
/// </summary>
public string ClientName { get; set; }
/// <summary>
/// 买卖方向
/// </summary>
public string BuySell { get; set; }
/// <summary>
/// 挂钩标的
/// </summary>
public string UnderlyingCode { get; set; }
/// <summary>
/// 挂钩标的名称
/// </summary>
public string UnderlyingName { get; set; }
/// <summary>
/// 期权类型
/// </summary>
public string OptionStructurType { get; set; }
/// <summary>
/// 名义规模(名义本金)
/// </summary>
public double NotionalPrincipal { get; set; }
/// <summary>
/// 期初价格
/// </summary>
public double SpotPrice { get; set; }
/// <summary>
/// 行权价%1
/// </summary>
public double StrikePrice1Percent { get; set; }
/// <summary>
/// 行权价1
/// </summary>
public double StrikePrice1 { get; set; }
/// <summary>
/// 行权价%2
/// </summary>
public double StrikePrice2Percent { get; set; }
/// <summary>
/// 行权价2
/// </summary>
public double StrikePrice2 { get; set; }
/// <summary>
/// 起始日
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// 结算日
/// </summary>
public DateTime SettlementDate { get; set; }
/// <summary>
/// 簿记账户
/// </summary>
public string AssetBookName { get; set; }
/// <summary>
/// 交易员
/// </summary>
public string TraderName { get; set; }
/// <summary>
/// 期权费
/// </summary>
public double Premium { get; set; }
/// <summary>
/// 期初年化期权费率
/// </summary>
public double InitialAnnualPremiumRate { get; set; }
/// <summary>
/// 期末年化期权费率
/// </summary>
public double ReleaseAnnualPremiumRate { get; set; }
/// <summary>
/// 到期日
/// </summary>
public DateTime ExpirationDate { get; set; }
/// <summary>
/// 预付金
/// </summary>
public double Margin { get; set; }
/// <summary>
/// 天数
/// </summary>
public short DurationDays { get; set; }
/// <summary>
/// 参与率
/// </summary>
public double ParticipationRate { get; set; }
/// <summary>
/// 波动率
/// </summary>
public double Vol { get; set; }
/// <summary>
/// 计算标识(0:全部,1:IgnoreMargin(忽略预付金计算使用初始预付金))
/// </summary>
public int CalcFlag { get; set; }
/// <summary>
/// 是否为期权年化,赋值为“是”或“否”
/// </summary>
public string IsAnnualized { get; set; }
/// <summary>
/// 年化系数
/// </summary>
public double AnnualizeFactor { get; set; }
/// <summary>
/// 备注
/// </summary>
public string Remark { get; set; }
public override string ToString()
{
return ContractNumber;
}
public class ImportMap : ClassMap<ImportModel>
{
public ImportMap()
{
Map(m => m.ContractNumber).Name("合同编号");
Map(m => m.ClientName).Name("对手方");
Map(m => m.BuySell).Name("买卖方向");
Map(m => m.UnderlyingCode).Name("挂钩标的");
Map(m => m.AssetBookName).Name("簿记账户");
Map(m => m.TraderName).Name("交易员");
Map(m => m.OptionStructurType).Name("期权类型");
Map(m => m.NotionalPrincipal).Name("名义规模");
Map(m => m.SpotPrice).Name("期初价格");
Map(m => m.StrikePrice1Percent).Name("行权价%1").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required);
Map(m => m.StrikePrice1).Name("行权价1");
Map(m => m.StrikePrice2Percent).Name("行权价%2").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required);
Map(m => m.StrikePrice2).Name("行权价2");
Map(m => m.StartDate).Name("起始日");
Map(m => m.SettlementDate).Name("结算日");
Map(m => m.Premium).Name("期权费");
Map(m => m.InitialAnnualPremiumRate).Name("期初费率").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required);
Map(m => m.ReleaseAnnualPremiumRate).Name("期末费率").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required);
Map(m => m.ExpirationDate).Name("到期日");
Map(m => m.Margin).Name("预付金");
Map(m => m.DurationDays).Name("天数");
Map(m => m.ParticipationRate).Name("参与率").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required);
Map(m => m.Vol).Name("波动率").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required);
Map(m => m.IsAnnualized).Name("期权年化");
Map(m => m.AnnualizeFactor).Name("年化系数");
Map(m => m.CalcFlag).Name("计算标识");
Map(m => m.UnderlyingName).Name("挂钩标的名称");
Map(m => m.Remark).Name("备注").TypeConverter(Helpers.CsvTypeConverts.StringConverter.NoValidator);
}
public string GenTemplate()
{
var sb = new StringBuilder(512);
foreach (var map in MemberMaps)
{
sb.AppendCSVCell(map.Data.Names.First()).Append(',');
}
if (sb.Length > 0)
{
sb.Remove(sb.Length - 1, 1);
}
return sb.ToString();
}
}
}
}