Lean  $LEAN_TAG$
Security.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Linq;
19 using System.Dynamic;
20 using System.Reflection;
21 using System.Globalization;
22 
23 using QuantConnect.Data;
31 using QuantConnect.Python;
32 using Python.Runtime;
37 
39 {
40  /// <summary>
41  /// A base vehicle properties class for providing a common interface to all assets in QuantConnect.
42  /// </summary>
43  /// <remarks>
44  /// Security object is intended to hold properties of the specific security asset. These properties can include trade start-stop dates,
45  /// price, market hours, resolution of the security, the holdings information for this security and the specific fill model.
46  /// </remarks>
47  public class Security : DynamicObject, ISecurityPrice
48  {
49  private LocalTimeKeeper _localTimeKeeper;
50 
51  /// <summary>
52  /// Collection of SubscriptionDataConfigs for this security.
53  /// Uses concurrent bag to avoid list enumeration threading issues
54  /// </summary>
55  /// <remarks>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</remarks>
56  private readonly List<SubscriptionDataConfig> _subscriptionsBag;
57 
58  /// <summary>
59  /// This securities <see cref="IShortableProvider"/>
60  /// </summary>
61  public IShortableProvider ShortableProvider { get; private set; }
62 
63  /// <summary>
64  /// A null security leverage value
65  /// </summary>
66  /// <remarks>This value is used to determine when the
67  /// <see cref="SecurityInitializer"/> leverage is used</remarks>
68  public const decimal NullLeverage = 0;
69 
70  /// <summary>
71  /// Gets all the subscriptions for this security
72  /// </summary>
73  public IEnumerable<SubscriptionDataConfig> Subscriptions
74  {
75  get
76  {
77  lock (_subscriptionsBag)
78  {
79  return _subscriptionsBag.ToList();
80  }
81  }
82  }
83 
84  /// <summary>
85  /// <see cref="Symbol"/> for the asset.
86  /// </summary>
87  public Symbol Symbol { get; }
88 
89  /// <summary>
90  /// Gets the Cash object used for converting the quote currency to the account currency
91  /// </summary>
92  public Cash QuoteCurrency
93  {
94  get;
95  }
96 
97  /// <summary>
98  /// Gets the symbol properties for this security
99  /// </summary>
101  {
102  get;
103  }
104 
105  /// <summary>
106  /// Type of the security.
107  /// </summary>
108  /// <remarks>
109  /// QuantConnect currently only supports Equities and Forex
110  /// </remarks>
112 
113  /// <summary>
114  /// Resolution of data requested for this security.
115  /// </summary>
116  /// <remarks>Tick, second or minute resolution for QuantConnect assets.</remarks>
117  [Obsolete("This property is obsolete. Use the 'SubscriptionDataConfig' exposed by 'SubscriptionManager'")]
118  public Resolution Resolution { get; private set; }
119 
120  /// <summary>
121  /// 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.
122  /// </summary>
123  [Obsolete("This property is obsolete. Use the 'SubscriptionDataConfig' exposed by 'SubscriptionManager'")]
124  public bool IsFillDataForward { get; private set; }
125 
126  /// <summary>
127  /// Indicates the security will continue feeding data after the primary market hours have closed. This was a configurable setting set in initialization.
128  /// </summary>
129  [Obsolete("This property is obsolete. Use the 'SubscriptionDataConfig' exposed by 'SubscriptionManager'")]
130  public bool IsExtendedMarketHours { get; private set; }
131 
132  /// <summary>
133  /// Gets the data normalization mode used for this security
134  /// </summary>
135  [Obsolete("This property is obsolete. Use the 'SubscriptionDataConfig' exposed by 'SubscriptionManager'")]
136  public DataNormalizationMode DataNormalizationMode { get; private set; }
137 
138  /// <summary>
139  /// Gets the subscription configuration for this security
140  /// </summary>
141  [Obsolete("This property returns only the first subscription. Use the 'Subscriptions' property for all of this security's subscriptions.")]
143  {
144  get
145  {
146  lock (_subscriptionsBag)
147  {
148  return _subscriptionsBag.FirstOrDefault();
149  }
150  }
151  }
152 
153  /// <summary>
154  /// There has been at least one datapoint since our algorithm started running for us to determine price.
155  /// </summary>
156  public bool HasData => GetLastData() != null;
157 
158  /// <summary>
159  /// Gets or sets whether or not this security should be considered tradable
160  /// </summary>
161  public virtual bool IsTradable
162  {
163  get; set;
164  }
165 
166  /// <summary>
167  /// True if the security has been delisted from exchanges and is no longer tradable
168  /// </summary>
169  public bool IsDelisted { get; set; }
170 
171  /// <summary>
172  /// Data cache for the security to store previous price information.
173  /// </summary>
174  /// <seealso cref="EquityCache"/>
175  /// <seealso cref="ForexCache"/>
176  public SecurityCache Cache
177  {
178  get; set;
179  }
180 
181  /// <summary>
182  /// Holdings class contains the portfolio, cash and processes order fills.
183  /// </summary>
184  /// <seealso cref="EquityHolding"/>
185  /// <seealso cref="ForexHolding"/>
187  {
188  get;
189  set;
190  }
191 
192  /// <summary>
193  /// Exchange class contains the market opening hours, along with pre-post market hours.
194  /// </summary>
195  /// <seealso cref="EquityExchange"/>
196  /// <seealso cref="ForexExchange"/>
198  {
199  get;
200  set;
201  }
202 
203  /// <summary>
204  /// Fee model used to compute order fees for this security
205  /// </summary>
206  public IFeeModel FeeModel
207  {
208  get;
209  set;
210  }
211 
212  /// <summary>
213  /// Fill model used to produce fill events for this security
214  /// </summary>
215  public IFillModel FillModel
216  {
217  get;
218  set;
219  }
220 
221  /// <summary>
222  /// Slippage model use to compute slippage of market orders
223  /// </summary>
225  {
226  get;
227  set;
228  }
229 
230  /// <summary>
231  /// Gets the portfolio model used by this security
232  /// </summary>
234  {
235  get;
236  set;
237  }
238 
239  /// <summary>
240  /// Gets the buying power model used for this security
241  /// </summary>
243  {
244  get;
245  set;
246  }
247 
248  /// <summary>
249  /// Gets the buying power model used for this security, an alias for <see cref="BuyingPowerModel"/>
250  /// </summary>
252  {
253  get { return BuyingPowerModel; }
254  set { BuyingPowerModel = value; }
255  }
256 
257  /// <summary>
258  /// Gets or sets the margin interest rate model
259  /// </summary>
261  {
262  get;
263  set;
264  }
265 
266  /// <summary>
267  /// Gets the settlement model used for this security
268  /// </summary>
270  {
271  get;
272  set;
273  }
274 
275  /// <summary>
276  /// Gets the volatility model used for this security
277  /// </summary>
279  {
280  get;
281  set;
282  }
283 
284  /// <summary>
285  /// Customizable data filter to filter outlier ticks before they are passed into user event handlers.
286  /// By default all ticks are passed into the user algorithms.
287  /// </summary>
288  /// <remarks>TradeBars (seconds and minute bars) are prefiltered to ensure the ticks which build the bars are realistically tradeable</remarks>
289  /// <seealso cref="EquityDataFilter"/>
290  /// <seealso cref="ForexDataFilter"/>
292  {
293  get;
294  set;
295  }
296 
297  /// <summary>
298  /// Customizable price variation model used to define the minimum price variation of this security.
299  /// By default minimum price variation is a constant find in the symbol-properties-database.
300  /// </summary>
301  /// <seealso cref="AdjustedPriceVariationModel"/>
302  /// <seealso cref="SecurityPriceVariationModel"/>
303  /// <seealso cref="EquityPriceVariationModel"/>
305  {
306  get;
307  set;
308  }
309 
310  /// <summary>
311  /// Provides dynamic access to data in the cache
312  /// </summary>
313  public dynamic Data
314  {
315  get;
316  }
317 
318  /// <summary>
319  /// Construct a new security vehicle based on the user options.
320  /// </summary>
321  public Security(SecurityExchangeHours exchangeHours,
322  SubscriptionDataConfig config,
323  Cash quoteCurrency,
324  SymbolProperties symbolProperties,
325  ICurrencyConverter currencyConverter,
326  IRegisteredSecurityDataTypesProvider registeredTypesProvider,
327  SecurityCache cache
328  )
329  : this(config,
330  quoteCurrency,
331  symbolProperties,
332  new SecurityExchange(exchangeHours),
333  cache,
335  new ImmediateFillModel(),
337  NullSlippageModel.Instance,
339  Securities.VolatilityModel.Null,
340  new SecurityMarginModel(),
341  new SecurityDataFilter(),
343  currencyConverter,
344  registeredTypesProvider,
345  Securities.MarginInterestRateModel.Null
346  )
347  {
348  }
349 
350  /// <summary>
351  /// Construct a new security vehicle based on the user options.
352  /// </summary>
353  public Security(Symbol symbol,
354  SecurityExchangeHours exchangeHours,
355  Cash quoteCurrency,
356  SymbolProperties symbolProperties,
357  ICurrencyConverter currencyConverter,
358  IRegisteredSecurityDataTypesProvider registeredTypesProvider,
359  SecurityCache cache
360  )
361  : this(symbol,
362  quoteCurrency,
363  symbolProperties,
364  new SecurityExchange(exchangeHours),
365  cache,
367  new ImmediateFillModel(),
369  NullSlippageModel.Instance,
371  Securities.VolatilityModel.Null,
372  new SecurityMarginModel(),
373  new SecurityDataFilter(),
375  currencyConverter,
376  registeredTypesProvider,
377  Securities.MarginInterestRateModel.Null
378  )
379  {
380  }
381 
382  /// <summary>
383  /// Construct a new security vehicle based on the user options.
384  /// </summary>
385  protected Security(Symbol symbol,
386  Cash quoteCurrency,
387  SymbolProperties symbolProperties,
388  SecurityExchange exchange,
389  SecurityCache cache,
390  ISecurityPortfolioModel portfolioModel,
391  IFillModel fillModel,
392  IFeeModel feeModel,
393  ISlippageModel slippageModel,
394  ISettlementModel settlementModel,
395  IVolatilityModel volatilityModel,
396  IBuyingPowerModel buyingPowerModel,
397  ISecurityDataFilter dataFilter,
398  IPriceVariationModel priceVariationModel,
399  ICurrencyConverter currencyConverter,
400  IRegisteredSecurityDataTypesProvider registeredTypesProvider,
401  IMarginInterestRateModel marginInterestRateModel
402  )
403  {
404  if (symbolProperties == null)
405  {
406  throw new ArgumentNullException(nameof(symbolProperties), Messages.Security.ValidSymbolPropertiesInstanceRequired);
407  }
408 
409  if (symbolProperties.QuoteCurrency != quoteCurrency.Symbol)
410  {
411  throw new ArgumentException(Messages.Security.UnmatchingQuoteCurrencies);
412  }
413 
414  Symbol = symbol;
415  _subscriptionsBag = new ();
416  QuoteCurrency = quoteCurrency;
417  SymbolProperties = symbolProperties;
418  IsTradable = true;
419  Cache = cache;
420  Exchange = exchange;
421  DataFilter = dataFilter;
422  PriceVariationModel = priceVariationModel;
423  PortfolioModel = portfolioModel;
424  BuyingPowerModel = buyingPowerModel;
425  FillModel = fillModel;
426  FeeModel = feeModel;
427  SlippageModel = slippageModel;
428  SettlementModel = settlementModel;
429  VolatilityModel = volatilityModel;
430  MarginInterestRateModel = marginInterestRateModel;
431  Holdings = new SecurityHolding(this, currencyConverter);
432  Data = new DynamicSecurityData(registeredTypesProvider, Cache);
434 
435  UpdateSubscriptionProperties();
436  }
437 
438 
439  /// <summary>
440  /// Temporary convenience constructor
441  /// </summary>
443  Cash quoteCurrency,
444  SymbolProperties symbolProperties,
445  SecurityExchange exchange,
446  SecurityCache cache,
447  ISecurityPortfolioModel portfolioModel,
448  IFillModel fillModel,
449  IFeeModel feeModel,
450  ISlippageModel slippageModel,
451  ISettlementModel settlementModel,
452  IVolatilityModel volatilityModel,
453  IBuyingPowerModel buyingPowerModel,
454  ISecurityDataFilter dataFilter,
455  IPriceVariationModel priceVariationModel,
456  ICurrencyConverter currencyConverter,
457  IRegisteredSecurityDataTypesProvider registeredTypesProvider,
458  IMarginInterestRateModel marginInterestRateModel
459  )
460  : this(config.Symbol,
461  quoteCurrency,
462  symbolProperties,
463  exchange,
464  cache,
465  portfolioModel,
466  fillModel,
467  feeModel,
468  slippageModel,
469  settlementModel,
470  volatilityModel,
471  buyingPowerModel,
472  dataFilter,
473  priceVariationModel,
474  currencyConverter,
475  registeredTypesProvider,
476  marginInterestRateModel
477  )
478  {
479  _subscriptionsBag.Add(config);
480  UpdateSubscriptionProperties();
481  }
482 
483  /// <summary>
484  /// Read only property that checks if we currently own stock in the company.
485  /// </summary>
486  public virtual bool HoldStock => Holdings.HoldStock;
487 
488  /// <summary>
489  /// Alias for HoldStock - Do we have any of this security
490  /// </summary>
491  public virtual bool Invested => HoldStock;
492 
493  /// <summary>
494  /// Local time for this market
495  /// </summary>
496  public virtual DateTime LocalTime
497  {
498  get
499  {
500  if (_localTimeKeeper == null)
501  {
502  throw new InvalidOperationException(Messages.Security.SetLocalTimeKeeperMustBeCalledBeforeUsingLocalTime);
503  }
504 
505  return _localTimeKeeper.LocalTime;
506  }
507  }
508 
509  /// <summary>
510  /// Get the current value of the security.
511  /// </summary>
512  public virtual decimal Price => Cache.Price;
513 
514  /// <summary>
515  /// Leverage for this Security.
516  /// </summary>
517  public virtual decimal Leverage => Holdings.Leverage;
518 
519  /// <summary>
520  /// If this uses tradebar data, return the most recent high.
521  /// </summary>
522  public virtual decimal High => Cache.High == 0 ? Price : Cache.High;
523 
524  /// <summary>
525  /// If this uses tradebar data, return the most recent low.
526  /// </summary>
527  public virtual decimal Low => Cache.Low == 0 ? Price : Cache.Low;
528 
529  /// <summary>
530  /// If this uses tradebar data, return the most recent close.
531  /// </summary>
532  public virtual decimal Close => Cache.Close == 0 ? Price : Cache.Close;
533 
534  /// <summary>
535  /// If this uses tradebar data, return the most recent open.
536  /// </summary>
537  public virtual decimal Open => Cache.Open == 0 ? Price : Cache.Open;
538 
539  /// <summary>
540  /// Access to the volume of the equity today
541  /// </summary>
542  public virtual decimal Volume => Cache.Volume;
543 
544  /// <summary>
545  /// Gets the most recent bid price if available
546  /// </summary>
547  public virtual decimal BidPrice => Cache.BidPrice == 0 ? Price : Cache.BidPrice;
548 
549  /// <summary>
550  /// Gets the most recent bid size if available
551  /// </summary>
552  public virtual decimal BidSize => Cache.BidSize;
553 
554  /// <summary>
555  /// Gets the most recent ask price if available
556  /// </summary>
557  public virtual decimal AskPrice => Cache.AskPrice == 0 ? Price : Cache.AskPrice;
558 
559  /// <summary>
560  /// Gets the most recent ask size if available
561  /// </summary>
562  public virtual decimal AskSize => Cache.AskSize;
563 
564  /// <summary>
565  /// Access to the open interest of the security today
566  /// </summary>
567  public virtual long OpenInterest => Cache.OpenInterest;
568 
569  /// <summary>
570  /// Gets the fundamental data associated with the security if there is any, otherwise null.
571  /// </summary>
573  {
574  get
575  {
576  return new Fundamental(LocalTime, Symbol);
577  }
578  }
579 
580  /// <summary>
581  /// Get the last price update set to the security if any else null
582  /// </summary>
583  /// <returns>BaseData object for this security</returns>
584  public BaseData GetLastData() => Cache.GetData();
585 
586  /// <summary>
587  /// Sets the <see cref="LocalTimeKeeper"/> to be used for this <see cref="Security"/>.
588  /// This is the source of this instance's time.
589  /// </summary>
590  /// <param name="localTimeKeeper">The source of this <see cref="Security"/>'s time.</param>
591  public virtual void SetLocalTimeKeeper(LocalTimeKeeper localTimeKeeper)
592  {
593  _localTimeKeeper = localTimeKeeper;
594  Exchange.SetLocalDateTimeFrontierProvider(localTimeKeeper);
595  }
596 
597  /// <summary>
598  /// Update any security properties based on the latest market data and time
599  /// </summary>
600  /// <param name="data">New data packet from LEAN</param>
601  public void SetMarketPrice(BaseData data)
602  {
603  //Add new point to cache:
604  if (data == null) return;
605  Cache.AddData(data);
606 
607  UpdateMarketPrice(data);
608  }
609 
610  /// <summary>
611  /// Updates all of the security properties, such as price/OHLCV/bid/ask based
612  /// on the data provided. Data is also stored into the security's data cache
613  /// </summary>
614  /// <param name="data">The security update data</param>
615  /// <param name="dataType">The data type</param>
616  /// <param name="containsFillForwardData">Flag indicating whether
617  /// <paramref name="data"/> contains any fill forward bar or not</param>
618  public void Update(IReadOnlyList<BaseData> data, Type dataType, bool? containsFillForwardData = null)
619  {
620  Cache.AddDataList(data, dataType, containsFillForwardData);
621 
622  UpdateMarketPrice(data[data.Count - 1]);
623  }
624 
625  /// <summary>
626  /// Returns true if the security contains at least one subscription that represents custom data
627  /// </summary>
628  [Obsolete("This method is obsolete. Use the 'SubscriptionDataConfig' exposed by" +
629  " 'SubscriptionManager' and the 'IsCustomData()' extension method")]
630  public bool IsCustomData()
631  {
632  if (Subscriptions == null || !Subscriptions.Any())
633  {
634  return false;
635  }
636 
637  return Subscriptions.Any(x => x.IsCustomData);
638  }
639 
640  /// <summary>
641  /// Set the leverage parameter for this security
642  /// </summary>
643  /// <param name="leverage">Leverage for this asset</param>
644  public void SetLeverage(decimal leverage)
645  {
646  if (Symbol.ID.SecurityType == SecurityType.Future || Symbol.ID.SecurityType.IsOption())
647  {
648  return;
649  }
650 
651  BuyingPowerModel.SetLeverage(this, leverage);
652  }
653 
654  /// <summary>
655  /// Sets the data normalization mode to be used by this security
656  /// </summary>
657  [Obsolete("This method is obsolete. Use the 'SubscriptionDataConfig' exposed by" +
658  " 'SubscriptionManager' and the 'SetDataNormalizationMode()' extension method")]
660  {
661  lock (_subscriptionsBag)
662  {
663  foreach (var subscription in _subscriptionsBag)
664  {
665  subscription.DataNormalizationMode = mode;
666  }
667  UpdateSubscriptionProperties();
668  }
669  }
670 
671  /// <summary>
672  /// This method will refresh the value of the <see cref="DataNormalizationMode"/> property.
673  /// This is required for backward-compatibility.
674  /// TODO: to be deleted with the DataNormalizationMode property
675  /// </summary>
677  {
678  lock (_subscriptionsBag)
679  {
680  DataNormalizationMode = _subscriptionsBag
681  .Select(x => x.DataNormalizationMode)
682  .DefaultIfEmpty(DataNormalizationMode.Adjusted)
683  .FirstOrDefault();
684  }
685  }
686 
687  /// <summary>
688  /// Sets the fee model
689  /// </summary>
690  /// <param name="feelModel">Model that represents a fee model</param>
691  public void SetFeeModel(IFeeModel feelModel)
692  {
693  FeeModel = feelModel;
694  }
695 
696  /// <summary>
697  /// Sets the fee model
698  /// </summary>
699  /// <param name="feelModel">Model that represents a fee model</param>
700  public void SetFeeModel(PyObject feelModel)
701  {
702  FeeModel = new FeeModelPythonWrapper(feelModel);
703  }
704 
705  /// <summary>
706  /// Sets the fill model
707  /// </summary>
708  /// <param name="fillModel">Model that represents a fill model</param>
709  public void SetFillModel(IFillModel fillModel)
710  {
711  FillModel = fillModel;
712  }
713 
714  /// <summary>
715  /// Sets the fill model
716  /// </summary>
717  /// <param name="fillModel">Model that represents a fill model</param>
718  public void SetFillModel(PyObject fillModel)
719  {
720  FillModel = new FillModelPythonWrapper(fillModel);
721  }
722 
723  /// <summary>
724  /// Sets the settlement model
725  /// </summary>
726  /// <param name="settlementModel"> Model that represents a settlement model</param>
727  public void SetSettlementModel(ISettlementModel settlementModel)
728  {
729  SettlementModel = settlementModel;
730  }
731 
732  /// <summary>
733  /// Sets the settlement model
734  /// </summary>
735  /// <param name="settlementModel">Model that represents a settlement model</param>
736  public void SetSettlementModel(PyObject settlementModel)
737  {
738  SettlementModel = new SettlementModelPythonWrapper(settlementModel);
739  }
740 
741  /// <summary>
742  /// Sets the slippage model
743  /// </summary>
744  /// <param name="slippageModel">Model that represents a slippage model</param>
745  public void SetSlippageModel(ISlippageModel slippageModel)
746  {
747  SlippageModel = slippageModel;
748  }
749 
750  /// <summary>
751  /// Sets the slippage model
752  /// </summary>
753  /// <param name="slippageModel">Model that represents a slippage model</param>
754  public void SetSlippageModel(PyObject slippageModel)
755  {
756  SlippageModel = new SlippageModelPythonWrapper(slippageModel);
757  }
758 
759  /// <summary>
760  /// Sets the volatility model
761  /// </summary>
762  /// <param name="volatilityModel">Model that represents a volatility model</param>
763  public void SetVolatilityModel(IVolatilityModel volatilityModel)
764  {
765  VolatilityModel = volatilityModel;
766  }
767 
768  /// <summary>
769  /// Sets the volatility model
770  /// </summary>
771  /// <param name="volatilityModel">Model that represents a volatility model</param>
772  public void SetVolatilityModel(PyObject volatilityModel)
773  {
774  VolatilityModel = new VolatilityModelPythonWrapper(volatilityModel);
775  }
776 
777  /// <summary>
778  /// Sets the buying power model
779  /// </summary>
780  /// <param name="buyingPowerModel">Model that represents a security's model of buying power</param>
781  public void SetBuyingPowerModel(IBuyingPowerModel buyingPowerModel)
782  {
783  BuyingPowerModel = buyingPowerModel;
784  }
785 
786  /// <summary>
787  /// Sets the buying power model
788  /// </summary>
789  /// <param name="pyObject">Model that represents a security's model of buying power</param>
790  public void SetBuyingPowerModel(PyObject pyObject)
791  {
793  }
794 
795  /// <summary>
796  /// Sets the margin interests rate model
797  /// </summary>
798  /// <param name="marginInterestRateModel">Model that represents a security's model of margin interest rate</param>
799  public void SetMarginInterestRateModel(IMarginInterestRateModel marginInterestRateModel)
800  {
801  MarginInterestRateModel = marginInterestRateModel;
802  }
803 
804  /// <summary>
805  /// Sets the margin interests rate model
806  /// </summary>
807  /// <param name="pyObject">Model that represents a security's model of margin interest rate</param>
808  public void SetMarginInterestRateModel(PyObject pyObject)
809  {
811  }
812 
813  /// <summary>
814  /// Sets the margin model
815  /// </summary>
816  /// <param name="marginModel">Model that represents a security's model of buying power</param>
817  public void SetMarginModel(IBuyingPowerModel marginModel)
818  {
819  MarginModel = marginModel;
820  }
821 
822  /// <summary>
823  /// Sets the margin model
824  /// </summary>
825  /// <param name="pyObject">Model that represents a security's model of buying power</param>
826  public void SetMarginModel(PyObject pyObject)
827  {
829  }
830 
831  /// <summary>
832  /// Set Python Shortable Provider for this <see cref="Security"/>
833  /// </summary>
834  /// <param name="pyObject">Python class that represents a custom shortable provider</param>
835  public void SetShortableProvider(PyObject pyObject)
836  {
837  if (pyObject.TryConvert<IShortableProvider>(out var shortableProvider))
838  {
839  SetShortableProvider(shortableProvider);
840  }
841  else if (Extensions.TryConvert<IShortableProvider>(pyObject, out _, allowPythonDerivative: true))
842  {
844  }
845  else
846  {
847  using (Py.GIL())
848  {
849  throw new Exception($"SetShortableProvider: {pyObject.Repr()} is not a valid argument");
850  }
851  }
852  }
853 
854  /// <summary>
855  /// Set Shortable Provider for this <see cref="Security"/>
856  /// </summary>
857  /// <param name="shortableProvider">Provider to use</param>
858  public void SetShortableProvider(IShortableProvider shortableProvider)
859  {
860  ShortableProvider = shortableProvider;
861  }
862 
863  /// <summary>
864  /// Set Security Data Filter
865  /// </summary>
866  /// <param name="pyObject">Python class that represents a custom Security Data Filter</param>
867  /// <exception cref="ArgumentException"></exception>
868  public void SetDataFilter(PyObject pyObject)
869  {
870  if (pyObject.TryConvert<ISecurityDataFilter>(out var dataFilter))
871  {
872  SetDataFilter(dataFilter);
873  }
874  else if (Extensions.TryConvert<ISecurityDataFilter>(pyObject, out _, allowPythonDerivative: true))
875  {
877  }
878  else
879  {
880  using (Py.GIL())
881  {
882  throw new ArgumentException($"SetDataFilter: {pyObject.Repr()} is not a valid argument");
883  }
884  }
885  }
886 
887  /// <summary>
888  /// Set Security Data Filter
889  /// </summary>
890  /// <param name="dataFilter">Security Data Filter</param>
891  public void SetDataFilter(ISecurityDataFilter dataFilter)
892  {
893  DataFilter = dataFilter;
894  }
895 
896  #region DynamicObject Overrides and Helper Methods
897 
898  /// <summary>
899  /// This is a <see cref="DynamicObject"/> override. Not meant for external use.
900  /// </summary>
901  public override bool TryGetMember(GetMemberBinder binder, out object result)
902  {
903  return Cache.Properties.TryGetValue(binder.Name, out result);
904  }
905 
906  /// <summary>
907  /// This is a <see cref="DynamicObject"/> override. Not meant for external use.
908  /// </summary>
909  public override bool TrySetMember(SetMemberBinder binder, object value)
910  {
911  Cache.Properties[binder.Name] = value;
912  return true;
913  }
914 
915  /// <summary>
916  /// This is a <see cref="DynamicObject"/> override. Not meant for external use.
917  /// </summary>
918  public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
919  {
920  try
921  {
922  result = Cache.Properties.GetType().InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, Cache.Properties, args,
923  CultureInfo.InvariantCulture);
924  return true;
925  }
926  catch
927  {
928  result = null;
929  return false;
930  }
931  }
932 
933  /// <summary>
934  /// Adds the specified custom property.
935  /// This allows us to use the security object as a dynamic object for quick storage.
936  /// </summary>
937  /// <param name="key">The property key</param>
938  /// <param name="value">The property value</param>
939  public void Add(string key, object value)
940  {
941  Cache.Properties[key] = value;
942  }
943 
944  /// <summary>
945  /// Gets the specified custom property
946  /// </summary>
947  /// <param name="key">The property key</param>
948  /// <param name="value">The property value</param>
949  /// <returns>True if the property is found.</returns>
950  /// <exception cref="InvalidCastException">If the property is found but its value cannot be casted to the speficied type</exception>
951  public bool TryGet<T>(string key, out T value)
952  {
953  if (Cache.Properties.TryGetValue(key, out var obj))
954  {
955  value = CastDynamicPropertyValue<T>(obj);
956  return true;
957  }
958  value = default;
959  return false;
960  }
961 
962  /// <summary>
963  /// Gets the specified custom property
964  /// </summary>
965  /// <param name="key">The property key</param>
966  /// <returns>The property value is found</returns>
967  /// <exception cref="KeyNotFoundException">If the property is not found</exception>
968  public T Get<T>(string key)
969  {
970  return CastDynamicPropertyValue<T>(Cache.Properties[key]);
971  }
972 
973  /// <summary>
974  /// Removes a custom property.
975  /// </summary>
976  /// <param name="key">The property key</param>
977  /// <returns>True if the property is successfully removed</returns>
978  public bool Remove(string key)
979  {
980  return Cache.Properties.Remove(key);
981  }
982 
983  /// <summary>
984  /// Removes a custom property.
985  /// </summary>
986  /// <param name="key">The property key</param>
987  /// <param name="value">The removed property value</param>
988  /// <returns>True if the property is successfully removed</returns>
989  public bool Remove<T>(string key, out T value)
990  {
991  value = default;
992  var result = Cache.Properties.Remove(key, out object objectValue);
993  if (result)
994  {
995  value = CastDynamicPropertyValue<T>(objectValue);
996  }
997  return result;
998  }
999 
1000  /// <summary>
1001  /// Removes every custom property that had been set.
1002  /// </summary>
1003  public void Clear()
1004  {
1005  Cache.Properties.Clear();
1006  }
1007 
1008  /// <summary>
1009  /// Gets or sets the specified custom property through the indexer.
1010  /// This is a wrapper around the <see cref="Get{T}(string)"/> and <see cref="Add(string,object)"/> methods.
1011  /// </summary>
1012  /// <param name="key">The property key</param>
1013  public object this[string key]
1014  {
1015  get
1016  {
1017  return Get<object>(key);
1018  }
1019  set
1020  {
1021  Add(key, value);
1022  }
1023  }
1024 
1025  #endregion
1026 
1027  /// <summary>
1028  /// Returns a string that represents the current object.
1029  /// </summary>
1030  /// <returns>
1031  /// A string that represents the current object.
1032  /// </returns>
1033  /// <filterpriority>2</filterpriority>
1034  public override string ToString()
1035  {
1036  return Symbol.ToString();
1037  }
1038 
1039  /// <summary>
1040  /// Adds the specified data subscription to this security.
1041  /// </summary>
1042  /// <param name="subscription">The subscription configuration to add. The Symbol and ExchangeTimeZone properties must match the existing Security object</param>
1043  internal void AddData(SubscriptionDataConfig subscription)
1044  {
1045  lock (_subscriptionsBag)
1046  {
1047  if (subscription.Symbol != Symbol)
1048  {
1049  throw new ArgumentException(Messages.Security.UnmatchingSymbols, $"{nameof(subscription)}.{nameof(subscription.Symbol)}");
1050  }
1051  if (!subscription.ExchangeTimeZone.Equals(Exchange.TimeZone))
1052  {
1053  throw new ArgumentException(Messages.Security.UnmatchingExchangeTimeZones, $"{nameof(subscription)}.{nameof(subscription.ExchangeTimeZone)}");
1054  }
1055  _subscriptionsBag.Add(subscription);
1056  UpdateSubscriptionProperties();
1057  }
1058  }
1059 
1060  /// <summary>
1061  /// Adds the specified data subscriptions to this security.
1062  /// </summary>
1063  /// <param name="subscriptions">The subscription configuration to add. The Symbol and ExchangeTimeZone properties must match the existing Security object</param>
1064  internal void AddData(SubscriptionDataConfigList subscriptions)
1065  {
1066  lock (_subscriptionsBag)
1067  {
1068  foreach (var subscription in subscriptions)
1069  {
1070  if (subscription.Symbol != Symbol)
1071  {
1072  throw new ArgumentException(Messages.Security.UnmatchingSymbols, $"{nameof(subscription)}.{nameof(subscription.Symbol)}");
1073  }
1074  if (!subscription.ExchangeTimeZone.Equals(Exchange.TimeZone))
1075  {
1076  throw new ArgumentException(Messages.Security.UnmatchingExchangeTimeZones, $"{nameof(subscription)}.{nameof(subscription.ExchangeTimeZone)}");
1077  }
1078  _subscriptionsBag.Add(subscription);
1079  }
1080  UpdateSubscriptionProperties();
1081  }
1082  }
1083 
1084  /// <summary>
1085  /// Update market price of this Security
1086  /// </summary>
1087  /// <param name="data">Data to pull price from</param>
1088  protected virtual void UpdateConsumersMarketPrice(BaseData data)
1089  {
1090  if (data is OpenInterest || data.Price == 0m) return;
1092  VolatilityModel.Update(this, data);
1093  }
1094 
1095  /// <summary>
1096  /// Caller should hold the lock on '_subscriptionsBag'
1097  /// </summary>
1098  private void UpdateSubscriptionProperties()
1099  {
1100  Resolution = _subscriptionsBag.Select(x => x.Resolution).DefaultIfEmpty(Resolution.Daily).Min();
1101  IsFillDataForward = _subscriptionsBag.Any(x => x.FillDataForward);
1102  IsExtendedMarketHours = _subscriptionsBag.Any(x => x.ExtendedMarketHours);
1104  }
1105 
1106  /// <summary>
1107  /// Updates consumers market price. It will do nothing if the passed data type is auxiliary.
1108  /// </summary>
1109  private void UpdateMarketPrice(BaseData data)
1110  {
1111  if (data.DataType != MarketDataType.Auxiliary)
1112  {
1114  }
1115  }
1116 
1117  /// <summary>
1118  /// Casts a dynamic property value to the specified type.
1119  /// Useful for cases where the property value is a PyObject and we want to cast it to the underlying type.
1120  /// </summary>
1121  private static T CastDynamicPropertyValue<T>(object obj)
1122  {
1123  T value;
1124  var pyObj = obj as PyObject;
1125  if (pyObj != null)
1126  {
1127  using (Py.GIL())
1128  {
1129  value = pyObj.As<T>();
1130  }
1131  }
1132  else
1133  {
1134  value = (T)obj;
1135  }
1136 
1137  return value;
1138  }
1139  }
1140 }