using System.Collections.Concurrent; using YieldChain.Commons; using YieldChain.Helpers; using YLErp.Abstract.DataProviders; using YLErp.BLL; using YLErp.Models; using YLErp.Modules.UnderlyingModule; namespace YLErp.Modules.DataCacheModule { public static partial class DataCacheManager { /// /// 标的数据源 /// class UnderlyingDataSource : GenericeCachedDataSource, IUnderlyingDataSource, IBasketPriceProvider { readonly ThrottleAction _updatePricethrottle; readonly Dictionary _dicEx; readonly ConcurrentDictionary _dicSynthetic; private UnderlyingDataSource() { _updatePricethrottle = new ThrottleAction(ThrottleUpdatePricesAction, 3, 180); _dicEx = new Dictionary(StringComparer.OrdinalIgnoreCase); _dicSynthetic = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); AfterUpdate = AfterUpdateHandle; AfterRemove = AfterRemoveHandle; //使用近2年的标的数据作为缓存 int.TryParse(PS.Config.ErpElement.UnderlyingCacheCfg, out int years); if (years < 0) years = -years; var date = DateTime.Today.AddYears(years < 2 ? -2 : -years); Filter = PredicateBuilder.Create( n => (n.UnderlyingInstrumentType != ConsGlobal.InstrumentType.CommodityFutures && n.UnderlyingInstrumentType != ConsGlobal.InstrumentType.StockIF) || n.MaturityDate >= date); } public override string TableName => nameof(underlying_manager); /// /// 扩展数据更新 /// private void AfterUpdateHandle(underlying_manager data) { if (string.IsNullOrWhiteSpace(data?.UnderlyingCode)) { return; } _dicEx[data.UnderlyingCode] = data; _dicSynthetic.TryRemove(data.UnderlyingCode, out _); var variety = DataCacheProvider.GetVarietyDataSource().GetData(data.UnderlyingTypeId); if (data.IsStock()) { data.CountRatio = 1; data.ContractSize = data.ContractSize > 0 ? data.ContractSize : 100; data.QuoteUnit = data.TradeUnit = "股"; data.PinYinFirst = PingYinHelper.GetFirstPinYin(data.UnderlyingName)?.ToUpperInvariant(); data.CommodityCode = variety?.VarietyCode; } else if (data.IsBasket()) { data.CountRatio = 1; data.ContractSize = data.ContractSize > 0 ? data.ContractSize : 100; data.QuoteUnit = data.TradeUnit = "股"; } else if (data.IsSynthetic()) { data.CountRatio = 1; data.ContractSize = data.ContractSize > 0 ? data.ContractSize : 1; } else { if (variety != null) { if (data.ContractSize < 1) { data.ContractSize = DBModels.Helpers.VarietyHelper.GetTradeUnitValue(variety.VarietyCode, variety.TradeUnit) ?? 1; } if (string.IsNullOrEmpty(data.QuoteUnit)) { data.QuoteUnit = DBModels.Helpers.VarietyHelper.GetQuoteUnitSingleOriginal(variety.QuoteUnit); } if (string.IsNullOrEmpty(data.TradeUnit)) { data.TradeUnit = DBModels.Helpers.VarietyHelper.GetTradeUnitSingle(variety.TradeUnit); } data.CountRatio = DBModels.Helpers.VarietyHelper.GetCountRatio(variety.QuoteUnit); data.CommodityCode = variety?.VarietyCode; } if (data.ContractSize < 1) { data.ContractSize = 1; } if (data.PriceTick < 1e-4) { data.PriceTick = DBModels.Helpers.VarietyHelper.ParseMinPriceChange(variety) ?? 0; } } if (data.PriceTick < 1e-4) { data.PriceTick = 0.01; } } /// /// 扩展数据移除操作 /// private void AfterRemoveHandle(underlying_manager data) { if (string.IsNullOrWhiteSpace(data?.UnderlyingCode)) { return; } _dicEx.Remove(data.UnderlyingCode); _dicSynthetic.TryRemove(data.UnderlyingCode, out _); } /// /// 根据标的代码获取标的数据 /// public underlying_manager GetData(string underlyingCode) { if (string.IsNullOrWhiteSpace(underlyingCode)) { return null; } lock (this) { if (_dicEx.TryGetValue(underlyingCode, out var underlying)) { var clone = underlying.Clone(); clone.Price = InnerGetPrice(clone); return clone; } using (var db = DbContextFactory.GetYLDbContext()) { var un = db.underlying_manager.AsNoTracking().FirstOrDefault(n => n.UnderlyingCode == underlyingCode); if (un != null) { _dic[un.id] = un; AfterUpdateHandle(un); if (un.IsBasket() && InnerTryGetPrice(un, out var price)) { un.Price = price; } } return un?.Clone(); } } } /// /// 根据标的ID获取标的数据 /// public override underlying_manager GetData(int keyId) { var un = base.GetData(keyId); if (un != null) { un.Price = InnerGetPrice(un); } return un; } #region----IPriceProvider---- /// /// 根据标的代码获取标的价格(包含组合标的) /// public double GetPrice(int underlyingId) { return InnerGetPrice(GetData(underlyingId)); } /// /// 根据标的代码获取标的价格(标的代码不区分大小写)(包含组合标的) /// public double GetPrice(string underlyingCode) { return InnerGetPrice(GetData(underlyingCode)); } /// /// 根据标的代码获取标的价格(包含组合标的) /// public bool TryGetPrice(int underlyingId, out double price) { return InnerTryGetPrice(GetData(underlyingId), out price); } /// /// 根据标的代码获取标的价格,标的代码不区分大小写(包含组合标的) /// public bool TryGetPrice(string underlyingCode, out double price) { return InnerTryGetPrice(GetData(underlyingCode), out price); } public bool InitData(List underlyingCodes) { lock (this) { var notInCaches = underlyingCodes.Where(p => !_dicEx.Keys.Contains(p)).ToList(); if (notInCaches != null && notInCaches.Count > 0) { List unList = null; using (var db = DbContextFactory.GetYLDbContext()) { unList = db.underlying_manager.AsNoTracking().Where(n => notInCaches.Contains(n.UnderlyingCode)).ToList(); } if (unList != null && unList.Count > 0) { unList.ForEach(un => { _dic[un.id] = un; AfterUpdateHandle(un); if (un.IsBasket() && InnerTryGetPrice(un, out var price)) { un.Price = price; } }); } } } return true; } private double InnerGetPrice(underlying_manager underlying) { return InnerTryGetPrice(underlying, out var price) ? price : 0; } private bool InnerTryGetPrice(underlying_manager un, out double price) { price = 0; if (un == null) { return false; } if (un.IsBasket()) { price = BasketUnderlyingHelper.GetBasketPrice(un, this, true).Price; } else { price = un.Price ?? 0; } return true; } /// /// 更新标的价格 /// public bool UpdatePrices(bool delay) { if (_updatePricethrottle.Execute()) { if (delay) { _updatePricethrottle.DelayExecute(60); } return true; } return false; } private void ThrottleUpdatePricesAction() { using (var db = DbContextFactory.GetYLDbContext()) { var query1 = from u in db.underlying_manager where u.UnderlyingState != "Matured" && u.UnderlyingType != "组合标的" select new PriceModel { InstrumentCode = u.UnderlyingCode, Price = u.Price ?? 0, PreClose = u.PrevClosePrice, PriceTime = u.LastUpdateTime }; var query2 = from su in db.synthetic_underlying join u in db.underlying_manager on su.Name equals u.UnderlyingCode join u1 in db.underlying_manager on su.UnderlyingCode1 equals u1.UnderlyingCode into u1t from u1 in u1t.DefaultIfEmpty() join u2 in db.underlying_manager on su.UnderlyingCode2 equals u2.UnderlyingCode into u2t from u2 in u2t.DefaultIfEmpty() join u3 in db.underlying_manager on su.UnderlyingCode3 equals u3.UnderlyingCode into u3t from u3 in u3t.DefaultIfEmpty() join u4 in db.underlying_manager on su.UnderlyingCode4 equals u4.UnderlyingCode into u4t from u4 in u4t.DefaultIfEmpty() where u.UnderlyingState == "Live" && u.LaunchState == "1" select new PriceModel { InstrumentCode = u.UnderlyingCode, Price = (u1.Price ?? 0) * (su.Coefficient1 ?? 0) + (u2.Price ?? 0) * (su.Coefficient2 ?? 0) + (u3.Price ?? 0) * (su.Coefficient3 ?? 0) + (u4.Price ?? 0) * (su.Coefficient4 ?? 0) + (su.Constant ?? 0), PreClose = (u1.PrevClosePrice ?? 0) * (su.Coefficient1 ?? 0) + (u2.PrevClosePrice ?? 0) * (su.Coefficient2 ?? 0) + (u3.PrevClosePrice ?? 0) * (su.Coefficient3 ?? 0) + (u4.PrevClosePrice ?? 0) * (su.Coefficient4 ?? 0) + (su.Constant ?? 0), PriceTime = u1.LastUpdateTime ?? u2.LastUpdateTime ?? u3.LastUpdateTime ?? u4.LastUpdateTime }; var datas = query1.Concat(query2).ToArray(); foreach (var item in datas) { if (_dicEx.TryGetValue(item.InstrumentCode, out var underlying)) { underlying.Price = item.Price; underlying.PrevClosePrice = item.PreClose; underlying.LastUpdateTime = item.PriceTime; } } } } /// /// 获取篮子的子标的价格 /// public bool TryGetSubPrice(string underlyingCode, out double price, out double settlePrice) { //篮子标的的子标的只能是普通标的 var data = GetData(underlyingCode); if (data != null) { price = settlePrice = data.Price ?? 0; return true; } price = settlePrice = 0; return false; } #endregion /// /// 获取组合标的信息 /// public SyntheticUnderlying GetSyntheticUnderlying(string underlyingCode) { if (string.IsNullOrEmpty(underlyingCode)) { return null; } if (_dicSynthetic.TryGetValue(underlyingCode, out var sy)) { return sy; } var un = GetData(underlyingCode); if (un?.CommodityCode != "组合标的") { return null; } using (var db = DbContextFactory.GetYLDbContext()) { sy = db.synthetic_underlying.AsNoTracking().FirstOrDefault(n => n.Name == underlyingCode); if (sy != null) { _dicSynthetic[sy.Name] = sy; } return sy?.Clone(); } } /// /// 获取数量转份额的乘积因子(最小为1) /// public int GetCountRatio(string underlyingCode) { return GetData(underlyingCode)?.CountRatio ?? 1; } public override void ResetDataSource() { base.ResetDataSource(); _dicEx.Clear(); _dicSynthetic.Clear(); UpdateData(null); } //排序规则:优先使用未过期的期货,然后股票/现货,然后没有过期日的期货,然后过期的期货 protected override underlying_manager[] PreProcessDatas(underlying_manager[] datas) { var valueDate = valuedateBLL.ValueDate; return datas.OrderBy(n => { var prefix = "1"; if (n.IsFutures()) { if (n.MaturityDate.HasValue) { prefix = (n.MaturityDate.Value > valueDate ? 0 : valueDate.Year - n.MaturityDate.Value.Year + 2).ToString(); } else { prefix = "2"; } } return prefix + (n.IsCombined() ? "1" : "0") + n.UnderlyingCode; }).ToArray(); } /// /// 更新标的资产启用状态 /// public void UpdateLauchState() { using (var db = DbContextFactory.GetYLDbContext()) { var datas = db.underlying_manager.Where(Filter).Select(n => new { n.id, n.LaunchState }).ToArray(); lock (_dic) { foreach (var item in datas) { if (_dic.TryGetValue(item.id, out var um)) { um.LaunchState = item.LaunchState; } } } } } //--------------------------------------------- public static readonly UnderlyingDataSource Default; static UnderlyingDataSource() { Default = new UnderlyingDataSource(); } } } }