411 lines
14 KiB
C#
411 lines
14 KiB
C#
using YLErp.BLL;
|
|
using YLErp.BLL.Eod;
|
|
using YLErp.DBModels.Consts;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.TradeModule
|
|
{
|
|
/// <summary>
|
|
/// 交易历史记录服务
|
|
/// </summary>
|
|
public class TradeHisDataService : TradeServiceBase
|
|
{
|
|
public TradeHisDataService(YLBaseService baseService) : base(baseService)
|
|
{
|
|
|
|
}
|
|
|
|
public TradeHisDataService(OptUserInfo userInfo) : base(userInfo)
|
|
{
|
|
}
|
|
|
|
#region----对冲波动率----
|
|
|
|
/// <summary>
|
|
/// 根据交易ID获取最新的对冲波动率
|
|
/// </summary>
|
|
/// <returns>返回交易ID为Key,对冲波动率为Value的字典</returns>
|
|
public Dictionary<int, double> GetLatestHedgeVol(IEnumerable<int> tradeIds)
|
|
{
|
|
if (tradeIds is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(tradeIds));
|
|
}
|
|
|
|
var count = tradeIds.Count();
|
|
var dic = new Dictionary<int, double>();
|
|
|
|
for (var i = 0; i < count; i += 1000)
|
|
{
|
|
var subIds = tradeIds.Skip(i).Take(1000);
|
|
|
|
var query2 = from tv in DbContext.trade_hedge_vol
|
|
where subIds.Contains(tv.TradeId)
|
|
group tv by tv.TradeId into g
|
|
select g.Max(n => n.id);
|
|
|
|
var query3 = from tv2 in query2
|
|
join tv in DbContext.trade_hedge_vol on tv2 equals tv.id
|
|
select new
|
|
{
|
|
tv.TradeId,
|
|
tv.TradeSavedVol
|
|
};
|
|
|
|
foreach (var item in query3)
|
|
{
|
|
dic.Add(item.TradeId, item.TradeSavedVol);
|
|
}
|
|
}
|
|
|
|
return dic;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存对冲波动率
|
|
/// </summary>
|
|
public int SaveHedgeVol(string tradeNumber, DateTime valueDate, double value)
|
|
{
|
|
if (string.IsNullOrEmpty(tradeNumber))
|
|
{
|
|
throw new ArgumentException("参数不能为空值", nameof(tradeNumber));
|
|
}
|
|
|
|
var td = DbContext.trade.FirstOrDefault(n => n.TradeNumber == tradeNumber);
|
|
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("保存对冲波动率失败,找不到交易信息,交易编号:" + tradeNumber);
|
|
}
|
|
|
|
return InnerSaveHedgeVol(td, valueDate, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存对冲波动率
|
|
/// </summary>
|
|
public int SaveHedgeVol(int tradeId, DateTime valueDate, double value)
|
|
{
|
|
if (tradeId <= 0)
|
|
{
|
|
throw new ServiceException($"{nameof(tradeId)}应该大于0");
|
|
}
|
|
|
|
var td = DbContext.trade.Find(tradeId);
|
|
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("保存对冲波动率失败,找不到交易信息,交易ID:" + tradeId);
|
|
}
|
|
|
|
return InnerSaveHedgeVol(td, valueDate, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存对冲波动率
|
|
/// </summary>
|
|
public int InnerSaveHedgeVol(trade td, DateTime valueDate, double value)
|
|
{
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("保存对冲波动率失败,找不到交易信息");
|
|
}
|
|
|
|
if (valueDate == valuedateBLL.ValueDate)
|
|
{
|
|
td.TradeSavedVol = value;
|
|
}
|
|
else if (valueDate > valuedateBLL.ValueDate)
|
|
{
|
|
throw new ServiceException("保存对冲波动率失败,因为赋值日期大于系统日期");
|
|
}
|
|
|
|
var hedgeTradeVol = DbContext.trade_hedge_vol.FirstOrDefault(v => v.TradeId == td.id && v.ValueDate == valueDate);
|
|
|
|
if (hedgeTradeVol == null)
|
|
{
|
|
hedgeTradeVol = new trade_hedge_vol
|
|
{
|
|
TradeId = td.id,
|
|
ValueDate = valueDate,
|
|
};
|
|
DbContext.trade_hedge_vol.Add(hedgeTradeVol);
|
|
}
|
|
|
|
SetDBModelOpt(hedgeTradeVol);
|
|
|
|
hedgeTradeVol.TradeSavedVol = value;
|
|
|
|
return DbContext.SaveChanges();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----无风险利率/分红率---
|
|
|
|
/// <summary>
|
|
/// 保存无风险利率历史
|
|
/// </summary>
|
|
public int SaveNoRiskRate(int tradeId, DateTime valueDate, double value)
|
|
{
|
|
return SaveHisData(tradeId, valueDate, ConsTradeField.NoRiskRate, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存无风险利率历史
|
|
/// </summary>
|
|
public int SaveNoRiskRate(string tradeNumber, DateTime valueDate, double value)
|
|
{
|
|
if (string.IsNullOrEmpty(tradeNumber))
|
|
{
|
|
throw new ArgumentException("参数不能为空值", nameof(tradeNumber));
|
|
}
|
|
|
|
var td = DbContext.trade.FirstOrDefault(n => n.TradeNumber == tradeNumber);
|
|
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("保存无风险利率失败,找不到交易信息,交易编号:" + tradeNumber);
|
|
}
|
|
|
|
return InnerSaveHisData(td, valueDate, ConsTradeField.NoRiskRate, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存分红率历史
|
|
/// </summary>
|
|
public int SaveDividendRate(int tradeId, DateTime valueDate, double value)
|
|
{
|
|
return SaveHisData(tradeId, valueDate, ConsTradeField.DividendRate, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存分红率历史
|
|
/// </summary>
|
|
public int SaveDividendRate(string tradeNumber, DateTime valueDate, double value)
|
|
{
|
|
if (string.IsNullOrEmpty(tradeNumber))
|
|
{
|
|
throw new ArgumentException("参数不能为空值", nameof(tradeNumber));
|
|
}
|
|
|
|
var td = DbContext.trade.FirstOrDefault(n => n.TradeNumber == tradeNumber);
|
|
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("保存分红率失败,找不到交易信息,交易编号:" + tradeNumber);
|
|
}
|
|
|
|
return InnerSaveHisData(td, valueDate, ConsTradeField.DividendRate, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存交易数据历史(valueType:ConsTradeField)
|
|
/// </summary>
|
|
public int SaveHisData(int tradeId, DateTime valueDate, string valueType, double value)
|
|
{
|
|
if (tradeId < 1)
|
|
{
|
|
throw new ServiceException($"{nameof(tradeId)}应该大于0");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(valueType))
|
|
{
|
|
throw new ArgumentException("不能为空", nameof(valueType));
|
|
}
|
|
|
|
var td = DbContext.trade.Find(tradeId);
|
|
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("更新失败,找不到交易信息,交易ID:" + tradeId);
|
|
}
|
|
|
|
return InnerSaveHisData(td, valueDate, valueType, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存交易数据历史(valueType:ConsTradeField)
|
|
/// </summary>
|
|
private int InnerSaveHisData(trade td, DateTime valueDate, string valueType, double value)
|
|
{
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("更新失败,找不到交易信息");
|
|
}
|
|
|
|
switch (valueType)
|
|
{
|
|
case ConsTradeField.NoRiskRate:
|
|
if (valueDate == valuedateBLL.ValueDate)
|
|
{
|
|
td.NoRiskRate = value;
|
|
}
|
|
else if (valueDate > valuedateBLL.ValueDate)
|
|
{
|
|
throw new ServiceException("保存无风险利率失败,因为赋值日期大于系统日期");
|
|
}
|
|
break;
|
|
case ConsTradeField.DividendRate:
|
|
if (valueDate == valuedateBLL.ValueDate)
|
|
{
|
|
td.DividendRate = value;
|
|
}
|
|
else if (valueDate > valuedateBLL.ValueDate)
|
|
{
|
|
throw new ServiceException("保存分红率失败,因为赋值日期大于系统日期");
|
|
}
|
|
break;
|
|
default: throw new ServiceException("不支持:" + valueType);
|
|
}
|
|
|
|
var dbModel = DbContext.TradeHisData.FirstOrDefault(v => v.TradeId == td.id && v.ValueDate == valueDate && v.ValueType == valueType);
|
|
|
|
if (dbModel == null)
|
|
{
|
|
dbModel = new TradeHisData
|
|
{
|
|
TradeId = td.id,
|
|
ValueDate = valueDate,
|
|
ValueType = valueType
|
|
};
|
|
|
|
DbContext.TradeHisData.Add(dbModel);
|
|
}
|
|
|
|
dbModel.Value = value;
|
|
|
|
SetDBModelOpt(dbModel);
|
|
|
|
return DbContext.SaveChanges();
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// 保存交易持仓波动率
|
|
/// </summary>
|
|
public TradeVolatility SaveTradeVolatility(TradeVolatilityDto req, bool refreshRisk)
|
|
{
|
|
var tradePositionVolatility = req.TradePositionVolatility ?? 0;
|
|
var tradeCloseVolatility = req.TradeCloseVolatility ?? 0;
|
|
var numOfSmoothingDays = req.NumOfSmoothingDays ?? 0;
|
|
|
|
if (tradePositionVolatility <= 0)
|
|
{
|
|
throw new ServiceException("持仓波动率必须大于0");
|
|
}
|
|
|
|
if (tradeCloseVolatility <= 0)
|
|
{
|
|
throw new ServiceException("目标波动率必须大于0");
|
|
}
|
|
|
|
if (numOfSmoothingDays <= 0)
|
|
{
|
|
throw new ServiceException("平滑天数必须大于0");
|
|
}
|
|
|
|
trade td = null;
|
|
|
|
if (req.TradeId > 0)
|
|
{
|
|
td = DbContext.trade.Find(req.TradeId);
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(req.TradeNumber))
|
|
{
|
|
td = DbContext.trade.FirstOrDefault(n => n.TradeNumber == req.TradeNumber);
|
|
}
|
|
else
|
|
{
|
|
throw new ServiceException("参数错误:req.TradeId(TradeNumber)");
|
|
}
|
|
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("找不到交易数据:" + req.TradeNumber);
|
|
}
|
|
|
|
var valueDate = valuedateBLL.ValueDate;
|
|
var remianDays = QdpCalendarHelper.GetNonHolidayDaysBetween(valueDate, td.ExerciseDate.Value) + 1;
|
|
if (td.ExerciseDate.HasValue && numOfSmoothingDays > remianDays)
|
|
{
|
|
throw new ServiceException($"平滑过渡天数{req.NumOfSmoothingDays}不能大于存续天数{remianDays}天");
|
|
}
|
|
|
|
td.NumOfSmoothingDays = numOfSmoothingDays;
|
|
td.TradeCloseVolatility = tradeCloseVolatility;
|
|
|
|
//数据库中tradeid+valuedate是唯一索引
|
|
|
|
var dbModel = DbContext.TradeVolatility.FirstOrDefault(x => x.ValueDate == valueDate && x.TradeId == td.id);
|
|
|
|
if (dbModel == null)
|
|
{
|
|
dbModel = new TradeVolatility
|
|
{
|
|
TradeId = td.id,
|
|
ValueDate = valueDate
|
|
};
|
|
DbContext.TradeVolatility.Add(dbModel);
|
|
}
|
|
|
|
SetDBModelOpt(dbModel);
|
|
|
|
dbModel.NumOfSmoothingDays = numOfSmoothingDays;
|
|
dbModel.TradePositionVolatility = tradePositionVolatility;
|
|
dbModel.TradeCloseVolatility = tradeCloseVolatility;
|
|
dbModel.IsFromTradeAdd = false;
|
|
|
|
DbContext.SaveChanges();
|
|
|
|
if (refreshRisk)
|
|
{
|
|
RealtimePnlCalc.RefreshRiskAndIntradayPositionForOtc(new List<trade>() { td }, UserInfo);
|
|
}
|
|
|
|
AddTradeOperationHistoryAndSetParentTradeInfo(true, td, "修改交易持仓波动率");
|
|
|
|
return dbModel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 使用历史数据更新交易数据(一般用于历史日期平仓结算)
|
|
/// </summary>
|
|
public void UpdateTradeWithHisData(OtcTradeBase td, DateTime hisDate)
|
|
{
|
|
if (td is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(td));
|
|
}
|
|
|
|
var query1 = DbContext.TradeHisData.AsNoTracking()
|
|
.Where(v => v.TradeId == td.id && v.ValueDate <= hisDate && v.ValueType == ConsTradeField.NoRiskRate)
|
|
.OrderByDescending(n => n.ValueDate).Select(n => new { n.ValueType, n.Value });
|
|
|
|
var query2 = DbContext.TradeHisData.AsNoTracking()
|
|
.Where(v => v.TradeId == td.id && v.ValueDate <= hisDate && v.ValueType == ConsTradeField.DividendRate)
|
|
.OrderByDescending(n => n.ValueDate).Select(n => new { n.ValueType, n.Value });
|
|
|
|
var datas = query1.Concat(query2).ToArray();
|
|
|
|
if (!datas.Any())
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var data in datas)
|
|
{
|
|
if (ConsTradeField.NoRiskRate.Equals(data.ValueType, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
td.NoRiskRate = data.Value;
|
|
}
|
|
else if (ConsTradeField.DividendRate.Equals(data.ValueType, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
td.DividendRate = data.Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|