/*
* 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 QuantConnect.Securities.Future;
namespace QuantConnect.ToolBox.RandomDataGenerator
{
///
/// Generates a new random future . The generates future contract Symbol will have an
/// expiry between the specified time range.
///
public class FutureSymbolGenerator : BaseSymbolGenerator
{
private readonly DateTime _minExpiry;
private readonly DateTime _maxExpiry;
private readonly string _market;
public FutureSymbolGenerator(RandomDataGeneratorSettings settings, IRandomValueGenerator random)
: base(settings, random)
{
_minExpiry = settings.Start;
_maxExpiry = settings.End;
_market = settings.Market;
}
///
/// Generates a new random future . The generates future contract Symbol will have an
/// expiry between the specified minExpiry and maxExpiry.
///
/// Optionally can provide a ticker that should be used
/// A new future contract Symbol with the specified expiration parameters
protected override IEnumerable GenerateAsset(string ticker = null)
{
if (ticker == null)
{
// get a valid ticker from the Symbol properties database
ticker = NextTickerFromSymbolPropertiesDatabase(SecurityType.Future, _market);
}
var marketHours = MarketHoursDatabase.GetExchangeHours(_market, ticker, SecurityType.Future);
var expiry = GetRandomExpiration(marketHours, _minExpiry, _maxExpiry);
// Try to get the specific expiry function for this future, if available
var symbol = Symbol.CreateFuture(ticker, _market, SecurityIdentifier.DefaultDate);
if (!FuturesExpiryFunctions.FuturesExpiryDictionary.TryGetValue(symbol, out var expiryFunction))
{
// If no expiry function is found, return the future using the previously chosen expiry
yield return Symbol.CreateFuture(ticker, _market, expiry);
yield break;
}
// Get all valid expiries in range using the expiry function
// HashSet ensures unique expiry dates since multiple reference dates may map to same expiry
var validExpiries = new HashSet();
// Extends range by ±1 month to catch all potential expiries.
// Some futures (like NG) calculate expiry based on next month's date
// (e.g., "3 business days before 1st of next month"), so we need to look ahead.
// This buffer ensures we don't miss expiries near range boundaries.
for (var date = _minExpiry.AddMonths(-1); date <= _maxExpiry.AddMonths(1); date = date.AddDays(1))
{
// Calculate expiry date using the futures-specific function
var newExpiry = expiryFunction(date);
// Only include expiries within our target range
if (_minExpiry < newExpiry && newExpiry <= _maxExpiry)
{
// Add to set of valid expiries (automatically handles duplicates)
validExpiries.Add(newExpiry);
}
}
if (validExpiries.Count == 0)
{
yield return Symbol.CreateFuture(ticker, _market, expiry);
yield break;
}
// Randomly select one expiry from the valid set
var skip = Random.NextInt(validExpiries.Count);
expiry = validExpiries.Skip(skip).First();
// Return the future contract using the randomly selected valid expiry
yield return Symbol.CreateFuture(ticker, _market, expiry);
}
///
/// There is no limit for the future symbols.
///
/// Returns int.MaxValue
public override int GetAvailableSymbolCount() => int.MaxValue;
}
}