472 lines
18 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
}
|