/*
* 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.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 static QuantConnect.StringExtensions;
using Microsoft.IO;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.FutureOption;
using QuantConnect.Securities.Option;
namespace QuantConnect
{
///
/// Extensions function collections - group all static extensions functions here.
///
public static class Extensions
{
private static RecyclableMemoryStreamManager MemoryManager = new RecyclableMemoryStreamManager();
private static ConcurrentBag Guids = new ConcurrentBag();
private static readonly Dictionary PythonActivators
= new Dictionary();
///
/// 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.
///
/// For performance will reuse a memory stream guid per thread. So
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryStream GetMemoryStream(Guid guid)
{
return MemoryManager.GetStream(guid);
}
///
/// Gets a unique id. Should be returned using
///
/// Creating a new is expensive
/// Used for
/// A unused
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Guid RentId()
{
Guid guid;
if (!Guids.TryTake(out guid))
{
guid = new Guid();
}
return guid;
}
///
/// Returns a rented unique id
///
/// Creating a new is expensive
/// Used for
/// The guid to return
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnId(Guid guid)
{
Guids.Add(guid);
}
/// Unix epoch (1970-01-01 00:00:00.000000000Z)
///
public static DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
///
/// Serialize a list of ticks using protobuf
///
/// The list of ticks to serialize
/// The resulting byte array
public static byte[] ProtobufSerialize(this List ticks)
{
var guid = RentId();
byte[] result;
using (var stream = GetMemoryStream(guid))
{
Serializer.Serialize(stream, ticks);
result = stream.ToArray();
}
ReturnId(guid);
return result;
}
///
/// Serialize a base data instance using protobuf
///
/// The data point to serialize
/// The resulting byte array
public static byte[] ProtobufSerialize(this IBaseData baseData)
{
var guid = RentId();
byte[] result;
using (var stream = GetMemoryStream(guid))
{
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;
}
result = stream.ToArray();
}
ReturnId(guid);
return result;
}
///
/// 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 $"{symbol}: The security does not have an accurate price as it has not yet received a bar of data. " +
"Before placing a trade (or using SetHoldings) warm up your algorithm with SetWarmup, or use slice.Contains(symbol)" +
" to confirm the Slice object has price before using the data. Data does not necessarily all arrive at the same" +
" time so your algorithm should confirm the data is ready before using it. In live trading this can mean you do" +
" not have an active subscription to the asset class you're trying to trade. If using custom data make sure you've" +
" set the 'Value' property.";
}
///
/// 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(): waiting for '{thread.Name}' thread to stop...");
// just in case we add a time out
if (!thread.Join(timeout))
{
Log.Error($"StopSafely(): Timeout waiting for '{thread.Name}' thread to stop");
thread.Abort();
}
}
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 int 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 stopMarket = order as StopMarketOrder;
if (stopMarket != null)
{
stopMarket.StopPrice = stopMarket.StopPrice.SmartRounding();
}
return JsonConvert.SerializeObject(pair.Value, Formatting.None);
}
)
);
return joinedOrders.GetHashCode();
}
///
/// 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 Series 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())
{
var method = instance.GetAttr(name);
var pythonType = method.GetPythonType();
var isPythonDefined = pythonType.Repr().Equals("");
return isPythonDefined ? method : null;
}
}
///
/// 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 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)
{
return targets.Select(x => new {
PortfolioTarget = x,
TargetQuantity = x.Quantity,
ExistingQuantity = algorithm.Portfolio[x.Symbol].Quantity
+ algorithm.Transactions.GetOpenOrderTickets(x.Symbol)
.Aggregate(0m, (d, t) => d + t.Quantity - t.QuantityFilled),
Security = algorithm.Securities[x.Symbol]
})
.Where(x => x.Security.HasData
&& (targetIsDelta ? Math.Abs(x.TargetQuantity) : Math.Abs(x.TargetQuantity - x.ExistingQuantity))
>= x.Security.SymbolProperties.LotSize
)
.Select(x => new {
PortfolioTarget = 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($"Data type \'{type.Name}\' missing parameterless constructor " +
$"E.g. public {type.Name}() {{ }}");
}
var instance = objectActivator.Invoke(new object[] { type });
if(instance == null)
{
// shouldn't happen but just in case...
throw new ArgumentException($"Failed to create instance of type \'{type.Name}\'");
}
// 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($"Data type \'{type.Name}\' does not inherit required {nameof(BaseData)}");
}
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;
}
///
/// 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();
using (var md5Hash = MD5.Create())
{
var data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(str));
foreach (var t in data) builder.Append(t.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 crypt = new SHA256Managed();
var hash = new StringBuilder();
var crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(data), 0, Encoding.UTF8.GetByteCount(data));
foreach (var theByte in crypto)
{
hash.Append(theByte.ToStringInvariant("x2"));
}
return hash.ToString();
}
///
/// 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();
}
///
/// 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));
}
///
/// Removes the specified element to the collection with the specified key. If the entry's count drops to
/// zero, then the entry will be removed.
///
/// The key type
/// The collection element type
/// The source dictionary to be added to
/// The key
/// The element to be added
public static ImmutableDictionary> Remove(
this ImmutableDictionary> dictionary,
TKey key,
TElement element
)
{
ImmutableHashSet set;
if (!dictionary.TryGetValue(key, out set))
{
return dictionary;
}
set = set.Remove(element);
if (set.Count == 0)
{
return dictionary.Remove(key);
}
return dictionary.SetItem(key, set);
}
///
/// Removes the specified element to the collection with the specified key. If the entry's count drops to
/// zero, then the entry will be removed.
///
/// The key type
/// The collection element type
/// The source dictionary to be added to
/// The key
/// The element to be added
public static ImmutableSortedDictionary> Remove(
this ImmutableSortedDictionary> dictionary,
TKey key,
TElement element
)
{
ImmutableHashSet set;
if (!dictionary.TryGetValue(key, out set))
{
return dictionary;
}
set = set.Remove(element);
if (set.Count == 0)
{
return dictionary.Remove(key);
}
return dictionary.SetItem(key, set);
}
///
/// 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))
{
list = new List(1);
dictionary.Add(key, list);
}
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 double RoundToSignificantDigits(this double d, int digits)
{
if (d == 0) return 0;
var scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);
return scale * Math.Round(d / scale, digits);
}
///
/// 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);
}
///
/// 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)
{
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();
}
///
/// 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
public static decimal SafeDecimalCast(this double input)
{
if (double.IsNaN(input) || double.IsInfinity(input))
{
throw new ArgumentException(
$"It is not possible to cast a non-finite floating-point value ({input}) as decimal. Please review math operations and verify the result is valid.",
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);
}
///
/// 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;
}
///
/// 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;
}
///
/// Check if a number is NaN or equal to zero
///
/// The double value to check
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
public static DateTime ExchangeRoundDown(this DateTime dateTime, TimeSpan interval, SecurityExchangeHours exchangeHours, bool extendedMarket)
{
// can't round against a zero interval
if (interval == TimeSpan.Zero) return dateTime;
var rounded = dateTime.RoundDown(interval);
while (!exchangeHours.IsOpen(rounded, rounded + interval, extendedMarket))
{
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
public static DateTime ExchangeRoundDownInTimeZone(this DateTime dateTime, TimeSpan interval, SecurityExchangeHours exchangeHours, DateTimeZone roundingTimeZone, bool extendedMarket)
{
// 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, extendedMarket))
{
// 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;
}
///
/// 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
public static DateTime ConvertTo(this DateTime time, DateTimeZone from, DateTimeZone to)
{
var instant = new Instant(time.Ticks - UnixEpoch.Ticks);
var fromOffset = from.GetUtcOffset(instant).ToTimeSpan();
var toOffset = to.GetUtcOffset(instant).ToTimeSpan();
return time - (fromOffset - toOffset);
}
///
/// 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)
{
return time + to.GetUtcOffset(new Instant(time.Ticks - UnixEpoch.Ticks)).ToTimeSpan();
}
///
/// 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
public static DateTime ConvertToUtc(this DateTime time, DateTimeZone from)
{
return time.Subtract(from.GetUtcOffset(Instant.FromTicksSinceUnixEpoch((time.Ticks - UnixEpoch.Ticks))).ToTimeSpan());
}
///
/// 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
public static TimeSpan ToTimeSpan(this Resolution resolution)
{
switch (resolution)
{
case Resolution.Tick:
// ticks can be instantaneous
return TimeSpan.FromTicks(0);
case Resolution.Second:
return TimeSpan.FromSeconds(1);
case Resolution.Minute:
return TimeSpan.FromMinutes(1);
case Resolution.Hour:
return TimeSpan.FromHours(1);
case Resolution.Daily:
return TimeSpan.FromDays(1);
default:
throw new ArgumentOutOfRangeException("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(Invariant($"Unable to exactly convert time span ('{timeSpan}') to resolution."));
}
// 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;
}
///
/// 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);
}
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 algirithmTypeName
///
/// 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 use that guy
// if there's more than one then find which one we should use using the algorithmTypeName specified
return names.Count == 1 ? names.Single() : names.SingleOrDefault(x => x.EndsWith("." + algorithmTypeName));
}
///
/// 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:
return true;
default:
return false;
}
}
///
/// 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 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.Commodity:
return "commodity";
case SecurityType.Forex:
return "forex";
case SecurityType.Future:
return "future";
case SecurityType.Cfd:
return "cfd";
case SecurityType.Crypto:
return "crypto";
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;
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.OptionExercise:
case OrderType.Market:
case OrderType.MarketOnOpen:
case OrderType.MarketOnClose:
limitPrice = order.Price;
stopPrice = order.Price;
break;
default:
throw new ArgumentOutOfRangeException();
}
var submitOrderRequest = new SubmitOrderRequest(order.Type,
order.SecurityType,
order.Symbol,
order.Quantity,
stopPrice,
limitPrice,
order.Time,
order.Tag,
order.Properties);
submitOrderRequest.SetOrderId(order.Id);
var orderTicket = new OrderTicket(transactionManager, submitOrderRequest);
orderTicket.SetOrder(order);
return orderTicket;
}
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
{
// Special case: Type
if (typeof(Type).IsAssignableFrom(type))
{
result = (T)pyObject.AsManagedObject(type);
return true;
}
// Special case: IEnumerable
if (typeof(IEnumerable).IsAssignableFrom(type))
{
result = (T)pyObject.AsManagedObject(type);
return true;
}
var pythonType = pyObject.GetPythonType();
var csharpType = pythonType.As();
if (!type.IsAssignableFrom(csharpType))
{
pythonType.Dispose();
return false;
}
result = (T)pyObject.AsManagedObject(type);
// 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();
pythonType.Dispose();
return allowPythonDerivative || 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);
if (!typeof(MulticastDelegate).IsAssignableFrom(type))
{
throw new ArgumentException($"TryConvertToDelegate cannot be used to convert a PyObject into {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.Handle);
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;
}
///
/// 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> ConvertToUniverseSelectionSymbolDelegate(this Func selector)
{
return data =>
{
var result = selector(data);
return ReferenceEquals(result, Universe.Unchanged)
? Universe.Unchanged : ((object[])result).Select(x => (Symbol)x);
};
}
///
/// 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($"ConvertToDelegate cannot be used to convert a PyObject into {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(
$"ConvertToDictionary cannot be used to convert a {inputType} into {targetType}. Reason: {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())
{
if (!PyList.IsListType(pyObject))
{
pyObject = new PyList(new[] {pyObject});
}
foreach (PyObject item in pyObject)
{
if (PyString.IsStringType(item))
{
yield return SymbolCache.GetSymbol(item.GetAndDispose());
}
else
{
Symbol symbol;
try
{
symbol = item.GetAndDispose();
}
catch (Exception e)
{
throw new ArgumentException(
"Argument type should be Symbol or a list of Symbol. " +
$"Object: {item}. Type: {item.GetPythonType()}",
e
);
}
yield return symbol;
}
}
}
}
///
/// Converts an IEnumerable to a PyList
///
/// IEnumerable object to convert
/// PyList
public static PyList ToPyList(this IEnumerable enumerable)
{
using (Py.GIL())
{
var pyList = new PyList();
foreach (var item in enumerable)
{
using (var pyObject = item.ToPython())
{
pyList.Append(pyObject);
}
}
return pyList;
}
}
///
/// 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
public static string GetEnumString(this int value, PyObject pyObject)
{
Type type;
if (pyObject.TryConvert(out type))
{
return value.ToStringInvariant().ConvertTo(type).ToString();
}
else
{
using (Py.GIL())
{
throw new ArgumentException($"GetEnumString(): {pyObject.Repr()} is not a C# Type.");
}
}
}
///
/// 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) &&
type != typeof(PythonQuandl) &&
type != typeof(PythonData))
{
return type;
}
PythonActivator pythonType;
if (!PythonActivators.TryGetValue(pyObject.Handle, out pythonType))
{
AssemblyName an;
using (Py.GIL())
{
an = new AssemblyName(pyObject.Repr().Split('\'')[1]);
}
var typeBuilder = AppDomain.CurrentDomain
.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run)
.DefineDynamicModule("MainModule")
.DefineType(an.Name, TypeAttributes.Class, 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;
}
///
/// 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();
}
///
/// 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))
{
return s.Substring(0, s.Length - ending.Length);
}
else
{
return s;
}
}
///
/// Normalizes the specified price based on the DataNormalizationMode
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static decimal GetNormalizedPrice(this SubscriptionDataConfig config, decimal price)
{
switch (config.DataNormalizationMode)
{
case DataNormalizationMode.Raw:
return price;
// the price scale factor will be set accordingly based on the mode in update scale factors
case DataNormalizationMode.Adjusted:
case DataNormalizationMode.SplitAdjusted:
return price * config.PriceScaleFactor;
case DataNormalizationMode.TotalReturn:
return (price * config.PriceScaleFactor) + config.SumOfDividends;
default:
throw new ArgumentOutOfRangeException();
}
}
///
/// 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)
{
switch (symbol.ID.SecurityType)
{
case SecurityType.Future:
return symbol.ID.Date;
case SecurityType.Option:
return OptionSymbol.GetLastDayOfTrading(symbol);
case SecurityType.FutureOption:
return FutureOptionSymbol.GetLastDayOfTrading(symbol);
default:
return mapFile?.DelistingDate ?? SecurityIdentifier.DefaultDate;
}
}
///
/// Scale data based on factor function
///
public static BaseData Scale(this BaseData data, Func factor)
{
switch (data.DataType)
{
case MarketDataType.TradeBar:
var tradeBar = data as TradeBar;
if (tradeBar != null)
{
tradeBar.Open = factor(tradeBar.Open);
tradeBar.High = factor(tradeBar.High);
tradeBar.Low = factor(tradeBar.Low);
tradeBar.Close = factor(tradeBar.Close);
}
break;
case MarketDataType.Tick:
var securityType = data.Symbol.SecurityType;
if (securityType != SecurityType.Equity &&
securityType != SecurityType.Option &&
securityType != SecurityType.FutureOption &&
securityType != SecurityType.Future)
{
break;
}
var tick = data as Tick;
if (tick == null || tick.TickType == TickType.OpenInterest)
{
break;
}
if (tick.TickType == TickType.Trade)
{
tick.Value = factor(tick.Value);
break;
}
tick.BidPrice = tick.BidPrice != 0 ? factor(tick.BidPrice) : 0;
tick.AskPrice = tick.AskPrice != 0 ? factor(tick.AskPrice) : 0;
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 = factor(quoteBar.Ask.Open);
quoteBar.Ask.High = factor(quoteBar.Ask.High);
quoteBar.Ask.Low = factor(quoteBar.Ask.Low);
quoteBar.Ask.Close = factor(quoteBar.Ask.Close);
}
if (quoteBar.Bid != null)
{
quoteBar.Bid.Open = factor(quoteBar.Bid.Open);
quoteBar.Bid.High = factor(quoteBar.Bid.High);
quoteBar.Bid.Low = factor(quoteBar.Bid.Low);
quoteBar.Bid.Close = factor(quoteBar.Bid.Close);
}
quoteBar.Value = quoteBar.Close;
}
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
///
public static BaseData Normalize(this BaseData data, SubscriptionDataConfig config)
{
return data?.Scale(p => config.GetNormalizedPrice(p));
}
///
/// Adjust prices based on price scale
///
/// Data to be adjusted
/// Price scale
///
public static BaseData Adjust(this BaseData data, decimal scale)
{
return data?.Scale(p => p * scale);
}
///
/// 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($"Source cannot be null or empty.");
}
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} :/"
);
}
}
///
/// 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)
{
if (symbol.SecurityType != SecurityType.Option && symbol.SecurityType != SecurityType.FutureOption)
{
throw new ArgumentException("CreateOptionChain requires an option symbol.");
}
// rewrite non-canonical symbols to be canonical
var market = symbol.ID.Market;
var underlying = symbol.Underlying;
if (!symbol.IsCanonical())
{
// The underlying can be a non-equity Symbol, so we must explicitly
// initialize the Symbol using the CreateOption(Symbol, ...) overload
// to ensure that the underlying SecurityType is preserved and not
// written as SecurityType.Equity.
var alias = $"?{underlying.Value}";
symbol = Symbol.CreateOption(
underlying,
market,
default(OptionStyle),
default(OptionRight),
0m,
SecurityIdentifier.DefaultDate,
alias);
}
// resolve defaults if not specified
var settings = universeSettings ?? algorithm.UniverseSettings;
// create canonical security object, but don't duplicate if it already exists
Security security;
Option optionChain;
if (!algorithm.Securities.TryGetValue(symbol, out security))
{
var config = algorithm.SubscriptionManager.SubscriptionDataConfigService.Add(
typeof(ZipEntryName),
symbol,
settings.Resolution,
settings.FillForward,
settings.ExtendedMarketHours,
false);
optionChain = (Option)algorithm.Securities.CreateSecurity(symbol, config, settings.Leverage, false);
}
else
{
optionChain = (Option)security;
}
// set the option chain contract filter function
optionChain.SetFilter(filter);
// force option chain security to not be directly tradable AFTER it's configured to ensure it's not overwritten
optionChain.IsTradable = false;
return new OptionChainUniverse(optionChain, settings, algorithm.LiveMode);
}
///
/// 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);
}
}
}