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);
+ }
}
}