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

472 lines
18 KiB
C#

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
{
/// <summary>
/// 标的数据源
/// </summary>
class UnderlyingDataSource : GenericeCachedDataSource<underlying_manager>, IUnderlyingDataSource, IBasketPriceProvider
{
readonly ThrottleAction _updatePricethrottle;
readonly Dictionary<string, underlying_manager> _dicEx;
readonly ConcurrentDictionary<string, SyntheticUnderlying> _dicSynthetic;
private UnderlyingDataSource()
{
_updatePricethrottle = new ThrottleAction(ThrottleUpdatePricesAction, 3, 180);
_dicEx = new Dictionary<string, underlying_manager>(StringComparer.OrdinalIgnoreCase);
_dicSynthetic = new ConcurrentDictionary<string, SyntheticUnderlying>(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<underlying_manager>(
n => (n.UnderlyingInstrumentType != ConsGlobal.InstrumentType.CommodityFutures
&& n.UnderlyingInstrumentType != ConsGlobal.InstrumentType.StockIF) || n.MaturityDate >= date);
}
public override string TableName => nameof(underlying_manager);
/// <summary>
/// 扩展数据更新
/// </summary>
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;
}
}
/// <summary>
/// 扩展数据移除操作
/// </summary>
private void AfterRemoveHandle(underlying_manager data)
{
if (string.IsNullOrWhiteSpace(data?.UnderlyingCode))
{
return;
}
_dicEx.Remove(data.UnderlyingCode);
_dicSynthetic.TryRemove(data.UnderlyingCode, out _);
}
/// <summary>
/// 根据标的代码获取标的数据
/// </summary>
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();
}
}
}
/// <summary>
/// 根据标的ID获取标的数据
/// </summary>
public override underlying_manager GetData(int keyId)
{
var un = base.GetData(keyId);
if (un != null)
{
un.Price = InnerGetPrice(un);
}
return un;
}
#region----IPriceProvider----
/// <summary>
/// 根据标的代码获取标的价格(包含组合标的)
/// </summary>
public double GetPrice(int underlyingId)
{
return InnerGetPrice(GetData(underlyingId));
}
/// <summary>
/// 根据标的代码获取标的价格(标的代码不区分大小写)(包含组合标的)
/// </summary>
public double GetPrice(string underlyingCode)
{
return InnerGetPrice(GetData(underlyingCode));
}
/// <summary>
/// 根据标的代码获取标的价格(包含组合标的)
/// </summary>
public bool TryGetPrice(int underlyingId, out double price)
{
return InnerTryGetPrice(GetData(underlyingId), out price);
}
/// <summary>
/// 根据标的代码获取标的价格,标的代码不区分大小写(包含组合标的)
/// </summary>
public bool TryGetPrice(string underlyingCode, out double price)
{
return InnerTryGetPrice(GetData(underlyingCode), out price);
}
public bool InitData(List<string> underlyingCodes)
{
lock (this)
{
var notInCaches = underlyingCodes.Where(p => !_dicEx.Keys.Contains(p)).ToList();
if (notInCaches != null && notInCaches.Count > 0)
{
List<underlying_manager> 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;
}
/// <summary>
/// 更新标的价格
/// </summary>
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;
}
}
}
}
/// <summary>
/// 获取篮子的子标的价格
/// </summary>
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
/// <summary>
/// 获取组合标的信息
/// </summary>
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();
}
}
/// <summary>
/// 获取数量转份额的乘积因子(最小为1)
/// </summary>
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();
}
/// <summary>
/// 更新标的资产启用状态
/// </summary>
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();
}
}
}
}