diff --git a/Algorithm.CSharp/ManuallySetMarketHoursAndSymbolPropertiesDatabaseEntriesAlgorithm.cs b/Algorithm.CSharp/ManuallySetMarketHoursAndSymbolPropertiesDatabaseEntriesAlgorithm.cs index 81217a605941..8417c366655d 100644 --- a/Algorithm.CSharp/ManuallySetMarketHoursAndSymbolPropertiesDatabaseEntriesAlgorithm.cs +++ b/Algorithm.CSharp/ManuallySetMarketHoursAndSymbolPropertiesDatabaseEntriesAlgorithm.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; using QuantConnect.Interfaces; +using QuantConnect.Securities; namespace QuantConnect.Algorithm.CSharp { @@ -54,7 +55,7 @@ public override void Initialize() throw new RegressionTestException("Expected the SPY CFD market hours to be the same as the underlying equity market hours."); } - if (!ReferenceEquals(spyCfd.SymbolProperties, equitySymbolProperties)) + if (!SymbolPropertiesAreEquivalent(spyCfd.SymbolProperties, equitySymbolProperties)) { throw new RegressionTestException("Expected the SPY CFD symbol properties to be the same as the underlying equity symbol properties."); } @@ -74,12 +75,25 @@ public override void Initialize() throw new RegressionTestException("Expected the AUDUSD CFD market hours to be the same as the underlying forex market hours."); } - if (!ReferenceEquals(audUsdCfd.SymbolProperties, audUsdForexSymbolProperties)) + if (!SymbolPropertiesAreEquivalent(audUsdCfd.SymbolProperties, audUsdForexSymbolProperties)) { throw new RegressionTestException("Expected the AUDUSD CFD symbol properties to be the same as the underlying forex symbol properties."); } } + private static bool SymbolPropertiesAreEquivalent(SymbolProperties a, SymbolProperties b) + { + return a.Description == b.Description && + a.QuoteCurrency == b.QuoteCurrency && + a.ContractMultiplier == b.ContractMultiplier && + a.MinimumPriceVariation == b.MinimumPriceVariation && + a.LotSize == b.LotSize && + a.MarketTicker == b.MarketTicker && + a.MinimumOrderSize == b.MinimumOrderSize && + a.PriceMagnifier == b.PriceMagnifier && + a.StrikeMultiplier == b.StrikeMultiplier; + } + /// /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. /// diff --git a/Algorithm.Python/ManuallySetMarketHoursAndSymbolPropertiesDatabaseEntriesAlgorithm.py b/Algorithm.Python/ManuallySetMarketHoursAndSymbolPropertiesDatabaseEntriesAlgorithm.py index 254d49bfb643..780867da0311 100644 --- a/Algorithm.Python/ManuallySetMarketHoursAndSymbolPropertiesDatabaseEntriesAlgorithm.py +++ b/Algorithm.Python/ManuallySetMarketHoursAndSymbolPropertiesDatabaseEntriesAlgorithm.py @@ -51,7 +51,7 @@ def initialize(self): if json.JsonConvert.serialize_object(spy_cfd.exchange.hours) != json.JsonConvert.serialize_object(equity_market_hours_entry.exchange_hours): raise AssertionError("Expected the SPY CFD market hours to be the same as the underlying equity market hours.") - if json.JsonConvert.serialize_object(spy_cfd.symbol_properties) != json.JsonConvert.serialize_object(equity_symbol_properties): + if not self.symbol_properties_are_equivalent(spy_cfd.symbol_properties, equity_symbol_properties): raise AssertionError("Expected the SPY CFD symbol properties to be the same as the underlying equity symbol properties.") # We can also do it for a specific ticker @@ -66,5 +66,16 @@ def initialize(self): if json.JsonConvert.serialize_object(aud_usd_cfd.exchange.hours) != json.JsonConvert.serialize_object(aud_usd_forex_market_hours_entry.exchange_hours): raise AssertionError("Expected the AUDUSD CFD market hours to be the same as the underlying forex market hours.") - if json.JsonConvert.serialize_object(aud_usd_cfd.symbol_properties) != json.JsonConvert.serialize_object(aud_usd_forex_symbol_properties): + if not self.symbol_properties_are_equivalent(aud_usd_cfd.symbol_properties, aud_usd_forex_symbol_properties): raise AssertionError("Expected the AUDUSD CFD symbol properties to be the same as the underlying forex symbol properties.") + + def symbol_properties_are_equivalent(self, a, b): + return (a.description == b.description and + a.quote_currency == b.quote_currency and + a.contract_multiplier == b.contract_multiplier and + a.minimum_price_variation == b.minimum_price_variation and + a.lot_size == b.lot_size and + a.market_ticker == b.market_ticker and + a.minimum_order_size == b.minimum_order_size and + a.price_magnifier == b.price_magnifier and + a.strike_multiplier == b.strike_multiplier) diff --git a/Common/Securities/Cfd/Cfd.cs b/Common/Securities/Cfd/Cfd.cs index 2bf86cac0d2e..4bcadcb2324b 100644 --- a/Common/Securities/Cfd/Cfd.cs +++ b/Common/Securities/Cfd/Cfd.cs @@ -27,6 +27,8 @@ namespace QuantConnect.Securities.Cfd /// public class Cfd : Security { + private readonly ContractSymbolProperties _symbolProperties; + /// /// Constructor for the CFD security /// @@ -45,7 +47,7 @@ public Cfd(SecurityExchangeHours exchangeHours, IRegisteredSecurityDataTypesProvider registeredTypes) : base(config, quoteCurrency, - symbolProperties, + new ContractSymbolProperties(symbolProperties), new CfdExchange(exchangeHours), new CfdCache(), new SecurityPortfolioModel(), @@ -63,6 +65,7 @@ public Cfd(SecurityExchangeHours exchangeHours, ) { Holdings = new CfdHolding(this, currencyConverter); + _symbolProperties = (ContractSymbolProperties)SymbolProperties; } /// @@ -85,7 +88,7 @@ public Cfd(Symbol symbol, SecurityCache securityCache) : base(symbol, quoteCurrency, - symbolProperties, + new ContractSymbolProperties(symbolProperties), new CfdExchange(exchangeHours), securityCache, new SecurityPortfolioModel(), @@ -103,14 +106,16 @@ public Cfd(Symbol symbol, ) { Holdings = new CfdHolding(this, currencyConverter); + _symbolProperties = (ContractSymbolProperties)SymbolProperties; } /// - /// Gets the contract multiplier for this CFD security + /// Gets or sets the contract multiplier for this CFD security /// public decimal ContractMultiplier { - get { return SymbolProperties.ContractMultiplier; } + get { return _symbolProperties.ContractMultiplier; } + set { _symbolProperties.SetContractMultiplier(value); } } /// diff --git a/Common/Securities/ContractSymbolProperties.cs b/Common/Securities/ContractSymbolProperties.cs new file mode 100644 index 000000000000..c5a3702c1fe4 --- /dev/null +++ b/Common/Securities/ContractSymbolProperties.cs @@ -0,0 +1,65 @@ +/* + * 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. +*/ + +namespace QuantConnect.Securities +{ + /// + /// Represents common properties for contract-based securities such as options and CFDs + /// + public class ContractSymbolProperties : SymbolProperties + { + /// + /// The contract multiplier for the security. + /// + /// + /// If manually set by a consumer, this value will be used instead of the + /// and also allows to make + /// sure it is not overridden when the symbol properties database gets updated. + /// + private decimal? _contractMultiplier; + + /// + /// The contract multiplier for the security + /// + public override decimal ContractMultiplier => _contractMultiplier ?? base.ContractMultiplier; + + /// + /// Creates an instance of the class from a instance + /// + public ContractSymbolProperties(SymbolProperties properties) + : base(properties) + { + } + + /// + /// Creates an instance of the class + /// + public ContractSymbolProperties(string description, string quoteCurrency, decimal contractMultiplier, + decimal minimumPriceVariation, decimal lotSize, string marketTicker, + decimal? minimumOrderSize = null, decimal priceMagnifier = 1, decimal strikeMultiplier = 1) + : base(description, quoteCurrency, contractMultiplier, minimumPriceVariation, lotSize, marketTicker, + minimumOrderSize, priceMagnifier, strikeMultiplier) + { + } + + /// + /// Sets a custom contract multiplier that persists through symbol properties database updates + /// + internal void SetContractMultiplier(decimal multiplier) + { + _contractMultiplier = multiplier; + } + } +} diff --git a/Common/Securities/Option/OptionSymbolProperties.cs b/Common/Securities/Option/OptionSymbolProperties.cs index c01176a9f63a..f6e83f74bce4 100644 --- a/Common/Securities/Option/OptionSymbolProperties.cs +++ b/Common/Securities/Option/OptionSymbolProperties.cs @@ -18,23 +18,8 @@ namespace QuantConnect.Securities.Option /// /// Represents common properties for a specific option contract /// - public class OptionSymbolProperties : SymbolProperties + public class OptionSymbolProperties : ContractSymbolProperties { - /// - /// The contract multiplier for the security. - /// - /// - /// If manually set by a consumer, this value will be used instead of the - /// and also allows to make - /// sure it is not overridden when the symbol properties database gets updated. - /// - private decimal? _contractMultiplier; - - /// - /// The contract multiplier for the security - /// - public override decimal ContractMultiplier => _contractMultiplier ?? base.ContractMultiplier; - /// /// When the holder of an equity option exercises one contract, or when the writer of an equity option is assigned /// an exercise notice on one contract, this unit of trade, usually 100 shares of the underlying security, changes hands. @@ -65,10 +50,5 @@ internal void SetContractUnitOfTrade(int unitOfTrade) { ContractUnitOfTrade = unitOfTrade; } - - internal void SetContractMultiplier(decimal multiplier) - { - _contractMultiplier = multiplier; - } } } diff --git a/Tests/Common/Securities/Cfd/CfdTests.cs b/Tests/Common/Securities/Cfd/CfdTests.cs index 37c749b59569..7835955b0f24 100644 --- a/Tests/Common/Securities/Cfd/CfdTests.cs +++ b/Tests/Common/Securities/Cfd/CfdTests.cs @@ -13,7 +13,6 @@ * limitations under the License. */ -using System; using NUnit.Framework; using QuantConnect.Data; using QuantConnect.Data.Market; @@ -27,12 +26,24 @@ public class CfdTests [Test] public void ConstructorExtractsQuoteCurrency() { - var symbol = Symbol.Create("DE30EUR", SecurityType.Cfd, Market.Oanda); - var config = new SubscriptionDataConfig(typeof(TradeBar), symbol, Resolution.Minute, TimeZones.Utc, TimeZones.NewYork, true, true, true); - var symbolProperties = new SymbolProperties("Dax German index", "EUR", 1, 1, 1, string.Empty); - var cfd = new QuantConnect.Securities.Cfd.Cfd(SecurityExchangeHours.AlwaysOpen(config.DataTimeZone), new Cash("EUR", 0, 0), config, symbolProperties, ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null); + var cfd = CreateCfd(contractMultiplier: 1m); Assert.AreEqual("EUR", cfd.QuoteCurrency.Symbol); } + [Test] + public void ContractMultiplierCanBeSetByUser() + { + var cfd = CreateCfd(contractMultiplier: 1m); + cfd.ContractMultiplier = 5m; + Assert.AreEqual(5m, cfd.ContractMultiplier); + } + + private static QuantConnect.Securities.Cfd.Cfd CreateCfd(decimal contractMultiplier) + { + var symbol = Symbol.Create("DE30EUR", SecurityType.Cfd, Market.Oanda); + var config = new SubscriptionDataConfig(typeof(TradeBar), symbol, Resolution.Minute, TimeZones.Utc, TimeZones.NewYork, true, true, true); + var symbolProperties = new SymbolProperties("Dax German index", "EUR", contractMultiplier, 1, 1, string.Empty); + return new QuantConnect.Securities.Cfd.Cfd(SecurityExchangeHours.AlwaysOpen(config.DataTimeZone), new Cash("EUR", 0, 0), config, symbolProperties, ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null); + } } }