Files
zszq-trs/YLErpDAL/BLL/MarginCalculation/MarginCalcHelper.cs
T
2024-05-09 14:06:26 +08:00

663 lines
25 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Qdp.Pricing.Base.Implementations;
using YLErp.Abstract.DataProviders;
using YLErp.Commons;
using YLErp.Modules;
using YLErp.Modules.CalculationModule;
using YLErp.Modules.DataProviderModule;
using YLErp.Modules.MarginModule;
using YLErp.QdpModule;
namespace YLErp.BLL.MarginCalculation
{
public partial class MarginCalculationBase
{
/// <summary>
/// 预付金计算帮助类
/// </summary>
protected class RunMarginCalculationHelper
{
public readonly RunMarginCalculationReq req;
readonly IUnderlyingDataProvider underlyingDataProvider;
HashSet<string> _underlyingCodeSet;
Dictionary<int, InnerClient> _clientDic;
readonly MarginParamProvider _mpProvider;
public RunMarginCalculationHelper(RunMarginCalculationReq req, IUnderlyingDataProvider underlyingDataProvider)
{
this.req = req;
this.underlyingDataProvider = underlyingDataProvider;
_mpProvider = new MarginParamProvider(req.UserInfo, req.settleDate);
}
#region----数据准备(内部方法)----
/// <summary>
/// 交易对应客户信息
/// </summary>
protected void PrepareClient()
{
if (_clientDic != null)
{
return;
}
var clientIds = req.tradeList.Select(t => t.ClientId).ToHashSet();
using (var db = DbContextFactory.GetClientDbContext(req.UserInfo))
{
var query = from c in db.client
join cl in db.clientlevel on c.LevelId equals cl.id into t_cl
from cl in t_cl.DefaultIfEmpty()
where clientIds.Contains(c.id)
select new InnerClient
{
ClientId = c.id,
Ratio = cl == null ? null : cl.Ratio,
Ratio1 = cl == null ? null : cl.Ratio1,
AddRatio = cl == null ? null : cl.AddRatio,
MarginOptionType = c.MarginOptionType,
ProperClientClass = c.ProperClientClass,
QuestionnaireScore = c.QuestionnaireScore,
RuleT0orT1 = c.RuleT0orT1,
BoundSide = c.BoundSide
};
_clientDic = query.ToDictionary(n => n.ClientId);
}
}
/// <summary>
///
/// </summary>
protected HashSet<string> PrepareUnderlyingCodes()
{
if (_underlyingCodeSet != null)
{
return _underlyingCodeSet;
}
_underlyingCodeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
void setUnderlyingCode(trade td)
{
if (td?.UnderlyingCode == null)
{
return;
}
_underlyingCodeSet.Add(td.UnderlyingCode);
switch (td.TradeType)
{
case "彩虹期权":
if (td.trade_rainbow_option != null)
{
_underlyingCodeSet.Add(td.trade_rainbow_option.UnderlyingAssetCode2);
}
break;
case "价差期权":
if (td.trade_spread_option != null)
{
var codes = td.trade_spread_option.UnderlyingAssetCodes();
foreach (var code in codes)
{
_underlyingCodeSet.Add(code);
}
}
break;
case "结构化交易":
if (td.SubTrades != null)
{
foreach (var std in td.SubTrades)
{
setUnderlyingCode(std);
}
}
break;
}
}
foreach (var td in req.tradeList)
{
setUnderlyingCode(td);
}
return _underlyingCodeSet;
}
#endregion
#region----提供数据(公共)----
/// <summary>
/// 获取标的数据
/// </summary>
public underlying_manager GetUnderlying(string underlyingCode)
{
return underlyingDataProvider.GetUnderlying(underlyingCode);
}
/// <summary>
/// 获取underlying code集合
/// </summary>
/// <returns></returns>
public HashSet<string> GetUnderlyingCodes()
{
return _underlyingCodeSet;
}
/// <summary>
/// 根据交易获取客户数据
/// </summary>
public InnerClient GetClient(trade trade)
{
return GetClient(trade.ClientId);
}
/// <summary>
/// 根据客户ID获取客户数据
/// </summary>
public InnerClient GetClient(int clientId)
{
if (clientId < 1)
{
return null;
}
if (_clientDic == null)
{
PrepareClient();
}
return _clientDic.TryGetValue(clientId, out var client) ? client : null;
}
public List<InnerClient> GetClients()
{
if (_clientDic == null)
{
PrepareClient();
}
return _clientDic.Values.ToList();
}
/// <summary>
/// 获取预付金参数提供
/// </summary>
public MarginParamProvider GetMarginParamProvider(MarginParamTypeEnum initTypeEnums = MarginParamTypeEnum.None)
{
if (initTypeEnums != MarginParamTypeEnum.None)
{
var umCodeSet = PrepareUnderlyingCodes();
_mpProvider.Initialize(umCodeSet, initTypeEnums);
}
return _mpProvider;
}
/// <summary>
/// 根据标的ID获取涨跌停价格
/// </summary>
public UpdownLimit GetUpDownLimit(string underlyingCode, double price, bool useMinPriceChange = false, double extendLimitRate = 1)
{
if (_mpProvider.TryGetUpdownLimit(underlyingCode, out var limit, out var isFixed))
{
if (!isFixed)
{
limit *= price;
}
}
else
{
limit = price * 0.05;
}
//注意:price价格可能为负值
limit = Math.Abs(limit);
if (useMinPriceChange)
{
var um = underlyingDataProvider.GetUnderlying(underlyingCode);
var minPriceChange = um?.PriceTick ?? 0.01;
var half = minPriceChange / 2;
var diff = limit % minPriceChange;
limit -= diff;
if (diff >= half)
{
limit += minPriceChange;
}
}
return new UpdownLimit
{
UpLimitPrice = price + limit * extendLimitRate,
DownLimitPrice = price - limit * extendLimitRate
};
}
/// <summary>
/// 根据涨跌停比例获取张跌停价
/// </summary>
/// <param name="price"></param>
/// <param name="limitRate"></param>
/// <returns></returns>
public UpdownLimit GetUpDownLimitByRate(double price, double limitRate)
{
return new UpdownLimit
{
UpLimitPrice = price * (1 + limitRate),
DownLimitPrice = price * (1 - limitRate)
};
}
public UpdownLimit GetStockUpDownLimit(string underlyingCode, double price)
{
var limit = 0.1;
double limitPrice;
if (underlyingCode.StartsWith("30") || underlyingCode.StartsWith("688"))
{
limit = 0.2;
}
limitPrice = price * limit;
var minPriceChange = 0.01;
var half = minPriceChange / 2;
var diff = limitPrice % minPriceChange;
limitPrice -= diff;
if (diff >= half)
{
limitPrice += minPriceChange;
}
return new UpdownLimit
{
UpLimitPrice = OtcFormatHelper.FormatValue(price + limitPrice, 2),
DownLimitPrice = OtcFormatHelper.FormatValue(price - limitPrice, 2)
};
}
/// <summary>
/// 获取涨跌停价格字典
/// </summary>
/// <param name="upLimitPrices"></param>
/// <param name="downLimitPrices"></param>
/// <param name="useMinPriceChange"></param>
/// <param name="extendLimitRate">涨跌停额外系数,默认为1</param>
public void GetUpDownLimitPrices(out IPriceProvider upLimitPrices, out IPriceProvider downLimitPrices, bool useMinPriceChange = false, double extendLimitRate = 1, bool isUsePriceLimit = true)
{
var umCodeSet = PrepareUnderlyingCodes();
var upLimitPrices2 = new ManualPriceProvider();
var downLimitPrices2 = new ManualPriceProvider();
//根据涨跌幅限制以及当日结算价计算涨停价以及跌停价
foreach (var code in umCodeSet)
{
var price = req.PriceProvider.GetPrice(code);
var updown = isUsePriceLimit ? GetUpDownLimit(code, price, useMinPriceChange, extendLimitRate) : GetUpDownLimitByRate(price, extendLimitRate);
upLimitPrices2.SetPrice(code, updown.UpLimitPrice);
downLimitPrices2.SetPrice(code, updown.DownLimitPrice);
}
upLimitPrices = upLimitPrices2;
downLimitPrices = downLimitPrices2;
}
/// <summary>
/// 获取涨跌停价格字典
/// <para>可通过SplitNumber控制涨停到跌停的分割数量</para>
/// <para>例如:涨停24,跌停6,splitNumber=6,</para>
/// <para>则返回的数组的lenght=7,分别为24,21,18,15,12,9,6</para>
/// </summary>
/// <param name="splitNumber">从涨停到跌停分割多少次</param>
/// <param name="useMinPriceChange"></param>
/// <param name="extendLimitRate"></param>
/// <param name="isUsePriceLimit">涨跌停额外系数,默认为1</param>
/// <returns></returns>
public IPriceProvider[] GetUpDownLimitPrices(int splitNumber, bool useMinPriceChange = false, double extendLimitRate = 1, bool isUsePriceLimit = true)
{
var umCodeSet = PrepareUnderlyingCodes();
var priceArr = new ManualPriceProvider[splitNumber + 1];
for (int i = 0; i < priceArr.Length; i++)
{
priceArr[i] = new ManualPriceProvider();
}
//根据涨跌幅限制以及当日结算价计算涨停价以及跌停价
foreach (var code in umCodeSet)
{
var price = req.PriceProvider.GetPrice(code);
var updown = isUsePriceLimit ? GetUpDownLimit(code, price, useMinPriceChange, extendLimitRate) : GetUpDownLimitByRate(price, extendLimitRate);
var gap = (updown.UpLimitPrice - updown.DownLimitPrice) / splitNumber;
for (int i = 0; i < priceArr.Length - 1; i++)
{
priceArr[i].SetPrice(code, (updown.UpLimitPrice - (gap * i)).FormatValue(10));
}
priceArr[priceArr.Length - 1].SetPrice(code, updown.DownLimitPrice);
}
return priceArr.Cast<IPriceProvider>().ToArray();
}
/// <summary>
/// 获取持仓波动率字典
/// </summary>
public void GetTradVolRateDic(out Dictionary<int, double> volRateDic, Func<trade, double> addVol = null)
{
var tradeVolRateArr = req.tradeList.Select(t => new
{
tradeId = t.id,
volRate = (_mpProvider.GetVolatilityRate(t.UnderlyingCode) ?? 0) + (addVol?.Invoke(t) ?? 0)
}).Where(d => d.volRate > 0).ToArray();
volRateDic = tradeVolRateArr.ToDictionary(d => d.tradeId, d => d.volRate);
}
/// <summary>
/// 获取持仓波动率字典
/// </summary>
public void GetMarginTradVolRateDic(out Dictionary<int, double> volRateDic, Func<trade, double> addVol = null)
{
var tradeVolRateArr = req.tradeList.Select(t => new
{
tradeId = t.id,
volRate = GetTradeVol(t.TradeNumber, req.settleDate) ?? 0
}).Where(d => d.volRate > 0).ToArray();
volRateDic = tradeVolRateArr.ToDictionary(d => d.tradeId, d => d.volRate);
}
/// <summary>
/// 获取持仓波动率字典
/// </summary>
public void GetUpDownVolRateDic(out Dictionary<int, double> upVolRateDic, out Dictionary<int, double> downVolRateDic)
{
var tradeVolRateArr = req.tradeList.Select(t => new
{
tradeId = t.id,
volRate = _mpProvider.GetVolatilityRate(t.UnderlyingCode) ?? 0
}).Where(d => d.volRate > 0).ToArray();
upVolRateDic = tradeVolRateArr.ToDictionary(d => d.tradeId, d => d.volRate);
downVolRateDic = tradeVolRateArr.ToDictionary(d => d.tradeId, d => -d.volRate);
}
#endregion
#region----实用方法(公共)----
/// <summary>
/// 将BuySell换做相反的方向,
/// 交易列表变更为克隆数据,所以不需要再对交易恢复原来的方向
/// </summary>
/// <param name="force">强制转换</param>
public void ReverseTradeSide(bool force = false)
{
if (!force && !req.forOtherSide)
{
return;
}
req.tradeList = req.tradeList.Select(n =>
{
var clone = n.Clone();
clone.BuySell = clone.BuySell == "买入" ? "卖出" : "买入";
if (clone.SubTrades != null && clone.SubTrades.Any())
{
clone.SubTrades = clone.SubTrades.Select(m =>
{
var xs = m.Clone();
xs.BuySell = xs.BuySell == "买入" ? "卖出" : "买入";
return xs;
}).ToArray();
}
return clone;
}).ToList();
}
/// <summary>
///
/// </summary>
public void SetFieldsByTradeType()
{
if (!req.hasOptionInfo)
{
tradeBLL.SetFieldsByTradeType(req.tradeList);
}
}
/// <summary>
/// 获取特殊预付金(手动维护或收益互换交易)
/// </summary>
public bool GetSpecialMargin(trade trade, double calcPv, out double value, bool isForOtherSide = false)
{
value = 0;
//首先获取手动维护(包括收益互换交易)的预付金值
using (var db = DbContextFactory.GetYLDbContext())
{
var predicate = PredicateBuilder.Create<eod_trade_risk_manual>(x => x.TradeId == trade.id && x.Margin != null && string.IsNullOrEmpty(x.VolType));
if (trade.TradeType == "自定义交易")
{
predicate = predicate.And(x => x.ValueDate <= req.settleDate);
}
else
{
//非自定义交易只取当天保存的持仓预付金
predicate = predicate.And(x => x.ValueDate == req.settleDate);
}
var manual = db.eod_trade_risk_manual.Where(predicate)
.OrderByDescending(x => x.ValueDate)
.Select(n => new { n.Margin, n.ValueDate }).FirstOrDefault();
if (manual != null && manual.Margin != null)
{
if (isForOtherSide)
{
value = 0;
}
else
{
value = manual.Margin.Value;
}
}
//自定义交易总是返回true并有一些特殊逻辑判断
if (trade.TradeType == "自定义交易")
{
//收盘时如果自定义交易还活着且没有维护当日风险,并且收的时系统日期当日的盘,抛出exception
if ((manual == null || manual.ValueDate != req.settleDate)
&& req.CalcMarginType == Enums.CalcMarginTypeEnum.EodMargin && !ConsTrade.TradeCompleteStatus.Contains(trade.TradeStatus)
&& req.settleDate == valuedateBLL.ValueDate && !PS.Config.Is润和 && !PS.Config.IsMustRiskManual)
{
throw new Exception($"交易'{trade.TradeNumber}'在{req.settleDate:yyyy-MM-dd}需先进行交易风险维护");
}
return true;
}
//非自定义交易如果有手工维护的预付金则返回true
//否则如果是收益互换交易则进行计算,其他交易返回false
if (manual != null && manual.Margin != null)
{
return true;
}
}
//获取收益互换交易的预付金值
if (trade.TradeType == "收益互换")
{
return true;
}
return false;
}
/// <summary>
/// 创建trade_span实例
/// </summary>
public trade_span CreateTradeSpan(trade td, underlying_manager un = null)
{
if (td is null)
{
throw new ArgumentNullException(nameof(td));
}
if (un == null || td.UnderlyingCode != un.UnderlyingCode)
{
un = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
}
return new trade_span
{
TradeId = td.id,
ClientId = td.ClientId,
ValueDate = req.settleDate,
VarietyId = un?.UnderlyingTypeId,
UnderlyingId = td.UnderlyingId,
UnderlyingCode = td.UnderlyingCode,
OptId = req.userId,
OptName = req.userName,
OptDate = DateTime.Now
};
}
/// <summary>
/// 获取计算一组交易的风险指标输入参数
/// </summary>
public CalculateRisksForTradesReq GetCalculateRisksForTradesReq(IPriceProvider priceProvider,
Dictionary<int, double> addVolRateDic, Dictionary<int, double> overrideVols, PricingRequest pricingRequest = QdpPricingRequest.PV_ONLY, bool isEodCalc = false, string pricekey = null)
{
var tlist = pricekey != null && pricekey.Contains("barPrice") ? req.tradeList.Where(l => l.UnderlyingCode == pricekey.Split(',')[1]).ToList() : req.tradeList;
var reqConv = new CalculateRisksForTradesReq
{
valueDate = req.settleDate,
tradeList = tlist,
priceProvider = priceProvider,
pricingRequest = pricingRequest,
addVolRateDic = addVolRateDic,
volType = req.volType,
settlementType = req.settlementType,
isUseTradeVol = PS.Config.IsTradeVol,
PreciseTimeMode = req.CalcMarginType != Enums.CalcMarginTypeEnum.EodMargin,
isAddVolPercent = true,
overrideVolsForTrade = overrideVols,
isMarginCalc = true,
isEodCalc = isEodCalc
};
if (req.CalcMarginType == Enums.CalcMarginTypeEnum.EodMargin)
{
reqConv.calcScenario = Enums.CalcScenarioEnum.EodSettlement;
}
if (req.CalcMarginType == Enums.CalcMarginTypeEnum.InitialMargin)
{
reqConv.calcScenario = Enums.CalcScenarioEnum.InitialMargin;
}
return reqConv;
}
public double? GetTradeVol(string TradeNumber, DateTime ValueDate)
{
using (var db = DbContextFactory.GetYLDbContext())
{
var tradeVol = db.trade_vol.Where(a => a.IsValid && a.TradeNumber == TradeNumber && a.ValueDate == ValueDate).FirstOrDefault();
if (tradeVol != null)
{
return tradeVol.Vol;
}
}
return null;
}
#endregion
}
#region----内部类----
/// <summary>
///
/// </summary>
protected class InnerClient
{
public int ClientId { get; set; }
/// <summary>
/// 维持预付金系数(R2
/// </summary>
public double? Ratio { get; set; }
/// <summary>
/// 初始预付金系数(R1
/// </summary>
public double? Ratio1 { get; set; }
/// <summary>
/// 商品预付金率加点
/// </summary>
public double? AddRatio { get; set; }
/// <summary>
/// 是否有双向预付金
/// </summary>
public int? MarginOptionType { get; set; }
/// <summary>
/// 适当性类型
/// </summary>
public string ProperClientClass { get; set; }
/// <summary>
/// 资信评估分数
/// </summary>
public int? QuestionnaireScore { get; set; }
/// <summary>
/// 方顿预付金模板选择
/// </summary>
public string RuleT0orT1 { get; set; }
/// <summary>
/// 资金流向
/// </summary>
public BoundSideEnum BoundSide { get; set; }
public override string ToString()
{
return ClientId.ToString();
}
}
/// <summary>
/// 涨跌停限制
/// </summary>
protected struct UpdownLimit
{
/// <summary>
/// 涨停价
/// </summary>
public double UpLimitPrice { get; set; }
/// <summary>
/// 跌停价
/// </summary>
public double DownLimitPrice { get; set; }
/// <summary>
/// 涨跌停幅度:创业板为20%,其他默认为10%
/// </summary>
public double limit { get; set; }
}
#endregion
}
}