/*
* 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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NodaTime;
using ProtoBuf;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Interfaces;
using QuantConnect.Logging;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Python;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using QuantConnect.Util;
using Timer = System.Timers.Timer;
using Microsoft.IO;
using NodaTime.TimeZones;
using QuantConnect.Configuration;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Exceptions;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.FutureOption;
using QuantConnect.Securities.Option;
using QuantConnect.Statistics;
using Newtonsoft.Json.Linq;
using QuantConnect.Orders.Fees;
namespace QuantConnect
{
///
/// Extensions function collections - group all static extensions functions here.
///
public static class Extensions
{
private static readonly Dictionary _emptyDirectories = new ();
private static readonly HashSet InvalidSecurityTypes = new HashSet();
private static readonly Regex DateCheck = new Regex(@"\d{8}", RegexOptions.Compiled);
private static RecyclableMemoryStreamManager MemoryManager = new RecyclableMemoryStreamManager();
private static readonly int DataUpdatePeriod = Config.GetInt("downloader-data-update-period", 7);
private static readonly Dictionary PythonActivators
= new Dictionary();
///
/// Maintains old behavior of NodaTime's (< 2.0) daylight savings mapping.
/// We keep the old behavior to ensure the FillForwardEnumerator does not get stuck on an infinite loop.
/// The test `ConvertToSkipsDiscontinuitiesBecauseOfDaylightSavingsStart_AddingOneHour` and other related tests
/// assert the expected behavior, which is to ignore discontinuities in daylight savings resolving.
///
/// More info can be found in the summary of the delegate.
///
private static readonly ZoneLocalMappingResolver _mappingResolver = Resolvers.CreateMappingResolver(Resolvers.ReturnLater, Resolvers.ReturnStartOfIntervalAfter);
///
/// The offset span from the market close to liquidate or exercise a security on the delisting date
///
/// Will no be used in live trading
/// By default span is negative 15 minutes. We want to liquidate before market closes if not, in some cases
/// like future options the market close would match the delisted event time and would cancel all orders and mark the security
/// as non tradable and delisted.
public static TimeSpan DelistingMarketCloseOffsetSpan { get; set; } = TimeSpan.FromMinutes(-15);
///
/// Helper method to get a property in a jobject if available
///
/// The property type
/// The jobject source
/// The property name
/// The property value if present or it's default value
public static T TryGetPropertyValue(this JObject jObject, string name)
{
T result = default;
if (jObject == null)
{
return result;
}
var jValue = jObject[name];
if (jValue != null && jValue.Type != JTokenType.Null)
{
result = jValue.Value();
}
return result;
}
///
/// Helper method to find all defined enums in the given value
///
public static IEnumerable GetFlags(long value) where T : Enum
{
foreach (T flag in Enum.GetValues(typeof(T)))
{
if ((value & Convert.ToInt64(flag, CultureInfo.InvariantCulture)) != 0)
{
yield return flag;
}
}
}
///
/// Determine if the file is out of date according to our download period.
/// Date based files are never out of date (Files with YYYYMMDD)
///
/// Path to the file
/// True if the file is out of date
public static bool IsOutOfDate(this string filepath)
{
var fileName = Path.GetFileName(filepath);
// helper to determine if file is date based using regex, matches a 8 digit value because we expect YYYYMMDD
return !DateCheck.IsMatch(fileName) && DateTime.Now - TimeSpan.FromDays(DataUpdatePeriod) > File.GetLastWriteTime(filepath);
}
///
/// Helper method to check if a directory exists and is not empty
///
/// The path to check
/// True if the directory does not exist or is empty
/// Will cache results
public static bool IsDirectoryEmpty(this string directoryPath)
{
lock (_emptyDirectories)
{
if(!_emptyDirectories.TryGetValue(directoryPath, out var result))
{
// is empty unless it exists and it has at least 1 file or directory in it
result = true;
if (Directory.Exists(directoryPath))
{
try
{
result = !Directory.EnumerateFileSystemEntries(directoryPath).Any();
}
catch (Exception exception)
{
Log.Error(exception);
}
}
_emptyDirectories[directoryPath] = result;
if (result)
{
Log.Trace($"Extensions.IsDirectoryEmpty(): directory '{directoryPath}' not found or empty");
}
}
return result;
}
}
///
/// Helper method to get a market hours entry
///
/// The market hours data base instance
/// The symbol to get the entry for
/// For custom data types can optionally provide data type so that a new entry is added
public static MarketHoursDatabase.Entry GetEntry(this MarketHoursDatabase marketHoursDatabase, Symbol symbol, IEnumerable dataTypes)
{
if (symbol.SecurityType == SecurityType.Base)
{
if (!marketHoursDatabase.TryGetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType, out var entry))
{
var type = dataTypes.Single();
var baseInstance = type.GetBaseDataInstance();
baseInstance.Symbol = symbol;
SecurityIdentifier.TryGetCustomDataType(symbol.ID.Symbol, out var customType);
// for custom types we will add an entry for that type
entry = marketHoursDatabase.SetEntryAlwaysOpen(symbol.ID.Market, customType != null ? $"TYPE.{customType}" : null, SecurityType.Base, baseInstance.DataTimeZone());
}
return entry;
}
var result = marketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
// For the OptionUniverse and FutureUniverse types, the exchange and data time zones are set to the same value (exchange tz).
// This is not actual options/futures data, just chains/universe selection, so we don't want any offsets
// between the exchange and data time zones.
// If the MHDB were data type dependent as well, this would be taken care in there.
if (result != null && dataTypes.Any(dataType => dataType.IsAssignableTo(typeof(BaseChainUniverseData))))
{
result = new MarketHoursDatabase.Entry(result.ExchangeHours.TimeZone, result.ExchangeHours);
}
return result;
}
///
/// Helper method to deserialize a json array into a list also handling single json values
///
/// The value to deserialize
public static List DeserializeList(this string jsonArray)
{
return DeserializeList(jsonArray);
}
///
/// Helper method to deserialize a json array into a list also handling single json values
///
/// The value to deserialize
public static List DeserializeList(this string jsonArray)
{
try
{
if (string.IsNullOrEmpty(jsonArray))
{
return new();
}
return JsonConvert.DeserializeObject>(jsonArray);
}
catch (Exception ex)
{
if (ex is not JsonReaderException && ex is not JsonSerializationException)
{
throw;
}
if (typeof(T) == typeof(string))
{
return new List { (T)Convert.ChangeType(jsonArray, typeof(T), CultureInfo.InvariantCulture) };
}
return new List { JsonConvert.DeserializeObject(jsonArray) };
}
}
///
/// Helper method to download a provided url as a string
///
/// The http client to use
/// The url to download data from
/// Add custom headers for the request
public static string DownloadData(this HttpClient client, string url, Dictionary headers = null)
{
if (headers != null)
{
foreach (var kvp in headers)
{
client.DefaultRequestHeaders.Add(kvp.Key, kvp.Value);
}
}
try
{
using (var response = client.GetAsync(url).Result)
{
using (var content = response.Content)
{
return content.ReadAsStringAsync().Result;
}
}
}
catch (WebException ex)
{
Log.Error(ex, $"DownloadData(): {Messages.Extensions.DownloadDataFailed(url)}");
return null;
}
}
///
/// Helper method to download a provided url as a string
///
/// The url to download data from
/// Add custom headers for the request
public static string DownloadData(this string url, Dictionary headers = null)
{
using var client = new HttpClient();
return client.DownloadData(url, headers);
}
///
/// Helper method to download a provided url as a byte array
///
/// The url to download data from
public static byte[] DownloadByteArray(this string url)
{
using (var wc = new HttpClient())
{
try
{
return wc.GetByteArrayAsync(url).Result;
}
catch (Exception ex)
{
Log.Error(ex, $"DownloadByteArray(): {Messages.Extensions.DownloadDataFailed(url)}");
return null;
}
}
}
///
/// Safe multiplies a decimal by 100
///
/// The decimal to multiply
/// The result, maxed out at decimal.MaxValue
public static decimal SafeMultiply100(this decimal value)
{
const decimal max = decimal.MaxValue / 100m;
if (value >= max) return decimal.MaxValue;
return value * 100m;
}
///
/// Will return a memory stream using the instance.
///
/// Unique guid
/// A memory stream
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryStream GetMemoryStream(Guid guid)
{
return MemoryManager.GetStream(guid);
}
///
/// Serialize a list of ticks using protobuf
///
/// The list of ticks to serialize
/// Unique guid
/// The resulting byte array
public static byte[] ProtobufSerialize(this List ticks, Guid guid)
{
byte[] result;
using (var stream = GetMemoryStream(guid))
{
Serializer.Serialize(stream, ticks);
result = stream.ToArray();
}
return result;
}
///
/// Serialize a base data instance using protobuf
///
/// The data point to serialize
/// Unique guid
/// The resulting byte array
public static byte[] ProtobufSerialize(this IBaseData baseData, Guid guid)
{
byte[] result;
using (var stream = GetMemoryStream(guid))
{
baseData.ProtobufSerialize(stream);
result = stream.ToArray();
}
return result;
}
///
/// Serialize a base data instance using protobuf
///
/// The data point to serialize
/// The destination stream
public static void ProtobufSerialize(this IBaseData baseData, Stream stream)
{
switch (baseData.DataType)
{
case MarketDataType.Tick:
Serializer.SerializeWithLengthPrefix(stream, baseData as Tick, PrefixStyle.Base128, 1);
break;
case MarketDataType.QuoteBar:
Serializer.SerializeWithLengthPrefix(stream, baseData as QuoteBar, PrefixStyle.Base128, 1);
break;
case MarketDataType.TradeBar:
Serializer.SerializeWithLengthPrefix(stream, baseData as TradeBar, PrefixStyle.Base128, 1);
break;
default:
Serializer.SerializeWithLengthPrefix(stream, baseData as BaseData, PrefixStyle.Base128, 1);
break;
}
}
///
/// Extension method to get security price is 0 messages for users
///
/// The value of this method is normalization
public static string GetZeroPriceMessage(this Symbol symbol)
{
return Messages.Extensions.ZeroPriceForSecurity(symbol);
}
///
/// Converts the provided string into camel case notation
///
public static string ToCamelCase(this string value)
{
if (string.IsNullOrEmpty(value))
{
return value;
}
if (value.Length == 1)
{
return value.ToLowerInvariant();
}
return char.ToLowerInvariant(value[0]) + value.Substring(1);
}
///
/// Helper method to batch a collection of into 1 single instance.
/// Will return null if the provided list is empty. Will keep the last Order instance per order id,
/// which is the latest. Implementations trusts the provided 'resultPackets' list to batch is in order
///
public static AlphaResultPacket Batch(this List resultPackets)
{
AlphaResultPacket resultPacket = null;
// batch result packets into a single packet
if (resultPackets.Count > 0)
{
// we will batch results into the first packet
resultPacket = resultPackets[0];
for (var i = 1; i < resultPackets.Count; i++)
{
var newerPacket = resultPackets[i];
// only batch current packet if there actually is data
if (newerPacket.Insights != null)
{
if (resultPacket.Insights == null)
{
// initialize the collection if it isn't there
resultPacket.Insights = new List();
}
resultPacket.Insights.AddRange(newerPacket.Insights);
}
// only batch current packet if there actually is data
if (newerPacket.OrderEvents != null)
{
if (resultPacket.OrderEvents == null)
{
// initialize the collection if it isn't there
resultPacket.OrderEvents = new List();
}
resultPacket.OrderEvents.AddRange(newerPacket.OrderEvents);
}
// only batch current packet if there actually is data
if (newerPacket.Orders != null)
{
if (resultPacket.Orders == null)
{
// initialize the collection if it isn't there
resultPacket.Orders = new List();
}
resultPacket.Orders.AddRange(newerPacket.Orders);
// GroupBy guarantees to respect original order, so we want to get the last order instance per order id
// this way we only keep the most updated version
resultPacket.Orders = resultPacket.Orders.GroupBy(order => order.Id)
.Select(ordersGroup => ordersGroup.Last()).ToList();
}
}
}
return resultPacket;
}
///
/// Helper method to safely stop a running thread
///
/// The thread to stop
/// The timeout to wait till the thread ends after which abort will be called
/// Cancellation token source to use if any
public static void StopSafely(this Thread thread, TimeSpan timeout, CancellationTokenSource token = null)
{
if (thread != null)
{
try
{
if (token != null && !token.IsCancellationRequested)
{
token.Cancel(false);
}
Log.Trace($"StopSafely(): {Messages.Extensions.WaitingForThreadToStopSafely(thread.Name)}");
// just in case we add a time out
if (!thread.Join(timeout))
{
Log.Error($"StopSafely(): {Messages.Extensions.TimeoutWaitingForThreadToStopSafely(thread.Name)}");
}
}
catch (Exception exception)
{
// just in case catch any exceptions
Log.Error(exception);
}
}
}
///
/// Generates a hash code from a given collection of orders
///
/// The order collection
/// The hash value
public static string GetHash(this IDictionary orders)
{
var joinedOrders = string.Join(
",",
orders
.OrderBy(pair => pair.Key)
.Select(pair =>
{
// this is required to avoid any small differences between python and C#
var order = pair.Value;
order.Price = order.Price.SmartRounding();
var limit = order as LimitOrder;
if (limit != null)
{
limit.LimitPrice = limit.LimitPrice.SmartRounding();
}
var stopLimit = order as StopLimitOrder;
if (stopLimit != null)
{
stopLimit.LimitPrice = stopLimit.LimitPrice.SmartRounding();
stopLimit.StopPrice = stopLimit.StopPrice.SmartRounding();
}
var trailingStop = order as TrailingStopOrder;
if (trailingStop != null)
{
trailingStop.TrailingAmount = trailingStop.TrailingAmount.SmartRounding();
}
var stopMarket = order as StopMarketOrder;
if (stopMarket != null)
{
stopMarket.StopPrice = stopMarket.StopPrice.SmartRounding();
}
var limitIfTouched = order as LimitIfTouchedOrder;
if (limitIfTouched != null)
{
limitIfTouched.LimitPrice = limitIfTouched.LimitPrice.SmartRounding();
limitIfTouched.TriggerPrice = limitIfTouched.TriggerPrice.SmartRounding();
}
return JsonConvert.SerializeObject(pair.Value, Formatting.None);
}
)
);
return joinedOrders.ToMD5();
}
///
/// Converts a date rule into a function that receives current time
/// and returns the next date.
///
/// The date rule to convert
/// A function that will enumerate the provided date rules
public static Func ToFunc(this IDateRule dateRule)
{
IEnumerator dates = null;
return timeUtc =>
{
if (dates == null)
{
dates = dateRule.GetDates(timeUtc, Time.EndOfTime).GetEnumerator();
if (!dates.MoveNext())
{
return Time.EndOfTime;
}
}
try
{
// only advance enumerator if provided time is past or at our current
if (timeUtc >= dates.Current)
{
if (!dates.MoveNext())
{
return Time.EndOfTime;
}
}
return dates.Current;
}
catch (InvalidOperationException)
{
// enumeration ended
return Time.EndOfTime;
}
};
}
///
/// Returns true if the specified instance holds no
///
public static bool IsEmpty(this BaseSeries series)
{
return series.Values.Count == 0;
}
///
/// Returns if the specified instance holds no
/// or they are all empty
///
public static bool IsEmpty(this Chart chart)
{
return chart.Series.Values.All(IsEmpty);
}
///
/// Gets a python method by name
///
/// The object instance to search the method in
/// The name of the method
/// The python method or null if not defined or CSharp implemented
public static dynamic GetPythonMethod(this PyObject instance, string name)
{
using (Py.GIL())
{
PyObject method;
// Let's try first with snake-case style in case the user is using it
var snakeCasedNamed = name.ToSnakeCase();
if (snakeCasedNamed != name)
{
method = instance.GetPythonMethodWithChecks(snakeCasedNamed);
if (method != null)
{
return method;
}
}
method = instance.GetAttr(name);
using var pythonType = method.GetPythonType();
var isPythonDefined = pythonType.Repr().Equals("", StringComparison.Ordinal);
if (isPythonDefined)
{
return method;
}
method.Dispose();
return null;
}
}
///
/// Gets a python property by name
///
/// The object instance to search the property in
/// The name of the property
/// The python property or null if not defined or CSharp implemented
public static dynamic GetPythonBoolProperty(this PyObject instance, string name)
{
using (Py.GIL())
{
var objectType = instance.GetPythonType();
if (!objectType.HasAttr(name))
{
return null;
}
var property = instance.GetAttr(name);
var pythonType = property.GetPythonType();
var isPythonDefined = pythonType.Repr().Equals("", StringComparison.Ordinal);
if (isPythonDefined)
{
return property;
}
return null;
}
}
///
/// Gets a python property by name
///
/// The object instance to search the property in
/// The name of the method
/// The python property or null if not defined or CSharp implemented
public static dynamic GetPythonBoolPropertyWithChecks(this PyObject instance, string name)
{
using (Py.GIL())
{
if (!instance.HasAttr(name))
{
return null;
}
return instance.GetPythonBoolProperty(name);
}
}
///
/// Gets a python method by name
///
/// The object instance to search the method in
/// The name of the method
/// The python method or null if not defined or CSharp implemented
public static dynamic GetPythonMethodWithChecks(this PyObject instance, string name)
{
using (Py.GIL())
{
if (!instance.HasAttr(name))
{
return null;
}
return instance.GetPythonMethod(name);
}
}
///
/// Gets a method from a instance by name.
/// First, it tries to get the snake-case version of the method name, in case the user is using that style.
/// Else, it tries to get the method with the original name, regardless of whether the class has a Python overload or not.
///
/// The object instance to search the method in
/// The name of the method
/// The method matching the name
public static dynamic GetMethod(this PyObject instance, string name)
{
using var _ = Py.GIL();
return instance.GetPythonMethodWithChecks(name.ToSnakeCase()) ?? instance.GetAttr(name);
}
///
/// Get a python methods arg count
///
/// The Python method
/// Count of arguments
public static int GetPythonArgCount(this PyObject method)
{
using (Py.GIL())
{
int argCount;
var pyArgCount = PyModule.FromString(Guid.NewGuid().ToString(),
"from inspect import signature\n" +
"def GetArgCount(method):\n" +
" return len(signature(method).parameters)\n"
).GetAttr("GetArgCount").Invoke(method);
pyArgCount.TryConvert(out argCount);
return argCount;
}
}
///
/// Returns an ordered enumerable where position reducing orders are executed first
/// and the remaining orders are executed in decreasing order value.
/// Will NOT return targets during algorithm warmup.
/// Will NOT return targets for securities that have no data yet.
/// Will NOT return targets for which current holdings + open orders quantity, sum up to the target quantity
///
/// The portfolio targets to order by margin
/// The algorithm instance
/// True if the target quantity is the delta between the
/// desired and existing quantity
public static IEnumerable OrderTargetsByMarginImpact(
this IEnumerable targets,
IAlgorithm algorithm,
bool targetIsDelta = false)
{
if (algorithm.IsWarmingUp)
{
return Enumerable.Empty();
}
return targets.Select(x =>
{
var security = algorithm.Securities[x.Symbol];
return new
{
PortfolioTarget = x,
TargetQuantity = OrderSizing.AdjustByLotSize(security, x.Quantity),
ExistingQuantity = algorithm.Transactions.GetProjectedHoldings(security).ProjectedQuantity,
Security = security
};
})
.Where(x => x.Security.HasData
&& x.Security.IsTradable
&& (targetIsDelta ? Math.Abs(x.TargetQuantity) : Math.Abs(x.TargetQuantity - x.ExistingQuantity))
>= x.Security.SymbolProperties.LotSize
)
.Select(x => new {
x.PortfolioTarget,
OrderValue = Math.Abs((targetIsDelta ? x.TargetQuantity : (x.TargetQuantity - x.ExistingQuantity)) * x.Security.Price),
IsReducingPosition = x.ExistingQuantity != 0
&& Math.Abs((targetIsDelta ? (x.TargetQuantity + x.ExistingQuantity) : x.TargetQuantity)) < Math.Abs(x.ExistingQuantity)
})
.OrderByDescending(x => x.IsReducingPosition)
.ThenByDescending(x => x.OrderValue)
.Select(x => x.PortfolioTarget);
}
///
/// Given a type will create a new instance using the parameterless constructor
/// and assert the type implements
///
/// One of the objectives of this method is to normalize the creation of the
/// BaseData instances while reducing code duplication
public static BaseData GetBaseDataInstance(this Type type)
{
var objectActivator = ObjectActivator.GetActivator(type);
if (objectActivator == null)
{
throw new ArgumentException(Messages.Extensions.DataTypeMissingParameterlessConstructor(type));
}
var instance = objectActivator.Invoke(new object[] { type });
if(instance == null)
{
// shouldn't happen but just in case...
throw new ArgumentException(Messages.Extensions.FailedToCreateInstanceOfType(type));
}
// we expect 'instance' to inherit BaseData in most cases so we use 'as' versus 'IsAssignableFrom'
// since it is slightly cheaper
var result = instance as BaseData;
if (result == null)
{
throw new ArgumentException(Messages.Extensions.TypeIsNotBaseData(type));
}
return result;
}
///
/// Helper method that will cast the provided
/// to a T type and dispose of it.
///
/// The target type
/// The instance to cast and dispose
/// The instance of type T. Will return default value if
/// provided instance is null
public static T GetAndDispose(this PyObject instance)
{
if (instance == null)
{
return default(T);
}
var returnInstance = instance.As();
// will reduce ref count
instance.Dispose();
return returnInstance;
}
///
/// Extension to move one element from list from A to position B.
///
/// Type of list
/// List we're operating on.
/// Index of variable we want to move.
/// New location for the variable
public static void Move(this List list, int oldIndex, int newIndex)
{
var oItem = list[oldIndex];
list.RemoveAt(oldIndex);
if (newIndex > oldIndex) newIndex--;
list.Insert(newIndex, oItem);
}
///
/// Extension method to convert a string into a byte array
///
/// String to convert to bytes.
/// Byte array
public static byte[] GetBytes(this string str)
{
var bytes = new byte[str.Length * sizeof(char)];
Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
///
/// Reads the entire content of a stream and returns it as a byte array.
///
/// Stream to read bytes from
/// The bytes read from the stream
public static byte[] GetBytes(this Stream stream)
{
using var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
///
/// Extentsion method to clear all items from a thread safe queue
///
/// Small risk of race condition if a producer is adding to the list.
/// Queue type
/// queue object
public static void Clear(this ConcurrentQueue queue)
{
T item;
while (queue.TryDequeue(out item)) {
// NOP
}
}
///
/// Extension method to convert a byte array into a string.
///
/// Byte array to convert.
/// The encoding to use for the conversion. Defaults to Encoding.ASCII
/// String from bytes.
public static string GetString(this byte[] bytes, Encoding encoding = null)
{
if (encoding == null) encoding = Encoding.ASCII;
return encoding.GetString(bytes);
}
///
/// Extension method to convert a string to a MD5 hash.
///
/// String we want to MD5 encode.
/// MD5 hash of a string
public static string ToMD5(this string str)
{
var builder = new StringBuilder(32);
var data = MD5.HashData(Encoding.UTF8.GetBytes(str));
for (var i = 0; i < 16; i++)
{
builder.Append(data[i].ToStringInvariant("x2"));
}
return builder.ToString();
}
///
/// Encrypt the token:time data to make our API hash.
///
/// Data to be hashed by SHA256
/// Hashed string.
public static string ToSHA256(this string data)
{
var hash = new StringBuilder(64);
var crypto = SHA256.HashData(Encoding.UTF8.GetBytes(data));
for (var i = 0; i < 32; i++)
{
hash.Append(crypto[i].ToStringInvariant("x2"));
}
return hash.ToString();
}
///
/// Converts a long to an uppercase alpha numeric string
///
public static string EncodeBase36(this ulong data)
{
var stack = new Stack(15);
while (data != 0)
{
var value = data % 36;
var c = value < 10
? (char)(value + '0')
: (char)(value - 10 + 'A');
stack.Push(c);
data /= 36;
}
return new string(stack.ToArray());
}
///
/// Converts an upper case alpha numeric string into a long
///
public static ulong DecodeBase36(this string symbol)
{
var result = 0ul;
var baseValue = 1ul;
for (var i = symbol.Length - 1; i > -1; i--)
{
var c = symbol[i];
// assumes alpha numeric upper case only strings
var value = (uint)(c <= 57
? c - '0'
: c - 'A' + 10);
result += baseValue * value;
baseValue *= 36;
}
return result;
}
///
/// Convert a string to Base64 Encoding
///
/// Text to encode
/// Encoded result
public static string EncodeBase64(this string text)
{
if (string.IsNullOrEmpty(text))
{
return text;
}
byte[] textBytes = Encoding.UTF8.GetBytes(text);
return Convert.ToBase64String(textBytes);
}
///
/// Decode a Base64 Encoded string
///
/// Text to decode
/// Decoded result
public static string DecodeBase64(this string base64EncodedText)
{
if (string.IsNullOrEmpty(base64EncodedText))
{
return base64EncodedText;
}
byte[] base64EncodedBytes = Convert.FromBase64String(base64EncodedText);
return Encoding.UTF8.GetString(base64EncodedBytes);
}
///
/// Lazy string to upper implementation.
/// Will first verify the string is not already upper and avoid
/// the call to if possible.
///
/// The string to upper
/// The upper string
public static string LazyToUpper(this string data)
{
// for performance only call to upper if required
var alreadyUpper = true;
for (int i = 0; i < data.Length && alreadyUpper; i++)
{
alreadyUpper = char.IsUpper(data[i]);
}
return alreadyUpper ? data : data.ToUpperInvariant();
}
///
/// Lazy string to lower implementation.
/// Will first verify the string is not already lower and avoid
/// the call to if possible.
///
/// The string to lower
/// The lower string
public static string LazyToLower(this string data)
{
// for performance only call to lower if required
var alreadyLower = true;
for (int i = 0; i < data.Length && alreadyLower; i++)
{
alreadyLower = char.IsLower(data[i]);
}
return alreadyLower ? data : data.ToLowerInvariant();
}
///
/// Extension method to automatically set the update value to same as "add" value for TryAddUpdate.
/// This makes the API similar for traditional and concurrent dictionaries.
///
/// Key type for dictionary
/// Value type for dictonary
/// Dictionary object we're operating on
/// Key we want to add or update.
/// Value we want to set.
public static void AddOrUpdate(this ConcurrentDictionary dictionary, K key, V value)
{
dictionary.AddOrUpdate(key, value, (oldkey, oldvalue) => value);
}
///
/// Extension method to automatically add/update lazy values in concurrent dictionary.
///
/// Key type for dictionary
/// Value type for dictonary
/// Dictionary object we're operating on
/// Key we want to add or update.
/// The function used to generate a value for an absent key
/// The function used to generate a new value for an existing key based on the key's existing value
public static TValue AddOrUpdate(this ConcurrentDictionary> dictionary, TKey key, Func addValueFactory, Func updateValueFactory)
{
var result = dictionary.AddOrUpdate(key, new Lazy(() => addValueFactory(key)), (key2, old) => new Lazy(() => updateValueFactory(key2, old.Value)));
return result.Value;
}
///
/// Adds the specified element to the collection with the specified key. If an entry does not exist for the
/// specified key then one will be created.
///
/// The key type
/// The collection element type
/// The collection type
/// The source dictionary to be added to
/// The key
/// The element to be added
public static void Add(this IDictionary dictionary, TKey key, TElement element)
where TCollection : ICollection, new()
{
TCollection list;
if (!dictionary.TryGetValue(key, out list))
{
list = new TCollection();
dictionary.Add(key, list);
}
list.Add(element);
}
///
/// Adds the specified element to the collection with the specified key. If an entry does not exist for the
/// specified key then one will be created.
///
/// The key type
/// The collection element type
/// The source dictionary to be added to
/// The key
/// The element to be added
public static ImmutableDictionary> Add(
this ImmutableDictionary> dictionary,
TKey key,
TElement element
)
{
ImmutableHashSet set;
if (!dictionary.TryGetValue(key, out set))
{
set = ImmutableHashSet.Empty.Add(element);
return dictionary.Add(key, set);
}
return dictionary.SetItem(key, set.Add(element));
}
///
/// Adds the specified element to the collection with the specified key. If an entry does not exist for the
/// specified key then one will be created.
///
/// The key type
/// The collection element type
/// The source dictionary to be added to
/// The key
/// The element to be added
public static ImmutableSortedDictionary> Add(
this ImmutableSortedDictionary> dictionary,
TKey key,
TElement element
)
{
ImmutableHashSet set;
if (!dictionary.TryGetValue(key, out set))
{
set = ImmutableHashSet.Empty.Add(element);
return dictionary.Add(key, set);
}
return dictionary.SetItem(key, set.Add(element));
}
///
/// Adds the specified Tick to the Ticks collection. If an entry does not exist for the specified key then one will be created.
///
/// The ticks dictionary
/// The symbol
/// The tick to add
/// For performance we implement this method based on
public static void Add(this Ticks dictionary, Symbol key, Tick tick)
{
List list;
if (!dictionary.TryGetValue(key, out list))
{
dictionary[key] = list = new List(1);
}
list.Add(tick);
}
///
/// Extension method to round a double value to a fixed number of significant figures instead of a fixed decimal places.
///
/// Double we're rounding
/// Number of significant figures
/// New double rounded to digits-significant figures
public static decimal RoundToSignificantDigits(this decimal d, int digits)
{
if (d == 0) return 0;
var scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10((double) Math.Abs(d))) + 1);
return scale * Math.Round(d / scale, digits);
}
///
/// Converts a decimal into a rounded number ending with K (thousands), M (millions), B (billions), etc.
///
/// Number to convert
/// Formatted number with figures written in shorthand form
public static string ToFinancialFigures(this decimal number)
{
if (number < 1000)
{
return number.ToStringInvariant();
}
// Subtract by multiples of 5 to round down to nearest round number
if (number < 10000)
{
return (number - 5m).ToString("#,.##", CultureInfo.InvariantCulture) + "K";
}
if (number < 100000)
{
return (number - 50m).ToString("#,.#", CultureInfo.InvariantCulture) + "K";
}
if (number < 1000000)
{
return (number - 500m).ToString("#,.", CultureInfo.InvariantCulture) + "K";
}
if (number < 10000000)
{
return (number - 5000m).ToString("#,,.##", CultureInfo.InvariantCulture) + "M";
}
if (number < 100000000)
{
return (number - 50000m).ToString("#,,.#", CultureInfo.InvariantCulture) + "M";
}
if (number < 1000000000)
{
return (number - 500000m).ToString("#,,.", CultureInfo.InvariantCulture) + "M";
}
return (number - 5000000m).ToString("#,,,.##", CultureInfo.InvariantCulture) + "B";
}
///
/// Discretizes the to a maximum precision specified by . Quanta
/// can be an arbitrary positive number and represents the step size. Consider a quanta equal to 0.15 and rounding
/// a value of 1.0. Valid values would be 0.9 (6 quanta) and 1.05 (7 quanta) which would be rounded up to 1.05.
///
/// The value to be rounded by discretization
/// The maximum precision allowed by the value
/// Specifies how to handle the rounding of half value, defaulting to away from zero.
///
public static decimal DiscretelyRoundBy(this decimal value, decimal quanta, MidpointRounding mode = MidpointRounding.AwayFromZero)
{
if (quanta == 0m)
{
return value;
}
// away from zero is the 'common sense' rounding.
// +0.5 rounded by 1 yields +1
// -0.5 rounded by 1 yields -1
var multiplicand = Math.Round(value / quanta, mode);
return quanta * multiplicand;
}
///
/// Will truncate the provided decimal, without rounding, to 3 decimal places
///
/// The value to truncate
/// New instance with just 3 decimal places
public static decimal TruncateTo3DecimalPlaces(this decimal value)
{
// we will multiply by 1k bellow, if its bigger it will stack overflow
if (value >= decimal.MaxValue / 1000
|| value <= decimal.MinValue / 1000
|| value == 0)
{
return value;
}
return Math.Truncate(1000 * value) / 1000;
}
///
/// Provides global smart rounding, numbers larger than 1000 will round to 4 decimal places,
/// while numbers smaller will round to 7 significant digits
///
public static decimal? SmartRounding(this decimal? input)
{
if (!input.HasValue)
{
return null;
}
return input.Value.SmartRounding();
}
///
/// Provides global smart rounding, numbers larger than 1000 will round to 4 decimal places,
/// while numbers smaller will round to 7 significant digits
///
public static decimal SmartRounding(this decimal input)
{
input = Normalize(input);
// any larger numbers we still want some decimal places
if (input > 1000)
{
return Math.Round(input, 4);
}
// this is good for forex and other small numbers
return input.RoundToSignificantDigits(7).Normalize();
}
///
/// Provides global smart rounding to a shorter version
///
public static decimal SmartRoundingShort(this decimal input)
{
input = Normalize(input);
if (input <= 1)
{
// 0.99 > input
return input;
}
else if (input <= 10)
{
// 1.01 to 9.99
return Math.Round(input, 2);
}
else if (input <= 100)
{
// 99.9 to 10.1
return Math.Round(input, 1);
}
// 100 to inf
return Math.Truncate(input);
}
///
/// Casts the specified input value to a decimal while acknowledging the overflow conditions
///
/// The value to be cast
/// The input value as a decimal, if the value is too large or to small to be represented
/// as a decimal, then the closest decimal value will be returned
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static decimal SafeDecimalCast(this double input)
{
if (input.IsNaNOrInfinity())
{
throw new ArgumentException(
Messages.Extensions.CannotCastNonFiniteFloatingPointValueToDecimal(input),
nameof(input),
new NotFiniteNumberException(input)
);
}
if (input <= (double) decimal.MinValue) return decimal.MinValue;
if (input >= (double) decimal.MaxValue) return decimal.MaxValue;
return (decimal) input;
}
///
/// Will remove any trailing zeros for the provided decimal input
///
/// The to remove trailing zeros from
/// Provided input with no trailing zeros
/// Will not have the expected behavior when called from Python,
/// since the returned will be converted to python float,
///
public static decimal Normalize(this decimal input)
{
// http://stackoverflow.com/a/7983330/1582922
return input / 1.000000000000000000000000000000000m;
}
///
/// Will remove any trailing zeros for the provided decimal and convert to string.
/// Uses .
///
/// The to convert to
/// Input converted to with no trailing zeros
public static string NormalizeToStr(this decimal input)
{
return Normalize(input).ToString(CultureInfo.InvariantCulture);
}
///
/// Helper method to determine the amount of decimal places associated with the given decimal
///
/// The value to get the decimal count from
/// The quantity of decimal places
public static int GetDecimalPlaces(this decimal input)
{
return BitConverter.GetBytes(decimal.GetBits(input)[3])[2];
}
///
/// Extension method for faster string to decimal conversion.
///
/// String to be converted to positive decimal value
///
/// Leading and trailing whitespace chars are ignored
///
/// Decimal value of the string
public static decimal ToDecimal(this string str)
{
long value = 0;
var decimalPlaces = 0;
var hasDecimals = false;
var index = 0;
var length = str.Length;
while (index < length && char.IsWhiteSpace(str[index]))
{
index++;
}
var isNegative = index < length && str[index] == '-';
if (isNegative)
{
index++;
}
while (index < length)
{
var ch = str[index++];
if (ch == '.')
{
hasDecimals = true;
decimalPlaces = 0;
}
else if (char.IsWhiteSpace(ch))
{
break;
}
else
{
value = value * 10 + (ch - '0');
decimalPlaces++;
}
}
var lo = (int)value;
var mid = (int)(value >> 32);
return new decimal(lo, mid, 0, isNegative, (byte)(hasDecimals ? decimalPlaces : 0));
}
///
/// Extension method for faster string to normalized decimal conversion, i.e. 20.0% should be parsed into 0.2
///
/// String to be converted to positive decimal value
///
/// Leading and trailing whitespace chars are ignored
///
/// Decimal value of the string
public static decimal ToNormalizedDecimal(this string str)
{
var trimmed = str.Trim();
var value = str.TrimEnd('%').ToDecimal();
if (trimmed.EndsWith("%"))
{
value /= 100;
}
return value;
}
///
/// Extension method for string to decimal conversion where string can represent a number with exponent xe-y
///
/// String to be converted to decimal value
/// Decimal value of the string
public static decimal ToDecimalAllowExponent(this string str)
{
return decimal.Parse(str, NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);
}
///
/// Extension method for faster string to Int32 conversion.
///
/// String to be converted to positive Int32 value
/// Method makes some assuptions - always numbers, no "signs" +,- etc.
/// Int32 value of the string
public static int ToInt32(this string str)
{
int value = 0;
for (var i = 0; i < str.Length; i++)
{
if (str[i] == '.')
break;
value = value * 10 + (str[i] - '0');
}
return value;
}
///
/// Extension method for faster string to Int64 conversion.
///
/// String to be converted to positive Int64 value
/// Method makes some assuptions - always numbers, no "signs" +,- etc.
/// Int32 value of the string
public static long ToInt64(this string str)
{
long value = 0;
for (var i = 0; i < str.Length; i++)
{
if (str[i] == '.')
break;
value = value * 10 + (str[i] - '0');
}
return value;
}
///
/// Helper method to determine if a data type implements the Stream reader method
///
public static bool ImplementsStreamReader(this Type baseDataType)
{
// we know these type implement the streamReader interface lets avoid dynamic reflection call to figure it out
if (baseDataType == typeof(TradeBar) || baseDataType == typeof(QuoteBar) || baseDataType == typeof(Tick))
{
return true;
}
var method = baseDataType.GetMethod("Reader",
new[] { typeof(SubscriptionDataConfig), typeof(StreamReader), typeof(DateTime), typeof(bool) });
if (method != null && method.DeclaringType == baseDataType)
{
return true;
}
return false;
}
///
/// Breaks the specified string into csv components, all commas are considered separators
///
/// The string to be broken into csv
/// The expected size of the output list
/// A list of the csv pieces
public static List ToCsv(this string str, int size = 4)
{
int last = 0;
var csv = new List(size);
for (int i = 0; i < str.Length; i++)
{
if (str[i] == ',')
{
if (last != 0) last = last + 1;
csv.Add(str.Substring(last, i - last));
last = i;
}
}
if (last != 0) last = last + 1;
csv.Add(str.Substring(last));
return csv;
}
///
/// Breaks the specified string into csv components, works correctly with commas in data fields
///
/// The string to be broken into csv
/// The expected size of the output list
/// The delimiter used to separate entries in the line
/// A list of the csv pieces
public static List ToCsvData(this string str, int size = 4, char delimiter = ',')
{
var csv = new List(size);
var last = -1;
var count = 0;
var textDataField = false;
for (var i = 0; i < str.Length; i++)
{
var current = str[i];
if (current == '"')
{
textDataField = !textDataField;
}
else if (!textDataField && current == delimiter)
{
csv.Add(str.Substring(last + 1, (i - last)).Trim(' ', ','));
last = i;
count++;
}
}
if (last != 0)
{
csv.Add(str.Substring(last + 1).Trim());
}
return csv;
}
///
/// Gets the value at the specified index from a CSV line.
///
/// The CSV line
/// The index of the value to be extracted from the CSV line
/// The value at the given index
/// Whether there was a value at the given index and could be extracted
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetFromCsv(this string csvLine, int index, out ReadOnlySpan result)
{
result = ReadOnlySpan.Empty;
if (string.IsNullOrEmpty(csvLine) || index < 0)
{
return false;
}
var span = csvLine.AsSpan();
for (int i = 0; i < index; i++)
{
var commaIndex = span.IndexOf(',');
if (commaIndex == -1)
{
return false;
}
span = span.Slice(commaIndex + 1);
}
var nextCommaIndex = span.IndexOf(',');
if (nextCommaIndex == -1)
{
nextCommaIndex = span.Length;
}
result = span.Slice(0, nextCommaIndex);
return true;
}
///
/// Gets the value at the specified index from a CSV line, converted into a decimal.
///
/// The CSV line
/// The index of the value to be extracted from the CSV line
/// The decimal value at the given index
/// Whether there was a value at the given index and could be extracted and converted into a decimal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetDecimalFromCsv(this string csvLine, int index, out decimal value)
{
value = decimal.Zero;
if (!csvLine.TryGetFromCsv(index, out var csvValue))
{
return false;
}
return decimal.TryParse(csvValue, NumberStyles.Any, CultureInfo.InvariantCulture, out value);
}
///
/// Gets the value at the specified index from a CSV line, converted into a decimal.
///
/// The CSV line
/// The index of the value to be extracted from the CSV line
/// The decimal value at the given index. If the index is invalid or conversion fails, it will return zero
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static decimal GetDecimalFromCsv(this string csvLine, int index)
{
csvLine.TryGetDecimalFromCsv(index, out var value);
return value;
}
///
/// Check if a number is NaN or infinity
///
/// The double value to check
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNaNOrInfinity(this double value)
{
return double.IsNaN(value) || double.IsInfinity(value);
}
///
/// Check if a number is NaN or equal to zero
///
/// The double value to check
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNaNOrZero(this double value)
{
return double.IsNaN(value) || Math.Abs(value) < double.Epsilon;
}
///
/// Gets the smallest positive number that can be added to a decimal instance and return
/// a new value that does not == the old value
///
public static decimal GetDecimalEpsilon()
{
return new decimal(1, 0, 0, false, 27); //1e-27m;
}
///
/// Extension method to extract the extension part of this file name if it matches a safe list, or return a ".custom" extension for ones which do not match.
///
/// String we're looking for the extension for.
/// Last 4 character string of string.
public static string GetExtension(this string str) {
var ext = str.Substring(Math.Max(0, str.Length - 4));
var allowedExt = new List { ".zip", ".csv", ".json", ".tsv" };
if (!allowedExt.Contains(ext))
{
ext = ".custom";
}
return ext;
}
///
/// Extension method to convert strings to stream to be read.
///
/// String to convert to stream
/// Stream instance
public static Stream ToStream(this string str)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(str);
writer.Flush();
stream.Position = 0;
return stream;
}
///
/// Extension method to round a timeSpan to nearest timespan period.
///
/// TimeSpan To Round
/// Rounding Unit
/// Rounding method
/// Rounded timespan
public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval, MidpointRounding roundingType)
{
if (roundingInterval == TimeSpan.Zero)
{
// divide by zero exception
return time;
}
return new TimeSpan(
Convert.ToInt64(Math.Round(
time.Ticks / (decimal)roundingInterval.Ticks,
roundingType
)) * roundingInterval.Ticks
);
}
///
/// Extension method to round timespan to nearest timespan period.
///
/// Base timespan we're looking to round.
/// Timespan period we're rounding.
/// Rounded timespan period
public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval)
{
return Round(time, roundingInterval, MidpointRounding.ToEven);
}
///
/// Extension method to round a datetime down by a timespan interval.
///
/// Base DateTime object we're rounding down.
/// Timespan interval to round to
/// Rounded datetime
/// Using this with timespans greater than 1 day may have unintended
/// consequences. Be aware that rounding occurs against ALL time, so when using
/// timespan such as 30 days we will see 30 day increments but it will be based
/// on 30 day increments from the beginning of time.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTime RoundDown(this DateTime dateTime, TimeSpan interval)
{
if (interval == TimeSpan.Zero)
{
// divide by zero exception
return dateTime;
}
var amount = dateTime.Ticks % interval.Ticks;
if (amount > 0)
{
return dateTime.AddTicks(-amount);
}
return dateTime;
}
///
/// Rounds the specified date time in the specified time zone. Careful with calling this method in a loop while modifying dateTime, check unit tests.
///
/// Date time to be rounded
/// Timespan rounding period
/// Time zone of the date time
/// Time zone in which the rounding is performed
/// The rounded date time in the source time zone
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTime RoundDownInTimeZone(this DateTime dateTime, TimeSpan roundingInterval, DateTimeZone sourceTimeZone, DateTimeZone roundingTimeZone)
{
var dateTimeInRoundingTimeZone = dateTime.ConvertTo(sourceTimeZone, roundingTimeZone);
var roundedDateTimeInRoundingTimeZone = dateTimeInRoundingTimeZone.RoundDown(roundingInterval);
return roundedDateTimeInRoundingTimeZone.ConvertTo(roundingTimeZone, sourceTimeZone);
}
///
/// Extension method to round a datetime down by a timespan interval until it's
/// within the specified exchange's open hours. This works by first rounding down
/// the specified time using the interval, then producing a bar between that
/// rounded time and the interval plus the rounded time and incrementally walking
/// backwards until the exchange is open
///
/// Time to be rounded down
/// Timespan interval to round to.
/// The exchange hours to determine open times
/// True for extended market hours, otherwise false
/// Rounded datetime
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTime ExchangeRoundDown(this DateTime dateTime, TimeSpan interval, SecurityExchangeHours exchangeHours, bool extendedMarketHours)
{
// can't round against a zero interval
if (interval == TimeSpan.Zero) return dateTime;
var rounded = dateTime.RoundDown(interval);
while (!exchangeHours.IsOpen(rounded, rounded + interval, extendedMarketHours))
{
rounded -= interval;
}
return rounded;
}
///
/// Extension method to round a datetime down by a timespan interval until it's
/// within the specified exchange's open hours. The rounding is performed in the
/// specified time zone
///
/// Time to be rounded down
/// Timespan interval to round to.
/// The exchange hours to determine open times
/// The time zone to perform the rounding in
/// True for extended market hours, otherwise false
/// Rounded datetime
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTime ExchangeRoundDownInTimeZone(this DateTime dateTime, TimeSpan interval, SecurityExchangeHours exchangeHours, DateTimeZone roundingTimeZone, bool extendedMarketHours)
{
// can't round against a zero interval
if (interval == TimeSpan.Zero) return dateTime;
var dateTimeInRoundingTimeZone = dateTime.ConvertTo(exchangeHours.TimeZone, roundingTimeZone);
var roundedDateTimeInRoundingTimeZone = dateTimeInRoundingTimeZone.RoundDown(interval);
var rounded = roundedDateTimeInRoundingTimeZone.ConvertTo(roundingTimeZone, exchangeHours.TimeZone);
while (!exchangeHours.IsOpen(rounded, rounded + interval, extendedMarketHours))
{
// Will subtract interval to 'dateTime' in the roundingTimeZone (using the same value type instance) to avoid issues with daylight saving time changes.
// GH issue 2368: subtracting interval to 'dateTime' in exchangeHours.TimeZone and converting back to roundingTimeZone
// caused the substraction to be neutralized by daylight saving time change, which caused an infinite loop situation in this loop.
// The issue also happens if substracting in roundingTimeZone and converting back to exchangeHours.TimeZone.
dateTimeInRoundingTimeZone -= interval;
roundedDateTimeInRoundingTimeZone = dateTimeInRoundingTimeZone.RoundDown(interval);
rounded = roundedDateTimeInRoundingTimeZone.ConvertTo(roundingTimeZone, exchangeHours.TimeZone);
}
return rounded;
}
///
/// Helper method to determine if a specific market is open
///
/// The target security
/// True if should consider extended market hours
/// True if the market is open
public static bool IsMarketOpen(this Security security, bool extendedMarketHours)
{
return security.Exchange.Hours.IsOpen(security.LocalTime, extendedMarketHours);
}
///
/// Helper method to determine if a specific market is open
///
/// The target symbol
/// The current UTC time
/// True if should consider extended market hours
/// True if the market is open
public static bool IsMarketOpen(this Symbol symbol, DateTime utcTime, bool extendedMarketHours)
{
var exchangeHours = MarketHoursDatabase.FromDataFolder()
.GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
var time = utcTime.ConvertFromUtc(exchangeHours.TimeZone);
return exchangeHours.IsOpen(time, extendedMarketHours);
}
///
/// Extension method to round a datetime to the nearest unit timespan.
///
/// Datetime object we're rounding.
/// Timespan rounding period.
/// Rounded datetime
public static DateTime Round(this DateTime datetime, TimeSpan roundingInterval)
{
return new DateTime((datetime - DateTime.MinValue).Round(roundingInterval).Ticks);
}
///
/// Extension method to explicitly round up to the nearest timespan interval.
///
/// Base datetime object to round up.
/// Timespan interval to round to
/// Rounded datetime
/// Using this with timespans greater than 1 day may have unintended
/// consequences. Be aware that rounding occurs against ALL time, so when using
/// timespan such as 30 days we will see 30 day increments but it will be based
/// on 30 day increments from the beginning of time.
public static DateTime RoundUp(this DateTime time, TimeSpan interval)
{
if (interval == TimeSpan.Zero)
{
// divide by zero exception
return time;
}
return new DateTime(((time.Ticks + interval.Ticks - 1) / interval.Ticks) * interval.Ticks);
}
///
/// Converts the specified time from the time zone to the time zone
///
/// The time to be converted in terms of the time zone
/// The time zone the specified is in
/// The time zone to be converted to
/// True for strict conversion, this will throw during ambiguitities, false for lenient conversion
/// The time in terms of the to time zone
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTime ConvertTo(this DateTime time, DateTimeZone from, DateTimeZone to, bool strict = false)
{
if (strict)
{
return from.AtStrictly(LocalDateTime.FromDateTime(time)).WithZone(to).ToDateTimeUnspecified();
}
// `InZone` sets the LocalDateTime's timezone, `WithZone` is the tz the time will be converted into.
return LocalDateTime.FromDateTime(time)
.InZone(from, _mappingResolver)
.WithZone(to)
.ToDateTimeUnspecified();
}
///
/// Converts the specified time from UTC to the time zone
///
/// The time to be converted expressed in UTC
/// The destinatio time zone
/// True for strict conversion, this will throw during ambiguitities, false for lenient conversion
/// The time in terms of the time zone
public static DateTime ConvertFromUtc(this DateTime time, DateTimeZone to, bool strict = false)
{
return time.ConvertTo(TimeZones.Utc, to, strict);
}
///
/// Converts the specified time from the time zone to
///
/// The time to be converted in terms of the time zone
/// The time zone the specified is in
/// True for strict conversion, this will throw during ambiguitities, false for lenient conversion
/// The time in terms of the to time zone
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTime ConvertToUtc(this DateTime time, DateTimeZone from, bool strict = false)
{
if (strict)
{
return from.AtStrictly(LocalDateTime.FromDateTime(time)).ToDateTimeUtc();
}
// Set the local timezone with `InZone` and convert to UTC
return LocalDateTime.FromDateTime(time)
.InZone(from, _mappingResolver)
.ToDateTimeUtc();
}
///
/// Business day here is defined as any day of the week that is not saturday or sunday
///
/// The date to be examined
/// A bool indicating wether the datetime is a weekday or not
public static bool IsCommonBusinessDay(this DateTime date)
{
return (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday);
}
///
/// Add the reset method to the System.Timer class.
///
/// System.timer object
public static void Reset(this Timer timer)
{
timer.Stop();
timer.Start();
}
///
/// Function used to match a type against a string type name. This function compares on the AssemblyQualfiedName,
/// the FullName, and then just the Name of the type.
///
/// The type to test for a match
/// The name of the type to match
/// True if the specified type matches the type name, false otherwise
public static bool MatchesTypeName(this Type type, string typeName)
{
if (type.AssemblyQualifiedName == typeName)
{
return true;
}
if (type.FullName == typeName)
{
return true;
}
if (type.Name == typeName)
{
return true;
}
return false;
}
///
/// Checks the specified type to see if it is a subclass of the . This method will
/// crawl up the inheritance heirarchy to check for equality using generic type definitions (if exists)
///
/// The type to be checked as a subclass of
/// The possible superclass of
/// True if is a subclass of the generic type definition
public static bool IsSubclassOfGeneric(this Type type, Type possibleSuperType)
{
while (type != null && type != typeof(object))
{
Type cur;
if (type.IsGenericType && possibleSuperType.IsGenericTypeDefinition)
{
cur = type.GetGenericTypeDefinition();
}
else
{
cur = type;
}
if (possibleSuperType == cur)
{
return true;
}
type = type.BaseType;
}
return false;
}
///
/// Gets a type's name with the generic parameters filled in the way they would look when
/// defined in code, such as converting Dictionary<`1,`2> to Dictionary<string,int>
///
/// The type who's name we seek
/// A better type name
public static string GetBetterTypeName(this Type type)
{
string name = type.Name;
if (type.IsGenericType)
{
var genericArguments = type.GetGenericArguments();
var toBeReplaced = "`" + (genericArguments.Length);
name = name.Replace(toBeReplaced, $"<{string.Join(", ", genericArguments.Select(x => x.GetBetterTypeName()))}>");
}
return name;
}
///
/// Converts the Resolution instance into a TimeSpan instance
///
/// The resolution to be converted
/// A TimeSpan instance that represents the resolution specified
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TimeSpan ToTimeSpan(this Resolution resolution)
{
switch (resolution)
{
case Resolution.Tick:
// ticks can be instantaneous
return TimeSpan.Zero;
case Resolution.Second:
return Time.OneSecond;
case Resolution.Minute:
return Time.OneMinute;
case Resolution.Hour:
return Time.OneHour;
case Resolution.Daily:
return Time.OneDay;
default:
throw new ArgumentOutOfRangeException(nameof(resolution));
}
}
///
/// Converts the specified time span into a resolution enum value. If an exact match
/// is not found and `requireExactMatch` is false, then the higher resoluion will be
/// returned. For example, timeSpan=5min will return Minute resolution.
///
/// The time span to convert to resolution
/// True to throw an exception if an exact match is not found
/// The resolution
public static Resolution ToHigherResolutionEquivalent(this TimeSpan timeSpan, bool requireExactMatch)
{
if (requireExactMatch)
{
if (TimeSpan.Zero == timeSpan) return Resolution.Tick;
if (Time.OneSecond == timeSpan) return Resolution.Second;
if (Time.OneMinute == timeSpan) return Resolution.Minute;
if (Time.OneHour == timeSpan) return Resolution.Hour;
if (Time.OneDay == timeSpan) return Resolution.Daily;
throw new InvalidOperationException(Messages.Extensions.UnableToConvertTimeSpanToResolution(timeSpan));
}
// for non-perfect matches
if (Time.OneSecond > timeSpan) return Resolution.Tick;
if (Time.OneMinute > timeSpan) return Resolution.Second;
if (Time.OneHour > timeSpan) return Resolution.Minute;
if (Time.OneDay > timeSpan) return Resolution.Hour;
return Resolution.Daily;
}
///
/// Attempts to convert the string into a enum value
///
/// string value to convert to SecurityType
/// SecurityType output
/// Ignore casing
/// true if parsed into a SecurityType successfully, false otherwise
///
/// Logs once if we've encountered an invalid SecurityType
///
public static bool TryParseSecurityType(this string value, out SecurityType securityType, bool ignoreCase = true)
{
if (Enum.TryParse(value, ignoreCase, out securityType))
{
return true;
}
if (InvalidSecurityTypes.Add(value))
{
Log.Error($"Extensions.TryParseSecurityType(): {Messages.Extensions.UnableToParseUnknownSecurityType(value)}");
}
return false;
}
///
/// Converts the specified string value into the specified type
///
/// The output type
/// The string value to be converted
/// The converted value
public static T ConvertTo(this string value)
{
return (T) value.ConvertTo(typeof (T));
}
///
/// Converts the specified string value into the specified type
///
/// The string value to be converted
/// The output type
/// The converted value
public static object ConvertTo(this string value, Type type)
{
if (type.IsEnum)
{
return Enum.Parse(type, value, true);
}
if (typeof (IConvertible).IsAssignableFrom(type))
{
return Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
}
// try and find a static parse method
var parse = type.GetMethod("Parse", new[] {typeof (string)});
if (parse != null)
{
var result = parse.Invoke(null, new object[] {value});
return result;
}
return JsonConvert.DeserializeObject(value, type);
}
///
/// Blocks the current thread until the current receives a signal, while observing a .
///
/// The wait handle to wait on
/// The to observe.
/// The maximum number of waiters has been exceeded.
/// was canceled.
/// The object has already been disposed or the that created has been disposed.
public static bool WaitOne(this WaitHandle waitHandle, CancellationToken cancellationToken)
{
return waitHandle.WaitOne(Timeout.Infinite, cancellationToken);
}
///
/// Blocks the current thread until the current is set, using a to measure the time interval, while observing a .
///
///
///
/// true if the was set; otherwise, false.
///
/// The wait handle to wait on
/// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely.
/// The to observe.
/// was canceled.
/// is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than .
/// The maximum number of waiters has been exceeded. The object has already been disposed or the that created has been disposed.
public static bool WaitOne(this WaitHandle waitHandle, TimeSpan timeout, CancellationToken cancellationToken)
{
return waitHandle.WaitOne((int) timeout.TotalMilliseconds, cancellationToken);
}
///
/// Blocks the current thread until the current is set, using a 32-bit signed integer to measure the time interval, while observing a .
///
///
///
/// true if the was set; otherwise, false.
///
/// The wait handle to wait on
/// The number of milliseconds to wait, or (-1) to wait indefinitely.
/// The to observe.
/// was canceled.
/// is a negative number other than -1, which represents an infinite time-out.
/// The maximum number of waiters has been exceeded.
/// The object has already been disposed or the that created has been disposed.
public static bool WaitOne(this WaitHandle waitHandle, int millisecondsTimeout, CancellationToken cancellationToken)
{
return WaitHandle.WaitAny(new[] { waitHandle, cancellationToken.WaitHandle }, millisecondsTimeout) == 0;
}
///
/// Gets the MD5 hash from a stream
///
/// The stream to compute a hash for
/// The MD5 hash
public static byte[] GetMD5Hash(this Stream stream)
{
using (var md5 = MD5.Create())
{
return md5.ComputeHash(stream);
}
}
///
/// Convert a string into the same string with a URL! :)
///
/// The source string to be converted
/// The same source string but with anchor tags around substrings matching a link regex
public static string WithEmbeddedHtmlAnchors(this string source)
{
var regx = new Regex("http(s)?://([\\w+?\\.\\w+])+([a-zA-Z0-9\\~\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)_\\-\\=\\+\\\\\\/\\?\\.\\:\\;\\'\\,]*([a-zA-Z0-9\\?\\#\\=\\/]){1})?", RegexOptions.IgnoreCase);
var matches = regx.Matches(source);
foreach (Match match in matches)
{
source = source.Replace(match.Value, $"{match.Value}");
}
return source;
}
///
/// Get the first occurence of a string between two characters from another string
///
/// The original string
/// Left bound of the substring
/// Right bound of the substring
/// Substring from original string bounded by the two characters
public static string GetStringBetweenChars(this string value, char left, char right)
{
var startIndex = 1 + value.IndexOf(left);
var length = value.IndexOf(right, startIndex) - startIndex;
if (length > 0)
{
value = value.Substring(startIndex, length);
startIndex = 1 + value.IndexOf(left);
return value.Substring(startIndex).Trim();
}
return string.Empty;
}
///
/// Return the first in the series of names, or find the one that matches the configured algorithmTypeName
///
/// The list of class names
/// The configured algorithm type name from the config
/// The name of the class being run
public static string SingleOrAlgorithmTypeName(this List names, string algorithmTypeName)
{
// If there's only one name use that guy
if (names.Count == 1) { return names.Single(); }
// If we have multiple names we need to search the names based on the given algorithmTypeName
// If the given name already contains dots (fully named) use it as it is
// otherwise add a dot to the beginning to avoid matching any subsets of other names
var searchName = algorithmTypeName.Contains('.', StringComparison.InvariantCulture) ? algorithmTypeName : "." + algorithmTypeName;
return names.SingleOrDefault(x => x.EndsWith(searchName));
}
///
/// Converts the specified value to its corresponding lower-case string representation
///
/// The enumeration value
/// A lower-case string representation of the specified enumeration value
public static string ToLower(this Enum @enum)
{
return @enum.ToString().ToLowerInvariant();
}
///
/// Asserts the specified value is valid
///
/// This method provides faster performance than which uses reflection
/// The SecurityType value
/// True if valid security type value
public static bool IsValid(this SecurityType securityType)
{
switch (securityType)
{
case SecurityType.Base:
case SecurityType.Equity:
case SecurityType.Option:
case SecurityType.FutureOption:
case SecurityType.Commodity:
case SecurityType.Forex:
case SecurityType.Future:
case SecurityType.Cfd:
case SecurityType.Crypto:
case SecurityType.CryptoFuture:
case SecurityType.Index:
case SecurityType.IndexOption:
return true;
default:
return false;
}
}
///
/// Determines if the provided SecurityType is a type of Option.
/// Valid option types are: Equity Options, Futures Options, and Index Options.
///
/// The SecurityType to check if it's an option asset
///
/// true if the asset has the makings of an option (exercisable, expires, and is a derivative of some underlying),
/// false otherwise.
///
public static bool IsOption(this SecurityType securityType)
{
switch (securityType)
{
case SecurityType.Option:
case SecurityType.FutureOption:
case SecurityType.IndexOption:
return true;
default:
return false;
}
}
///
/// Determines if the provided SecurityType has a matching option SecurityType, used to represent
/// the current SecurityType as a derivative.
///
/// The SecurityType to check if it has options available
/// true if there are options for the SecurityType, false otherwise
public static bool HasOptions(this SecurityType securityType)
{
switch (securityType)
{
case SecurityType.Equity:
case SecurityType.Future:
case SecurityType.Index:
return true;
default:
return false;
}
}
///
/// Gets the default for the provided
///
/// SecurityType to get default OptionStyle for
/// Default OptionStyle for the SecurityType
/// The SecurityType has no options available for it or it is not an option
public static OptionStyle DefaultOptionStyle(this SecurityType securityType)
{
if (!securityType.HasOptions() && !securityType.IsOption())
{
throw new ArgumentException(Messages.Extensions.NoDefaultOptionStyleForSecurityType(securityType));
}
switch (securityType)
{
case SecurityType.Index:
case SecurityType.IndexOption:
return OptionStyle.European;
default:
return OptionStyle.American;
}
}
///
/// Converts the specified string to its corresponding OptionStyle
///
/// This method provides faster performance than enum parse
/// The OptionStyle string value
/// The OptionStyle value
public static OptionStyle ParseOptionStyle(this string optionStyle)
{
switch (optionStyle.LazyToLower())
{
case "american":
return OptionStyle.American;
case "european":
return OptionStyle.European;
default:
throw new ArgumentException(Messages.Extensions.UnknownOptionStyle(optionStyle));
}
}
///
/// Converts the specified string to its corresponding OptionRight
///
/// This method provides faster performance than enum parse
/// The optionRight string value
/// The OptionRight value
public static OptionRight ParseOptionRight(this string optionRight)
{
switch (optionRight.LazyToLower())
{
case "call":
return OptionRight.Call;
case "put":
return OptionRight.Put;
default:
throw new ArgumentException(Messages.Extensions.UnknownOptionRight(optionRight));
}
}
///
/// Converts the specified value to its corresponding string representation
///
/// This method provides faster performance than enum
/// The optionRight value
/// A string representation of the specified OptionRight value
public static string ToStringPerformance(this OptionRight optionRight)
{
switch (optionRight)
{
case OptionRight.Call:
return "Call";
case OptionRight.Put:
return "Put";
default:
// just in case
return optionRight.ToString();
}
}
///
/// Converts the specified value to its corresponding lower-case string representation
///
/// This method provides faster performance than
/// The optionRight value
/// A lower case string representation of the specified OptionRight value
public static string OptionRightToLower(this OptionRight optionRight)
{
switch (optionRight)
{
case OptionRight.Call:
return "call";
case OptionRight.Put:
return "put";
default:
throw new ArgumentException(Messages.Extensions.UnknownOptionRight(optionRight));
}
}
///
/// Converts the specified value to its corresponding lower-case string representation
///
/// This method provides faster performance than
/// The optionStyle value
/// A lower case string representation of the specified optionStyle value
public static string OptionStyleToLower(this OptionStyle optionStyle)
{
switch (optionStyle)
{
case OptionStyle.American:
return "american";
case OptionStyle.European:
return "european";
default:
throw new ArgumentException(Messages.Extensions.UnknownOptionStyle(optionStyle));
}
}
///
/// Converts the specified string to its corresponding DataMappingMode
///
/// This method provides faster performance than enum parse
/// The dataMappingMode string value
/// The DataMappingMode value
public static DataMappingMode? ParseDataMappingMode(this string dataMappingMode)
{
if (string.IsNullOrEmpty(dataMappingMode))
{
return null;
}
switch (dataMappingMode.LazyToLower())
{
case "0":
case "lasttradingday":
return DataMappingMode.LastTradingDay;
case "1":
case "firstdaymonth":
return DataMappingMode.FirstDayMonth;
case "2":
case "openinterest":
return DataMappingMode.OpenInterest;
case "3":
case "openinterestannual":
return DataMappingMode.OpenInterestAnnual;
default:
throw new ArgumentException(Messages.Extensions.UnknownDataMappingMode(dataMappingMode));
}
}
///
/// Converts the specified value to its corresponding lower-case string representation
///
/// This method provides faster performance than
/// The SecurityType value
/// A lower-case string representation of the specified SecurityType value
public static string SecurityTypeToLower(this SecurityType securityType)
{
switch (securityType)
{
case SecurityType.Base:
return "base";
case SecurityType.Equity:
return "equity";
case SecurityType.Option:
return "option";
case SecurityType.FutureOption:
return "futureoption";
case SecurityType.IndexOption:
return "indexoption";
case SecurityType.Commodity:
return "commodity";
case SecurityType.Forex:
return "forex";
case SecurityType.Future:
return "future";
case SecurityType.Index:
return "index";
case SecurityType.Cfd:
return "cfd";
case SecurityType.Crypto:
return "crypto";
case SecurityType.CryptoFuture:
return "cryptofuture";
default:
// just in case
return securityType.ToLower();
}
}
///
/// Converts the specified value to its corresponding lower-case string representation
///
/// This method provides faster performance than
/// The tickType value
/// A lower-case string representation of the specified tickType value
public static string TickTypeToLower(this TickType tickType)
{
switch (tickType)
{
case TickType.Trade:
return "trade";
case TickType.Quote:
return "quote";
case TickType.OpenInterest:
return "openinterest";
default:
// just in case
return tickType.ToLower();
}
}
///
/// Converts the specified value to its corresponding lower-case string representation
///
/// This method provides faster performance than
/// The resolution value
/// A lower-case string representation of the specified resolution value
public static string ResolutionToLower(this Resolution resolution)
{
switch (resolution)
{
case Resolution.Tick:
return "tick";
case Resolution.Second:
return "second";
case Resolution.Minute:
return "minute";
case Resolution.Hour:
return "hour";
case Resolution.Daily:
return "daily";
default:
// just in case
return resolution.ToLower();
}
}
///
/// Turn order into an order ticket
///
/// The being converted
/// The transaction manager,
///
public static OrderTicket ToOrderTicket(this Order order, SecurityTransactionManager transactionManager)
{
var limitPrice = 0m;
var stopPrice = 0m;
var triggerPrice = 0m;
var trailingAmount = 0m;
var trailingAsPercentage = false;
switch (order.Type)
{
case OrderType.Limit:
var limitOrder = order as LimitOrder;
limitPrice = limitOrder.LimitPrice;
break;
case OrderType.StopMarket:
var stopMarketOrder = order as StopMarketOrder;
stopPrice = stopMarketOrder.StopPrice;
break;
case OrderType.StopLimit:
var stopLimitOrder = order as StopLimitOrder;
stopPrice = stopLimitOrder.StopPrice;
limitPrice = stopLimitOrder.LimitPrice;
break;
case OrderType.TrailingStop:
var trailingStopOrder = order as TrailingStopOrder;
stopPrice = trailingStopOrder.StopPrice;
trailingAmount = trailingStopOrder.TrailingAmount;
trailingAsPercentage = trailingStopOrder.TrailingAsPercentage;
break;
case OrderType.LimitIfTouched:
var limitIfTouched = order as LimitIfTouchedOrder;
triggerPrice = limitIfTouched.TriggerPrice;
limitPrice = limitIfTouched.LimitPrice;
break;
case OrderType.OptionExercise:
case OrderType.Market:
case OrderType.MarketOnOpen:
case OrderType.MarketOnClose:
case OrderType.ComboMarket:
limitPrice = order.Price;
stopPrice = order.Price;
break;
case OrderType.ComboLimit:
limitPrice = order.GroupOrderManager.LimitPrice;
break;
case OrderType.ComboLegLimit:
var legLimitOrder = order as ComboLegLimitOrder;
limitPrice = legLimitOrder.LimitPrice;
break;
default:
throw new ArgumentOutOfRangeException();
}
var submitOrderRequest = new SubmitOrderRequest(order.Type,
order.SecurityType,
order.Symbol,
order.Quantity,
stopPrice,
limitPrice,
triggerPrice,
trailingAmount,
trailingAsPercentage,
order.Time,
order.Tag,
order.Properties,
order.GroupOrderManager);
submitOrderRequest.SetOrderId(order.Id);
var orderTicket = new OrderTicket(transactionManager, submitOrderRequest);
orderTicket.SetOrder(order);
return orderTicket;
}
///
/// Process all items in collection through given handler
///
///
/// Collection to process
/// Handler to process those items with
public static void ProcessUntilEmpty(this IProducerConsumerCollection collection, Action handler)
{
T item;
while (collection.TryTake(out item))
{
handler(item);
}
}
///
/// Returns a that represents the current
///
/// The being converted
/// string that represents the current PyObject
public static string ToSafeString(this PyObject pyObject)
{
using (Py.GIL())
{
var value = "";
// PyObject objects that have the to_string method, like some pandas objects,
// can use this method to convert them into string objects
if (pyObject.HasAttr("to_string"))
{
var pyValue = pyObject.InvokeMethod("to_string");
value = Environment.NewLine + pyValue;
pyValue.Dispose();
}
else
{
value = pyObject.ToString();
if (string.IsNullOrWhiteSpace(value))
{
var pythonType = pyObject.GetPythonType();
if (pythonType.GetType() == typeof(PyObject))
{
value = pythonType.ToString();
}
else
{
var type = pythonType.As();
value = pyObject.AsManagedObject(type).ToString();
}
pythonType.Dispose();
}
}
return value;
}
}
///
/// Tries to convert a into a managed object
///
/// This method is not working correctly for a wrapped instance,
/// probably because it is a struct, using is a valid work around.
/// Not used here because it caused errors
///
/// Target type of the resulting managed object
/// PyObject to be converted
/// Managed object
/// True will convert python subclasses of T
/// True if successful conversion
public static bool TryConvert(this PyObject pyObject, out T result, bool allowPythonDerivative = false)
{
result = default(T);
var type = typeof(T);
if (pyObject == null)
{
return true;
}
using (Py.GIL())
{
try
{
// We must first check if allowPythonDerivative is true to then only return true
// when the PyObject is assignable from Type or IEnumerable and is a C# type
// wrapped in PyObject
if (allowPythonDerivative)
{
result = (T)pyObject.AsManagedObject(type);
return true;
}
// Special case: Type
if (typeof(Type).IsAssignableFrom(type))
{
result = (T)pyObject.AsManagedObject(type);
// pyObject is a C# object wrapped in PyObject, in this case return true
if(!pyObject.HasAttr("__name__"))
{
return true;
}
// Otherwise, pyObject is a python object that subclass a C# class, only return true if 'allowPythonDerivative'
var castedResult = (Type)pyObject.AsManagedObject(type);
var pythonName = pyObject.GetAttr("__name__").GetAndDispose();
return pythonName == castedResult.Name;
}
// Special case: IEnumerable
if (typeof(IEnumerable).IsAssignableFrom(type))
{
result = (T)pyObject.AsManagedObject(type);
return true;
}
using var pythonType = pyObject.GetPythonType();
var csharpType = pythonType.As();
if (!type.IsAssignableFrom(csharpType))
{
return false;
}
result = (T)pyObject.AsManagedObject(type);
// The PyObject is a Python object of a Python class that is a subclass of a C# class.
// In this case, we return false just because we want the actual Python object
// so it gets wrapped in a python wrapper, not the C# object.
if (result is IPythonDerivedType)
{
return false;
}
// If the python type object is just a representation of the C# type, the conversion is direct,
// the python object is an instance of the C# class.
// We can compare by reference because pythonnet caches the PyTypes and because the behavior of
// PyObject.Equals is not exactly what we want:
// e.g. type(class PyClass(CSharpClass)) == type(CSharpClass) is true in Python
if (PythonReferenceComparer.Instance.Equals(PyType.Get(csharpType), pythonType))
{
return true;
}
// If the PyObject type and the managed object names are the same,
// pyObject is a C# object wrapped in PyObject, in this case return true
// Otherwise, pyObject is a python object that subclass a C# class, only return true if 'allowPythonDerivative'
var name = (((dynamic)pythonType).__name__ as PyObject).GetAndDispose();
return name == result.GetType().Name;
}
catch
{
// Do not throw or log the exception.
// Return false as an exception means that the conversion could not be made.
}
}
return false;
}
///
/// Tries to convert a into a managed object
///
/// Target type of the resulting managed object
/// PyObject to be converted
/// Managed object
/// True if successful conversion
public static bool TryConvertToDelegate(this PyObject pyObject, out T result)
{
var type = typeof(T);
// The PyObject is a C# object wrapped
if (TryConvert(pyObject, out result))
{
return true;
}
if (!typeof(MulticastDelegate).IsAssignableFrom(type))
{
throw new ArgumentException(Messages.Extensions.ConvertToDelegateCannotConverPyObjectToType("TryConvertToDelegate", type));
}
result = default(T);
if (pyObject == null)
{
return true;
}
var code = string.Empty;
var types = type.GetGenericArguments();
using (Py.GIL())
{
var locals = new PyDict();
try
{
for (var i = 0; i < types.Length; i++)
{
var iString = i.ToStringInvariant();
code += $",t{iString}";
locals.SetItem($"t{iString}", types[i].ToPython());
}
locals.SetItem("pyObject", pyObject);
var name = type.FullName.Substring(0, type.FullName.IndexOf('`'));
code = $"import System; delegate = {name}[{code.Substring(1)}](pyObject)";
PythonEngine.Exec(code, null, locals);
result = (T)locals.GetItem("delegate").AsManagedObject(typeof(T));
locals.Dispose();
return true;
}
catch
{
// Do not throw or log the exception.
// Return false as an exception means that the conversion could not be made.
}
locals.Dispose();
}
return false;
}
///
/// Safely convert PyObject to ManagedObject using Py.GIL Lock
/// If no type is given it will convert the PyObject's Python Type to a ManagedObject Type
/// in a attempt to resolve the target type to convert to.
///
/// PyObject to convert to managed
/// The target type to convert to
/// The resulting ManagedObject
public static dynamic SafeAsManagedObject(this PyObject pyObject, Type typeToConvertTo = null)
{
using (Py.GIL())
{
if (typeToConvertTo == null)
{
typeToConvertTo = pyObject.GetPythonType().AsManagedObject(typeof(Type)) as Type;
}
return pyObject.AsManagedObject(typeToConvertTo);
}
}
///
/// Converts a Python function to a managed function returning a Symbol
///
/// Universe filter function from Python
/// Function that provides and returns an enumerable of Symbols
public static Func, IEnumerable> ConvertPythonUniverseFilterFunction(this PyObject universeFilterFunc) where T : BaseData
{
Func, object> convertedFunc;
Func, IEnumerable> filterFunc = null;
if (universeFilterFunc != null && universeFilterFunc.TryConvertToDelegate(out convertedFunc))
{
filterFunc = convertedFunc.ConvertToUniverseSelectionSymbolDelegate();
}
return filterFunc;
}
///
/// Wraps the provided universe selection selector checking if it returned
/// and returns it instead, else enumerates result as
///
/// This method is a work around for the fact that currently we can not create a delegate which returns
/// an from a python method returning an array, plus the fact that
/// can not be cast to an array
public static Func, IEnumerable> ConvertToUniverseSelectionSymbolDelegate(this Func, object> selector) where T : BaseData
{
if (selector == null)
{
return (dataPoints) => dataPoints.Select(x => x.Symbol);
}
return selector.ConvertSelectionSymbolDelegate();
}
///
/// Wraps the provided universe selection selector checking if it returned
/// and returns it instead, else enumerates result as
///
/// This method is a work around for the fact that currently we can not create a delegate which returns
/// an from a python method returning an array, plus the fact that
/// can not be cast to an array
public static Func> ConvertSelectionSymbolDelegate(this Func selector)
{
return data =>
{
var result = selector(data);
return ReferenceEquals(result, Universe.Unchanged)
? Universe.Unchanged
: ((object[])result).Select(x =>
{
if (x is Symbol castedSymbol)
{
return castedSymbol;
}
return SymbolCache.TryGetSymbol((string)x, out var symbol) ? symbol : null;
});
};
}
///
/// Wraps the provided universe selection selector checking if it returned
/// and returns it instead, else enumerates result as
///
/// This method is a work around for the fact that currently we can not create a delegate which returns
/// an from a python method returning an array, plus the fact that
/// can not be cast to an array
public static Func> ConvertToUniverseSelectionStringDelegate(this Func selector)
{
return data =>
{
var result = selector(data);
return ReferenceEquals(result, Universe.Unchanged)
? Universe.Unchanged : ((object[])result).Select(x => (string)x);
};
}
///
/// Convert a into a managed object
///
/// Target type of the resulting managed object
/// PyObject to be converted
/// Instance of type T
public static T ConvertToDelegate(this PyObject pyObject)
{
T result;
if (pyObject.TryConvertToDelegate(out result))
{
return result;
}
else
{
throw new ArgumentException(Messages.Extensions.ConvertToDelegateCannotConverPyObjectToType("ConvertToDelegate", typeof(T)));
}
}
///
/// Convert a into a managed dictionary
///
/// Target type of the resulting dictionary key
/// Target type of the resulting dictionary value
/// PyObject to be converted
/// Dictionary of TValue keyed by TKey
public static Dictionary ConvertToDictionary(this PyObject pyObject)
{
var result = new List>();
using (Py.GIL())
{
var inputType = pyObject.GetPythonType().ToString();
var targetType = nameof(PyDict);
try
{
using (var pyDict = new PyDict(pyObject))
{
targetType = $"{typeof(TKey).Name}: {typeof(TValue).Name}";
foreach (PyObject item in pyDict.Items())
{
inputType = $"{item[0].GetPythonType()}: {item[1].GetPythonType()}";
var key = item[0].As();
var value = item[1].As();
result.Add(new KeyValuePair(key, value));
}
}
}
catch (Exception e)
{
throw new ArgumentException(Messages.Extensions.ConvertToDictionaryFailed(inputType, targetType, e.Message), e);
}
}
return result.ToDictionary();
}
///
/// Gets Enumerable of from a PyObject
///
/// PyObject containing Symbol or Array of Symbol
/// Enumerable of Symbol
public static IEnumerable ConvertToSymbolEnumerable(this PyObject pyObject)
{
using (Py.GIL())
{
Exception exception = null;
if (!PyList.IsListType(pyObject))
{
// it's not a pylist try to conver directly
Symbol result = null;
try
{
// we shouldn't dispose of an object we haven't created
result = ConvertToSymbol(pyObject, dispose: false);
}
catch (Exception ex)
{
exception = ex;
}
if (result != null)
{
// happy case
yield return result;
}
}
else
{
using var iterator = pyObject.GetIterator();
foreach (PyObject item in iterator)
{
Symbol result;
try
{
result = ConvertToSymbol(item, dispose: true);
}
catch (Exception ex)
{
exception = ex;
break;
}
yield return result;
}
}
// let's give it once last try, relying on pythonnet internal conversions, else throw
if (exception != null)
{
if (pyObject.TryConvert(out IEnumerable symbols))
{
foreach (var symbol in symbols)
{
yield return symbol;
}
}
else
{
throw exception;
}
}
}
}
///
/// Converts an IEnumerable to a PyList
///
/// IEnumerable object to convert
/// PyList
public static PyList ToPyList(this IEnumerable enumerable)
{
using (Py.GIL())
{
return enumerable.ToPyListUnSafe();
}
}
///
/// Converts an IEnumerable to a PyList
///
/// IEnumerable object to convert
/// Requires the caller to own the GIL
/// PyList
public static PyList ToPyListUnSafe(this IEnumerable enumerable)
{
var pyList = new PyList();
foreach (var item in enumerable)
{
using (var pyObject = item.ToPython())
{
pyList.Append(pyObject);
}
}
return pyList;
}
///
/// Gets the from a that represents a C# type.
/// It throws an if the is not a C# type.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Type GetType(PyObject pyObject)
{
if (pyObject.TryConvert(out Type type))
{
return type;
}
using (Py.GIL())
{
throw new ArgumentException($"GetType(): {Messages.Extensions.ObjectFromPythonIsNotACSharpType(pyObject.Repr())}");
}
}
///
/// Converts the numeric value of one or more enumerated constants to an equivalent enumerated string.
///
/// Numeric value
/// Python object that encapsulated a Enum Type
/// String that represents the enumerated object
[Obsolete("Deprecated as of 2025-07. Please use `str()`.")]
public static string GetEnumString(this int value, PyObject pyObject)
{
var type = GetType(pyObject);
return value.ToStringInvariant().ConvertTo(type).ToString();
}
///
/// Converts the numeric value of one or more enumerated constants to an equivalent enumerated string.
///
/// Numeric value
/// Python object that encapsulated a Enum Type
/// String that represents the enumerated object
[Obsolete("Deprecated as of 2025-07. Please use `str()`.")]
public static string GetEnumString(this Enum value, PyObject pyObject)
{
var type = GetType(pyObject);
return value.ToString();
}
///
/// Try to create a type with a given name, if PyObject is not a CLR type. Otherwise, convert it.
///
/// Python object representing a type.
/// Type object
/// True if was able to create the type
public static bool TryCreateType(this PyObject pyObject, out Type type)
{
if (pyObject.TryConvert(out type))
{
// handles pure C# types
return true;
}
if (!PythonActivators.TryGetValue(pyObject.Handle, out var pythonType))
{
// Some examples:
// pytype: ""
// method: "]"
if (pyObject.ToString().StartsWith("
/// Creates a type with a given name, if PyObject is not a CLR type. Otherwise, convert it.
///
/// Python object representing a type.
/// Type object
public static Type CreateType(this PyObject pyObject)
{
Type type;
if (pyObject.TryConvert(out type))
{
return type;
}
PythonActivator pythonType;
if (!PythonActivators.TryGetValue(pyObject.Handle, out pythonType))
{
var assemblyName = pyObject.GetAssemblyName();
var typeBuilder = AssemblyBuilder
.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run)
.DefineDynamicModule("MainModule")
// creating the type as public is required to allow 'dynamic' to be able to bind at runtime
.DefineType(assemblyName.Name, TypeAttributes.Class | TypeAttributes.Public, type);
pythonType = new PythonActivator(typeBuilder.CreateType(), pyObject);
ObjectActivator.AddActivator(pythonType.Type, pythonType.Factory);
// Save to prevent future additions
PythonActivators.Add(pyObject.Handle, pythonType);
}
return pythonType.Type;
}
///
/// Helper method to get the assembly name from a python type
///
/// Python object pointing to the python type.
/// The python type assembly name
public static AssemblyName GetAssemblyName(this PyObject pyObject)
{
using (Py.GIL())
{
return new AssemblyName(pyObject.Repr().Split('\'')[1]);
}
}
///
/// Performs on-line batching of the specified enumerator, emitting chunks of the requested batch size
///
/// The enumerable item type
/// The enumerable to be batched
/// The number of items per batch
/// An enumerable of lists
public static IEnumerable> BatchBy(this IEnumerable enumerable, int batchSize)
{
using (var enumerator = enumerable.GetEnumerator())
{
List list = null;
while (enumerator.MoveNext())
{
if (list == null)
{
list = new List {enumerator.Current};
}
else if (list.Count < batchSize)
{
list.Add(enumerator.Current);
}
else
{
yield return list;
list = new List {enumerator.Current};
}
}
if (list?.Count > 0)
{
yield return list;
}
}
}
///
/// Safely blocks until the specified task has completed executing
///
/// The task's result type
/// The task to be awaited
/// The result of the task
public static TResult SynchronouslyAwaitTaskResult(this Task task)
{
return task.ConfigureAwait(false).GetAwaiter().GetResult();
}
///
/// Safely blocks until the specified task has completed executing
///
/// The task to be awaited
/// The result of the task
public static void SynchronouslyAwaitTask(this Task task)
{
task.ConfigureAwait(false).GetAwaiter().GetResult();
}
///
/// Safely blocks until the specified task has completed executing
///
/// The task to be awaited
/// The result of the task
public static T SynchronouslyAwaitTask(this Task task)
{
return SynchronouslyAwaitTaskResult(task);
}
///
/// Safely blocks until the specified task has completed executing
///
/// The task to be awaited
/// The result of the task
public static void SynchronouslyAwaitTask(this ValueTask task)
{
if (task.IsCompleted)
{
return;
}
task.ConfigureAwait(false).GetAwaiter().GetResult();
}
///
/// Safely blocks until the specified task has completed executing
///
/// The task to be awaited
/// The result of the task
public static T SynchronouslyAwaitTask(this ValueTask task)
{
if (task.IsCompleted)
{
return task.Result;
}
return task.ConfigureAwait(false).GetAwaiter().GetResult();
}
///
/// Convert dictionary to query string
///
///
///
public static string ToQueryString(this IDictionary pairs)
{
return string.Join("&", pairs.Select(pair => $"{pair.Key}={pair.Value}"));
}
///
/// Returns a new string in which specified ending in the current instance is removed.
///
/// original string value
/// the string to be removed
///
public static string RemoveFromEnd(this string s, string ending)
{
if (s.EndsWith(ending, StringComparison.InvariantCulture))
{
return s.Substring(0, s.Length - ending.Length);
}
else
{
return s;
}
}
///
/// Returns a new string in which specified start in the current instance is removed.
///
/// original string value
/// the string to be removed
/// Substring with start removed
public static string RemoveFromStart(this string s, string start)
{
if (!string.IsNullOrEmpty(s) && !string.IsNullOrEmpty(start) && s.StartsWith(start, StringComparison.InvariantCulture))
{
return s.Substring(start.Length);
}
else
{
return s;
}
}
///
/// Helper method to determine symbol for a live subscription
///
/// Useful for continuous futures where we subscribe to the underlying
public static bool TryGetLiveSubscriptionSymbol(this Symbol symbol, out Symbol mapped)
{
mapped = null;
if (symbol.SecurityType == SecurityType.Future && symbol.IsCanonical() && symbol.HasUnderlying)
{
mapped = symbol.Underlying;
return true;
}
return false;
}
///
/// Gets the delisting date for the provided Symbol
///
/// The symbol to lookup the last trading date
/// Map file to use for delisting date. Defaults to SID.DefaultDate if no value is passed and is equity.
///
public static DateTime GetDelistingDate(this Symbol symbol, MapFile mapFile = null)
{
if (symbol.IsCanonical())
{
return Time.EndOfTime;
}
switch (symbol.ID.SecurityType)
{
case SecurityType.Option:
return OptionSymbol.GetLastDayOfTrading(symbol);
case SecurityType.FutureOption:
return FutureOptionSymbol.GetLastDayOfTrading(symbol);
case SecurityType.Future:
case SecurityType.IndexOption:
return symbol.ID.Date;
default:
return mapFile?.DelistingDate ?? Time.EndOfTime;
}
}
///
/// Helper method to determine if a given symbol is of custom data
///
public static bool IsCustomDataType(this Symbol symbol)
{
return symbol.SecurityType == SecurityType.Base
&& SecurityIdentifier.TryGetCustomDataType(symbol.ID.Symbol, out var type)
&& type.Equals(typeof(T).Name, StringComparison.InvariantCultureIgnoreCase);
}
///
/// Helper method that will return a back month, with future expiration, future contract based on the given offset
///
/// The none canonical future symbol
/// The quantity of contracts to move into the future expiration chain
/// A new future expiration symbol instance
public static Symbol AdjustSymbolByOffset(this Symbol symbol, uint offset)
{
if (symbol.SecurityType != SecurityType.Future || symbol.IsCanonical())
{
throw new InvalidOperationException(Messages.Extensions.ErrorAdjustingSymbolByOffset);
}
var expiration = symbol.ID.Date;
for (var i = 0; i < offset; i++)
{
var expiryFunction = FuturesExpiryFunctions.FuturesExpiryFunction(symbol);
DateTime newExpiration;
// for the current expiration we add a month to get the next one
var monthOffset = 0;
do
{
monthOffset++;
newExpiration = expiryFunction(expiration.AddMonths(monthOffset)).Date;
} while (newExpiration <= expiration);
expiration = newExpiration;
symbol = Symbol.CreateFuture(symbol.ID.Symbol, symbol.ID.Market, newExpiration);
}
return symbol;
}
///
/// Helper method to unsubscribe a given configuration, handling any required mapping
///
public static void UnsubscribeWithMapping(this IDataQueueHandler dataQueueHandler, SubscriptionDataConfig dataConfig)
{
if (dataConfig.Symbol.TryGetLiveSubscriptionSymbol(out var mappedSymbol))
{
dataConfig = new SubscriptionDataConfig(dataConfig, symbol: mappedSymbol, mappedConfig: true);
}
dataQueueHandler.Unsubscribe(dataConfig);
}
///
/// Helper method to subscribe a given configuration, handling any required mapping
///
public static IEnumerator SubscribeWithMapping(this IDataQueueHandler dataQueueHandler,
SubscriptionDataConfig dataConfig,
EventHandler newDataAvailableHandler,
Func isExpired,
out SubscriptionDataConfig subscribedConfig)
{
subscribedConfig = dataConfig;
if (dataConfig.Symbol.TryGetLiveSubscriptionSymbol(out var mappedSymbol))
{
subscribedConfig = new SubscriptionDataConfig(dataConfig, symbol: mappedSymbol, mappedConfig: true);
}
// during warmup we might get requested to add some asset which has already expired in which case the live enumerator will be empty
IEnumerator result = null;
if (!isExpired(subscribedConfig))
{
result = dataQueueHandler.Subscribe(subscribedConfig, newDataAvailableHandler);
}
else
{
Log.Trace($"SubscribeWithMapping(): skip live subscription for expired asset {subscribedConfig}");
}
return result ?? Enumerable.Empty().GetEnumerator();
}
///
/// Helper method to stream read lines from a file
///
/// The data provider to use
/// The file path to read from
/// Enumeration of lines in file
public static IEnumerable ReadLines(this IDataProvider dataProvider, string file)
{
if(dataProvider == null)
{
throw new ArgumentException(Messages.Extensions.NullDataProvider);
}
var stream = dataProvider.Fetch(file);
if (stream == null)
{
yield break;
}
using (var streamReader = new StreamReader(stream))
{
string line;
do
{
line = streamReader.ReadLine();
if (line != null)
{
yield return line;
}
}
while (line != null);
}
}
///
/// Scale data based on factor function
///
/// Data to Adjust
/// Function to factor prices by
/// Factor to multiply volume/askSize/bidSize/quantity by
/// Price scale
/// The current dividend sum
/// Volume values are rounded to the nearest integer, lot size purposefully not considered
/// as scaling only applies to equities
public static BaseData Scale(this BaseData data, Func factorFunc, decimal volumeFactor, decimal factor, decimal sumOfDividends)
{
switch (data.DataType)
{
case MarketDataType.TradeBar:
var tradeBar = data as TradeBar;
if (tradeBar != null)
{
tradeBar.Open = factorFunc(tradeBar.Open, factor, sumOfDividends);
tradeBar.High = factorFunc(tradeBar.High, factor, sumOfDividends);
tradeBar.Low = factorFunc(tradeBar.Low, factor, sumOfDividends);
tradeBar.Close = factorFunc(tradeBar.Close, factor, sumOfDividends);
tradeBar.Volume = Math.Round(tradeBar.Volume * volumeFactor);
}
break;
case MarketDataType.Tick:
var securityType = data.Symbol.SecurityType;
if (securityType != SecurityType.Equity &&
securityType != SecurityType.Future &&
!securityType.IsOption())
{
break;
}
var tick = data as Tick;
if (tick == null || tick.TickType == TickType.OpenInterest)
{
break;
}
if (tick.TickType == TickType.Trade)
{
tick.Value = factorFunc(tick.Value, factor, sumOfDividends);
tick.Quantity = Math.Round(tick.Quantity * volumeFactor);
break;
}
tick.BidPrice = tick.BidPrice != 0 ? factorFunc(tick.BidPrice, factor, sumOfDividends) : 0;
tick.BidSize = Math.Round(tick.BidSize * volumeFactor);
tick.AskPrice = tick.AskPrice != 0 ? factorFunc(tick.AskPrice, factor, sumOfDividends) : 0;
tick.AskSize = Math.Round(tick.AskSize * volumeFactor);
if (tick.BidPrice == 0)
{
tick.Value = tick.AskPrice;
break;
}
if (tick.AskPrice == 0)
{
tick.Value = tick.BidPrice;
break;
}
tick.Value = (tick.BidPrice + tick.AskPrice) / 2m;
break;
case MarketDataType.QuoteBar:
var quoteBar = data as QuoteBar;
if (quoteBar != null)
{
if (quoteBar.Ask != null)
{
quoteBar.Ask.Open = factorFunc(quoteBar.Ask.Open, factor, sumOfDividends);
quoteBar.Ask.High = factorFunc(quoteBar.Ask.High, factor, sumOfDividends);
quoteBar.Ask.Low = factorFunc(quoteBar.Ask.Low, factor, sumOfDividends);
quoteBar.Ask.Close = factorFunc(quoteBar.Ask.Close, factor, sumOfDividends);
}
if (quoteBar.Bid != null)
{
quoteBar.Bid.Open = factorFunc(quoteBar.Bid.Open, factor, sumOfDividends);
quoteBar.Bid.High = factorFunc(quoteBar.Bid.High, factor, sumOfDividends);
quoteBar.Bid.Low = factorFunc(quoteBar.Bid.Low, factor, sumOfDividends);
quoteBar.Bid.Close = factorFunc(quoteBar.Bid.Close, factor, sumOfDividends);
}
quoteBar.Value = quoteBar.Close;
quoteBar.LastAskSize = Math.Round(quoteBar.LastAskSize * volumeFactor);
quoteBar.LastBidSize = Math.Round(quoteBar.LastBidSize * volumeFactor);
}
break;
case MarketDataType.Auxiliary:
case MarketDataType.Base:
case MarketDataType.OptionChain:
case MarketDataType.FuturesChain:
break;
default:
throw new ArgumentOutOfRangeException();
}
return data;
}
///
/// Normalize prices based on configuration
///
/// Data to be normalized
/// Price scale
/// The price scaling normalization mode
/// The current dividend sum
/// The provided data point adjusted
public static BaseData Normalize(this BaseData data, decimal factor, DataNormalizationMode normalizationMode, decimal sumOfDividends)
{
switch (normalizationMode)
{
case DataNormalizationMode.Adjusted:
case DataNormalizationMode.SplitAdjusted:
case DataNormalizationMode.ScaledRaw:
return data?.Scale(TimesFactor, 1 / factor, factor, decimal.Zero);
case DataNormalizationMode.TotalReturn:
return data.Scale(TimesFactor, 1 / factor, factor, sumOfDividends);
case DataNormalizationMode.BackwardsRatio:
return data.Scale(TimesFactor, 1, factor, decimal.Zero);
case DataNormalizationMode.BackwardsPanamaCanal:
return data.Scale(AdditionFactor, 1, factor, decimal.Zero);
case DataNormalizationMode.ForwardPanamaCanal:
return data.Scale(AdditionFactor, 1, factor, decimal.Zero);
case DataNormalizationMode.Raw:
default:
return data;
}
}
///
/// Applies a times factor. We define this so we don't need to create it constantly
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static decimal TimesFactor(decimal target, decimal factor, decimal sumOfDividends)
{
return target * factor + sumOfDividends;
}
///
/// Applies an addition factor. We define this so we don't need to create it constantly
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static decimal AdditionFactor(decimal target, decimal factor, decimal _)
{
return target + factor;
}
///
/// Helper method to determine if price scales need an update based on the given data point
///
public static DateTime GetUpdatePriceScaleFrontier(this BaseData data)
{
if (data != null)
{
var priceScaleFrontier = data.Time;
if (data.Time.Date != data.EndTime.Date && data.EndTime.TimeOfDay > TimeSpan.Zero)
{
// if the data point goes from one day to another after midnight we use EndTime, this is due to differences between 'data' and 'exchage' time zone,
// for example: NYMEX future CL 'data' TZ is UTC while 'exchange' TZ is NY, so daily bars go from 8PM 'X day' to 8PM 'X+1 day'. Note that the data
// in the daily bar itself is filtered by exchange open, so it has data from 09:30 'X+1 day' to 17:00 'X+1 day' as expected.
// A potential solution to avoid the need of this check is to adjust the daily data time zone to match the exchange time zone, following this example above
// the daily bar would go from midnight X+1 day to midnight X+2
// TODO: see related issue https://github.com/QuantConnect/Lean/issues/6964 which would avoid the need for this
priceScaleFrontier = data.EndTime;
}
return priceScaleFrontier;
}
return DateTime.MinValue;
}
///
/// Thread safe concurrent dictionary order by implementation by using
///
/// See https://stackoverflow.com/questions/47630824/is-c-sharp-linq-orderby-threadsafe-when-used-with-concurrentdictionarytkey-tva
public static IOrderedEnumerable> OrderBySafe(
this ConcurrentDictionary source, Func, TSource> keySelector
)
{
return source.SafeEnumeration().OrderBy(keySelector);
}
///
/// Thread safe concurrent dictionary order by implementation by using
///
/// See https://stackoverflow.com/questions/47630824/is-c-sharp-linq-orderby-threadsafe-when-used-with-concurrentdictionarytkey-tva
public static IOrderedEnumerable> OrderBySafe(
this ConcurrentDictionary source, Func, TKey> keySelector
)
{
return source.SafeEnumeration().OrderBy(keySelector);
}
///
/// Force concurrent dictionary enumeration using a thread safe implementation
///
/// See https://stackoverflow.com/questions/47630824/is-c-sharp-linq-orderby-threadsafe-when-used-with-concurrentdictionarytkey-tva
public static IEnumerable> SafeEnumeration(
this ConcurrentDictionary source)
{
foreach (var kvp in source)
{
yield return kvp;
}
}
///
/// Helper method to determine the right data mapping mode to use by default
///
public static DataMappingMode GetUniverseNormalizationModeOrDefault(this UniverseSettings universeSettings, SecurityType securityType, string market)
{
switch (securityType)
{
case SecurityType.Future:
if ((universeSettings.DataMappingMode == DataMappingMode.OpenInterest
|| universeSettings.DataMappingMode == DataMappingMode.OpenInterestAnnual)
&& (market == Market.HKFE || market == Market.EUREX || market == Market.ICE))
{
// circle around default OI for currently no OI available data
return DataMappingMode.LastTradingDay;
}
return universeSettings.DataMappingMode;
default:
return universeSettings.DataMappingMode;
}
}
///
/// Helper method to determine the right data normalization mode to use by default
///
public static DataNormalizationMode GetUniverseNormalizationModeOrDefault(this UniverseSettings universeSettings, SecurityType securityType)
{
switch (securityType)
{
case SecurityType.Future:
if (universeSettings.DataNormalizationMode is DataNormalizationMode.BackwardsRatio
or DataNormalizationMode.BackwardsPanamaCanal or DataNormalizationMode.ForwardPanamaCanal
or DataNormalizationMode.Raw)
{
return universeSettings.DataNormalizationMode;
}
return DataNormalizationMode.BackwardsRatio;
default:
return universeSettings.DataNormalizationMode;
}
}
///
/// Returns a hex string of the byte array.
///
/// the byte array to be represented as string
/// A new string containing the items in the enumerable
public static string ToHexString(this byte[] source)
{
if (source == null || source.Length == 0)
{
throw new ArgumentException(Messages.Extensions.NullOrEmptySourceToConvertToHexString);
}
var hex = new StringBuilder(source.Length * 2);
foreach (var b in source)
{
hex.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", b);
}
return hex.ToString();
}
///
/// Gets the option exercise order direction resulting from the specified and
/// whether or not we wrote the option ( is true
) or bought to
/// option ( is false
)
///
/// The option right
/// True if we wrote the option, false if we purchased the option
/// The order direction resulting from an exercised option
public static OrderDirection GetExerciseDirection(this OptionRight right, bool isShort)
{
switch (right)
{
case OptionRight.Call:
return isShort ? OrderDirection.Sell : OrderDirection.Buy;
default:
return isShort ? OrderDirection.Buy : OrderDirection.Sell;
}
}
///
/// Gets the for the specified
///
public static OrderDirection GetOrderDirection(decimal quantity)
{
var sign = Math.Sign(quantity);
switch (sign)
{
case 1: return OrderDirection.Buy;
case 0: return OrderDirection.Hold;
case -1: return OrderDirection.Sell;
default:
throw new ApplicationException(
$"The skies are falling and the oceans are rising! Math.Sign({quantity}) returned {sign} :/"
);
}
}
///
/// Helper method to process an algorithms security changes, will add and remove securities according to them
///
public static void ProcessSecurityChanges(this IAlgorithm algorithm, SecurityChanges securityChanges)
{
foreach (var security in securityChanges.AddedSecurities)
{
// uses TryAdd, so don't need to worry about duplicates here
algorithm.Securities.Add(security);
}
var activeSecurities = algorithm.UniverseManager.ActiveSecurities;
foreach (var security in securityChanges.RemovedSecurities)
{
if (!activeSecurities.ContainsKey(security.Symbol))
{
security.Reset();
}
}
}
///
/// Helper method to set the property to true
/// for the given security when possible
///
public static void MakeTradable(this Security security)
{
if (security.Type != SecurityType.Index || (security as Securities.Index.Index).ManualSetIsTradable)
{
security.IsTradable = true;
}
}
///
/// Helper method to set an algorithm runtime exception in a normalized fashion
///
public static void SetRuntimeError(this IAlgorithm algorithm, Exception exception, string context)
{
Log.Error(exception, $"Extensions.SetRuntimeError(): {Messages.Extensions.RuntimeError(algorithm, context)}");
exception = StackExceptionInterpreter.Instance.Value.Interpret(exception);
algorithm.RunTimeError = exception;
algorithm.SetStatus(AlgorithmStatus.RuntimeError);
}
///
/// Creates a for a given symbol
///
/// The algorithm instance to create universes for
/// Symbol of the option
/// The option filter to use
/// The universe settings, will use algorithm settings if null
/// for the given symbol
public static OptionChainUniverse CreateOptionChain(this IAlgorithm algorithm, Symbol symbol, PyObject filter, UniverseSettings universeSettings = null)
{
var result = CreateOptionChain(algorithm, symbol, out var option, universeSettings);
option.SetFilter(filter);
return result;
}
///
/// Creates a for a given symbol
///
/// The algorithm instance to create universes for
/// Symbol of the option
/// The option filter to use
/// The universe settings, will use algorithm settings if null
/// for the given symbol
public static OptionChainUniverse CreateOptionChain(this IAlgorithm algorithm, Symbol symbol, Func filter, UniverseSettings universeSettings = null)
{
var result = CreateOptionChain(algorithm, symbol, out var option, universeSettings);
option.SetFilter(filter);
return result;
}
///
/// Creates a for a given symbol
///
/// The algorithm instance to create universes for
/// Symbol of the option
/// The universe settings, will use algorithm settings if null
/// for the given symbol
private static OptionChainUniverse CreateOptionChain(this IAlgorithm algorithm, Symbol symbol, out Option option, UniverseSettings universeSettings = null)
{
if (!symbol.SecurityType.IsOption())
{
throw new ArgumentException(Messages.Extensions.CreateOptionChainRequiresOptionSymbol);
}
// resolve defaults if not specified
var settings = universeSettings ?? algorithm.UniverseSettings;
option = (Option)algorithm.AddSecurity(symbol.Canonical, settings.Resolution, settings.FillForward, settings.Leverage, settings.ExtendedMarketHours);
return (OptionChainUniverse)algorithm.UniverseManager.Values.Single(universe => universe.Configuration.Symbol == symbol.Canonical);
}
///
/// Creates a for a given symbol
///
/// The algorithm instance to create universes for
/// Symbol of the future
/// The future filter to use
/// The universe settings, will use algorithm settings if null
public static IEnumerable CreateFutureChain(this IAlgorithm algorithm, Symbol symbol, PyObject filter, UniverseSettings universeSettings = null)
{
var result = CreateFutureChain(algorithm, symbol, out var future, universeSettings);
future.SetFilter(filter);
return result;
}
///
/// Creates a for a given symbol
///
/// The algorithm instance to create universes for
/// Symbol of the future
/// The future filter to use
/// The universe settings, will use algorithm settings if null
public static IEnumerable CreateFutureChain(this IAlgorithm algorithm, Symbol symbol, Func filter, UniverseSettings universeSettings = null)
{
var result = CreateFutureChain(algorithm, symbol, out var future, universeSettings);
future.SetFilter(filter);
return result;
}
///
/// Creates a for a given symbol
///
private static IEnumerable CreateFutureChain(this IAlgorithm algorithm, Symbol symbol, out Future future, UniverseSettings universeSettings = null)
{
if (symbol.SecurityType != SecurityType.Future)
{
throw new ArgumentException(Messages.Extensions.CreateFutureChainRequiresFutureSymbol);
}
// resolve defaults if not specified
var settings = universeSettings ?? algorithm.UniverseSettings;
var dataNormalizationMode = settings.GetUniverseNormalizationModeOrDefault(symbol.SecurityType);
future = (Future)algorithm.AddSecurity(symbol.Canonical, settings.Resolution, settings.FillForward, settings.Leverage, settings.ExtendedMarketHours,
settings.DataMappingMode, dataNormalizationMode, settings.ContractDepthOffset);
// let's yield back both the future chain and the continuous future universe
return algorithm.UniverseManager.Values.Where(universe => universe.Configuration.Symbol == symbol.Canonical || ContinuousContractUniverse.CreateSymbol(symbol.Canonical) == universe.Configuration.Symbol);
}
private static bool _notifiedUniverseSettingsUsed;
private static readonly HashSet _supportedSecurityTypes = new()
{
SecurityType.Equity,
SecurityType.Forex,
SecurityType.Cfd,
SecurityType.Option,
SecurityType.Future,
SecurityType.FutureOption,
SecurityType.IndexOption,
SecurityType.Crypto,
SecurityType.CryptoFuture
};
///
/// Gets the security for the specified symbol from the algorithm's securities collection.
/// In case the security is not found, it will be created using the
/// and a best effort configuration setup.
///
/// The algorithm instance
/// The symbol which security is being looked up
/// The found or added security instance
/// Callback to invoke in case of unsupported security type
/// True if the security was found or added
public static bool GetOrAddUnrequestedSecurity(this IAlgorithm algorithm, Symbol symbol, out Security security,
Action> onError = null)
{
if (!algorithm.Securities.TryGetValue(symbol, out security))
{
if (!_supportedSecurityTypes.Contains(symbol.SecurityType))
{
Log.Error("GetOrAddUnrequestedSecurity(): Unsupported security type: " + symbol.SecurityType + "-" + symbol.Value);
onError?.Invoke(_supportedSecurityTypes);
return false;
}
var resolution = algorithm.UniverseSettings.Resolution;
var fillForward = algorithm.UniverseSettings.FillForward;
var leverage = algorithm.UniverseSettings.Leverage;
var extendedHours = algorithm.UniverseSettings.ExtendedMarketHours;
if (!_notifiedUniverseSettingsUsed)
{
// let's just send the message once
_notifiedUniverseSettingsUsed = true;
var leverageMsg = $" Leverage = {leverage};";
if (leverage == Security.NullLeverage)
{
leverageMsg = $" Leverage = default;";
}
algorithm.Debug($"Will use UniverseSettings for automatically added securities for open orders and holdings. UniverseSettings:" +
$" Resolution = {resolution};{leverageMsg} FillForward = {fillForward}; ExtendedHours = {extendedHours}");
}
Log.Trace("GetOrAddUnrequestedSecurity(): Adding unrequested security: " + symbol.Value);
if (symbol.SecurityType.IsOption())
{
// add current option contract to the system
security = algorithm.AddOptionContract(symbol, resolution, fillForward, leverage, extendedHours);
}
else if (symbol.SecurityType == SecurityType.Future)
{
// add current future contract to the system
security = algorithm.AddFutureContract(symbol, resolution, fillForward, leverage, extendedHours);
}
else
{
// for items not directly requested set leverage to 1 and at the min resolution
security = algorithm.AddSecurity(symbol.SecurityType, symbol.Value, resolution, symbol.ID.Market, fillForward, leverage, extendedHours);
}
}
return true;
}
///
/// Inverts the specified
///
public static OptionRight Invert(this OptionRight right)
{
switch (right)
{
case OptionRight.Call: return OptionRight.Put;
case OptionRight.Put: return OptionRight.Call;
default:
throw new ArgumentOutOfRangeException(nameof(right), right, null);
}
}
///
/// Compares two values using given operator
///
///
/// Comparison operator
/// The first value
/// The second value
/// Returns true if its left-hand operand meets the operator value to its right-hand operand, false otherwise
public static bool Compare(this ComparisonOperatorTypes op, T arg1, T arg2) where T : IComparable
{
return ComparisonOperator.Compare(op, arg1, arg2);
}
///
/// Converts a instance to a instance
///
/// History request
///
/// Set to true if this subscription is added for the sole purpose of providing currency conversion rates,
/// setting this flag to true will prevent the data from being sent into the algorithm's OnData methods
///
/// True if this subscription should have filters applied to it (market hours/user filters from security), false otherwise
/// Subscription data configuration
public static SubscriptionDataConfig ToSubscriptionDataConfig(this Data.HistoryRequest request, bool isInternalFeed = false, bool isFilteredSubscription = true)
{
return new SubscriptionDataConfig(request.DataType,
request.Symbol,
request.Resolution,
request.DataTimeZone,
request.ExchangeHours.TimeZone,
request.FillForwardResolution.HasValue,
request.IncludeExtendedMarketHours,
isInternalFeed,
request.IsCustomData,
request.TickType,
isFilteredSubscription,
request.DataNormalizationMode,
request.DataMappingMode,
request.ContractDepthOffset
);
}
///
/// Centralized logic used at the top of the subscription enumerator stacks to determine if we should emit base data points
/// based on the configuration for this subscription and the type of data we are handling.
///
/// Currently we only want to emit split/dividends/delisting events for non internal configurations
/// this last part is because equities also have subscriptions which will also subscribe to the
/// same aux events and we don't want duplicate emits of these events in the TimeSliceFactory
///
/// The "TimeSliceFactory" does not allow for multiple dividends/splits per symbol in the same time slice
/// but we don't want to rely only on that to filter out duplicated aux data so we use this at the top of
/// our data enumerator stacks to define what subscription should emit this data.
/// We use this function to filter aux data at the top of the subscription enumerator stack instead of
/// stopping the subscription stack from subscribing to aux data at the bottom because of a
/// dependency with the FF enumerators requiring that they receive aux data to properly handle delistings.
/// Otherwise we would have issues with delisted symbols continuing to fill forward after expiry/delisting.
/// Reference PR #5485 and related issues for more.
public static bool ShouldEmitData(this SubscriptionDataConfig config, BaseData data, bool isUniverse = false)
{
// For now we are only filtering Auxiliary data; so if its another type just return true or if it's a margin interest rate which we want to emit always
if (data.DataType != MarketDataType.Auxiliary)
{
return true;
}
// This filter does not apply to auxiliary data outside of delisting/splits/dividends so lets those emit
var type = data.GetType();
var expectedType = type.IsAssignableTo(config.Type);
// Check our config type first to be lazy about using data.GetType() unless required
var configTypeFilter = (config.Type == typeof(TradeBar) || config.Type.IsAssignableTo(typeof(BaseChainUniverseData)) ||
config.Type == typeof(Tick) && config.TickType == TickType.Trade || config.IsCustomData);
if (!configTypeFilter)
{
return expectedType;
}
// We don't want to pump in any data to `Universe.SelectSymbols(...)` if the
// type is not configured to be consumed by the universe. This change fixes
// a case where a `SymbolChangedEvent` was being passed to an ETF constituent universe
// for filtering/selection, and would result in either a runtime error
// if casting into the expected type explicitly, or call the filter function with
// no data being provided, resulting in all universe Symbols being de-selected.
if (isUniverse && !expectedType)
{
return (data as Delisting)?.Type == DelistingType.Delisted;
}
// We let delistings through. We need to emit delistings for all subscriptions, even internals like
// continuous futures mapped contracts. For instance, an algorithm might hold a position for a mapped
// contract and then the continuous future is mapped to a different contract. If the previously mapped
// contract is delisted, we need to let the delisting through so that positions are closed out and the
// security is removed from the algorithm and marked as delisted and non-tradable.
if (!(type == typeof(Split) || type == typeof(Dividend)))
{
return true;
}
// If we made it here then only filter it if its an InternalFeed
return !config.IsInternalFeed;
}
///
/// Gets the that corresponds to the specified
///
/// The position side to be converted
/// The order direction that maps from the provided position side
public static OrderDirection ToOrderDirection(this PositionSide side)
{
switch (side)
{
case PositionSide.Short: return OrderDirection.Sell;
case PositionSide.None: return OrderDirection.Hold;
case PositionSide.Long: return OrderDirection.Buy;
default:
throw new ArgumentOutOfRangeException(nameof(side), side, null);
}
}
///
/// Determines if an order with the specified would close a position with the
/// specified
///
/// The direction of the order, buy/sell
/// The side of the position, long/short
/// True if the order direction would close the position, otherwise false
public static bool Closes(this OrderDirection direction, PositionSide side)
{
switch (side)
{
case PositionSide.Short:
switch (direction)
{
case OrderDirection.Buy: return true;
case OrderDirection.Sell: return false;
case OrderDirection.Hold: return false;
default:
throw new ArgumentOutOfRangeException(nameof(direction), direction, null);
}
case PositionSide.Long:
switch (direction)
{
case OrderDirection.Buy: return false;
case OrderDirection.Sell: return true;
case OrderDirection.Hold: return false;
default:
throw new ArgumentOutOfRangeException(nameof(direction), direction, null);
}
case PositionSide.None:
return false;
default:
throw new ArgumentOutOfRangeException(nameof(side), side, null);
}
}
///
/// Determines if the two lists are equal, including all items at the same indices.
///
/// The element type
/// The left list
/// The right list
/// True if the two lists have the same counts and items at each index evaluate as equal
public static bool ListEquals(this IReadOnlyList left, IReadOnlyList right)
{
var count = left.Count;
if (count != right.Count)
{
return false;
}
for (int i = 0; i < count; i++)
{
if (!left[i].Equals(right[i]))
{
return false;
}
}
return true;
}
///
/// Computes a deterministic hash code based on the items in the list. This hash code is dependent on the
/// ordering of items.
///
/// The element type
/// The list
/// A hash code dependent on the ordering of elements in the list
public static int GetListHashCode(this IReadOnlyList list)
{
unchecked
{
var hashCode = 17;
for (int i = 0; i < list.Count; i++)
{
hashCode += (hashCode * 397) ^ list[i].GetHashCode();
}
return hashCode;
}
}
///
/// Determine if this SecurityType requires mapping
///
/// Type to check
/// True if it needs to be mapped
public static bool RequiresMapping(this Symbol symbol)
{
switch (symbol.SecurityType)
{
case SecurityType.Base:
return symbol.HasUnderlying && symbol.Underlying.RequiresMapping();
case SecurityType.Future:
return symbol.IsCanonical();
case SecurityType.Equity:
case SecurityType.Option:
return true;
default:
return false;
}
}
///
/// Checks whether the fill event for closing a trade is a winning trade
///
/// The fill event
/// The security being traded
/// The profit-loss for the closed trade
///
/// Whether the trade is a win.
/// For options assignments this depends on whether the option is ITM or OTM and the position side.
/// See for more information.
///
public static bool IsWin(this OrderEvent fill, Security security, decimal profitLoss)
{
// For non-options or non-exercise orders, the trade is a win if the profit-loss is positive
if (!fill.Symbol.SecurityType.IsOption() || fill.Ticket.OrderType != OrderType.OptionExercise)
{
return profitLoss > 0;
}
var option = (Option)security;
// If the fill is a sell, the original transaction was a buy
if (fill.Direction == OrderDirection.Sell)
{
// If the option is ITM, the trade is a win only if the profit is greater than the ITM amount
return fill.IsInTheMoney && Math.Abs(profitLoss) < option.InTheMoneyAmount(fill.FillQuantity);
}
// It is a win if the buyer paid more than what they saved (the ITM amount)
return !fill.IsInTheMoney || Math.Abs(profitLoss) > option.InTheMoneyAmount(fill.FillQuantity);
}
///
/// Gets the option's ITM amount for the given quantity.
///
/// The option security
/// The quantity
/// The ITM amount for the absolute quantity
/// The returned value can be negative, which would mean the option is actually OTM.
public static ConvertibleCashAmount InTheMoneyAmount(this Option option, decimal quantity)
{
return option.Holdings.GetQuantityValue(Math.Abs(quantity), option.GetPayOff(option.Underlying.Price));
}
///
/// Gets the greatest common divisor of a list of numbers
///
/// List of numbers which greatest common divisor is requested
/// The greatest common divisor for the given list of numbers
public static int GreatestCommonDivisor(this IEnumerable values)
{
int? result = null;
foreach (var value in values)
{
if (result.HasValue)
{
result = GreatestCommonDivisor(result.Value, value);
}
else
{
result = value;
}
}
if (!result.HasValue)
{
throw new ArgumentException(Messages.Extensions.GreatestCommonDivisorEmptyList);
}
return result.Value;
}
///
/// Gets the greatest common divisor of two numbers
///
private static int GreatestCommonDivisor(int a, int b)
{
int remainder;
while (b != 0)
{
remainder = a % b;
a = b;
b = remainder;
}
return Math.Abs(a);
}
///
/// Safe method to perform divisions avoiding DivideByZeroException and Overflow/Underflow exceptions
///
/// Value to be returned if the denominator is zero
/// The numerator divided by the denominator if the denominator is not
/// zero. Otherwise, the default failValue or the provided one
public static decimal SafeDivision(this decimal numerator, decimal denominator, decimal failValue = 0)
{
try
{
return (denominator == 0) ? failValue : (numerator / denominator);
}
catch
{
return failValue;
}
}
///
/// Retrieve a common custom data types from the given symbols if any
///
/// The target symbols to search
/// The custom data type or null
public static Type GetCustomDataTypeFromSymbols(Symbol[] symbols)
{
if (symbols.Length != 0)
{
if (!SecurityIdentifier.TryGetCustomDataTypeInstance(symbols[0].ID.Symbol, out var dataType)
|| symbols.Any(x => !SecurityIdentifier.TryGetCustomDataTypeInstance(x.ID.Symbol, out var customDataType) || customDataType != dataType))
{
return null;
}
return dataType;
}
return null;
}
///
/// Determines if certain data type is custom
///
/// Symbol associated with the data type
/// Data type to determine if it's custom
public static bool IsCustomDataType(Symbol symbol, Type type)
{
return type.Namespace != typeof(Bar).Namespace || Extensions.GetCustomDataTypeFromSymbols(new Symbol[] { symbol }) != null;
}
///
/// Returns the amount of fee's charged by executing a market order with the given arguments
///
/// Security for which we would like to make a market order
/// Quantity of the security we are seeking to trade
/// Time the order was placed
/// This out parameter will contain the market order constructed
public static CashAmount GetMarketOrderFees(Security security, decimal quantity, DateTime time, out MarketOrder marketOrder)
{
marketOrder = new MarketOrder(security.Symbol, quantity, time);
return security.FeeModel.GetOrderFee(new OrderFeeParameters(security, marketOrder)).Value;
}
private static Symbol ConvertToSymbol(PyObject item, bool dispose)
{
if (PyString.IsStringType(item))
{
return SymbolCache.GetSymbol(dispose ? item.GetAndDispose() : item.As());
}
else
{
Symbol symbol;
try
{
symbol = dispose ? item.GetAndDispose() : item.As();
}
catch (Exception e)
{
throw new ArgumentException(Messages.Extensions.ConvertToSymbolEnumerableFailed(item), e);
}
return symbol;
}
}
}
}