/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Linq; using System.Dynamic; using System.Reflection; using System.Globalization; using QuantConnect.Data; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Interfaces; using QuantConnect.Data.Market; using QuantConnect.Python; using Python.Runtime; using QuantConnect.Data.Fundamental; using QuantConnect.Interfaces; using QuantConnect.Data.Shortable; namespace QuantConnect.Securities { /// /// A base vehicle properties class for providing a common interface to all assets in QuantConnect. /// /// /// Security object is intended to hold properties of the specific security asset. These properties can include trade start-stop dates, /// price, market hours, resolution of the security, the holdings information for this security and the specific fill model. /// public class Security : DynamicObject, ISecurityPrice { private SecurityExchange _exchange; private LocalTimeKeeper _localTimeKeeper; /// /// Collection of SubscriptionDataConfigs for this security. /// Uses concurrent bag to avoid list enumeration threading issues /// /// Just use a list + lock, not concurrent bag, avoid garbage it creates for features we don't need here. See https://github.com/dotnet/runtime/issues/23103 private readonly HashSet _subscriptionsBag; /// /// Flag to keep track of initialized securities, to avoid double initialization. /// internal bool IsInitialized { get; set; } /// /// This securities /// public IShortableProvider ShortableProvider { get; private set; } /// /// A null security leverage value /// /// This value is used to determine when the /// leverage is used public const decimal NullLeverage = 0; /// /// Gets all the subscriptions for this security /// public IEnumerable Subscriptions { get { lock (_subscriptionsBag) { return _subscriptionsBag.ToList(); } } } /// /// for the asset. /// public Symbol Symbol { get; } /// /// Gets the Cash object used for converting the quote currency to the account currency /// public Cash QuoteCurrency { get; } /// /// Gets the symbol properties for this security /// public SymbolProperties SymbolProperties { get; protected set; } /// /// Type of the security. /// /// /// QuantConnect currently only supports Equities and Forex /// public SecurityType Type => Symbol.ID.SecurityType; /// /// Resolution of data requested for this security. /// /// Tick, second or minute resolution for QuantConnect assets. [Obsolete("This property is obsolete. Use the 'SubscriptionDataConfig' exposed by 'SubscriptionManager'")] public Resolution Resolution { get; private set; } /// /// Indicates the data will use previous bars when there was no trading in this time period. This was a configurable datastream setting set in initialization. /// [Obsolete("This property is obsolete. Use the 'SubscriptionDataConfig' exposed by 'SubscriptionManager'")] public bool IsFillDataForward { get; private set; } /// /// Indicates the security will continue feeding data after the primary market hours have closed. This was a configurable setting set in initialization. /// [Obsolete("This property is obsolete. Use the 'SubscriptionDataConfig' exposed by 'SubscriptionManager'")] public bool IsExtendedMarketHours { get; private set; } /// /// Gets the data normalization mode used for this security /// [Obsolete("This property is obsolete. Use the 'SubscriptionDataConfig' exposed by 'SubscriptionManager'")] public DataNormalizationMode DataNormalizationMode { get; private set; } /// /// Gets the subscription configuration for this security /// [Obsolete("This property returns only the first subscription. Use the 'Subscriptions' property for all of this security's subscriptions.")] public SubscriptionDataConfig SubscriptionDataConfig { get { lock (_subscriptionsBag) { return _subscriptionsBag.FirstOrDefault(); } } } /// /// There has been at least one datapoint since our algorithm started running for us to determine price. /// public bool HasData => GetLastData() != null; /// /// Gets or sets whether or not this security should be considered tradable /// public virtual bool IsTradable { get; set; } /// /// True if the security has been delisted from exchanges and is no longer tradable /// public bool IsDelisted { get; set; } /// /// Data cache for the security to store previous price information. /// /// /// public SecurityCache Cache { get; set; } /// /// Holdings class contains the portfolio, cash and processes order fills. /// /// /// public SecurityHolding Holdings { get; set; } /// /// Exchange class contains the market opening hours, along with pre-post market hours. /// /// /// public SecurityExchange Exchange { get => _exchange; set { _exchange = value; if (_localTimeKeeper != null) { _exchange.SetLocalDateTimeFrontierProvider(_localTimeKeeper); } } } /// /// Fee model used to compute order fees for this security /// public IFeeModel FeeModel { get; set; } /// /// Fill model used to produce fill events for this security /// public IFillModel FillModel { get; set; } /// /// Slippage model use to compute slippage of market orders /// public ISlippageModel SlippageModel { get; set; } /// /// Gets the portfolio model used by this security /// public ISecurityPortfolioModel PortfolioModel { get; set; } /// /// Gets the buying power model used for this security /// public IBuyingPowerModel BuyingPowerModel { get; set; } /// /// Gets the buying power model used for this security, an alias for /// public IBuyingPowerModel MarginModel { get { return BuyingPowerModel; } set { BuyingPowerModel = value; } } /// /// Gets or sets the margin interest rate model /// public IMarginInterestRateModel MarginInterestRateModel { get; set; } /// /// Gets the settlement model used for this security /// public ISettlementModel SettlementModel { get; set; } /// /// Gets the volatility model used for this security /// public IVolatilityModel VolatilityModel { get; set; } /// /// Customizable data filter to filter outlier ticks before they are passed into user event handlers. /// By default all ticks are passed into the user algorithms. /// /// TradeBars (seconds and minute bars) are prefiltered to ensure the ticks which build the bars are realistically tradeable /// /// public ISecurityDataFilter DataFilter { get; set; } /// /// Customizable price variation model used to define the minimum price variation of this security. /// By default minimum price variation is a constant find in the symbol-properties-database. /// /// /// /// public IPriceVariationModel PriceVariationModel { get; set; } /// /// Provides dynamic access to data in the cache /// public dynamic Data { get; } /// /// Construct a new security vehicle based on the user options. /// public Security(SecurityExchangeHours exchangeHours, SubscriptionDataConfig config, Cash quoteCurrency, SymbolProperties symbolProperties, ICurrencyConverter currencyConverter, IRegisteredSecurityDataTypesProvider registeredTypesProvider, SecurityCache cache ) : this(config, quoteCurrency, symbolProperties, new SecurityExchange(exchangeHours), cache, new SecurityPortfolioModel(), new ImmediateFillModel(), new InteractiveBrokersFeeModel(), NullSlippageModel.Instance, new ImmediateSettlementModel(), Securities.VolatilityModel.Null, new SecurityMarginModel(), new SecurityDataFilter(), new SecurityPriceVariationModel(), currencyConverter, registeredTypesProvider, Securities.MarginInterestRateModel.Null ) { } /// /// Construct a new security vehicle based on the user options. /// public Security(Symbol symbol, SecurityExchangeHours exchangeHours, Cash quoteCurrency, SymbolProperties symbolProperties, ICurrencyConverter currencyConverter, IRegisteredSecurityDataTypesProvider registeredTypesProvider, SecurityCache cache ) : this(symbol, quoteCurrency, symbolProperties, new SecurityExchange(exchangeHours), cache, new SecurityPortfolioModel(), new ImmediateFillModel(), new InteractiveBrokersFeeModel(), NullSlippageModel.Instance, new ImmediateSettlementModel(), Securities.VolatilityModel.Null, new SecurityMarginModel(), new SecurityDataFilter(), new SecurityPriceVariationModel(), currencyConverter, registeredTypesProvider, Securities.MarginInterestRateModel.Null ) { } /// /// Construct a new security vehicle based on the user options. /// protected Security(Symbol symbol, Cash quoteCurrency, SymbolProperties symbolProperties, SecurityExchange exchange, SecurityCache cache, ISecurityPortfolioModel portfolioModel, IFillModel fillModel, IFeeModel feeModel, ISlippageModel slippageModel, ISettlementModel settlementModel, IVolatilityModel volatilityModel, IBuyingPowerModel buyingPowerModel, ISecurityDataFilter dataFilter, IPriceVariationModel priceVariationModel, ICurrencyConverter currencyConverter, IRegisteredSecurityDataTypesProvider registeredTypesProvider, IMarginInterestRateModel marginInterestRateModel ) { if (symbolProperties == null) { throw new ArgumentNullException(nameof(symbolProperties), Messages.Security.ValidSymbolPropertiesInstanceRequired); } if (symbolProperties.QuoteCurrency != quoteCurrency.Symbol) { throw new ArgumentException(Messages.Security.UnmatchingQuoteCurrencies); } Symbol = symbol; _subscriptionsBag = new(); QuoteCurrency = quoteCurrency; SymbolProperties = symbolProperties; if (Symbol.SecurityType != SecurityType.Index) { IsTradable = true; } Cache = cache; Exchange = exchange; DataFilter = dataFilter; PriceVariationModel = priceVariationModel; PortfolioModel = portfolioModel; BuyingPowerModel = buyingPowerModel; FillModel = fillModel; FeeModel = feeModel; SlippageModel = slippageModel; SettlementModel = settlementModel; VolatilityModel = volatilityModel; MarginInterestRateModel = marginInterestRateModel; Holdings = new SecurityHolding(this, currencyConverter); Data = new DynamicSecurityData(registeredTypesProvider, Cache); ShortableProvider = NullShortableProvider.Instance; UpdateSubscriptionProperties(); } /// /// Temporary convenience constructor /// protected Security(SubscriptionDataConfig config, Cash quoteCurrency, SymbolProperties symbolProperties, SecurityExchange exchange, SecurityCache cache, ISecurityPortfolioModel portfolioModel, IFillModel fillModel, IFeeModel feeModel, ISlippageModel slippageModel, ISettlementModel settlementModel, IVolatilityModel volatilityModel, IBuyingPowerModel buyingPowerModel, ISecurityDataFilter dataFilter, IPriceVariationModel priceVariationModel, ICurrencyConverter currencyConverter, IRegisteredSecurityDataTypesProvider registeredTypesProvider, IMarginInterestRateModel marginInterestRateModel ) : this(config.Symbol, quoteCurrency, symbolProperties, exchange, cache, portfolioModel, fillModel, feeModel, slippageModel, settlementModel, volatilityModel, buyingPowerModel, dataFilter, priceVariationModel, currencyConverter, registeredTypesProvider, marginInterestRateModel ) { _subscriptionsBag.Add(config); UpdateSubscriptionProperties(); } /// /// Read only property that checks if we currently own stock in the company. /// public virtual bool HoldStock => Holdings.HoldStock; /// /// Alias for HoldStock - Do we have any of this security /// public virtual bool Invested => HoldStock; /// /// Local time for this market /// public virtual DateTime LocalTime { get { if (_localTimeKeeper == null) { throw new InvalidOperationException(Messages.Security.SetLocalTimeKeeperMustBeCalledBeforeUsingLocalTime); } return _localTimeKeeper.LocalTime; } } /// /// Get the current value of the security. /// public virtual decimal Price => Cache.Price; /// /// Leverage for this Security. /// public virtual decimal Leverage => Holdings.Leverage; /// /// If this uses tradebar data, return the most recent high. /// public virtual decimal High => Cache.High == 0 ? Price : Cache.High; /// /// If this uses tradebar data, return the most recent low. /// public virtual decimal Low => Cache.Low == 0 ? Price : Cache.Low; /// /// If this uses tradebar data, return the most recent close. /// public virtual decimal Close => Cache.Close == 0 ? Price : Cache.Close; /// /// If this uses tradebar data, return the most recent open. /// public virtual decimal Open => Cache.Open == 0 ? Price : Cache.Open; /// /// Access to the volume of the equity today /// public virtual decimal Volume => Cache.Volume; /// /// Gets the most recent bid price if available /// public virtual decimal BidPrice => Cache.BidPrice == 0 ? Price : Cache.BidPrice; /// /// Gets the most recent bid size if available /// public virtual decimal BidSize => Cache.BidSize; /// /// Gets the most recent ask price if available /// public virtual decimal AskPrice => Cache.AskPrice == 0 ? Price : Cache.AskPrice; /// /// Gets the most recent ask size if available /// public virtual decimal AskSize => Cache.AskSize; /// /// Access to the open interest of the security today /// public virtual long OpenInterest => Cache.OpenInterest; /// /// Gets the fundamental data associated with the security if there is any, otherwise null. /// public Fundamental Fundamentals { get { return new Fundamental(LocalTime, Symbol); } } /// /// Get the last price update set to the security if any else null /// /// BaseData object for this security public BaseData GetLastData() => Cache.GetData(); /// /// Sets the to be used for this . /// This is the source of this instance's time. /// /// The source of this 's time. public virtual void SetLocalTimeKeeper(LocalTimeKeeper localTimeKeeper) { _localTimeKeeper = localTimeKeeper; Exchange.SetLocalDateTimeFrontierProvider(localTimeKeeper); } /// /// Update any security properties based on the latest market data and time /// /// New data packet from LEAN public void SetMarketPrice(BaseData data) { //Add new point to cache: if (data == null) return; Cache.AddData(data); UpdateMarketPrice(data); } /// /// Updates all of the security properties, such as price/OHLCV/bid/ask based /// on the data provided. Data is also stored into the security's data cache /// /// The security update data /// The data type /// Flag indicating whether /// contains any fill forward bar or not public void Update(IReadOnlyList data, Type dataType, bool? containsFillForwardData = null) { Cache.AddDataList(data, dataType, containsFillForwardData); UpdateMarketPrice(data[data.Count - 1]); } /// /// Returns true if the security contains at least one subscription that represents custom data /// [Obsolete("This method is obsolete. Use the 'SubscriptionDataConfig' exposed by" + " 'SubscriptionManager' and the 'IsCustomData()' extension method")] public bool IsCustomData() { if (Subscriptions == null || !Subscriptions.Any()) { return false; } return Subscriptions.Any(x => x.IsCustomData); } /// /// Set the leverage parameter for this security /// /// Leverage for this asset public void SetLeverage(decimal leverage) { if (Symbol.ID.SecurityType == SecurityType.Future || Symbol.ID.SecurityType.IsOption()) { return; } BuyingPowerModel.SetLeverage(this, leverage); } /// /// Sets the data normalization mode to be used by this security /// [Obsolete("This method is obsolete. Use the 'SubscriptionDataConfig' exposed by" + " 'SubscriptionManager' and the 'SetDataNormalizationMode()' extension method")] public virtual void SetDataNormalizationMode(DataNormalizationMode mode) { lock (_subscriptionsBag) { foreach (var subscription in _subscriptionsBag) { subscription.DataNormalizationMode = mode; } UpdateSubscriptionProperties(); } } /// /// This method will refresh the value of the property. /// This is required for backward-compatibility. /// TODO: to be deleted with the DataNormalizationMode property /// public void RefreshDataNormalizationModeProperty() { lock (_subscriptionsBag) { DataNormalizationMode = _subscriptionsBag .Select(x => x.DataNormalizationMode) .DefaultIfEmpty(DataNormalizationMode.Adjusted) .FirstOrDefault(); } } /// /// Sets the fee model /// /// Model that represents a fee model public void SetFeeModel(IFeeModel feelModel) { FeeModel = feelModel; } /// /// Sets the fee model /// /// Model that represents a fee model public void SetFeeModel(PyObject feelModel) { FeeModel = new FeeModelPythonWrapper(feelModel); } /// /// Sets the fill model /// /// Model that represents a fill model public void SetFillModel(IFillModel fillModel) { FillModel = fillModel; } /// /// Sets the fill model /// /// Model that represents a fill model public void SetFillModel(PyObject fillModel) { FillModel = new FillModelPythonWrapper(fillModel); } /// /// Sets the settlement model /// /// Model that represents a settlement model public void SetSettlementModel(ISettlementModel settlementModel) { SettlementModel = settlementModel; } /// /// Sets the settlement model /// /// Model that represents a settlement model public void SetSettlementModel(PyObject settlementModel) { SettlementModel = new SettlementModelPythonWrapper(settlementModel); } /// /// Sets the slippage model /// /// Model that represents a slippage model public void SetSlippageModel(ISlippageModel slippageModel) { SlippageModel = slippageModel; } /// /// Sets the slippage model /// /// Model that represents a slippage model public void SetSlippageModel(PyObject slippageModel) { SlippageModel = new SlippageModelPythonWrapper(slippageModel); } /// /// Sets the volatility model /// /// Model that represents a volatility model public void SetVolatilityModel(IVolatilityModel volatilityModel) { VolatilityModel = volatilityModel; } /// /// Sets the volatility model /// /// Model that represents a volatility model public void SetVolatilityModel(PyObject volatilityModel) { VolatilityModel = new VolatilityModelPythonWrapper(volatilityModel); } /// /// Sets the buying power model /// /// Model that represents a security's model of buying power public void SetBuyingPowerModel(IBuyingPowerModel buyingPowerModel) { BuyingPowerModel = buyingPowerModel; } /// /// Sets the buying power model /// /// Model that represents a security's model of buying power public void SetBuyingPowerModel(PyObject pyObject) { SetBuyingPowerModel(new BuyingPowerModelPythonWrapper(pyObject)); } /// /// Sets the margin interests rate model /// /// Model that represents a security's model of margin interest rate public void SetMarginInterestRateModel(IMarginInterestRateModel marginInterestRateModel) { MarginInterestRateModel = marginInterestRateModel; } /// /// Sets the margin interests rate model /// /// Model that represents a security's model of margin interest rate public void SetMarginInterestRateModel(PyObject pyObject) { SetMarginInterestRateModel(new MarginInterestRateModelPythonWrapper(pyObject)); } /// /// Sets the margin model /// /// Model that represents a security's model of buying power public void SetMarginModel(IBuyingPowerModel marginModel) { MarginModel = marginModel; } /// /// Sets the margin model /// /// Model that represents a security's model of buying power public void SetMarginModel(PyObject pyObject) { SetMarginModel(new BuyingPowerModelPythonWrapper(pyObject)); } /// /// Set Python Shortable Provider for this /// /// Python class that represents a custom shortable provider public void SetShortableProvider(PyObject pyObject) { if (pyObject.TryConvert(out var shortableProvider)) { SetShortableProvider(shortableProvider); } else if (Extensions.TryConvert(pyObject, out _, allowPythonDerivative: true)) { SetShortableProvider(new ShortableProviderPythonWrapper(pyObject)); } else { using (Py.GIL()) { throw new Exception($"SetShortableProvider: {pyObject.Repr()} is not a valid argument"); } } } /// /// Set Shortable Provider for this /// /// Provider to use public void SetShortableProvider(IShortableProvider shortableProvider) { ShortableProvider = shortableProvider; } /// /// Set Security Data Filter /// /// Python class that represents a custom Security Data Filter /// public void SetDataFilter(PyObject pyObject) { if (pyObject.TryConvert(out var dataFilter)) { SetDataFilter(dataFilter); } else if (Extensions.TryConvert(pyObject, out _, allowPythonDerivative: true)) { SetDataFilter(new SecurityDataFilterPythonWrapper(pyObject)); } else { using (Py.GIL()) { throw new ArgumentException($"SetDataFilter: {pyObject.Repr()} is not a valid argument"); } } } /// /// Set Security Data Filter /// /// Security Data Filter public void SetDataFilter(ISecurityDataFilter dataFilter) { DataFilter = dataFilter; } #region DynamicObject Overrides and Helper Methods /// /// This is a override. Not meant for external use. /// public override bool TryGetMember(GetMemberBinder binder, out object result) { return Cache.Properties.TryGetValue(binder.Name, out result); } /// /// This is a override. Not meant for external use. /// public override bool TrySetMember(SetMemberBinder binder, object value) { Cache.Properties[binder.Name] = value; return true; } /// /// This is a override. Not meant for external use. /// public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { try { result = Cache.Properties.GetType().InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, Cache.Properties, args, CultureInfo.InvariantCulture); return true; } catch { result = null; return false; } } /// /// Adds the specified custom property. /// This allows us to use the security object as a dynamic object for quick storage. /// /// The property key /// The property value public void Add(string key, object value) { Set(key, value); } /// /// Sets the specified custom property. /// This allows us to use the security object as a dynamic object for quick storage. /// /// The property key /// The property value public void Set(string key, object value) { Cache.Properties[key] = value; } /// /// Gets the specified custom property /// /// The property key /// The property value /// True if the property is found. /// If the property is found but its value cannot be casted to the speficied type public bool TryGet(string key, out T value) { if (Cache.Properties.TryGetValue(key, out var obj)) { value = CastDynamicPropertyValue(obj); return true; } value = default; return false; } /// /// Gets the specified custom property /// /// The property key /// The property value is found /// If the property is not found public T Get(string key) { return CastDynamicPropertyValue(Cache.Properties[key]); } /// /// Removes a custom property. /// /// The property key /// True if the property is successfully removed public bool Remove(string key) { return Cache.Properties.Remove(key); } /// /// Removes a custom property. /// /// The property key /// The removed property value /// True if the property is successfully removed public bool Remove(string key, out T value) { value = default; var result = Cache.Properties.Remove(key, out object objectValue); if (result) { value = CastDynamicPropertyValue(objectValue); } return result; } /// /// Removes every custom property that had been set. /// public void Clear() { Cache.Properties.Clear(); } /// /// Gets or sets the specified custom property through the indexer. /// This is a wrapper around the and methods. /// /// The property key public object this[string key] { get { return Get(key); } set { Add(key, value); } } #endregion /// /// Returns a string that represents the current object. /// /// /// A string that represents the current object. /// /// 2 public override string ToString() { return Symbol.ToString(); } /// /// Adds the specified data subscription to this security. /// /// The subscription configuration to add. The Symbol and ExchangeTimeZone properties must match the existing Security object internal void AddData(SubscriptionDataConfig subscription) { lock (_subscriptionsBag) { if (subscription.Symbol != Symbol) { throw new ArgumentException(Messages.Security.UnmatchingSymbols, $"{nameof(subscription)}.{nameof(subscription.Symbol)}"); } if (!subscription.ExchangeTimeZone.Equals(Exchange.TimeZone)) { throw new ArgumentException(Messages.Security.UnmatchingExchangeTimeZones, $"{nameof(subscription)}.{nameof(subscription.ExchangeTimeZone)}"); } _subscriptionsBag.Add(subscription); UpdateSubscriptionProperties(); } } /// /// Adds the specified data subscriptions to this security. /// /// The subscription configuration to add. The Symbol and ExchangeTimeZone properties must match the existing Security object internal void AddData(SubscriptionDataConfigList subscriptions) { lock (_subscriptionsBag) { foreach (var subscription in subscriptions) { if (subscription.Symbol != Symbol) { throw new ArgumentException(Messages.Security.UnmatchingSymbols, $"{nameof(subscription)}.{nameof(subscription.Symbol)}"); } if (!subscription.ExchangeTimeZone.Equals(Exchange.TimeZone)) { throw new ArgumentException(Messages.Security.UnmatchingExchangeTimeZones, $"{nameof(subscription)}.{nameof(subscription.ExchangeTimeZone)}"); } _subscriptionsBag.Add(subscription); } UpdateSubscriptionProperties(); } } /// /// Update market price of this Security /// /// Data to pull price from protected virtual void UpdateConsumersMarketPrice(BaseData data) { if (data is OpenInterest || data.Price == 0m) return; Holdings.UpdateMarketPrice(Price); VolatilityModel.Update(this, data); } /// /// Caller should hold the lock on '_subscriptionsBag' /// private void UpdateSubscriptionProperties() { Resolution = _subscriptionsBag.Select(x => x.Resolution).DefaultIfEmpty(Resolution.Daily).Min(); IsFillDataForward = _subscriptionsBag.Any(x => x.FillDataForward); IsExtendedMarketHours = _subscriptionsBag.Any(x => x.ExtendedMarketHours); RefreshDataNormalizationModeProperty(); } /// /// Updates consumers market price. It will do nothing if the passed data type is auxiliary. /// private void UpdateMarketPrice(BaseData data) { if (data.DataType != MarketDataType.Auxiliary) { UpdateConsumersMarketPrice(data); } } /// /// Casts a dynamic property value to the specified type. /// Useful for cases where the property value is a PyObject and we want to cast it to the underlying type. /// private static T CastDynamicPropertyValue(object obj) { T value; var pyObj = obj as PyObject; if (pyObj != null) { using (Py.GIL()) { value = pyObj.As(); } } else { value = (T)obj; } return value; } /// /// Applies the split to the security /// internal void ApplySplit(Split split) { Cache.ApplySplit(split); UpdateMarketPrice(Cache.GetData()); } /// /// Updates the symbol properties of this security /// internal virtual void UpdateSymbolProperties(SymbolProperties symbolProperties) { if (symbolProperties != null) { SymbolProperties = symbolProperties; } } /// /// Resets the security to its initial state by marking it as uninitialized and non-tradable /// and clearing the subscriptions. /// public virtual void Reset() { IsInitialized = false; IsTradable = false; // Reset the subscriptions lock (_subscriptionsBag) { _subscriptionsBag.Clear(); UpdateSubscriptionProperties(); } } } }