chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,127 @@
/*
* 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 Python.Runtime;
using QuantConnect.Interfaces;
using System.Collections.Generic;
using QuantConnect.Util;
namespace QuantConnect.Algorithm.Framework.Alphas.Analysis
{
/// <summary>
/// Encapsulates the storage of insights.
/// </summary>
public class InsightManager : InsightCollection
{
private readonly IAlgorithm _algorithm;
private IInsightScoreFunction _insightScoreFunction;
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="algorithm">The associated algorithm instance</param>
public InsightManager(IAlgorithm algorithm)
{
_algorithm = algorithm;
}
/// <summary>
/// Process a new time step handling insights scoring
/// </summary>
/// <param name="utcNow">The current utc time</param>
public void Step(DateTime utcNow)
{
_insightScoreFunction?.Score(this, utcNow);
}
/// <summary>
/// Sets the insight score function to use
/// </summary>
/// <param name="insightScoreFunction">Model that scores insights</param>
public void SetInsightScoreFunction(IInsightScoreFunction insightScoreFunction)
{
_insightScoreFunction = insightScoreFunction;
}
/// <summary>
/// Sets the insight score function to use
/// </summary>
/// <param name="insightScoreFunction">Model that scores insights</param>
public void SetInsightScoreFunction(PyObject insightScoreFunction)
{
_insightScoreFunction = PythonUtil.CreateInstanceOrWrapper<IInsightScoreFunction>(
insightScoreFunction,
py => new InsightScoreFunctionPythonWrapper(py)
);
}
/// <summary>
/// Expire the insights of the given symbols
/// </summary>
/// <param name="symbols">Symbol we want to expire insights for</param>
public void Expire(IEnumerable<Symbol> symbols)
{
if (symbols == null)
{
return;
}
foreach (var symbol in symbols)
{
if (TryGetValue(symbol, out var insights))
{
Expire(insights);
}
}
}
/// <summary>
/// Cancel the insights of the given symbols
/// </summary>
/// <param name="symbols">Symbol we want to cancel insights for</param>
public void Cancel(IEnumerable<Symbol> symbols)
{
Expire(symbols);
}
/// <summary>
/// Expire the given insights
/// </summary>
/// <param name="insights">Insights to expire</param>
public void Expire(IEnumerable<Insight> insights)
{
if (insights == null)
{
return;
}
var currentUtcTime = _algorithm.UtcTime;
foreach (var insight in insights)
{
insight.Expire(currentUtcTime);
}
}
/// <summary>
/// Cancel the given insights
/// </summary>
/// <param name="insights">Insights to cancel</param>
public void Cancel(IEnumerable<Insight> insights)
{
Expire(insights);
}
}
}
@@ -0,0 +1,47 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Defines a collection of insights that were generated at the same time step
/// </summary>
public class GeneratedInsightsCollection
{
/// <summary>
/// The utc date time the insights were generated
/// </summary>
public DateTime DateTimeUtc { get; }
/// <summary>
/// The generated insights
/// </summary>
public Insight[] Insights { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GeneratedInsightsCollection"/> class
/// </summary>
/// <param name="dateTimeUtc">The utc date time the sinals were generated</param>
/// <param name="insights">The generated insights</param>
public GeneratedInsightsCollection(DateTime dateTimeUtc, Insight[] insights)
{
DateTimeUtc = dateTimeUtc;
Insights = insights;
}
}
}
@@ -0,0 +1,31 @@
/*
* 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 QuantConnect.Algorithm.Framework.Alphas.Analysis;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Abstraction in charge of scoring insights
/// </summary>
public interface IInsightScoreFunction
{
/// <summary>
/// Method to evaluate and score insights for each time step
/// </summary>
void Score(InsightManager insightManager, DateTime utcTime);
}
}
@@ -0,0 +1,747 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using QuantConnect.Algorithm.Framework.Alphas.Serialization;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Defines a alpha prediction for a single symbol generated by the algorithm
/// </summary>
/// <remarks>
/// Serialization of this type is delegated to the <see cref="InsightJsonConverter"/> which uses the <see cref="SerializedInsight"/> as a model.
/// </remarks>
[JsonConverter(typeof(InsightJsonConverter))]
public class Insight
{
private readonly IPeriodSpecification _periodSpecification;
/// <summary>
/// Gets the unique identifier for this insight
/// </summary>
public Guid Id { get; protected set; }
/// <summary>
/// Gets the group id this insight belongs to, null if not in a group
/// </summary>
public Guid? GroupId { get; protected set; }
/// <summary>
/// Gets an identifier for the source model that generated this insight.
/// </summary>
public string SourceModel { get; set; }
/// <summary>
/// Gets the utc time this insight was generated
/// </summary>
/// <remarks>
/// The algorithm framework handles setting this value appropriately.
/// If providing custom <see cref="Insight"/> implementation, be sure
/// to set this value to algorithm.UtcTime when the insight is generated.
/// </remarks>
public DateTime GeneratedTimeUtc { get; set; }
/// <summary>
/// Gets the insight's prediction end time. This is the time when this
/// insight prediction is expected to be fulfilled. This time takes into
/// account market hours, weekends, as well as the symbol's data resolution
/// </summary>
public DateTime CloseTimeUtc { get; set; }
/// <summary>
/// Gets the symbol this insight is for
/// </summary>
public Symbol Symbol { get; private set; }
/// <summary>
/// Gets the type of insight, for example, price insight or volatility insight
/// </summary>
public InsightType Type { get; private set; }
/// <summary>
/// Gets the initial reference value this insight is predicting against. The value is dependent on the specified <see cref="InsightType"/>
/// </summary>
public decimal ReferenceValue { get; set; }
/// <summary>
/// Gets the final reference value, used for scoring, this insight is predicting against. The value is dependent on the specified <see cref="InsightType"/>
/// </summary>
public decimal ReferenceValueFinal { get; set; }
/// <summary>
/// Gets the predicted direction, down, flat or up
/// </summary>
public InsightDirection Direction { get; private set; }
/// <summary>
/// Gets the period over which this insight is expected to come to fruition
/// </summary>
public TimeSpan Period { get; internal set; }
/// <summary>
/// Gets the predicted percent change in the insight type (price/volatility)
/// </summary>
public double? Magnitude { get; private set; }
/// <summary>
/// Gets the confidence in this insight
/// </summary>
public double? Confidence { get; private set; }
/// <summary>
/// Gets the portfolio weight of this insight
/// </summary>
public double? Weight { get; private set; }
/// <summary>
/// Gets the most recent scores for this insight
/// </summary>
public InsightScore Score { get; protected set; }
/// <summary>
/// Gets the estimated value of this insight in the account currency
/// </summary>
public decimal EstimatedValue { get; set; }
/// <summary>
/// The insight's tag containing additional information
/// </summary>
public string Tag { get; protected set; }
/// <summary>
/// Determines whether or not this insight is considered expired at the specified <paramref name="utcTime"/>
/// </summary>
/// <param name="utcTime">The algorithm's current time in UTC. See <see cref="IAlgorithm.UtcTime"/></param>
/// <returns>True if this insight is expired, false otherwise</returns>
public bool IsExpired(DateTime utcTime)
{
return CloseTimeUtc < utcTime;
}
/// <summary>
/// Determines whether or not this insight is considered active at the specified <paramref name="utcTime"/>
/// </summary>
/// <param name="utcTime">The algorithm's current time in UTC. See <see cref="IAlgorithm.UtcTime"/></param>
/// <returns>True if this insight is active, false otherwise</returns>
public bool IsActive(DateTime utcTime)
{
return !IsExpired(utcTime);
}
/// <summary>
/// Expire this insight
/// </summary>
/// <param name="utcTime">The algorithm's current time in UTC. See <see cref="IAlgorithm.UtcTime"/></param>
public void Expire(DateTime utcTime)
{
if (IsActive(utcTime))
{
CloseTimeUtc = utcTime.Add(-Time.OneSecond);
Period = CloseTimeUtc - GeneratedTimeUtc;
}
}
/// <summary>
/// Cancel this insight
/// </summary>
/// <param name="utcTime">The algorithm's current time in UTC. See <see cref="IAlgorithm.UtcTime"/></param>
public void Cancel(DateTime utcTime)
{
Expire(utcTime);
}
/// <summary>
/// Initializes a new instance of the <see cref="Insight"/> class
/// </summary>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="period">The period over which the prediction will come true</param>
/// <param name="type">The type of insight, price/volatility</param>
/// <param name="direction">The predicted direction</param>
/// <param name="tag">The insight's tag containing additional information</param>
public Insight(Symbol symbol, TimeSpan period, InsightType type, InsightDirection direction, string tag = "")
: this(symbol, period, type, direction, null, null, null, null, tag)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Insight"/> class
/// </summary>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="period">The period over which the prediction will come true</param>
/// <param name="type">The type of insight, price/volatility</param>
/// <param name="direction">The predicted direction</param>
/// <param name="magnitude">The predicted magnitude as a percentage change</param>
/// <param name="confidence">The confidence in this insight</param>
/// <param name="sourceModel">An identifier defining the model that generated this insight</param>
/// <param name="weight">The portfolio weight of this insight</param>
/// <param name="tag">The insight's tag containing additional information</param>
public Insight(Symbol symbol, TimeSpan period, InsightType type, InsightDirection direction, double? magnitude, double? confidence, string sourceModel = null, double? weight = null, string tag = "")
{
Id = Guid.NewGuid();
Score = new InsightScore();
SourceModel = sourceModel;
Symbol = symbol;
Type = type;
Direction = direction;
Period = period;
// Optional
Magnitude = magnitude;
Confidence = confidence;
Weight = weight;
Tag = tag;
_periodSpecification = new TimeSpanPeriodSpecification(period);
}
/// <summary>
/// Initializes a new instance of the <see cref="Insight"/> class
/// </summary>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="expiryFunc">Func that defines the expiry time</param>
/// <param name="type">The type of insight, price/volatility</param>
/// <param name="direction">The predicted direction</param>
/// <param name="tag">The insight's tag containing additional information</param>
public Insight(Symbol symbol, Func<DateTime, DateTime> expiryFunc, InsightType type, InsightDirection direction, string tag = "")
: this(symbol, expiryFunc, type, direction, null, null, null, null, tag)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Insight"/> class
/// </summary>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="expiryFunc">Func that defines the expiry time</param>
/// <param name="type">The type of insight, price/volatility</param>
/// <param name="direction">The predicted direction</param>
/// <param name="magnitude">The predicted magnitude as a percentage change</param>
/// <param name="confidence">The confidence in this insight</param>
/// <param name="sourceModel">An identifier defining the model that generated this insight</param>
/// <param name="weight">The portfolio weight of this insight</param>
/// <param name="tag">The insight's tag containing additional information</param>
public Insight(Symbol symbol, Func<DateTime, DateTime> expiryFunc, InsightType type, InsightDirection direction, double? magnitude, double? confidence, string sourceModel = null, double? weight = null, string tag = "")
: this(symbol, new FuncPeriodSpecification(expiryFunc), type, direction, magnitude, confidence, sourceModel, weight, tag)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Insight"/> class.
/// This constructor is provided mostly for testing purposes. When running inside an algorithm,
/// the generated and close times are set based on the algorithm's time.
/// </summary>
/// <param name="generatedTimeUtc">The time this insight was generated in utc</param>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="period">The period over which the prediction will come true</param>
/// <param name="type">The type of insight, price/volatility</param>
/// <param name="direction">The predicted direction</param>
/// <param name="magnitude">The predicted magnitude as a percentage change</param>
/// <param name="confidence">The confidence in this insight</param>
/// <param name="sourceModel">An identifier defining the model that generated this insight</param>
/// <param name="weight">The portfolio weight of this insight</param>
/// <param name="tag">The insight's tag containing additional information</param>
public Insight(DateTime generatedTimeUtc, Symbol symbol, TimeSpan period, InsightType type, InsightDirection direction, double? magnitude, double? confidence, string sourceModel = null, double? weight = null, string tag = "")
: this(symbol, period, type, direction, magnitude, confidence, sourceModel, weight, tag)
{
GeneratedTimeUtc = generatedTimeUtc;
CloseTimeUtc = generatedTimeUtc + period;
}
/// <summary>
/// Private constructor used to keep track of how a user defined the insight period.
/// </summary>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="periodSpec">A specification defining how the insight's period was defined, via time span, via resolution/barcount, via close time</param>
/// <param name="type">The type of insight, price/volatility</param>
/// <param name="direction">The predicted direction</param>
/// <param name="magnitude">The predicted magnitude as a percentage change</param>
/// <param name="confidence">The confidence in this insight</param>
/// <param name="sourceModel">An identifier defining the model that generated this insight</param>
/// <param name="weight">The portfolio weight of this insight</param>
/// <param name="tag">The insight's tag containing additional information</param>
private Insight(Symbol symbol, IPeriodSpecification periodSpec, InsightType type, InsightDirection direction, double? magnitude, double? confidence, string sourceModel = null, double? weight = null, string tag = "")
{
Id = Guid.NewGuid();
Score = new InsightScore();
SourceModel = sourceModel;
Symbol = symbol;
Type = type;
Direction = direction;
// Optional
Magnitude = magnitude;
Confidence = confidence;
Weight = weight;
Tag = tag;
_periodSpecification = periodSpec;
// keep existing behavior of Insight.Price such that we set the period immediately
var period = (periodSpec as TimeSpanPeriodSpecification)?.Period;
if (period != null)
{
Period = period.Value;
}
}
/// <summary>
/// Sets the insight period and close times if they have not already been set.
/// </summary>
/// <param name="exchangeHours">The insight's security exchange hours</param>
public void SetPeriodAndCloseTime(SecurityExchangeHours exchangeHours)
{
if (GeneratedTimeUtc == default(DateTime))
{
throw new InvalidOperationException(Messages.Insight.GeneratedTimeUtcNotSet(this));
}
_periodSpecification.SetPeriodAndCloseTime(this, exchangeHours);
}
/// <summary>
/// Creates a deep clone of this insight instance
/// </summary>
/// <returns>A new insight with identical values, but new instances</returns>
public virtual Insight Clone()
{
return new Insight(Symbol, Period, Type, Direction, Magnitude, Confidence, weight: Weight, tag: Tag)
{
GeneratedTimeUtc = GeneratedTimeUtc,
CloseTimeUtc = CloseTimeUtc,
Score = Score,
Id = Id,
EstimatedValue = EstimatedValue,
ReferenceValue = ReferenceValue,
ReferenceValueFinal = ReferenceValueFinal,
SourceModel = SourceModel,
GroupId = GroupId
};
}
/// <summary>
/// Creates a new insight for predicting the percent change in price over the specified period
/// </summary>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="resolution">The resolution used to define the insight's period and also used to determine the insight's close time</param>
/// <param name="barCount">The number of resolution time steps to make in market hours to compute the insight's closing time</param>
/// <param name="direction">The predicted direction</param>
/// <param name="magnitude">The predicted magnitude as a percent change</param>
/// <param name="confidence">The confidence in this insight</param>
/// <param name="sourceModel">The model generating this insight</param>
/// <param name="weight">The portfolio weight of this insight</param>
/// <param name="tag">The insight's tag containing additional information</param>
/// <returns>A new insight object for the specified parameters</returns>
public static Insight Price(Symbol symbol, Resolution resolution, int barCount, InsightDirection direction, double? magnitude = null, double? confidence = null, string sourceModel = null, double? weight = null, string tag = "")
{
if (barCount < 1)
{
throw new ArgumentOutOfRangeException(nameof(barCount), Messages.Insight.InvalidBarCount);
}
var spec = new ResolutionBarCountPeriodSpecification(resolution, barCount);
return new Insight(symbol, spec, InsightType.Price, direction, magnitude, confidence, sourceModel, weight, tag);
}
/// <summary>
/// Creates a new insight for predicting the percent change in price over the specified period
/// </summary>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="closeTimeLocal">The insight's closing time in the security's exchange time zone</param>
/// <param name="direction">The predicted direction</param>
/// <param name="magnitude">The predicted magnitude as a percent change</param>
/// <param name="confidence">The confidence in this insight</param>
/// <param name="sourceModel">The model generating this insight</param>
/// <param name="weight">The portfolio weight of this insight</param>
/// <param name="tag">The insight's tag containing additional information</param>
/// <returns>A new insight object for the specified parameters</returns>
public static Insight Price(Symbol symbol, DateTime closeTimeLocal, InsightDirection direction, double? magnitude = null, double? confidence = null, string sourceModel = null, double? weight = null, string tag = "")
{
var spec = closeTimeLocal == Time.EndOfTime ? (IPeriodSpecification)
new EndOfTimeCloseTimePeriodSpecification() : new CloseTimePeriodSpecification(closeTimeLocal);
return new Insight(symbol, spec, InsightType.Price, direction, magnitude, confidence, sourceModel, weight, tag);
}
/// <summary>
/// Creates a new insight for predicting the percent change in price over the specified period
/// </summary>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="period">The period over which the prediction will come true</param>
/// <param name="direction">The predicted direction</param>
/// <param name="magnitude">The predicted magnitude as a percent change</param>
/// <param name="confidence">The confidence in this insight</param>
/// <param name="sourceModel">The model generating this insight</param>
/// <param name="weight">The portfolio weight of this insight</param>
/// <param name="tag">The insight's tag containing additional information</param>
/// <returns>A new insight object for the specified parameters</returns>
public static Insight Price(Symbol symbol, TimeSpan period, InsightDirection direction, double? magnitude = null, double? confidence = null, string sourceModel = null, double? weight = null, string tag = "")
{
if (period < Time.OneSecond)
{
throw new ArgumentOutOfRangeException(nameof(period), Messages.Insight.InvalidPeriod);
}
var spec = period == Time.EndOfTimeTimeSpan ? (IPeriodSpecification)
new EndOfTimeCloseTimePeriodSpecification() : new TimeSpanPeriodSpecification(period);
return new Insight(symbol, spec, InsightType.Price, direction, magnitude, confidence, sourceModel, weight, tag);
}
/// <summary>
/// Creates a new insight for predicting the percent change in price over the specified period
/// </summary>
/// <param name="symbol">The symbol this insight is for</param>
/// <param name="expiryFunc">Func that defines the expiry time</param>
/// <param name="direction">The predicted direction</param>
/// <param name="magnitude">The predicted magnitude as a percent change</param>
/// <param name="confidence">The confidence in this insight</param>
/// <param name="sourceModel">The model generating this insight</param>
/// <param name="weight">The portfolio weight of this insight</param>
/// <param name="tag">The insight's tag containing additional information</param>
/// <returns>A new insight object for the specified parameters</returns>
public static Insight Price(Symbol symbol, Func<DateTime, DateTime> expiryFunc, InsightDirection direction, double? magnitude = null, double? confidence = null, string sourceModel = null, double? weight = null, string tag = "")
{
return new Insight(symbol, expiryFunc, InsightType.Price, direction, magnitude, confidence, sourceModel, weight, tag);
}
/// <summary>
/// Creates a new, unique group id and sets it on each insight
/// </summary>
/// <param name="insights">The insights to be grouped</param>
public static IEnumerable<Insight> Group(params Insight[] insights)
{
if (insights == null)
{
throw new ArgumentNullException(nameof(insights));
}
var groupId = Guid.NewGuid();
foreach (var insight in insights)
{
if (insight.GroupId.HasValue)
{
throw new InvalidOperationException(Messages.Insight.InsightAlreadyAssignedToAGroup(insight));
}
insight.GroupId = groupId;
}
return insights;
}
/// <summary>
/// Creates a new, unique group id and sets it on each insight
/// </summary>
/// <param name="insight">The insight to be grouped</param>
public static IEnumerable<Insight> Group(Insight insight) => Group(new[] {insight});
/// <summary>
/// Creates a new <see cref="Insight"/> object from the specified serialized form
/// </summary>
/// <param name="serializedInsight">The insight DTO</param>
/// <returns>A new insight containing the information specified</returns>
public static Insight FromSerializedInsight(SerializedInsight serializedInsight)
{
var sid = SecurityIdentifier.Parse(serializedInsight.Symbol);
var insight = new Insight(
Time.UnixTimeStampToDateTime(serializedInsight.CreatedTime),
new Symbol(sid, serializedInsight.Ticker ?? sid.Symbol),
TimeSpan.FromSeconds(serializedInsight.Period),
serializedInsight.Type,
serializedInsight.Direction,
serializedInsight.Magnitude,
serializedInsight.Confidence,
serializedInsight.SourceModel,
serializedInsight.Weight,
serializedInsight.Tag
)
{
Id = Guid.Parse(serializedInsight.Id),
CloseTimeUtc = Time.UnixTimeStampToDateTime(serializedInsight.CloseTime),
EstimatedValue = serializedInsight.EstimatedValue,
ReferenceValue = serializedInsight.ReferenceValue,
ReferenceValueFinal = serializedInsight.ReferenceValueFinal,
GroupId = string.IsNullOrEmpty(serializedInsight.GroupId) ? (Guid?) null : Guid.Parse(serializedInsight.GroupId)
};
// only set score values if non-zero or if they're the final scores
if (serializedInsight.ScoreIsFinal)
{
insight.Score.SetScore(InsightScoreType.Magnitude, serializedInsight.ScoreMagnitude, insight.CloseTimeUtc);
insight.Score.SetScore(InsightScoreType.Direction, serializedInsight.ScoreDirection, insight.CloseTimeUtc);
insight.Score.Finalize(insight.CloseTimeUtc);
}
else
{
if (serializedInsight.ScoreMagnitude != 0)
{
insight.Score.SetScore(InsightScoreType.Magnitude, serializedInsight.ScoreMagnitude, insight.CloseTimeUtc);
}
if (serializedInsight.ScoreDirection != 0)
{
insight.Score.SetScore(InsightScoreType.Direction, serializedInsight.ScoreDirection, insight.CloseTimeUtc);
}
}
return insight;
}
/// <summary>
/// Computes the insight closing time from the given generated time, resolution and bar count.
/// This will step through market hours using the given resolution, respecting holidays, early closes, weekends, etc..
/// </summary>
/// <param name="exchangeHours">The exchange hours of the insight's security</param>
/// <param name="generatedTimeUtc">The insight's generated time in utc</param>
/// <param name="resolution">The resolution used to 'step-through' market hours to compute a reasonable close time</param>
/// <param name="barCount">The number of resolution steps to take</param>
/// <returns>The insight's closing time in utc</returns>
public static DateTime ComputeCloseTime(SecurityExchangeHours exchangeHours, DateTime generatedTimeUtc, Resolution resolution, int barCount)
{
if (barCount < 1)
{
throw new ArgumentOutOfRangeException(nameof(barCount), Messages.Insight.InvalidBarCount);
}
// remap ticks to seconds
resolution = resolution == Resolution.Tick ? Resolution.Second : resolution;
if (resolution == Resolution.Hour)
{
// remap hours to minutes to avoid complications w/ stepping through
// for example 9->10 is an hour step but market opens at 9:30
barCount *= 60;
resolution = Resolution.Minute;
}
var barSize = resolution.ToTimeSpan();
var startTimeLocal = generatedTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
var closeTimeLocal = Time.GetEndTimeForTradeBars(exchangeHours, startTimeLocal, barSize, barCount, false);
return closeTimeLocal.ConvertToUtc(exchangeHours.TimeZone);
}
/// <summary>
/// computs the insight closing time from the given generated time and period
/// </summary>
/// <param name="exchangeHours">The exchange hours of the insight's security</param>
/// <param name="generatedTimeUtc">The insight's generated time in utc</param>
/// <param name="period">The insight's period</param>
/// <returns>The insight's closing time in utc</returns>
public static DateTime ComputeCloseTime(SecurityExchangeHours exchangeHours, DateTime generatedTimeUtc, TimeSpan period)
{
if (period < Time.OneSecond)
{
throw new ArgumentOutOfRangeException(nameof(period), Messages.Insight.InvalidPeriod);
}
var barSize = period.ToHigherResolutionEquivalent(false);
// remap ticks to seconds
barSize = barSize == Resolution.Tick ? Resolution.Second : barSize;
// remap hours to minutes to avoid complications w/ stepping through, for example 9->10 is an hour step but market opens at 9:30
barSize = barSize == Resolution.Hour ? Resolution.Minute : barSize;
var barCount = (int)(period.Ticks / barSize.ToTimeSpan().Ticks);
var closeTimeUtc = ComputeCloseTime(exchangeHours, generatedTimeUtc, barSize, barCount);
if (closeTimeUtc == generatedTimeUtc)
{
return ComputeCloseTime(exchangeHours, generatedTimeUtc, Resolution.Second, 1);
}
var totalPeriodUsed = barSize.ToTimeSpan().Multiply(barCount);
if (totalPeriodUsed != period)
{
var delta = period - totalPeriodUsed;
// interpret the remainder as fractional trading days
if (barSize == Resolution.Daily)
{
var percentOfDay = delta.Ticks / (double) Time.OneDay.Ticks;
delta = exchangeHours.RegularMarketDuration.Multiply(percentOfDay);
}
if (delta != TimeSpan.Zero)
{
// continue stepping forward using minute resolution for the remainder
barCount = (int) (delta.Ticks / Time.OneMinute.Ticks);
if (barCount > 0)
{
closeTimeUtc = ComputeCloseTime(exchangeHours, closeTimeUtc, Resolution.Minute, barCount);
}
}
}
return closeTimeUtc;
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return Messages.Insight.ToString(this);
}
/// <summary>
/// Returns a short string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
public string ShortToString()
{
return Messages.Insight.ShortToString(this);
}
/// <summary>
/// Distinguishes between the different ways an insight's period/close times can be specified
/// This was really only required since we can't properly acces certain data from within a static
/// context (such as Insight.Price) or from within a constructor w/out requiring the users to properly
/// fetch the required data and supply it as an argument.
/// </summary>
private interface IPeriodSpecification
{
void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours);
}
/// <summary>
/// User defined the insight's period using a time span
/// </summary>
private class TimeSpanPeriodSpecification : IPeriodSpecification
{
public readonly TimeSpan Period;
public TimeSpanPeriodSpecification(TimeSpan period)
{
if (period == TimeSpan.Zero)
{
period = Time.OneSecond;
}
Period = period;
}
public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
{
insight.Period = Period;
insight.CloseTimeUtc = ComputeCloseTime(exchangeHours, insight.GeneratedTimeUtc, Period);
}
}
/// <summary>
/// User defined insight's period using a resolution and bar count
/// </summary>
private class ResolutionBarCountPeriodSpecification : IPeriodSpecification
{
public readonly Resolution Resolution;
public readonly int BarCount;
public ResolutionBarCountPeriodSpecification(Resolution resolution, int barCount)
{
if (resolution == Resolution.Tick)
{
resolution = Resolution.Second;
}
if (resolution == Resolution.Hour)
{
// remap hours to minutes to avoid errors w/ half hours, for example, 9:30 open
barCount *= 60;
resolution = Resolution.Minute;
}
Resolution = resolution;
BarCount = barCount;
}
public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
{
insight.CloseTimeUtc = ComputeCloseTime(exchangeHours, insight.GeneratedTimeUtc, Resolution, BarCount);
insight.Period = insight.CloseTimeUtc - insight.GeneratedTimeUtc;
}
}
/// <summary>
/// User defined the insight's local closing time
/// </summary>
private class CloseTimePeriodSpecification : IPeriodSpecification
{
public readonly DateTime CloseTimeLocal;
public CloseTimePeriodSpecification(DateTime closeTimeLocal)
{
CloseTimeLocal = closeTimeLocal;
}
public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
{
// Prevent close time to be defined to a date/time in closed market
var closeTimeLocal = exchangeHours.IsOpen(CloseTimeLocal, false)
? CloseTimeLocal
: exchangeHours.GetNextMarketOpen(CloseTimeLocal, false);
insight.CloseTimeUtc = closeTimeLocal.ConvertToUtc(exchangeHours.TimeZone);
if (insight.GeneratedTimeUtc > insight.CloseTimeUtc)
{
throw new ArgumentOutOfRangeException(nameof(closeTimeLocal), $"Insight closeTimeLocal must not be in the past.");
}
insight.Period = insight.CloseTimeUtc - insight.GeneratedTimeUtc;
}
}
/// <summary>
/// Special case for insights which close time is defined by a function
/// and want insights to expiry with calendar rules
/// </summary>
private class FuncPeriodSpecification : IPeriodSpecification
{
public readonly Func<DateTime, DateTime> _expiryFunc;
public FuncPeriodSpecification(Func<DateTime, DateTime> expiryFunc)
{
_expiryFunc = expiryFunc;
}
public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
{
var closeTimeLocal = insight.GeneratedTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
closeTimeLocal = _expiryFunc(closeTimeLocal);
// Prevent close time to be defined to a date/time in closed market
if (!exchangeHours.IsOpen(closeTimeLocal, false))
{
closeTimeLocal = exchangeHours.GetNextMarketOpen(closeTimeLocal, false);
}
insight.CloseTimeUtc = closeTimeLocal.ConvertToUtc(exchangeHours.TimeZone);
insight.Period = insight.CloseTimeUtc - insight.GeneratedTimeUtc;
}
}
/// <summary>
/// Special case for insights where we do not know whats the
/// <see cref="Period"/> or <see cref="CloseTimeUtc"/>.
/// </summary>
private class EndOfTimeCloseTimePeriodSpecification : IPeriodSpecification
{
public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
{
insight.Period = Time.EndOfTimeTimeSpan;
insight.CloseTimeUtc = Time.EndOfTime;
}
}
}
}
@@ -0,0 +1,395 @@
/*
* 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.Linq;
using Python.Runtime;
using System.Collections;
using System.Collections.Generic;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Provides a collection for managing insights. This type provides collection access semantics
/// as well as dictionary access semantics through TryGetValue, ContainsKey, and this[symbol]
/// </summary>
public class InsightCollection : IEnumerable<Insight>
{
private int _totalInsightCount;
private int _openInsightCount;
private readonly List<Insight> _insightsComplete = new();
private readonly Dictionary<Symbol, List<Insight>> _insights = new();
/// <summary>
/// The open insight count
/// </summary>
public int Count
{
get
{
lock (_insights)
{
return _openInsightCount;
}
}
}
/// <summary>
/// The total insight count
/// </summary>
public int TotalCount
{
get
{
lock (_insights)
{
return _totalInsightCount;
}
}
}
/// <summary>Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
public void Add(Insight item)
{
lock (_insights)
{
_openInsightCount++;
_totalInsightCount++;
_insightsComplete.Add(item);
if (!_insights.TryGetValue(item.Symbol, out var existingInsights))
{
_insights[item.Symbol] = existingInsights = new();
}
existingInsights.Add(item);
}
}
/// <summary>
/// Adds each item in the specified enumerable of insights to this collection
/// </summary>
/// <param name="insights">The insights to add to this collection</param>
public void AddRange(IEnumerable<Insight> insights)
{
foreach (var insight in insights)
{
Add(insight);
}
}
/// <summary>Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> contains a specific value.</summary>
/// <returns>true if <paramref name="item" /> is found in the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false.</returns>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
public bool Contains(Insight item)
{
lock(_insights)
{
return _insights.TryGetValue(item.Symbol, out var symbolInsights)
&& symbolInsights.Contains(item);
}
}
/// <summary>
/// Determines whether insights exist in this collection for the specified symbol
/// </summary>
/// <param name="symbol">The symbol key</param>
/// <returns>True if there are insights for the symbol in this collection</returns>
public bool ContainsKey(Symbol symbol)
{
lock (_insights)
{
return _insights.TryGetValue(symbol, out var symbolInsights)
&& symbolInsights.Count > 0;
}
}
/// <summary>Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <returns>true if <paramref name="item" /> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false. This method also returns false if <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
public bool Remove(Insight item)
{
lock (_insights)
{
if (_insights.TryGetValue(item.Symbol, out var symbolInsights))
{
if (symbolInsights.Remove(item))
{
_openInsightCount--;
// remove empty list from dictionary
if (symbolInsights.Count == 0)
{
_insights.Remove(item.Symbol);
}
return true;
}
}
}
return false;
}
/// <summary>
/// Dictionary accessor returns a list of insights for the specified symbol
/// </summary>
/// <param name="symbol">The symbol key</param>
/// <returns>List of insights for the symbol</returns>
public List<Insight> this[Symbol symbol]
{
get
{
lock(_insights)
{
return _insights[symbol]?.ToList();
}
}
set
{
lock (_insights)
{
if (_insights.TryGetValue(symbol, out var existingInsights))
{
_openInsightCount -= existingInsights?.Count ?? 0;
}
if (value != null)
{
_openInsightCount += value.Count;
_totalInsightCount += value.Count;
}
_insights[symbol] = value;
}
}
}
/// <summary>
/// Attempts to get the list of insights with the specified symbol key
/// </summary>
/// <param name="symbol">The symbol key</param>
/// <param name="insights">The insights for the specified symbol, or null if not found</param>
/// <returns>True if insights for the specified symbol were found, false otherwise</returns>
public bool TryGetValue(Symbol symbol, out List<Insight> insights)
{
lock (_insights)
{
var result = _insights.TryGetValue(symbol, out insights);
if (result)
{
// for thread safety we need to return a copy of the collection
insights = insights.ToList();
}
return result;
}
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.</returns>
/// <filterpriority>1</filterpriority>
public IEnumerator<Insight> GetEnumerator()
{
lock (_insights)
{
return _insights.SelectMany(kvp => kvp.Value).ToList().GetEnumerator();
}
}
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
/// <filterpriority>2</filterpriority>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Removes the symbol and its insights
/// </summary>
/// <param name="symbols">List of symbols that will be removed</param>
public void Clear(Symbol[] symbols)
{
lock (_insights)
{
foreach (var symbol in symbols)
{
if (_insights.Remove(symbol, out var existingInsights))
{
_openInsightCount -= existingInsights.Count;
}
}
}
}
/// <summary>
/// Gets the next expiry time UTC
/// </summary>
public DateTime? GetNextExpiryTime()
{
lock(_insights)
{
if (_openInsightCount == 0)
{
return null;
}
// we can't store expiration time because it can change
return _insights.Min(x => x.Value.Min(i => i.CloseTimeUtc));
}
}
/// <summary>
/// Gets the last generated active insight
/// </summary>
/// <returns>Collection of insights that are active</returns>
public ICollection<Insight> GetActiveInsights(DateTime utcTime)
{
var activeInsights = new List<Insight>();
lock (_insights)
{
foreach (var kvp in _insights)
{
foreach (var insight in kvp.Value)
{
if (insight.IsActive(utcTime))
{
activeInsights.Add(insight);
}
}
}
return activeInsights;
}
}
/// <summary>
/// Returns true if there are active insights for a given symbol and time
/// </summary>
/// <param name="symbol">The symbol key</param>
/// <param name="utcTime">Time that determines whether the insight has expired</param>
/// <returns></returns>
public bool HasActiveInsights(Symbol symbol, DateTime utcTime)
{
lock (_insights)
{
if(_insights.TryGetValue(symbol, out var existingInsights))
{
return existingInsights.Any(i => i.IsActive(utcTime));
}
}
return false;
}
/// <summary>
/// Remove all expired insights from the collection and retuns them
/// </summary>
/// <param name="utcTime">Time that determines whether the insight has expired</param>
/// <returns>Expired insights that were removed</returns>
public ICollection<Insight> RemoveExpiredInsights(DateTime utcTime)
{
var removedInsights = new List<Insight>();
lock (_insights)
{
foreach (var kvp in _insights)
{
foreach (var insight in kvp.Value)
{
if (insight.IsExpired(utcTime))
{
removedInsights.Add(insight);
}
}
}
foreach (var insight in removedInsights)
{
Remove(insight);
}
}
return removedInsights;
}
/// <summary>
/// Will remove insights from the complete insight collection
/// </summary>
/// <param name="filter">The function that will determine which insight to remove</param>
public void RemoveInsights(Func<Insight, bool> filter)
{
lock (_insights)
{
_insightsComplete.RemoveAll(insight => filter(insight));
// for consistentcy remove from open insights just in case
List<Insight> insightsToRemove = null;
foreach (var insights in _insights.Values)
{
foreach (var insight in insights)
{
if (filter(insight))
{
insightsToRemove ??= new ();
insightsToRemove.Add(insight);
}
}
}
if(insightsToRemove != null)
{
foreach (var insight in insightsToRemove)
{
Remove(insight);
}
}
}
}
/// <summary>
/// Will return insights from the complete insight collection
/// </summary>
/// <param name="filter">The function that will determine which insight to return</param>
/// <returns>A new list containing the selected insights</returns>
public List<Insight> GetInsights(Func<Insight, bool> filter = null)
{
lock (_insights)
{
if(filter == null)
{
return _insightsComplete.ToList();
}
return _insightsComplete.Where(filter).ToList();
}
}
/// <summary>
/// Will return insights from the complete insight collection
/// </summary>
/// <param name="filter">The function that will determine which insight to return</param>
/// <returns>A new list containing the selected insights</returns>
public List<Insight> GetInsights(PyObject filter)
{
Func<Insight, bool> convertedFilter;
if (filter.TrySafeAs(out convertedFilter))
{
return GetInsights(convertedFilter);
}
else
{
using (Py.GIL())
{
throw new ArgumentException($"InsightCollection.GetInsights: {filter.Repr()} is not a valid argument.");
}
}
}
}
}
@@ -0,0 +1,42 @@
/*
* 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 Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Specifies the predicted direction for a insight (price/volatility)
/// </summary>
[JsonConverter(typeof(StringEnumConverter), true)]
public enum InsightDirection
{
/// <summary>
/// The value will go down (-1)
/// </summary>
Down = -1,
/// <summary>
/// The value will stay flat (0)
/// </summary>
Flat = 0,
/// <summary>
/// The value will go up (1)
/// </summary>
Up = 1
}
}
@@ -0,0 +1,136 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Defines the scores given to a particular insight
/// </summary>
public class InsightScore
{
/// <summary>
/// Gets the time these scores were last updated
/// </summary>
[JsonProperty]
public DateTime UpdatedTimeUtc { get; private set; }
/// <summary>
/// Gets the direction score
/// </summary>
[JsonProperty]
public double Direction { get; private set; }
/// <summary>
/// Gets the magnitude score
/// </summary>
[JsonProperty]
public double Magnitude { get; private set; }
/// <summary>
/// Gets whether or not this is the insight's final score
/// </summary>
[JsonProperty]
public bool IsFinalScore { get; private set; }
/// <summary>
/// Initializes a new, default instance of the <see cref="InsightScore"/> class
/// </summary>
public InsightScore()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="InsightScore"/> class
/// </summary>
/// <param name="direction">The insight direction score</param>
/// <param name="magnitude">The insight percent change score</param>
/// <param name="updatedTimeUtc">The algorithm utc time these scores were computed</param>
public InsightScore(double direction, double magnitude, DateTime updatedTimeUtc)
{
Direction = direction;
Magnitude = magnitude;
UpdatedTimeUtc = updatedTimeUtc;
}
/// <summary>
/// Sets the specified score type with the value
/// </summary>
/// <param name="type">The score type to be set, Direction/Magnitude</param>
/// <param name="value">The new value for the score</param>
/// <param name="algorithmUtcTime">The algorithm's utc time at which time the new score was computed</param>
public void SetScore(InsightScoreType type, double value, DateTime algorithmUtcTime)
{
if (IsFinalScore) return;
UpdatedTimeUtc = algorithmUtcTime;
switch (type)
{
case InsightScoreType.Direction:
Direction = Math.Max(0, Math.Min(1, value));
break;
case InsightScoreType.Magnitude:
Magnitude = Math.Max(0, Math.Min(1, value));
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
/// <summary>
/// Marks the score as finalized, preventing any further updates.
/// </summary>
/// <param name="algorithmUtcTime">The algorithm's utc time at which time these scores were finalized</param>
public void Finalize(DateTime algorithmUtcTime)
{
IsFinalScore = true;
UpdatedTimeUtc = algorithmUtcTime;
}
/// <summary>
/// Gets the specified score
/// </summary>
/// <param name="type">The type of score to get, Direction/Magnitude</param>
/// <returns>The requested score</returns>
public double GetScore(InsightScoreType type)
{
switch (type)
{
case InsightScoreType.Direction:
return Direction;
case InsightScoreType.Magnitude:
return Magnitude;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return Messages.InsightScore.ToString(this);
}
}
}
@@ -0,0 +1,45 @@
/*
* 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 Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas.Analysis;
using QuantConnect.Python;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// A python implementation insight evaluator wrapper
/// </summary>
public class InsightScoreFunctionPythonWrapper : BasePythonWrapper<IInsightScoreFunction>, IInsightScoreFunction
{
/// <summary>
/// Creates a new python wrapper instance
/// </summary>
/// <param name="insightEvaluator">The python instance to wrap</param>
public InsightScoreFunctionPythonWrapper(PyObject insightEvaluator)
: base(insightEvaluator)
{
}
/// <summary>
/// Method to evaluate and score insights for each time step
/// </summary>
public void Score(InsightManager insightManager, DateTime utcTime)
{
InvokeMethod(nameof(Score), insightManager, utcTime);
}
}
}
@@ -0,0 +1,38 @@
/*
* 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 Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Defines a specific type of score for a insight
/// </summary>
[JsonConverter(typeof(StringEnumConverter), true)]
public enum InsightScoreType
{
/// <summary>
/// Directional accuracy (0)
/// </summary>
Direction,
/// <summary>
/// Magnitude accuracy (1)
/// </summary>
Magnitude
}
}
@@ -0,0 +1,37 @@
/*
* 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 Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Specifies the type of insight
/// </summary>
[JsonConverter(typeof(StringEnumConverter), true)]
public enum InsightType
{
/// <summary>
/// The insight is for a security's price (0)
/// </summary>
Price,
/// <summary>
/// The insight is for a security's price volatility (1)
/// </summary>
Volatility
}
}
@@ -0,0 +1,46 @@
/*
* 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 QuantConnect.Util;
namespace QuantConnect.Algorithm.Framework.Alphas.Serialization
{
/// <summary>
/// Defines how insights should be serialized to json
/// </summary>
public class InsightJsonConverter : TypeChangeJsonConverter<Insight, SerializedInsight>
{
/// <summary>
/// Convert the input value to a value to be serialized
/// </summary>
/// <param name="value">The input value to be converted before serialization</param>
/// <returns>A new instance of TResult that is to be serialized</returns>
protected override SerializedInsight Convert(Insight value)
{
return new SerializedInsight(value);
}
/// <summary>
/// Converts the input value to be deserialized
/// </summary>
/// <param name="value">The deserialized value that needs to be converted to T</param>
/// <returns>The converted value</returns>
protected override Insight Convert(SerializedInsight value)
{
return Insight.FromSerializedInsight(value);
}
}
}
@@ -0,0 +1,292 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Util;
using System;
namespace QuantConnect.Algorithm.Framework.Alphas.Serialization
{
/// <summary>
/// DTO used for serializing an insight that was just generated by an algorithm.
/// This type does not contain any of the analysis dependent fields, such as scores
/// and estimated value
/// </summary>
public class SerializedInsight
{
private double _createdTime;
/// <summary>
/// See <see cref="Insight.Id"/>
/// </summary>
public string Id { get; set; }
/// <summary>
/// See <see cref="Insight.GroupId"/>
/// </summary>
public string GroupId { get; set; }
/// <summary>
/// See <see cref="Insight.SourceModel"/>
/// </summary>
public string SourceModel { get; set; }
/// <summary>
/// Pass-through for <see cref="CreatedTime"/>
/// </summary>
[Obsolete("Deprecated as of 2020-01-23. Please use the `CreatedTime` property instead.")]
public double GeneratedTime
{
get { return _createdTime; }
set { _createdTime = value; }
}
/// <summary>
/// See <see cref="Insight.GeneratedTimeUtc"/>
/// </summary>
public double CreatedTime
{
get { return _createdTime; }
set { _createdTime = value; }
}
/// <summary>
/// See <see cref="Insight.CloseTimeUtc"/>
/// </summary>
public double CloseTime { get; set; }
/// <summary>
/// See <see cref="Insight.Symbol"/>
/// The symbol's security identifier string
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// See <see cref="Insight.Symbol"/>
/// The symbol's ticker at the generated time
/// </summary>
public string Ticker { get; set; }
/// <summary>
/// See <see cref="Insight.Type"/>
/// </summary>
public InsightType Type { get; set; }
/// <summary>
/// See <see cref="Insight.ReferenceValue"/>
/// </summary>
[JsonProperty("reference")]
public decimal ReferenceValue { get; set; }
/// <summary>
/// See <see cref="Insight.ReferenceValueFinal"/>
/// </summary>
public decimal ReferenceValueFinal { get; set; }
/// <summary>
/// See <see cref="Insight.Direction"/>
/// </summary>
public InsightDirection Direction { get; set; }
/// <summary>
/// See <see cref="Insight.Period"/>
/// </summary>
public double Period { get; set; }
/// <summary>
/// See <see cref="Insight.Magnitude"/>
/// </summary>
[JsonConverter(typeof(JsonRoundingConverter))]
public double? Magnitude { get; set; }
/// <summary>
/// See <see cref="Insight.Confidence"/>
/// </summary>
[JsonConverter(typeof(JsonRoundingConverter))]
public double? Confidence { get; set; }
/// <summary>
/// See <see cref="Insight.Weight"/>
/// </summary>
public double? Weight { get; set; }
/// <summary>
/// See <see cref="InsightScore.IsFinalScore"/>
/// </summary>
public bool ScoreIsFinal { get; set; }
/// <summary>
/// See <see cref="InsightScore.Magnitude"/>
/// </summary>
[JsonConverter(typeof(JsonRoundingConverter))]
public double ScoreMagnitude { get; set; }
/// <summary>
/// See <see cref="InsightScore.Direction"/>
/// </summary>
[JsonConverter(typeof(JsonRoundingConverter))]
public double ScoreDirection { get; set; }
/// <summary>
/// See <see cref="Insight.EstimatedValue"/>
/// </summary>
[JsonConverter(typeof(JsonRoundingConverter))]
public decimal EstimatedValue { get; set; }
/// <summary>
/// See <see cref="Insight.Tag"/>
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Initializes a new default instance of the <see cref="SerializedInsight"/> class
/// </summary>
public SerializedInsight()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SerializedInsight "/> class by copying the specified insight
/// </summary>
/// <param name="insight">The insight to copy</param>
public SerializedInsight(Insight insight)
{
Id = insight.Id.ToStringInvariant("N");
SourceModel = insight.SourceModel;
GroupId = insight.GroupId?.ToStringInvariant("N");
CreatedTime = Time.DateTimeToUnixTimeStamp(insight.GeneratedTimeUtc);
CloseTime = Time.DateTimeToUnixTimeStamp(insight.CloseTimeUtc);
Symbol = insight.Symbol.ID.ToString();
Ticker = insight.Symbol.Value;
Type = insight.Type;
ReferenceValue = insight.ReferenceValue;
ReferenceValueFinal = insight.ReferenceValueFinal;
Direction = insight.Direction;
Period = insight.Period.TotalSeconds;
Magnitude = insight.Magnitude;
Confidence = insight.Confidence;
ScoreIsFinal = insight.Score.IsFinalScore;
ScoreMagnitude = insight.Score.Magnitude;
ScoreDirection = insight.Score.Direction;
EstimatedValue = insight.EstimatedValue;
Weight = insight.Weight;
Tag = insight.Tag;
}
#region BackwardsCompatibility
[JsonProperty("group-id")]
string OldGroupId
{
set
{
GroupId = value;
}
}
[JsonProperty("source-model")]
string OldSourceModel
{
set
{
SourceModel = value;
}
}
/// <summary>
/// Pass-through for <see cref="CreatedTime"/>
/// </summary>
[JsonProperty("generated-time")]
double OldGeneratedTime
{
set
{
GeneratedTime = value;
}
}
/// <summary>
/// See <see cref="Insight.GeneratedTimeUtc"/>
/// </summary>
[JsonProperty("created-time")]
public double OldCreatedTime
{
set
{
CreatedTime = value;
}
}
/// <summary>
/// See <see cref="Insight.CloseTimeUtc"/>
/// </summary>
[JsonProperty("close-time")]
public double OldCloseTime
{
set
{
CloseTime = value;
}
}
[JsonProperty("reference-final")]
decimal OldReferenceValueFinal
{
set
{
ReferenceValueFinal = value;
}
}
[JsonProperty("score-final")]
bool OldScoreIsFinal
{
set
{
ScoreIsFinal = value;
}
}
[JsonProperty("score-magnitude")]
[JsonConverter(typeof(JsonRoundingConverter))]
double OldScoreMagnitude
{
set
{
ScoreMagnitude = value;
}
}
[JsonProperty("score-direction")]
[JsonConverter(typeof(JsonRoundingConverter))]
double OldScoreDirection
{
set
{
ScoreDirection = value;
}
}
[JsonProperty("estimated-value")]
[JsonConverter(typeof(JsonRoundingConverter))]
decimal OldEstimatedValue
{
set
{
EstimatedValue = value;
}
}
#endregion
}
}
@@ -0,0 +1,39 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Represents a portfolio target. This may be a percentage of total portfolio value
/// or it may be a fixed number of shares.
/// </summary>
public interface IPortfolioTarget
{
/// <summary>
/// Gets the symbol of this target
/// </summary>
Symbol Symbol { get; }
/// <summary>
/// Gets the quantity of this symbol the algorithm should hold
/// </summary>
decimal Quantity { get; }
/// <summary>
/// Portfolio target tag with additional information
/// </summary>
string Tag { get; }
}
}
@@ -0,0 +1,203 @@
/*
* 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 QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Securities.Positions;
using QuantConnect.Algorithm.Framework.Alphas;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of <see cref="IPortfolioTarget"/> that specifies a
/// specified quantity of a security to be held by the algorithm
/// </summary>
public class PortfolioTarget : IPortfolioTarget
{
/// <summary>
/// Flag to determine if the minimum order margin portfolio percentage warning should or has already been sent to the user algorithm
/// <see cref="IAlgorithmSettings.MinimumOrderMarginPortfolioPercentage"/>
/// </summary>
public static bool? MinimumOrderMarginPercentageWarningSent { get; set; }
/// <summary>
/// Gets the symbol of this target
/// </summary>
public Symbol Symbol { get; }
/// <summary>
/// Gets the target quantity for the symbol
/// </summary>
public decimal Quantity { get; }
/// <summary>
/// Portfolio target tag with additional information
/// </summary>
public string Tag { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PortfolioTarget"/> class
/// </summary>
/// <param name="symbol">The symbol this target is for</param>
/// <param name="quantity">The target quantity</param>
/// <param name="tag">The target tag with additional information</param>
public PortfolioTarget(Symbol symbol, decimal quantity, string tag = "")
{
Symbol = symbol;
Quantity = quantity;
Tag = tag;
}
/// <summary>
/// Initializes a new instance of the <see cref="PortfolioTarget"/> class
/// </summary>
/// <param name="symbol">The symbol this target is for</param>
/// <param name="quantity">The target quantity</param>
/// <param name="tag">The target tag with additional information</param>
public PortfolioTarget(Symbol symbol, int quantity, string tag = "")
: this(symbol, (decimal)quantity, tag)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PortfolioTarget"/> class
/// </summary>
/// <param name="symbol">The symbol this target is for</param>
/// <param name="insightDirection">
/// The insight direction, which will be used to calculate the target quantity
/// (1 for Up, 0 for flat, -1 for down)
/// </param>
/// <param name="tag">The target tag with additional information</param>
public PortfolioTarget(Symbol symbol, InsightDirection insightDirection, string tag = "")
: this(symbol,
insightDirection switch
{
InsightDirection.Up => 1m,
InsightDirection.Down => -1m,
InsightDirection.Flat => 0m,
_ => throw new ArgumentOutOfRangeException(nameof(insightDirection), insightDirection,
Messages.PortfolioTarget.InvalidInsightDirection(symbol, insightDirection)),
},
tag)
{
}
/// <summary>
/// Creates a new target for the specified percent
/// </summary>
/// <param name="algorithm">The algorithm instance, used for getting total portfolio value and current security price</param>
/// <param name="symbol">The symbol the target is for</param>
/// <param name="percent">The requested target percent of total portfolio value</param>
/// <returns>A portfolio target for the specified symbol/percent</returns>
public static IPortfolioTarget Percent(IAlgorithm algorithm, Symbol symbol, double percent)
{
return Percent(algorithm, symbol, percent.SafeDecimalCast());
}
/// <summary>
/// Creates a new target for the specified percent
/// </summary>
/// <param name="algorithm">The algorithm instance, used for getting total portfolio value and current security price</param>
/// <param name="symbol">The symbol the target is for</param>
/// <param name="percent">The requested target percent of total portfolio value</param>
/// <param name="tag">The target tag with additional information</param>
/// <returns>A portfolio target for the specified symbol/percent</returns>
public static IPortfolioTarget Percent(IAlgorithm algorithm, Symbol symbol, double percent, string tag)
{
return Percent(algorithm, symbol, percent.SafeDecimalCast(), tag: tag);
}
/// <summary>
/// Creates a new target for the specified percent
/// </summary>
/// <param name="algorithm">The algorithm instance, used for getting total portfolio value and current security price</param>
/// <param name="symbol">The symbol the target is for</param>
/// <param name="percent">The requested target percent of total portfolio value</param>
/// <param name="returnDeltaQuantity">True, result quantity will be the Delta required to reach target percent.
/// False, the result quantity will be the Total quantity to reach the target percent, including current holdings</param>
/// <param name="tag">The target tag with additional information</param>
/// <returns>A portfolio target for the specified symbol/percent</returns>
public static IPortfolioTarget Percent(IAlgorithm algorithm, Symbol symbol, decimal percent, bool returnDeltaQuantity = false, string tag = "")
{
var absolutePercentage = Math.Abs(percent);
if (absolutePercentage > algorithm.Settings.MaxAbsolutePortfolioTargetPercentage
|| absolutePercentage != 0 && absolutePercentage < algorithm.Settings.MinAbsolutePortfolioTargetPercentage)
{
algorithm.Error(Messages.PortfolioTarget.InvalidTargetPercent(algorithm, percent));
return null;
}
Security security;
try
{
security = algorithm.Securities[symbol];
}
catch (KeyNotFoundException)
{
algorithm.Error(Messages.PortfolioTarget.SymbolNotFound(symbol));
return null;
}
if (security.Price == 0)
{
algorithm.Error(symbol.GetZeroPriceMessage());
return null;
}
// Factoring in FreePortfolioValuePercentage.
var adjustedPercent = percent * algorithm.Portfolio.TotalPortfolioValueLessFreeBuffer / algorithm.Portfolio.TotalPortfolioValue;
// we normalize the target buying power by the leverage so we work in the land of margin
var targetFinalMarginPercentage = adjustedPercent / security.BuyingPowerModel.GetLeverage(security);
var positionGroup = algorithm.Portfolio.Positions.GetOrCreateDefaultGroup(security);
var result = positionGroup.BuyingPowerModel.GetMaximumLotsForTargetBuyingPower(
new GetMaximumLotsForTargetBuyingPowerParameters(algorithm.Portfolio, positionGroup,
targetFinalMarginPercentage, algorithm.Settings.MinimumOrderMarginPortfolioPercentage));
if (result.IsError)
{
algorithm.Error(Messages.PortfolioTarget.UnableToComputeOrderQuantityDueToNullResult(symbol, result));
return null;
}
if (MinimumOrderMarginPercentageWarningSent.HasValue && !MinimumOrderMarginPercentageWarningSent.Value)
{
// we send the warning once
MinimumOrderMarginPercentageWarningSent = true;
algorithm.Debug(Messages.BuyingPowerModel.TargetOrderMarginNotAboveMinimum());
}
// be sure to back out existing holdings quantity since the buying power model yields
// the required delta quantity to reach a final target portfolio value for a symbol
var lotSize = security.SymbolProperties.LotSize;
var quantity = result.NumberOfLots * lotSize + (returnDeltaQuantity ? 0 : security.Holdings.Quantity);
return new PortfolioTarget(symbol, quantity, tag);
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return Messages.PortfolioTarget.ToString(this);
}
}
}
@@ -0,0 +1,435 @@
/*
* 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.Linq;
using System.Collections;
using QuantConnect.Interfaces;
using System.Collections.Generic;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides a collection for managing <see cref="IPortfolioTarget"/>s for each symbol
/// </summary>
public class PortfolioTargetCollection : ICollection<IPortfolioTarget>, IDictionary<Symbol, IPortfolioTarget>
{
private List<IPortfolioTarget> _enumerable;
private List<KeyValuePair<Symbol, IPortfolioTarget>> _kvpEnumerable;
private readonly Dictionary<Symbol, IPortfolioTarget> _targets = new ();
/// <summary>
/// Gets the number of targets in this collection
/// </summary>
public int Count
{
get
{
lock (_targets)
{
return _targets.Count;
}
}
}
/// <summary>
/// True if there is no target in the collection
/// </summary>
public bool IsEmpty
{
get
{
lock (_targets)
{
return _targets.Count == 0;
}
}
}
/// <summary>
/// Gets `false`. This collection is not read-only.
/// </summary>
public bool IsReadOnly => false;
/// <summary>
/// Gets the symbol keys for this collection
/// </summary>
public ICollection<Symbol> Keys
{
get
{
lock (_targets)
{
return _targets.Keys.ToList();
}
}
}
/// <summary>
/// Gets all portfolio targets in this collection
/// Careful, will return targets for securities that might have no data yet.
/// </summary>
public ICollection<IPortfolioTarget> Values
{
get
{
var result = _enumerable;
if (result == null)
{
lock (_targets)
{
result = _enumerable = _targets.Values.ToList();
}
}
return result;
}
}
/// <summary>
/// Adds the specified target to the collection. If a target for the same symbol
/// already exists it wil be overwritten.
/// </summary>
/// <param name="target">The portfolio target to add</param>
public void Add(IPortfolioTarget target)
{
if (target == null)
{
return;
}
lock (_targets)
{
_enumerable = null;
_kvpEnumerable = null;
_targets[target.Symbol] = target;
}
}
/// <summary>
/// Adds the specified target to the collection. If a target for the same symbol
/// already exists it wil be overwritten.
/// </summary>
/// <param name="target">The portfolio target to add</param>
public void Add(KeyValuePair<Symbol, IPortfolioTarget> target)
{
Add(target);
}
/// <summary>
/// Adds the specified target to the collection. If a target for the same symbol
/// already exists it wil be overwritten.
/// </summary>
/// <param name="symbol">The symbol key</param>
/// <param name="target">The portfolio target to add</param>
public void Add(Symbol symbol, IPortfolioTarget target)
{
Add(target);
}
/// <summary>
/// Adds the specified targets to the collection. If a target for the same symbol
/// already exists it will be overwritten.
/// </summary>
/// <param name="targets">The portfolio targets to add</param>
public void AddRange(IEnumerable<IPortfolioTarget> targets)
{
lock (_targets)
{
_enumerable = null;
_kvpEnumerable = null;
foreach (var item in targets)
{
_targets[item.Symbol] = item;
}
}
}
/// <summary>
/// Adds the specified targets to the collection. If a target for the same symbol
/// already exists it will be overwritten.
/// </summary>
/// <param name="targets">The portfolio targets to add</param>
public void AddRange(IPortfolioTarget[] targets)
{
AddRange((IEnumerable<IPortfolioTarget>)targets);
}
/// <summary>
/// Removes all portfolio targets from this collection
/// </summary>
public void Clear()
{
lock (_targets)
{
_enumerable = null;
_kvpEnumerable = null;
_targets.Clear();
}
}
/// <summary>
/// Removes fulfilled portfolio targets from this collection.
/// Will only take into account actual holdings and ignore open orders.
/// </summary>
public void ClearFulfilled(IAlgorithm algorithm)
{
foreach (var target in this)
{
var security = algorithm.Securities[target.Symbol];
var holdings = security.Holdings.Quantity;
// check to see if we're done with this target
if (Math.Abs(target.Quantity - holdings) < security.SymbolProperties.LotSize)
{
Remove(target.Symbol);
}
}
}
/// <summary>
/// Determines whether or not the specified target exists in this collection.
/// NOTE: This checks for the exact specified target, not by symbol. Use ContainsKey
/// to check by symbol.
/// </summary>
/// <param name="target">The portfolio target to check for existence.</param>
/// <returns>True if the target exists, false otherwise</returns>
public bool Contains(IPortfolioTarget target)
{
if (target == null)
{
return false;
}
lock (_targets)
{
return _targets.ContainsKey(target.Symbol);
}
}
/// <summary>
/// Determines whether the specified symbol/target pair exists in this collection
/// </summary>
/// <param name="target">The symbol/target pair</param>
/// <returns>True if the pair exists, false otherwise</returns>
public bool Contains(KeyValuePair<Symbol, IPortfolioTarget> target)
{
return Contains(target);
}
/// <summary>
/// Determines whether the specified symbol exists as a key in this collection
/// </summary>
/// <param name="symbol">The symbol key</param>
/// <returns>True if the symbol exists in this collection, false otherwise</returns>
public bool ContainsKey(Symbol symbol)
{
lock (_targets)
{
return _targets.ContainsKey(symbol);
}
}
/// <summary>
/// Copies the targets in this collection to the specified array
/// </summary>
/// <param name="array">The destination array to copy to</param>
/// <param name="arrayIndex">The index in the array to start copying to</param>
public void CopyTo(IPortfolioTarget[] array, int arrayIndex)
{
lock (_targets)
{
_targets.Values.CopyTo(array, arrayIndex);
}
}
/// <summary>
/// Copies the targets in this collection to the specified array
/// </summary>
/// <param name="array">The destination array to copy to</param>
/// <param name="arrayIndex">The index in the array to start copying to</param>
public void CopyTo(KeyValuePair<Symbol, IPortfolioTarget>[] array, int arrayIndex)
{
WithDictionary(d => d.CopyTo(array, arrayIndex));
}
/// <summary>
/// Removes the target for the specified symbol if it exists in this collection.
/// </summary>
/// <param name="symbol">The symbol to remove</param>
/// <returns>True if the symbol's target was removed, false if it doesn't exist in the collection</returns>
public bool Remove(Symbol symbol)
{
lock (_targets)
{
if (_targets.Remove(symbol))
{
_enumerable = null;
_kvpEnumerable = null;
return true;
}
return false;
}
}
/// <summary>
/// Removes the target for the specified symbol/target pair if it exists in this collection.
/// </summary>
/// <param name="target">The symbol/target pair to remove</param>
/// <returns>True if the symbol's target was removed, false if it doesn't exist in the collection</returns>
public bool Remove(KeyValuePair<Symbol, IPortfolioTarget> target)
{
return Remove(target.Value);
}
/// <summary>
/// Removes the target if it exists in this collection.
/// </summary>
/// <param name="target">The target to remove</param>
/// <returns>True if the target was removed, false if it doesn't exist in the collection</returns>
public bool Remove(IPortfolioTarget target)
{
if (target == null)
{
return false;
}
lock (_targets)
{
IPortfolioTarget existing;
if (_targets.TryGetValue(target.Symbol, out existing))
{
// need to confirm that we're removing the requested target and not a different target w/ the same symbol key
if (existing.Equals(target))
{
return Remove(target.Symbol);
}
}
}
return false;
}
/// <summary>
/// Attempts to retrieve the target for the specified symbol
/// </summary>
/// <param name="symbol">The symbol</param>
/// <param name="target">The portfolio target for the symbol, or null if not found</param>
/// <returns>True if the symbol's target was found, false if it does not exist in this collection</returns>
public bool TryGetValue(Symbol symbol, out IPortfolioTarget target)
{
lock (_targets)
{
return _targets.TryGetValue(symbol, out target);
}
}
/// <summary>
/// Gets or sets the portfolio target for the specified symbol
/// </summary>
/// <param name="symbol">The symbol</param>
/// <returns>The symbol's portfolio target if it exists in this collection, if not a <see cref="KeyNotFoundException"/> will be thrown.</returns>
public IPortfolioTarget this[Symbol symbol]
{
get
{
lock (_targets)
{
return _targets[symbol];
}
}
set
{
lock (_targets)
{
_enumerable = null;
_kvpEnumerable = null;
_targets[symbol] = value;
}
}
}
/// <summary>
/// Gets an enumerator to iterator over the symbol/target key value pairs in this collection.
/// </summary>
/// <returns>Symbol/target key value pair enumerator</returns>
IEnumerator<KeyValuePair<Symbol, IPortfolioTarget>> IEnumerable<KeyValuePair<Symbol, IPortfolioTarget>>.GetEnumerator()
{
var result = _kvpEnumerable;
if (result == null)
{
lock (_targets)
{
_kvpEnumerable = result = _targets.ToList();
}
}
return result.GetEnumerator();
}
/// <summary>
/// Gets an enumerator to iterator over all portfolio targets in this collection.
/// This is the default enumerator for this collection.
/// </summary>
/// <returns>Portfolio targets enumerator</returns>
public IEnumerator<IPortfolioTarget> GetEnumerator()
{
var result = _enumerable;
if (result == null)
{
lock (_targets)
{
_enumerable = result = _targets.Values.ToList();
}
}
return result.GetEnumerator();
}
/// <summary>
/// Gets an enumerator to iterator over all portfolio targets in this collection.
/// This is the default enumerator for this collection.
/// Careful, will return targets for securities that might have no data yet.
/// </summary>
/// <returns>Portfolio targets enumerator</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Helper function to easily access explicitly implemented interface methods against concurrent dictionary
/// </summary>
private void WithDictionary(Action<IDictionary<Symbol, IPortfolioTarget>> action)
{
lock (_targets)
{
action(_targets);
}
}
/// <summary>
/// Returned 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
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
public IEnumerable<IPortfolioTarget> OrderByMarginImpact(IAlgorithm algorithm)
{
if (IsEmpty)
{
return Enumerable.Empty<IPortfolioTarget>();
}
return this.OrderTargetsByMarginImpact(algorithm);
}
}
}
@@ -0,0 +1,115 @@
/*
* 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 QuantConnect.Interfaces;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
using System.Net.Http;
namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports
{
/// <summary>
/// Base class to send signals to different 3rd party API's
/// </summary>
public abstract class BaseSignalExport : ISignalExportTarget
{
/// <summary>
/// Lazy initialization of HttpClient to be used to sent signals to different 3rd party API's
/// </summary>
private Lazy<HttpClient> _lazyClient = new Lazy<HttpClient>();
/// <summary>
/// List of all SecurityTypes present in LEAN
/// </summary>
private HashSet<SecurityType> _defaultAllowedSecurityTypes = new HashSet<SecurityType>
{
SecurityType.Equity,
SecurityType.Forex,
SecurityType.Option,
SecurityType.Future,
SecurityType.FutureOption,
SecurityType.Crypto,
SecurityType.CryptoFuture,
SecurityType.Cfd,
SecurityType.IndexOption,
};
/// <summary>
/// The name of this signal export
/// </summary>
protected abstract string Name { get; }
/// <summary>
/// Property to access a HttpClient
/// </summary>
protected HttpClient HttpClient => _lazyClient.Value;
/// <summary>
/// Default hashset of allowed Security types
/// </summary>
protected virtual HashSet<SecurityType> AllowedSecurityTypes
{
get => _defaultAllowedSecurityTypes;
}
/// <summary>
/// Sends positions to different 3rd party API's
/// </summary>
/// <param name="parameters">Holdings the user have defined to be sent to certain 3rd party API and the algorithm being ran</param>
/// <returns>True if the positions were sent correctly and the 3rd party API sent no errors. False, otherwise</returns>
public virtual bool Send(SignalExportTargetParameters parameters)
{
if (parameters.Targets.Count == 0)
{
parameters.Algorithm.Debug("Portfolio target is empty");
return false;
}
return VerifyTargets(parameters);
}
/// <summary>
/// Verifies the security type of every holding in the given list is allowed
/// </summary>
/// <param name="parameters">Holdings the user have defined to be sent to certain 3rd party API and the algorithm being ran</param>
/// <returns>True if all the targets were allowed, false otherwise</returns>
private bool VerifyTargets(SignalExportTargetParameters parameters)
{
foreach (var signal in parameters.Targets)
{
if (!AllowedSecurityTypes.Contains(signal.Symbol.SecurityType))
{
parameters.Algorithm.Debug($"{signal.Symbol.SecurityType} security type is not supported by {Name}. Allowed security types: [{string.Join(",", AllowedSecurityTypes)}]");
return false;
}
}
return true;
}
/// <summary>
/// If created, dispose of HttpClient we used for the requests to the different 3rd party API's
/// </summary>
public void Dispose()
{
if (_lazyClient.IsValueCreated)
{
_lazyClient.Value.DisposeSafely();
}
}
}
}
@@ -0,0 +1,633 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports
{
/// <summary>
/// Exports signals of desired positions to Collective2 API using JSON and HTTPS.
/// Accepts signals in quantity(number of shares) i.e symbol:"SPY", quant:40
/// </summary>
public class Collective2SignalExport : BaseSignalExport
{
/// <summary>
/// Hashset of symbols whose market is unknown but have already been seen by
/// this signal export manager
/// </summary>
private HashSet<string> _unknownMarketSymbols;
/// <summary>
/// Hashset of security types seen that are unsupported by C2 API
/// </summary>
private HashSet<SecurityType> _unknownSecurityTypes;
/// <summary>
/// API key provided by Collective2
/// </summary>
private readonly string _apiKey;
/// <summary>
/// Trading system's ID number
/// </summary>
private readonly int _systemId;
/// <summary>
/// Algorithm being ran
/// </summary>
private IAlgorithm _algorithm;
/// <summary>
/// C2 accepts only standard minilots (10,000 currency units).
/// The smallest quantity C2 trades is "1" which is a mini-lot.
/// Thus, the smallest trade a strategy manager can type into C2 is, for example,
/// MarketOrder("EURUSD", 1m)
/// which will trade 10,000 Euros.
/// No fractions nor numbers smaller than 1 are accepted by C2.
/// https://support.collective2.com/hc/en-us/articles/360038042774-Forex-minilots
/// </summary>
private const int _forexMinilots = 10000;
/// <summary>
/// Flag to track if the minilot warning has already been printed.
/// </summary>
private bool _isForexMinilotsWarningPrinted;
/// <summary>
/// Flag to track if the warning has already been printed.
/// </summary>
private bool _isZeroPriceWarningPrinted;
/// <summary>
/// Collective2 API endpoint
/// </summary>
public Uri Destination { get; set; }
/// <summary>
/// The name of this signal export
/// </summary>
protected override string Name { get; } = "Collective2";
/// <summary>
/// Lazy initialization of Symbol Properties Database
/// </summary>
private static Lazy<SymbolPropertiesDatabase> _symbolPropertiesDatabase = new (() => SymbolPropertiesDatabase.FromDataFolder());
/// <summary>
/// Lazy initialization of ten seconds rate limiter
/// </summary>
private static Lazy<RateGate> _tenSecondsRateLimiter = new Lazy<RateGate>(() => new RateGate(100, TimeSpan.FromMilliseconds(1000)));
/// <summary>
/// Lazy initialization of one hour rate limiter
/// </summary>
private static Lazy<RateGate> _hourlyRateLimiter = new Lazy<RateGate>(() => new RateGate(1000, TimeSpan.FromHours(1)));
/// <summary>
/// Lazy initialization of one day rate limiter
/// </summary>
private static Lazy<RateGate> _dailyRateLimiter = new Lazy<RateGate>(() => new RateGate(20000, TimeSpan.FromDays(1)));
/// <summary>
/// Collective2SignalExport constructor. It obtains the entry information for Collective2 API requests.
/// See API documentation at https://trade.collective2.com/c2-api
/// </summary>
/// <param name="apiKey">API key provided by Collective2</param>
/// <param name="systemId">Trading system's ID number</param>
/// <param name="useWhiteLabelApi">Whether to use the white-label API instead of the general one</param>
public Collective2SignalExport(string apiKey, int systemId, bool useWhiteLabelApi = false)
{
_unknownMarketSymbols = new HashSet<string>();
_unknownSecurityTypes = new HashSet<SecurityType>();
_apiKey = apiKey;
_systemId = systemId;
// SetDesiredPositions: The list of positions that must exist in the strategy.
// https://api-docs.collective2.com/apis/general/swagger/strategies/c2_api_strategies/setdesiredpositions_post#strategies/c2_api_strategies/setdesiredpositions_post/t=request&path=positions
Destination = new Uri(useWhiteLabelApi
? "https://api4-wl.collective2.com/Strategies/SetDesiredPositions"
: "https://api4-general.collective2.com/Strategies/SetDesiredPositions");
}
/// <summary>
/// Creates a JSON message with the desired positions using the expected
/// Collective2 API format and then sends it
/// </summary>
/// <param name="parameters">A list of holdings from the portfolio
/// expected to be sent to Collective2 API and the algorithm being ran</param>
/// <returns>True if the positions were sent correctly and Collective2 sent no errors, false otherwise</returns>
public override bool Send(SignalExportTargetParameters parameters)
{
if (!base.Send(parameters))
{
return false;
}
if (!ConvertHoldingsToCollective2(parameters, out List<Collective2Position> positions))
{
return false;
}
var message = CreateMessage(positions);
_tenSecondsRateLimiter.Value.WaitToProceed();
_hourlyRateLimiter.Value.WaitToProceed();
_dailyRateLimiter.Value.WaitToProceed();
var result = SendPositions(message);
return result;
}
/// <summary>
/// Converts a list of targets to a list of Collective2 positions
/// </summary>
/// <param name="parameters">A list of targets from the portfolio
/// expected to be sent to Collective2 API and the algorithm being ran</param>
/// <param name="positions">A list of Collective2 positions</param>
/// <returns>True if the given targets could be converted to a Collective2Position list, false otherwise</returns>
protected bool ConvertHoldingsToCollective2(SignalExportTargetParameters parameters, out List<Collective2Position> positions)
{
_algorithm = parameters.Algorithm;
var targets = parameters.Targets;
positions = [];
foreach (var target in targets)
{
if (target == null)
{
_algorithm.Error("One portfolio target was null");
return false;
}
var securityType = GetSecurityTypeAcronym(target.Symbol.SecurityType);
if (securityType == null)
{
continue;
}
var maturityMonthYear = GetMaturityMonthYear(target.Symbol);
if (maturityMonthYear?.Length == 0)
{
continue;
}
var exchangeSymbol = new C2ExchangeSymbol
{
Symbol = GetSymbol(target.Symbol),
Currency = GetCurrency(_algorithm, target.Symbol),
SecurityExchange = GetMICExchangeCode(target.Symbol),
SecurityType = securityType,
MaturityMonthYear = maturityMonthYear,
PutOrCall = GetPutOrCallValue(target.Symbol),
StrikePrice = GetStrikePrice(target.Symbol)
};
// Quantity must be non-zero.
// To close a position, simply omit it from the Positions array.
var quantity = ConvertPercentageToQuantity(_algorithm, target);
if (quantity == 0)
{
continue;
}
positions.Add(new() { ExchangeSymbol = exchangeSymbol, Quantity = quantity });
}
return true;
}
/// <summary>
/// Converts a given percentage of a position into the number of shares of it
/// </summary>
/// <param name="algorithm">Algorithm being ran</param>
/// <param name="target">Desired position to be sent to the Collective2 API</param>
/// <returns>Number of shares hold of the given position</returns>
protected int ConvertPercentageToQuantity(IAlgorithm algorithm, PortfolioTarget target)
{
var numberShares = PortfolioTarget.Percent(algorithm, target.Symbol, target.Quantity);
if (numberShares == null)
{
if (algorithm.Securities.TryGetValue(target.Symbol, out var security) && security.Price == 0 && target.Quantity == 0)
{
if (!_isZeroPriceWarningPrinted)
{
_isZeroPriceWarningPrinted = true;
algorithm.Debug($"Warning: Collective2 failed to calculate target quantity for {target}. The price for {target.Symbol} is 0, and the target quantity is 0. Will return 0 for all similar cases.");
}
return 0;
}
throw new InvalidOperationException($"Collective2 failed to calculate target quantity for {target}");
}
var quantity = (int)numberShares.Quantity;
if (target.Symbol.ID.SecurityType == SecurityType.Forex)
{
quantity /= _forexMinilots;
if (!_isForexMinilotsWarningPrinted && quantity == 0)
{
_isForexMinilotsWarningPrinted = true;
algorithm.Debug($"Warning: Collective2 failed to calculate target quantity for {target}. The smallest quantity C2 trades is \"1\" which is a mini-lot (10,000 currency units), and the target quantity is {numberShares.Quantity}. Will return 0 for all similar cases.");
}
}
return quantity;
}
/// <summary>
/// Serializes the list of desired positions with the needed credentials in JSON format
/// </summary>
/// <param name="positions">List of Collective2 positions to be sent to Collective2 API</param>
/// <returns>A JSON request string of the desired positions to be sent by a POST request to Collective2 API</returns>
protected string CreateMessage(List<Collective2Position> positions)
{
var payload = new
{
StrategyId = _systemId,
Positions = positions,
};
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
var jsonMessage = JsonConvert.SerializeObject(payload, settings);
return jsonMessage;
}
/// <summary>
/// Sends the desired positions list in JSON format to Collective2 API using a POST request. It logs
/// the message retrieved by the Collective2 API if there was a HttpRequestException
/// </summary>
/// <param name="message">A JSON request string of the desired positions list with the credentials</param>
/// <returns>True if the positions were sent correctly and Collective2 API sent no errors, false otherwise</returns>
private bool SendPositions(string message)
{
using var httpMessage = new StringContent(message, Encoding.UTF8, "application/json");
//Add the QuantConnect app header
httpMessage.Headers.Add("X-AppId", "OPA1N90E71");
//Add the Authorization header
HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _apiKey);
//Send the message
using HttpResponseMessage response = HttpClient.PostAsync(Destination, httpMessage).Result;
//Parse it
var responseObject = response.Content.ReadFromJsonAsync<C2Response>().Result;
//For debugging purposes, append the message sent to Collective2 to the algorithms log
var debuggingMessage = Logging.Log.DebuggingEnabled ? $" | Message={message}" : string.Empty;
if (!response.IsSuccessStatusCode)
{
_algorithm.Error($"Collective2 API returned the following errors: {string.Join(",", PrintErrors(responseObject.ResponseStatus.Errors))}{debuggingMessage}");
return false;
}
else if (responseObject.Results.Count > 0)
{
_algorithm.Debug($"Collective2: NewSignals={string.Join(',', responseObject.Results[0].NewSignals)} | CanceledSignals={string.Join(',', responseObject.Results[0].CanceledSignals)}{debuggingMessage}");
}
return true;
}
private static string PrintErrors(List<ResponseError> errors)
{
if (errors?.Count == 0)
{
return "NULL";
}
StringBuilder sb = new StringBuilder();
foreach (var error in errors)
{
sb.AppendLine(CultureInfo.InvariantCulture, $"({error.ErrorCode}) {error.FieldName}: {error.Message}");
}
return sb.ToString();
}
/// <summary>
/// The main C2 response class for this endpoint
/// </summary>
private class C2Response
{
[JsonProperty(PropertyName = "Results")]
public virtual List<DesiredPositionResponse> Results { get; set; }
[JsonProperty(PropertyName = "ResponseStatus")]
public ResponseStatus ResponseStatus { get; set; }
}
/// <summary>
/// The Results object
/// </summary>
private class DesiredPositionResponse
{
[JsonProperty(PropertyName = "NewSignals")]
public List<long> NewSignals { get; set; } = new List<long>();
[JsonProperty(PropertyName = "CanceledSignals")]
public List<long> CanceledSignals { get; set; } = new List<long>();
}
/// <summary>
/// Returns the given symbol in the expected C2 format
/// </summary>
private string GetSymbol(Symbol symbol)
{
if (CurrencyPairUtil.TryDecomposeCurrencyPair(symbol, out var baseCurrency, out var quoteCurrency))
{
return $"{baseCurrency}/{quoteCurrency}";
}
else if (symbol.SecurityType.IsOption())
{
return symbol.Underlying.Value;
}
else
{
return symbol.ID.Symbol;
}
}
/// <summary>
/// Returns the Symbol currency. USD for Forex.
/// </summary>
private string GetCurrency(IAlgorithm algorithm, Symbol symbol)
{
if (symbol.ID.SecurityType == SecurityType.Forex)
{
return "USD";
}
if (algorithm.Securities.TryGetValue(symbol, out var security))
{
return security.QuoteCurrency.Symbol;
}
var properties = _symbolPropertiesDatabase.Value.GetSymbolProperties(symbol.ID.Market, symbol, symbol.ID.SecurityType, algorithm.AccountCurrency);
return properties.QuoteCurrency;
}
private string GetMICExchangeCode(Symbol symbol)
{
if (symbol.SecurityType == SecurityType.Equity || symbol.SecurityType.IsOption())
{
return "DEFAULT";
}
switch (symbol.ID.Market)
{
case Market.India:
return "XNSE";
case Market.HKFE:
return "XHKF";
case Market.NYSELIFFE:
return "XNLI";
case Market.EUREX:
return "XEUR";
case Market.ICE:
return "IEPA";
case Market.CBOE:
return "XCBO";
case Market.CFE:
return "XCBF";
case Market.CBOT:
return "XCBT";
case Market.COMEX:
return "XCEC";
case Market.NYMEX:
return "XNYM";
case Market.SGX:
return "XSES";
case Market.FXCM:
return symbol.ID.Market.ToUpper();
case Market.OSE:
case Market.CME:
return $"X{symbol.ID.Market.ToUpper()}";
default:
if (_unknownMarketSymbols.Add(symbol.Value))
{
_algorithm.Debug($"The market of the symbol {symbol.Value} was unexpected: {symbol.ID.Market}. Using 'DEFAULT' as market");
}
return "DEFAULT";
}
}
/// <summary>
/// Returns the given security type in the format C2 expects
/// </summary>
private string GetSecurityTypeAcronym(SecurityType securityType)
{
switch (securityType)
{
case SecurityType.Equity:
return "CS";
case SecurityType.Future:
return "FUT";
case SecurityType.Option:
case SecurityType.IndexOption:
return "OPT";
case SecurityType.Forex:
return "FOR";
default:
if (_unknownSecurityTypes.Add(securityType))
{
_algorithm.Debug($"Unexpected security type found: {securityType}. Collective2 just accepts: Equity, Future, Option, Index Option and Stock");
}
return null;
}
}
/// <summary>
/// Returns the expiration date in the format C2 expects
/// </summary>
private string GetMaturityMonthYear(Symbol symbol)
{
var delistingDate = symbol.GetDelistingDate();
if (delistingDate == Time.EndOfTime) // The given symbol is equity or forex
{
return null;
}
if (delistingDate < _algorithm.Securities[symbol].LocalTime.Date) // The given symbol has already expired
{
_algorithm.Error($"Instrument {symbol} has already expired. Its delisting date was: {delistingDate}. This signal won't be sent to Collective2.");
return string.Empty;
}
return $"{delistingDate:yyyyMMdd}";
}
private int? GetPutOrCallValue(Symbol symbol)
{
if (symbol.SecurityType.IsOption())
{
switch (symbol.ID.OptionRight)
{
case OptionRight.Put:
return 0;
case OptionRight.Call:
return 1;
}
}
return null;
}
private decimal? GetStrikePrice(Symbol symbol)
{
if (symbol.SecurityType.IsOption())
{
return symbol.ID.StrikePrice;
}
else
{
return null;
}
}
/// <summary>
/// The C2 ResponseStatus object
/// </summary>
private class ResponseStatus
{
/* Example:
"ResponseStatus":
{
"ErrorCode": ""401",
"Message": ""Unauthorized",
"Errors": [
{
"ErrorCode": "2015",
"FieldName": "APIKey",
"Message": ""Unknown API Key"
}
]
}
*/
[JsonProperty(PropertyName = "ErrorCode")]
public string ErrorCode { get; set; }
[JsonProperty(PropertyName = "Message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "Errors")]
public List<ResponseError> Errors { get; set; }
}
/// <summary>
/// The ResponseError object
/// </summary>
private class ResponseError
{
[JsonProperty(PropertyName = "ErrorCode")]
public string ErrorCode { get; set; }
[JsonProperty(PropertyName = "FieldName")]
public string FieldName { get; set; }
[JsonProperty(PropertyName = "Message")]
public string Message { get; set; }
}
/// <summary>
/// Stores position's needed information to be serialized in JSON format
/// and then sent to Collective2 API
/// </summary>
protected class Collective2Position
{
/// <summary>
/// Position symbol
/// </summary>
[JsonProperty(PropertyName = "exchangeSymbol")]
public C2ExchangeSymbol ExchangeSymbol { get; set; }
/// <summary>
/// Number of shares/contracts of the given symbol. Positive quantites are long positions
/// and negative short positions.
/// </summary>
[JsonProperty(PropertyName = "quantity")]
public decimal Quantity { get; set; } // number of shares, not % of the portfolio
}
/// <summary>
/// The Collective2 symbol
/// </summary>
protected class C2ExchangeSymbol
{
/// <summary>
/// The exchange root symbol e.g. AAPL
/// </summary>
[JsonProperty(PropertyName = "symbol")]
public string Symbol { get; set; }
/// <summary>
/// The 3-character ISO instrument currency. E.g. 'USD'
/// </summary>
[JsonProperty(PropertyName = "currency")]
public string Currency { get; set; }
/// <summary>
/// The MIC Exchange code e.g. DEFAULT (for stocks & options),
/// XCME, XEUR, XICE, XLIF, XNYB, XNYM, XASX, XCBF, XCBT, XCEC,
/// XKBT, XSES. See details at http://www.iso15022.org/MIC/homepageMIC.htm
/// </summary>
[JsonProperty(PropertyName = "securityExchange")]
public string SecurityExchange { get; set; }
/// <summary>
/// The SecurityType e.g. 'CS'(Common Stock), 'FUT' (Future), 'OPT' (Option), 'FOR' (Forex)
/// </summary>
[JsonProperty(PropertyName = "securityType")]
public string SecurityType { get; set; }
/// <summary>
/// The MaturityMonthYear e.g. '202103' (March 2021), or if the contract requires a day: '20210521' (May 21, 2021)
/// </summary>
[JsonProperty(PropertyName = "maturityMonthYear")]
public string MaturityMonthYear { get; set; }
/// <summary>
/// The Option PutOrCall e.g. 0 = Put, 1 = Call
/// </summary>
[JsonProperty(PropertyName = "putOrCall")]
public int? PutOrCall { get; set; }
/// <summary>
/// The ISO Option Strike Price. Zero means none
/// </summary>
[JsonProperty(PropertyName = "strikePrice")]
public decimal? StrikePrice { get; set; }
}
}
}
@@ -0,0 +1,290 @@
/*
* 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 Newtonsoft.Json.Linq;
using QuantConnect.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports
{
/// <summary>
/// Exports signals of the desired positions to CrunchDAO API.
/// Accepts signals in percentage i.e ticker:"SPY", date: "2020-10-04", signal:0.54
/// </summary>
public class CrunchDAOSignalExport : BaseSignalExport
{
/// <summary>
/// CrunchDAO API key
/// </summary>
private readonly string _apiKey;
/// <summary>
/// CrunchDAO API endpoint
/// </summary>
private readonly Uri _destination;
/// <summary>
/// Model ID or Name
/// </summary>
private readonly string _model;
/// <summary>
/// Submission Name (Optional)
/// </summary>
private readonly string _submissionName;
/// <summary>
/// Comment (Optional)
/// </summary>
private readonly string _comment;
/// <summary>
/// Algorithm being ran
/// </summary>
private IAlgorithm _algorithm;
/// <summary>
/// HashSet of allowed SecurityTypes for CrunchDAO
/// </summary>
private readonly HashSet<SecurityType> _allowedSecurityTypes = new()
{
SecurityType.Equity
};
/// <summary>
/// The name of this signal export
/// </summary>
protected override string Name { get; } = "CrunchDAO";
/// <summary>
/// HashSet property of allowed SecurityTypes for CrunchDAO
/// </summary>
protected override HashSet<SecurityType> AllowedSecurityTypes => _allowedSecurityTypes;
/// <summary>
/// CrunchDAOSignalExport constructor. It obtains the required information for CrunchDAO API requests.
/// See (https://colab.research.google.com/drive/1YW1xtHrIZ8ZHW69JvNANWowmxPcnkNu0?authuser=1#scrollTo=aPyWNxtuDc-X)
/// </summary>
/// <param name="apiKey">API key provided by CrunchDAO</param>
/// <param name="model">Model ID or Name</param>
/// <param name="submissionName">Submission Name (Optional)</param>
/// <param name="comment">Comment (Optional)</param>
public CrunchDAOSignalExport(string apiKey, string model, string submissionName = "", string comment = "")
{
_apiKey = apiKey;
_model = model;
_submissionName = submissionName;
_comment = comment;
_destination = new Uri($"https://api.tournament.crunchdao.com/v3/alpha-submissions?apiKey={apiKey}");
}
/// <summary>
/// Verifies every holding is a stock, creates a message with the desired positions
/// using the expected CrunchDAO API format, verifies there is an open round and then
/// sends the positions with the other required body features. If another signal was
/// submitted before, it deletes the last signal and sends the new one</summary>
/// <param name="parameters">A list of holdings from the portfolio,
/// expected to be sent to CrunchDAO API and the algorithm being ran</param>
/// <returns>True if the positions were sent to CrunchDAO succesfully and errors were returned, false otherwise</returns>
public override bool Send(SignalExportTargetParameters parameters)
{
if (!base.Send(parameters))
{
return false;
}
if (!ConvertToCSVFormat(parameters, out string positions))
{
return false;
}
if (!GetCurrentRoundID(out int currentRoundId))
{
return false;
}
if (GetLastSubmissionId(currentRoundId, out int lastSubmissionId))
{
_algorithm.Debug($"You have already submitted a signal for round {currentRoundId}. Your last submission is going to be overwritten with the new one");
if (!DeleteLastSubmission(lastSubmissionId))
{
return false;
}
}
var result = SendPositions(positions);
return result;
}
/// <summary>
/// Converts the list of holdings into a CSV format string
/// </summary>
/// <param name="parameters">A list of holdings from the portfolio,
/// expected to be sent to CrunchDAO API and the algorithm being ran</param>
/// <param name="positions">A CSV format string of the given holdings with the required features(ticker, date, signal)</param>
/// <returns>True if a string message with the positions could be obtained, false otherwise</returns>
protected bool ConvertToCSVFormat(SignalExportTargetParameters parameters, out string positions)
{
var holdings = parameters.Targets;
_algorithm = parameters.Algorithm;
positions = "ticker,date,signal\n";
foreach (var holding in holdings)
{
if (holding.Quantity < 0 || holding.Quantity > 1)
{
_algorithm.Error($"All signals must be between 0 and 1, but {holding.Symbol.Value} signal was {holding.Quantity}");
return false;
}
positions += $"{_algorithm.Ticker(holding.Symbol)},{_algorithm.Securities[holding.Symbol].LocalTime.ToString("yyyy-MM-dd")},{holding.Quantity.ToStringInvariant()}\n";
}
return true;
}
/// <summary>
/// Sends the desired positions, with the other required features, to CrunchDAO API using a POST request. It logs
/// the message retrieved by the API if there was a HttpRequestException
/// </summary>
/// <param name="positions">A CSV format string of the given holdings with the required features</param>
/// <returns>True if the positions were sent to CrunchDAO successfully and errors were returned. False, otherwise</returns>
private bool SendPositions(string positions)
{
// Create positions stream
var positionsStream = new MemoryStream();
using var writer = new StreamWriter(positionsStream);
writer.Write(positions);
writer.Flush();
positionsStream.Position = 0;
// Create the required body features for the POST request
using var file = new StreamContent(positionsStream);
using var model = new StringContent(_model);
using var submissionName = new StringContent(_submissionName);
using var comment = new StringContent(_comment);
// Crete the httpMessage to be sent and add the different POST request body features
using var httpMessage = new MultipartFormDataContent
{
{ model, "model" },
{ submissionName, "label" },
{ comment, "comment" },
{ file, "file", "submission.csv" }
};
// Send the httpMessage
using HttpResponseMessage response = HttpClient.PostAsync(_destination, httpMessage).Result;
if (response.StatusCode == System.Net.HttpStatusCode.Locked || response.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
var responseContent = response.Content.ReadAsStringAsync().Result;
var parsedResponseContent = JObject.Parse(responseContent);
_algorithm.Error($"CrunchDAO API returned code: {parsedResponseContent["code"]} message:{parsedResponseContent["message"]}");
return false;
}
if (!response.IsSuccessStatusCode)
{
_algorithm.Error($"CrunchDAO API returned HttpRequestException {response.StatusCode}");
return false;
}
return true;
}
/// <summary>
/// Checks if there is an open round, if so it assigns the current round ID to currentRoundId
/// parameter
/// </summary>
/// <param name="currentRoundId">Current round ID</param>
/// <returns>True if there is an open round, false otherwise</returns>
private bool GetCurrentRoundID(out int currentRoundId)
{
// Assign a default value to currentRoundId
currentRoundId = -1;
using HttpResponseMessage roundIdResponse = HttpClient.GetAsync("https://api.tournament.crunchdao.com/v2/rounds/@current").Result;
if (roundIdResponse.StatusCode == System.Net.HttpStatusCode.NotFound)
{
var responseContent = roundIdResponse.Content.ReadAsStringAsync().Result;
var parsedResponseContent = JObject.Parse(responseContent);
_algorithm.Error($"CrunchDAO API returned code: {parsedResponseContent["code"]} message:{parsedResponseContent["message"]}");
return false;
}
else if (!roundIdResponse.IsSuccessStatusCode)
{
_algorithm.Error($"CrunchDAO API returned HttpRequestException {roundIdResponse.StatusCode}");
return false;
}
var roundIdResponseContent = roundIdResponse.Content.ReadAsStringAsync().Result;
currentRoundId = (int)(JObject.Parse(roundIdResponseContent)["id"]);
return true;
}
/// <summary>
/// Checks if there is a submission for the current round, if so it assigns its ID to
/// lastSubmissionId
/// </summary>
/// <param name="currentRoundId">Current round ID</param>
/// <param name="lastSubmissionId">Last submission ID (for the current round)</param>
/// <returns>True if there's a submission for the current round, false otherwise</returns>
private bool GetLastSubmissionId(int currentRoundId, out int lastSubmissionId)
{
using HttpResponseMessage submissionIdResponse = HttpClient.GetAsync($"https://tournament.crunchdao.com/api/v3/alpha-submissions?includeAll=false&roundId={currentRoundId}&apiKey={_apiKey}").Result;
if (!submissionIdResponse.IsSuccessStatusCode)
{
_algorithm.Error($"CrunchDAO API returned the following Error Code: {submissionIdResponse.StatusCode}");
lastSubmissionId = -1;
return false;
}
var submissionIdResponseContent = submissionIdResponse.Content.ReadAsStringAsync().Result;
var parsedSubmissionIdResponseContent = JArray.Parse(submissionIdResponseContent);
if (!parsedSubmissionIdResponseContent.HasValues)
{
// Default value for lastSubmissionId
lastSubmissionId = -1;
return false;
}
lastSubmissionId = (int)parsedSubmissionIdResponseContent[0]["id"];
return true;
}
/// <summary>
/// Deletes the last submission for the current round
/// </summary>
/// <param name="lastSubmissionId">Last submission ID for the current round</param>
/// <returns>True if the last submission could be deleted sucessfully, false otherwise</returns>
private bool DeleteLastSubmission(int lastSubmissionId)
{
using HttpResponseMessage deleteSubmissionResponse = HttpClient.DeleteAsync($"https://tournament.crunchdao.com/api/v3/alpha-submissions/{lastSubmissionId}?&apiKey={_apiKey}").Result;
if (!deleteSubmissionResponse.IsSuccessStatusCode)
{
var responseContent = deleteSubmissionResponse.Content.ReadAsStringAsync().Result;
var parsedResponseContent = JObject.Parse(responseContent);
_algorithm.Error($"CrunchDAO API returned code: {parsedResponseContent["code"]} message:{parsedResponseContent["message"]}. Last submission could not be deleted");
return false;
}
_algorithm.Debug($"Last submission has been deleted");
return true;
}
}
}
@@ -0,0 +1,281 @@
/*
* 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 Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using QuantConnect.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports
{
/// <summary>
/// Exports signals of the desired positions to Numerai API.
/// Accepts signals in percentage i.e numerai_ticker:"IBM US", signal:0.234
/// </summary>
/// <remarks>It does not take into account flags as
/// NUMERAI_COMPUTE_ID (https://github.com/numerai/numerapi/blob/master/numerapi/signalsapi.py#L164) and
/// TRIGGER_ID(https://github.com/numerai/numerapi/blob/master/numerapi/signalsapi.py#L164)</remarks>
public class NumeraiSignalExport : BaseSignalExport
{
/// <summary>
/// Numerai API submission endpoint
/// </summary>
private readonly Uri _destination;
/// <summary>
/// PUBLIC_ID provided by Numerai
/// </summary>
private readonly string _publicId;
/// <summary>
/// SECRET_ID provided by Numerai
/// </summary>
private readonly string _secretId;
/// <summary>
/// ID of the Numerai Model being used
/// </summary>
private readonly string _modelId;
/// <summary>
/// Signal file's name
/// </summary>
private readonly string _fileName;
/// <summary>
/// Algorithm being ran
/// </summary>
private IAlgorithm _algorithm;
/// <summary>
/// Dictionary to obtain corresponding Numerai Market name for the given LEAN market name
/// </summary>
private readonly Dictionary<string, string> _numeraiMarketFormat = new() // There can be stocks from other markets
{
{Market.USA, "US" },
{Market.SGX, "SP" }
};
/// <summary>
/// Hashset of Numerai allowed SecurityTypes
/// </summary>
private readonly HashSet<SecurityType> _allowedSecurityTypes = new()
{
SecurityType.Equity
};
/// <summary>
/// The name of this signal export
/// </summary>
protected override string Name { get; } = "Numerai";
/// <summary>
/// Hashset property of Numerai allowed SecurityTypes
/// </summary>
protected override HashSet<SecurityType> AllowedSecurityTypes => _allowedSecurityTypes;
/// <summary>
/// NumeraiSignalExport Constructor. It obtains the required information for Numerai API requests
/// </summary>
/// <param name="publicId">PUBLIC_ID provided by Numerai</param>
/// <param name="secretId">SECRET_ID provided by Numerai</param>
/// <param name="modelId">ID of the Numerai Model being used</param>
/// <param name="fileName">Signal file's name</param>
public NumeraiSignalExport(string publicId, string secretId, string modelId, string fileName = "predictions.csv")
{
_destination = new Uri("https://api-tournament.numer.ai");
_publicId = publicId;
_secretId = secretId;
_modelId = modelId;
_fileName = fileName;
}
/// <summary>
/// Verifies all the given holdings are accepted by Numerai, creates a message with those holdings in the expected
/// Numerai API format and sends them to Numerai API
/// </summary>
/// <param name="parameters">A list of portfolio holdings expected to be sent to Numerai API and the algorithm being ran</param>
/// <returns>True if the positions were sent to Numerai API correctly and no errors were returned, false otherwise</returns>
public override bool Send(SignalExportTargetParameters parameters)
{
if (!base.Send(parameters))
{
return false;
}
_algorithm = parameters.Algorithm;
if (parameters.Targets.Count < 10)
{
_algorithm.Error($"Numerai Signals API accepts minimum 10 different signals, just found {parameters.Targets.Count}");
return false;
}
if (!ConvertTargetsToNumerai(parameters, out string positions))
{
return false;
}
var result = SendPositions(positions);
return result;
}
/// <summary>
/// Verifies each holding's signal is between 0 and 1 (exclusive)
/// </summary>
/// <param name="parameters">A list of portfolio holdings expected to be sent to Numerai API</param>
/// <param name="positions">A message with the desired positions in the expected Numerai API format</param>
/// <returns>True if a string message with the positions could be obtained, false otherwise</returns>
protected bool ConvertTargetsToNumerai(SignalExportTargetParameters parameters, out string positions)
{
positions = "numerai_ticker,signal\n";
foreach ( var holding in parameters.Targets)
{
if (holding.Quantity <= 0 || holding.Quantity >= 1)
{
_algorithm.Error($"All signals must be between 0 and 1 (exclusive), but {holding.Symbol.Value} signal was {holding.Quantity}");
return false;
}
positions += $"{parameters.Algorithm.Ticker(holding.Symbol)} {_numeraiMarketFormat[holding.Symbol.ID.Market]},{holding.Quantity.ToStringInvariant()}\n";
}
return true;
}
/// <summary>
/// Sends the given positions message to Numerai API. It first sends an authentication POST request then a
/// PUT request to put the positions in certain endpoint and finally sends a submission POST request
/// </summary>
/// <param name="positions">A message with the desired positions in the expected Numerai API format</param>
/// <returns>True if the positions were sent to Numerai API correctly and no errors were returned, false otherwise</returns>
private bool SendPositions(string positions)
{
// AUTHENTICATION REQUEST
var authQuery = @"query($filename: String!
$modelId: String) {
submissionUploadSignalsAuth(filename: $filename
modelId: $modelId) {
filename
url
}
}";
var arguments = new
{
filename = _fileName,
modelId = _modelId
};
var argumentsMessage = JsonConvert.SerializeObject(arguments);
using var variables = new StringContent(argumentsMessage, Encoding.UTF8, "application/json");
using var query = new StringContent(authQuery, Encoding.UTF8, "application/json");
var httpMessage = new MultipartFormDataContent
{
{ query, "query"},
{ variables, "variables" }
};
using var authRequest = new HttpRequestMessage(HttpMethod.Post, _destination);
authRequest.Headers.Add("Accept", "application/json");
authRequest.Headers.Add("Authorization", $"Token {_publicId}${_secretId}");
authRequest.Content = httpMessage;
var response = HttpClient.SendAsync(authRequest).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
if (!response.IsSuccessStatusCode)
{
_algorithm.Error($"Numerai API returned HttpRequestException {response.StatusCode}");
return false;
}
var parsedResponseContent = JObject.Parse(responseContent);
if (!parsedResponseContent["data"]["submissionUploadSignalsAuth"].HasValues)
{
_algorithm.Error($"Numerai API returned the following errors: {string.Join(",", parsedResponseContent["errors"])}");
return false;
}
var putUrl = new Uri((string)parsedResponseContent["data"]["submissionUploadSignalsAuth"]["url"]);
var submissionFileName = (string)parsedResponseContent["data"]["submissionUploadSignalsAuth"]["filename"];
// PUT REQUEST
// Create positions stream
var positionsStream = new MemoryStream();
using var writer = new StreamWriter(positionsStream);
writer.Write(positions);
writer.Flush();
positionsStream.Position = 0;
using var putRequest = new HttpRequestMessage(HttpMethod.Put, putUrl)
{
Content = new StreamContent(positionsStream)
};
var putResponse = HttpClient.SendAsync(putRequest).Result;
// SUBMISSION REQUEST
var createQuery = @"mutation($filename: String!
$modelId: String
$triggerId: String) {
createSignalsSubmission(filename: $filename
modelId: $modelId
triggerId: $triggerId
source: ""numerapi"") {
id
firstEffectiveDate
}
}";
var createArguments = new
{
filename = submissionFileName,
modelId = _modelId
};
var createArgumentsMessage = JsonConvert.SerializeObject(createArguments);
using var submissionQuery = new StringContent(createQuery, Encoding.UTF8, "application/json");
using var submissionVariables = new StringContent(createArgumentsMessage, Encoding.UTF8, "application/json");
var submissionMessage = new MultipartFormDataContent
{
{submissionQuery, "query"},
{submissionVariables, "variables"}
};
using var submissionRequest = new HttpRequestMessage(HttpMethod.Post, _destination);
submissionRequest.Headers.Add("Authorization", $"Token {_publicId}${_secretId}");
submissionRequest.Content = submissionMessage;
var submissionResponse = HttpClient.SendAsync(submissionRequest).Result;
var submissionResponseContent = submissionResponse.Content.ReadAsStringAsync().Result;
if (!submissionResponse.IsSuccessStatusCode)
{
_algorithm.Error($"Numerai API returned HttpRequestException {submissionResponseContent}");
return false;
}
var parsedSubmissionResponseContent = JObject.Parse(submissionResponseContent);
if (!parsedSubmissionResponseContent["data"]["createSignalsSubmission"].HasValues)
{
_algorithm.Error($"Numerai API returned the following errors: {string.Join(",", parsedSubmissionResponseContent["errors"])}");
return false;
}
return true;
}
}
}
@@ -0,0 +1,276 @@
/*
* 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 Python.Runtime;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Python;
using QuantConnect.Securities;
using System.Collections.Generic;
using System;
using System.Linq;
using QuantConnect.Util;
namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports
{
/// <summary>
/// Class manager to send portfolio targets to different 3rd party API's
/// For example, it allows Collective2, CrunchDAO and Numerai signal export providers
/// </summary>
public class SignalExportManager
{
/// <summary>
/// Records the time of the first order event of a group of events
/// </summary>
private ReferenceWrapper<DateTime> _initialOrderEventTimeUtc = new(Time.EndOfTime);
/// <summary>
/// List of signal export providers
/// </summary>
private List<ISignalExportTarget> _signalExports;
/// <summary>
/// Algorithm being ran
/// </summary>
private readonly IAlgorithm _algorithm;
/// <summary>
/// Flag to indicate if the user has tried to send signals with live mode off
/// </summary>
private bool _isLiveWarningModeLog;
/// <summary>
/// Gets the maximim time span elapsed to export signals after an order event
/// If null, disable automatic export.
/// </summary>
public TimeSpan? AutomaticExportTimeSpan { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// SignalExportManager Constructor, obtains the entry information needed to send signals
/// and initializes the fields to be used
/// </summary>
/// <param name="algorithm">Algorithm being run</param>
public SignalExportManager(IAlgorithm algorithm)
{
_algorithm = algorithm;
_isLiveWarningModeLog = false;
}
/// <summary>
/// Adds a new signal exports provider
/// </summary>
/// <param name="signalExport">Signal export provider</param>
public void AddSignalExportProvider(ISignalExportTarget signalExport)
{
_signalExports ??= [];
_signalExports.Add(signalExport);
}
/// <summary>
/// Adds a new signal exports provider
/// </summary>
/// <param name="signalExport">Signal export provider</param>
public void AddSignalExportProvider(PyObject signalExport)
{
var managedSignalExport = PythonUtil.CreateInstanceOrWrapper<ISignalExportTarget>(
signalExport,
py => new SignalExportTargetPythonWrapper(py)
);
AddSignalExportProvider(managedSignalExport);
}
/// <summary>
/// Adds one or more new signal exports providers
/// </summary>
/// <param name="signalExports">One or more signal export provider</param>
public void AddSignalExportProviders(params ISignalExportTarget[] signalExports)
{
signalExports.DoForEach(AddSignalExportProvider);
}
/// <summary>
/// Adds one or more new signal exports providers
/// </summary>
/// <param name="signalExports">One or more signal export provider</param>
public void AddSignalExportProviders(PyObject signalExports)
{
using var _ = Py.GIL();
if (!signalExports.IsIterable())
{
AddSignalExportProvider(signalExports);
return;
}
PyList.AsList(signalExports).DoForEach(AddSignalExportProvider);
}
/// <summary>
/// Sets the portfolio targets from the algorihtm's Portfolio and sends them with the
/// algorithm being ran to the signal exports providers already set
/// </summary>
/// <returns>True if the target list could be obtained from the algorithm's Portfolio and they
/// were successfully sent to the signal export providers</returns>
public bool SetTargetPortfolioFromPortfolio()
{
if (!GetPortfolioTargets(out PortfolioTarget[] targets))
{
return false;
}
var result = SetTargetPortfolio(targets);
return result;
}
/// <summary>
/// Obtains an array of portfolio targets from algorithm's Portfolio and returns them.
/// See <see cref="PortfolioTarget.Percent(IAlgorithm, Symbol, decimal, bool, string)"/> for more
/// information about how each symbol quantity was calculated
/// </summary>
/// <param name="targets">An array of portfolio targets from the algorithm's Portfolio</param>
/// <returns>True if TotalPortfolioValue was bigger than zero, false otherwise</returns>
protected bool GetPortfolioTargets(out PortfolioTarget[] targets)
{
var totalPortfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
if (totalPortfolioValue <= 0)
{
_algorithm.Error("Total portfolio value was less than or equal to 0");
targets = Array.Empty<PortfolioTarget>();
return false;
}
targets = GetPortfolioTargets(totalPortfolioValue).ToArray();
return true;
}
/// <summary>
/// Sets the portfolio targets with the given entries and sends them with the algorithm
/// being ran to the signal exports providers set, as long as the algorithm is in live mode
/// </summary>
/// <param name="portfolioTargets">One or more portfolio targets to be sent to the defined signal export providers</param>
/// <returns>True if the portfolio targets could be sent to the different signal export providers successfully, false otherwise</returns>
public bool SetTargetPortfolio(params PortfolioTarget[] portfolioTargets)
{
if (!_algorithm.LiveMode)
{
if (!_isLiveWarningModeLog)
{
_algorithm.Debug("Portfolio targets are only sent in live mode");
_isLiveWarningModeLog = true;
}
return true;
}
if (_signalExports.IsNullOrEmpty())
{
return false;
}
if (portfolioTargets == null || portfolioTargets.Length == 0)
{
_algorithm.Debug("No portfolio target given");
return false;
}
var targets = new List<PortfolioTarget>(portfolioTargets);
var signalExportTargetParameters = new SignalExportTargetParameters
{
Targets = targets,
Algorithm = _algorithm
};
var result = true;
foreach (var signalExport in _signalExports)
{
result &= signalExport.Send(signalExportTargetParameters);
}
return result;
}
private IEnumerable<PortfolioTarget> GetPortfolioTargets(decimal totalPortfolioValue)
{
foreach (var holding in _algorithm.Portfolio.Values)
{
var security = _algorithm.Securities[holding.Symbol];
// Skip non-tradeable securities except canonical futures as some signal providers
// like Collective2 accept them.
// See https://collective2.com/api-docs/latest#Basic_submitsignal_format
if (!security.IsTradable && !security.Symbol.IsCanonical())
{
continue;
}
var marginParameters = new InitialMarginParameters(security, holding.Quantity);
var adjustedPercent = Math.Abs(security.BuyingPowerModel.GetInitialMarginRequirement(marginParameters) / totalPortfolioValue);
// See PortfolioTarget.Percent:
// we normalize the target buying power by the leverage so we work in the land of margin
var holdingPercent = adjustedPercent * security.BuyingPowerModel.GetLeverage(security);
// FreePortfolioValue is used for orders not to be rejected due to volatility when using SetHoldings and CalculateOrderQuantity
// Then, we need to substract its value from the TotalPortfolioValue and obtain again the holding percentage for our holding
var adjustedHoldingPercent = (holdingPercent * totalPortfolioValue) / _algorithm.Portfolio.TotalPortfolioValueLessFreeBuffer;
if (holding.Quantity < 0)
{
adjustedHoldingPercent *= -1;
}
yield return new PortfolioTarget(holding.Symbol, adjustedHoldingPercent);
}
}
/// <summary>
/// New order event handler: on order status changes (filled, partially filled, cancelled etc).
/// </summary>
/// <param name="orderEvent">Event information</param>
public void OnOrderEvent(OrderEvent orderEvent)
{
if (_initialOrderEventTimeUtc.Value == Time.EndOfTime && orderEvent.Status.IsFill())
{
_initialOrderEventTimeUtc = new(orderEvent.UtcTime);
}
}
/// <summary>
/// Set the target portfolio after order events.
/// </summary>
/// <param name="currentTimeUtc">The current time of synchronous events</param>
public void Flush(DateTime currentTimeUtc)
{
var initialOrderEventTimeUtc = _initialOrderEventTimeUtc.Value;
if (_signalExports.IsNullOrEmpty() || initialOrderEventTimeUtc == Time.EndOfTime || !AutomaticExportTimeSpan.HasValue)
{
return;
}
if (currentTimeUtc - initialOrderEventTimeUtc < AutomaticExportTimeSpan)
{
return;
}
try
{
SetTargetPortfolioFromPortfolio();
}
catch (Exception exception)
{
// SetTargetPortfolioFromPortfolio logs all known error on LEAN side.
// Exceptions occurs in the ISignalExportTarget.Send method (user-defined).
_algorithm.Error($"Failed to send portfolio target(s). Reason: {exception.Message}.{Environment.NewLine}{exception.StackTrace}");
}
_initialOrderEventTimeUtc = new(Time.EndOfTime);
}
}
}
@@ -0,0 +1,36 @@
/*
* 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 QuantConnect.Interfaces;
using System.Collections.Generic;
namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports
{
/// <summary>
/// Class to wrap objects needed to send signals to the different 3rd party API's
/// </summary>
public class SignalExportTargetParameters
{
/// <summary>
/// List of portfolio targets to be sent to some 3rd party API
/// </summary>
public List<PortfolioTarget> Targets { get; set; }
/// <summary>
/// Algorithm being ran
/// </summary>
public IAlgorithm Algorithm { get; set; }
}
}
@@ -0,0 +1,178 @@
/*
* 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 QuantConnect.Interfaces;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports
{
/// <summary>
/// Exports signals of desired positions to vBase stamping API using JSON and HTTPS.
/// Accepts signals in quantity(number of shares) i.e symbol:"SPY", quant:40
/// </summary>
public class VBaseSignalExport : BaseSignalExport
{
private const string ApiBaseUrl = "https://app.vbase.com/api";
/// <summary>
/// API key provided by vBase
/// </summary>
private readonly string _apiKey;
/// <summary>
/// The collection CID (SHA3-256 hash of collection name) to which we stamp signals
/// </summary>
private readonly string _collectionCid;
/// <summary>
/// Whether vBase should store the stamped file (defaults true)
/// </summary>
private readonly bool _storeStampedFile;
/// <summary>
/// Whether this request is idempotent (if true only first identical portfolio stored)
/// </summary>
private readonly bool _idempotent;
/// <summary>
/// The name of this signal export
/// </summary>
protected override string Name => "vBase";
private static RateGate _requestsRateLimiter;
private readonly Uri _stampApiUrl;
/// <summary>
/// Initializes a new instance of the <see cref="VBaseSignalExport"/> class.
/// </summary>
/// <param name="apiKey">The API key for vBase authentication.</param>
/// <param name="collectionName">The target collection name.</param>
/// <param name="storeStampedFile">Whether to store the stamped file (default true).</param>
/// <param name="idempotent">
/// A boolean indicating whether to make the request idempotent.
/// If the request is idempotent, only the first stamp for a given portfolio will be made.
/// If the request is not idempotent, a new stamp will be made for each request.
/// </param>
public VBaseSignalExport(
string apiKey,
string collectionName,
bool storeStampedFile = true,
bool idempotent = false)
{
_apiKey = apiKey;
if (string.IsNullOrWhiteSpace(_apiKey))
{
throw new ArgumentException("vBaseSignalExport: API key not provided");
}
if (string.IsNullOrWhiteSpace(collectionName))
{
throw new ArgumentException("vBaseSignalExport: Collection name not provided");
}
_stampApiUrl = new Uri(ApiBaseUrl.TrimEnd('/') + "/v1/stamp/"); ;
var collectionCidBytes = SHA3_256.HashData(Encoding.UTF8.GetBytes(collectionName));
_collectionCid = "0x" + collectionCidBytes.ToHexString().ToLowerInvariant();
_storeStampedFile = storeStampedFile;
_idempotent = idempotent;
_requestsRateLimiter = new RateGate(10, TimeSpan.FromMinutes(5));
}
/// <summary>
/// Converts targets to CSV and posts them to vBase stamping endpoint
/// </summary>
/// <param name="parameters">Signal export parameters (targets + algorithm)</param>
/// <returns>True if request succeeded</returns>
public override bool Send(SignalExportTargetParameters parameters)
{
if (!base.Send(parameters))
{
return false;
}
var csv = BuildCsv(parameters);
_requestsRateLimiter?.WaitToProceed();
return Stamp(csv, parameters.Algorithm);
}
/// <summary>
/// Builds a CSV (sym,wt) for the given targets
/// </summary>
/// <param name="parameters">Signal export parameters</param>
/// <returns>Resulting CSV string</returns>
protected virtual string BuildCsv(SignalExportTargetParameters parameters)
{
var csv = "sym,wt\n";
foreach (var target in parameters.Targets)
{
csv += $"{target.Symbol.Value},{target.Quantity.ToStringInvariant()}\n";
}
return csv;
}
/// <summary>
/// Sends the CSV payload to the vBase stamping API
/// </summary>
private bool Stamp(string csv, IAlgorithm algorithm)
{
try
{
var contentPairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("collectionCid", _collectionCid),
new KeyValuePair<string, string>("data", csv),
new KeyValuePair<string, string>("storeStampedFile", _storeStampedFile.ToString()),
new KeyValuePair<string, string>("idempotent", _idempotent.ToString())
};
using var httpContent = new FormUrlEncodedContent(contentPairs);
using var request = new HttpRequestMessage(HttpMethod.Post, _stampApiUrl)
{
Content = httpContent
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
using var response = HttpClient.SendAsync(request).Result;
var body = response.Content.ReadAsStringAsync().Result;
if (!response.IsSuccessStatusCode)
{
algorithm.Error($"vBase API returned {response.StatusCode}. Body: {body}");
return false;
}
return true;
}
catch (Exception e)
{
algorithm.Error($"vBase signal export failed: {e.Message}");
return false;
}
}
}
}
+145
View File
@@ -0,0 +1,145 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Util;
using QuantConnect.Packets;
using QuantConnect.Interfaces;
using QuantConnect.Brokerages;
using System.Collections.Generic;
namespace QuantConnect
{
/// <summary>
/// This class includes algorithm configuration settings and parameters.
/// This is used to include configuration parameters in the result packet to be used for report generation.
/// </summary>
public class AlgorithmConfiguration
{
/// <summary>
/// The algorithm's name
/// </summary>
public string Name { get; set; }
/// <summary>
/// List of tags associated with the algorithm
/// </summary>
public ISet<string> Tags { get; set; }
/// <summary>
/// The algorithm's account currency
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string AccountCurrency { get; set; }
/// <summary>
/// The algorithm's brokerage model
/// </summary>
/// <remarks> Required to set the correct brokerage model on report generation.</remarks>
public BrokerageName Brokerage { get; set; }
/// <summary>
/// The algorithm's account type
/// </summary>
/// <remarks> Required to set the correct brokerage model on report generation.</remarks>
public AccountType AccountType { get; set; }
/// <summary>
/// The parameters used by the algorithm
/// </summary>
public IReadOnlyDictionary<string, string> Parameters { get; set; }
/// <summary>
/// Backtest maximum end date
/// </summary>
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
public DateTime? OutOfSampleMaxEndDate { get; set; }
/// <summary>
/// The backtest out of sample day count
/// </summary>
public int OutOfSampleDays { get; set; }
/// <summary>
/// The backtest start date
/// </summary>
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
public DateTime StartDate { get; set; }
/// <summary>
/// The backtest end date
/// </summary>
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
public DateTime EndDate { get; set; }
/// <summary>
/// Number of trading days per year for Algorithm's portfolio statistics.
/// </summary>
public int TradingDaysPerYear { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="AlgorithmConfiguration"/> class
/// </summary>
public AlgorithmConfiguration(string name, ISet<string> tags, string accountCurrency, BrokerageName brokerageName,
AccountType accountType, IReadOnlyDictionary<string, string> parameters, DateTime startDate, DateTime endDate,
DateTime? outOfSampleMaxEndDate, int outOfSampleDays = 0, int tradingDaysPerYear = 0)
{
Name = name;
Tags = tags;
OutOfSampleMaxEndDate = outOfSampleMaxEndDate;
TradingDaysPerYear = tradingDaysPerYear;
OutOfSampleDays = outOfSampleDays;
AccountCurrency = accountCurrency;
Brokerage = brokerageName;
AccountType = accountType;
Parameters = parameters;
StartDate = startDate;
EndDate = endDate;
}
/// <summary>
/// Initializes a new empty instance of the <see cref="AlgorithmConfiguration"/> class
/// </summary>
public AlgorithmConfiguration()
{
// use default value for backwards compatibility
TradingDaysPerYear = 252;
}
/// <summary>
/// Provides a convenience method for creating a <see cref="AlgorithmConfiguration"/> for a given algorithm.
/// </summary>
/// <param name="algorithm">Algorithm for which the configuration object is being created</param>
/// <param name="backtestNodePacket">The associated backtest node packet if any</param>
/// <returns>A new AlgorithmConfiguration object for the specified algorithm</returns>
public static AlgorithmConfiguration Create(IAlgorithm algorithm, BacktestNodePacket backtestNodePacket)
{
return new AlgorithmConfiguration(
algorithm.Name,
algorithm.Tags,
algorithm.AccountCurrency,
BrokerageModel.GetBrokerageName(algorithm.BrokerageModel),
algorithm.BrokerageModel.AccountType,
algorithm.GetParameters(),
algorithm.StartDate,
algorithm.EndDate,
backtestNodePacket?.OutOfSampleMaxEndDate,
backtestNodePacket?.OutOfSampleDays ?? 0,
// use value = 252 like default for backwards compatibility
algorithm?.Settings?.TradingDaysPerYear ?? 252);
}
}
}
+103
View File
@@ -0,0 +1,103 @@
# 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.
import os
import sys
# The runtimeconfig.json is stored alongside start.py, but start.py may be a
# symlink and the directory start.py is stored in is not necessarily the
# current working directory. We therefore construct the absolute path to the
# start.py file, and find the runtimeconfig.json relative to that.
path = os.path.dirname(os.path.realpath(__file__))
sys.path.append(path)
from clr import AddReference
AddReference("System")
#Load assemblies
for file in os.listdir(path):
if file.endswith(".dll") and file.startswith("QuantConnect."):
AddReference(file.replace(".dll", ""))
from System import *
from System.Drawing import *
from QuantConnect import *
from QuantConnect.Api import *
from QuantConnect.Util import *
from QuantConnect.Data import *
from QuantConnect.Orders import *
from QuantConnect.Python import *
from QuantConnect.Storage import *
from QuantConnect.Research import *
from QuantConnect.Commands import *
from QuantConnect.Algorithm import *
from QuantConnect.Statistics import *
from QuantConnect.Parameters import *
from QuantConnect.Benchmarks import *
from QuantConnect.Brokerages import *
from QuantConnect.Securities import *
from QuantConnect.Indicators import *
from QuantConnect.Interfaces import *
from QuantConnect.Scheduling import *
from QuantConnect.DataSource import *
from QuantConnect.Orders.Fees import *
from QuantConnect.Data.Custom import *
from QuantConnect.Data.Market import *
from QuantConnect.Lean.Engine import *
from QuantConnect.Orders.Fills import *
from QuantConnect.Configuration import *
from QuantConnect.Notifications import *
from QuantConnect.Data.Auxiliary import *
from QuantConnect.Data.Shortable import *
from QuantConnect.Orders.Slippage import *
from QuantConnect.Securities.Forex import *
from QuantConnect.Data.Fundamental import *
from QuantConnect.Securities.Crypto import *
from QuantConnect.Securities.Option import *
from QuantConnect.Securities.Equity import *
from QuantConnect.Securities.Future import *
from QuantConnect.Data.Consolidators import *
from QuantConnect.Orders.TimeInForces import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Selection import *
from QuantConnect.Securities.Positions import *
from QuantConnect.Orders.OptionExercise import *
from QuantConnect.Securities.Volatility import *
from QuantConnect.Securities.Interfaces import *
from QuantConnect.Data.UniverseSelection import *
from QuantConnect.Securities.IndexOption import *
from QuantConnect.Data.Custom.IconicTypes import *
from QuantConnect.Securities.CryptoFuture import *
from QuantConnect.Algorithm.Framework.Risk import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Portfolio import *
from QuantConnect.Indicators.CandlestickPatterns import *
from QuantConnect.Algorithm.Framework.Portfolio.SignalExports import *
from QuantConnect.Algorithm.Framework.Selection import *
try:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
except:
pass
from datetime import date, time, datetime, timedelta
from typing import *
import math
import json
QCAlgorithmFramework = QCAlgorithm
QCAlgorithmFrameworkBridge = QCAlgorithm
+203
View File
@@ -0,0 +1,203 @@
/*
* 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 QuantConnect.Interfaces;
using QuantConnect.Securities;
using QuantConnect.Orders.Fills;
using QuantConnect.Configuration;
namespace QuantConnect
{
/// <summary>
/// This class includes user settings for the algorithm which can be changed in the <see cref="IAlgorithm.Initialize"/> method
/// </summary>
public class AlgorithmSettings : IAlgorithmSettings
{
private static TimeSpan _defaultDatabasesRefreshPeriod =
TimeSpan.TryParse(Config.Get("databases-refresh-period", "1.00:00:00"), out var refreshPeriod) ? refreshPeriod : Time.OneDay;
// We default this to true so that we don't terminate live algorithms when the
// brokerage account has existing holdings for an asset that is not supported by Lean.
// Users can override this on initialization so that the algorithm is not terminated when
// placing orders for assets without a correct definition or mapping.
private static bool _defaultIgnoreUnknownAssetHoldings = Config.GetBool("ignore-unknown-asset-holdings", true);
/// <summary>
/// Gets whether or not WarmUpIndicator is allowed to warm up indicators
/// </summary>
public bool AutomaticIndicatorWarmUp { get; set; }
/// <summary>
/// True if should rebalance portfolio on security changes. True by default
/// </summary>
public bool? RebalancePortfolioOnSecurityChanges { get; set; }
/// <summary>
/// True if should rebalance portfolio on new insights or expiration of insights. True by default
/// </summary>
public bool? RebalancePortfolioOnInsightChanges { get; set; }
/// <summary>
/// The absolute maximum valid total portfolio value target percentage
/// </summary>
/// <remarks>This setting is currently being used to filter out undesired target percent values,
/// caused by the IPortfolioConstructionModel implementation being used.
/// For example rounding errors, math operations</remarks>
public decimal MaxAbsolutePortfolioTargetPercentage { get; set; }
/// <summary>
/// The absolute minimum valid total portfolio value target percentage
/// </summary>
/// <remarks>This setting is currently being used to filter out undesired target percent values,
/// caused by the IPortfolioConstructionModel implementation being used.
/// For example rounding errors, math operations</remarks>
public decimal MinAbsolutePortfolioTargetPercentage { get; set; }
/// <summary>
/// Configurable minimum order margin portfolio percentage to ignore bad orders, orders with unrealistic small sizes
/// </summary>
/// <remarks>Default value is 0.1% of the portfolio value. This setting is useful to avoid small trading noise when using SetHoldings</remarks>
public decimal MinimumOrderMarginPortfolioPercentage { get; set; }
/// <summary>
/// Gets/sets the maximum number of concurrent market data subscriptions available
/// </summary>
/// <remarks>
/// All securities added with <see cref="IAlgorithm.AddSecurity"/> are counted as one,
/// with the exception of options and futures where every single contract in a chain counts as one.
/// </remarks>
[Obsolete("This property is deprecated. Please observe data subscription limits set by your brokerage to avoid runtime errors.")]
public int DataSubscriptionLimit { get; set; } = int.MaxValue;
/// <summary>
/// Gets/sets the SetHoldings buffers value.
/// The buffer is used for orders not to be rejected due to volatility when using SetHoldings and CalculateOrderQuantity
/// </summary>
public decimal? FreePortfolioValue { get; set; }
/// <summary>
/// Gets/sets the SetHoldings buffers value percentage.
/// This percentage will be used to set the <see cref="FreePortfolioValue"/>
/// based on the <see cref="SecurityPortfolioManager.TotalPortfolioValue"/>
/// </summary>
public decimal FreePortfolioValuePercentage { get; set; }
/// <summary>
/// Gets/sets if Liquidate() is enabled
/// </summary>
public bool LiquidateEnabled { get; set; }
/// <summary>
/// Gets/sets the minimum time span elapsed to consider a market fill price as stale (defaults to one hour)
/// </summary>
/// <remarks>
/// In the default fill models, a market order on an hour or daily resolution subscription is not filled on
/// data older than this time span; instead it waits for fresh data (e.g. the next bar), avoiding a
/// fill at the stale previous close. Market orders on minute/second/tick subscriptions still fill on stale
/// data, only adding a warning message. Tighten it (e.g. to one minute) to make hour/daily orders wait for
/// the next bar more aggressively.
/// </remarks>
/// <seealso cref="FillModel"/>
/// <seealso cref="ImmediateFillModel"/>
public TimeSpan StalePriceTimeSpan { get; set; }
/// <summary>
/// The warmup resolution to use if any
/// </summary>
/// <remarks>This allows improving the warmup speed by setting it to a lower resolution than the one added in the algorithm</remarks>
public Resolution? WarmupResolution { get; set; }
/// <summary>
/// The warmup resolution to use if any
/// </summary>
/// <remarks>This allows improving the warmup speed by setting it to a lower resolution than the one added in the algorithm.
/// Pass through version to be user friendly</remarks>
public Resolution? WarmUpResolution
{
get
{
return WarmupResolution;
}
set
{
WarmupResolution = value;
}
}
/// <summary>
/// Number of trading days per year for this Algorithm's portfolio statistics.
/// </summary>
/// <remarks>Effect on
/// <see cref="Statistics.PortfolioStatistics.AnnualVariance"/>,
/// <seealso cref="Statistics.PortfolioStatistics.AnnualStandardDeviation"/>,
/// <seealso cref="Statistics.PortfolioStatistics.SharpeRatio"/>,
/// <seealso cref="Statistics.PortfolioStatistics.SortinoRatio"/>,
/// <seealso cref="Statistics.PortfolioStatistics.TrackingError"/>,
/// <seealso cref="Statistics.PortfolioStatistics.InformationRatio"/>.
/// </remarks>
public int? TradingDaysPerYear { get; set; }
/// <summary>
/// True if daily strict end times are enabled
/// </summary>
public bool DailyPreciseEndTime { get; set; }
/// <summary>
/// True if extended market hours should be used for daily consolidation, when extended market hours is enabled
/// </summary>
public bool DailyConsolidationUseExtendedMarketHours { get; set; }
/// <summary>
/// Gets the time span used to refresh the market hours and symbol properties databases
/// </summary>
public TimeSpan DatabasesRefreshPeriod { get; set; }
/// <summary>
/// Determines whether to terminate the algorithm when an asset holding is not supported by Lean or the brokerage.
/// Defaults to true, meaning that the algorithm will not be terminated if an asset holding is not supported.
/// </summary>
public bool IgnoreUnknownAssetHoldings { get; set; }
/// <summary>
/// Performance tracking sample period to use if any, useful to debug performance issues
/// </summary>
public TimeSpan PerformanceSamplePeriod { get; set; }
/// <summary>
/// Determines whether to seed initial prices for all selected and manually added securities.
/// </summary>
public bool SeedInitialPrices { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="AlgorithmSettings"/> class
/// </summary>
public AlgorithmSettings()
{
LiquidateEnabled = true;
DailyPreciseEndTime = true;
FreePortfolioValuePercentage = 0.0025m;
// Because the free portfolio value has a trailing behavior by default, let's add a default minimum order margin portfolio percentage
// to avoid tiny trades when rebalancing, defaulting to 0.1% of the TPV
MinimumOrderMarginPortfolioPercentage = 0.001m;
StalePriceTimeSpan = Time.OneHour;
MaxAbsolutePortfolioTargetPercentage = 1000000000;
MinAbsolutePortfolioTargetPercentage = 0.0000000001m;
DatabasesRefreshPeriod = _defaultDatabasesRefreshPeriod;
IgnoreUnknownAssetHoldings = _defaultIgnoreUnknownAssetHoldings;
SeedInitialPrices = false;
}
}
}
+85
View File
@@ -0,0 +1,85 @@
/*
* 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 QuantConnect.Interfaces;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect
{
/// <summary>
/// Provides utility methods for or related to algorithms
/// </summary>
public static class AlgorithmUtils
{
/// <summary>
/// Seeds the provided securities with their last known prices from the algorithm
/// </summary>
/// <param name="securities">The securities to seed</param>
/// <param name="algorithm">The algorithm instance</param>
public static void SeedSecurities(IReadOnlyCollection<Security> securities, IAlgorithm algorithm)
{
var securitiesToSeed = securities.Where(x => x.Price == 0);
var data = algorithm.GetLastKnownPrices(securitiesToSeed.Select(x => x.Symbol));
foreach (var security in securitiesToSeed)
{
if (data.TryGetValue(security.Symbol, out var seedData))
{
foreach (var datum in seedData)
{
security.SetMarketPrice(datum);
}
}
}
}
/// <summary>
/// Seeds an initial conversion rate for the cashbook currencies that don't have one yet, so they are
/// non-zero right away instead of waiting for the first conversion pair bar to arrive
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="currenciesToUpdateWhiteList">
/// If passed, only the currencies in the CashBook contained in this list will be updated.
/// By default, if not passed (null), all currencies in the cashbook without a properly set up currency conversion will be updated.
/// </param>
public static void SeedCurrencyConversionRates(IAlgorithm algorithm, IReadOnlyCollection<string> currenciesToUpdateWhiteList = null)
{
Func<Cash, bool> cashToUpdateFilter = currenciesToUpdateWhiteList == null
? (x) => x.CurrencyConversion != null && x.ConversionRate == 0
: (x) => currenciesToUpdateWhiteList.Contains(x.Symbol);
var cashToUpdate = algorithm.Portfolio.CashBook.Values.Where(cashToUpdateFilter).ToList();
if (cashToUpdate.Count == 0)
{
return;
}
var securitiesToUpdate = cashToUpdate
.SelectMany(x => x.CurrencyConversion.ConversionRateSecurities)
.Distinct()
.ToList();
SeedSecurities(securitiesToUpdate, algorithm);
foreach (var cash in cashToUpdate)
{
cash.Update();
}
}
}
}
+57
View File
@@ -0,0 +1,57 @@
/*
* 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 Newtonsoft.Json;
using System.Collections.Generic;
namespace QuantConnect
{
/// <summary>
/// Represents the outcome of a single backtest diagnostic analysis,
/// containing the analysis name, diagnostic context, and a list of solutions.
/// </summary>
public class Analysis(string name, string issue, object sample, int? count, IReadOnlyList<string> solutions)
{
/// <summary>
/// Gets or sets the name of the analysis that produced this result.
/// </summary>
public string Name { get; set; } = name;
/// <summary>
/// Gets or sets a short description of why the analysis was triggered.
/// </summary>
public string Issue { get; set; } = issue;
/// <summary>
/// Gets or sets a representative sample value of the issue detected by the analysis.
/// It can be something like a log message, an order or an order event.
/// </summary>
public object Sample { get; set; } = sample;
/// <summary>
/// Gets or sets the total number of matching occurrences found by the analysis.
/// If null, the analysis is reporting a single issue with the provided sample;
/// if not null, the sample represents one of multiple occurrences of the same issue.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int? Count { get; set; } = count;
/// <summary>
/// Gets or sets human-readable suggestions for resolving the detected issue.
/// </summary>
public IReadOnlyList<string> Solutions { get; set; } = solutions;
}
}
+64
View File
@@ -0,0 +1,64 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Account information for an organization
/// </summary>
public class Account : RestResponse
{
/// <summary>
/// The organization Id
/// </summary>
public string OrganizationId { get; set; }
/// <summary>
/// The current account balance
/// </summary>
public decimal CreditBalance { get; set; }
/// <summary>
/// The current organizations credit card
/// </summary>
public Card Card { get; set; }
}
/// <summary>
/// Credit card
/// </summary>
public class Card
{
/// <summary>
/// Credit card brand
/// </summary>
public string Brand { get; set; }
/// <summary>
/// The credit card expiration
/// </summary>
[JsonConverter(typeof(Time.MonthYearJsonConverter))]
public DateTime Expiration { get; set; }
/// <summary>
/// The last 4 digits of the card
/// </summary>
[JsonProperty(PropertyName = "last4")]
public decimal LastFourDigits { get; set; }
}
}
+126
View File
@@ -0,0 +1,126 @@
/*
* 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.Web;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace QuantConnect.Api
{
/// <summary>
/// Helper methods for api authentication and interaction
/// </summary>
public static class Authentication
{
/// <summary>
/// Generate a secure hash for the authorization headers.
/// </summary>
/// <returns>Time based hash of user token and timestamp.</returns>
public static string Hash(int timestamp)
{
return Hash(timestamp, Globals.UserToken);
}
/// <summary>
/// Generate a secure hash for the authorization headers.
/// </summary>
/// <returns>Time based hash of user token and timestamp.</returns>
public static string Hash(int timestamp, string token)
{
// Create a new hash using current UTC timestamp.
// Hash must be generated fresh each time.
var data = $"{token}:{timestamp.ToStringInvariant()}";
return data.ToSHA256();
}
/// <summary>
/// Create an authenticated link for the target endpoint using the optional given payload
/// </summary>
/// <param name="endpoint">The endpoint</param>
/// <param name="payload">The payload</param>
/// <returns>The authenticated link to trigger the request</returns>
public static string Link(string endpoint, IEnumerable<KeyValuePair<string, object>> payload = null)
{
var queryString = HttpUtility.ParseQueryString(string.Empty);
var timestamp = (int)Time.TimeStamp();
queryString.Add("authorization", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Globals.UserId}:{Hash(timestamp)}")));
queryString.Add("timestamp", timestamp.ToStringInvariant());
PopulateQueryString(queryString, payload);
return $"{Globals.Api}{endpoint.RemoveFromStart("/").RemoveFromEnd("/")}?{queryString}";
}
/// <summary>
/// Helper method to populate a query string with the given payload
/// </summary>
/// <remarks>Useful for testing purposes</remarks>
public static void PopulateQueryString(NameValueCollection queryString, IEnumerable<KeyValuePair<string, object>> payload = null)
{
if (payload != null)
{
foreach (var kv in payload)
{
AddToQuery(queryString, kv);
}
}
}
/// <summary>
/// Will add the given key value pairs to the query encoded as xform data
/// </summary>
private static void AddToQuery(NameValueCollection queryString, KeyValuePair<string, object> keyValuePairs)
{
var objectType = keyValuePairs.Value.GetType();
if (objectType.IsValueType || objectType == typeof(string))
{
// straight
queryString.Add(keyValuePairs.Key, keyValuePairs.Value.ToString());
}
else
{
// let's take advantage of json to load the properties we should include
var serialized = JsonConvert.SerializeObject(keyValuePairs.Value);
foreach (var jObject in JObject.Parse(serialized))
{
var subKey = $"{keyValuePairs.Key}[{jObject.Key}]";
if (jObject.Value is JObject)
{
// inception
AddToQuery(queryString, new KeyValuePair<string, object>(subKey, jObject.Value.ToObject<object>()));
}
else if(jObject.Value is JArray jArray)
{
var counter = 0;
foreach (var value in jArray.ToObject<List<object>>())
{
queryString.Add($"{subKey}[{counter++}]", value.ToString());
}
}
else
{
queryString.Add(subKey, jObject.Value.ToString());
}
}
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace QuantConnect.Api
{
/// <summary>
/// Verify if the credentials are OK.
/// </summary>
public class AuthenticationResponse : RestResponse
{
}
}
+346
View File
@@ -0,0 +1,346 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Statistics;
using System.Collections.Generic;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Util;
namespace QuantConnect.Api
{
/// <summary>
/// A power gauge for backtests, time and parameters to estimate the overfitting risk
/// </summary>
public class ResearchGuide
{
/// <summary>
/// Number of minutes used in developing the current backtest
/// </summary>
public int Minutes { get; set; }
/// <summary>
/// The quantity of backtests run in the project
/// </summary>
public int BacktestCount { get; set; }
/// <summary>
/// Number of parameters detected
/// </summary>
public int Parameters { get; set; }
/// <summary>
/// Project ID
/// </summary>
public int ProjectId { get; set; }
}
/// <summary>
/// Base class for backtest result object response
/// </summary>
public class BasicBacktest : RestResponse
{
/// <summary>
/// Backtest error message
/// </summary>
public string Error { get; set; }
/// <summary>
/// Backtest error stacktrace
/// </summary>
public string Stacktrace { get; set; }
/// <summary>
/// Assigned backtest Id
/// </summary>
public string BacktestId { get; set; }
/// <summary>
/// Status of the backtest
/// </summary>
public string Status { get; set; }
/// <summary>
/// Name of the backtest
/// </summary>
public string Name { get; set; }
/// <summary>
/// Backtest creation date and time
/// </summary>
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
public DateTime Created { get; set; }
/// <summary>
/// Progress of the backtest in percent 0-1.
/// </summary>
public decimal Progress { get; set; }
/// <summary>
/// Optimization task ID, if the backtest is part of an optimization
/// </summary>
public string OptimizationId { get; set; }
/// <summary>
/// Number of tradeable days
/// </summary>
public int TradeableDates { get; set; }
/// <summary>
/// Optimization parameters
/// </summary>
public ParameterSet ParameterSet { get; set; }
/// <summary>
/// Snapshot id of this backtest result
/// </summary>
public int SnapShotId { get; set; }
}
/// <summary>
/// Results object class. Results are exhaust from backtest or live algorithms running in LEAN
/// </summary>
public class Backtest : BasicBacktest
{
/// <summary>
/// Note on the backtest attached by the user
/// </summary>
public string Note { get; set; }
/// <summary>
/// Boolean true when the backtest is completed.
/// </summary>
public bool Completed { get; set; }
/// <summary>
/// Organization ID
/// </summary>
public string OrganizationId { get; set; }
/// <summary>
/// Rolling window detailed statistics.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, AlgorithmPerformance> RollingWindow { get; set; }
/// <summary>
/// Total algorithm performance statistics.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public AlgorithmPerformance TotalPerformance { get; set; }
/// <summary>
/// Charts updates for the live algorithm since the last result packet
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, Chart> Charts { get; set; }
/// <summary>
/// Statistics information sent during the algorithm operations.
/// </summary>
/// <remarks>Intended for update mode -- send updates to the existing statistics in the result GUI. If statistic key does not exist in GUI, create it</remarks>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, string> Statistics { get; set; }
/// <summary>
/// Runtime banner/updating statistics in the title banner of the live algorithm GUI.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, string> RuntimeStatistics { get; set; }
/// <summary>
/// A power gauge for backtests, time and parameters to estimate the overfitting risk
/// </summary>
public ResearchGuide ResearchGuide { get; set; }
/// <summary>
/// The starting time of the backtest
/// </summary>
public DateTime? BacktestStart { get; set; }
/// <summary>
/// The ending time of the backtest
/// </summary>
public DateTime? BacktestEnd { get; set; }
/// <summary>
/// Indicates if the backtest has error during initialization
/// </summary>
public bool HasInitializeError { get; set; }
/// <summary>
/// The backtest node name
/// </summary>
public string NodeName { get; set; }
/// <summary>
/// The associated project id
/// </summary>
public int ProjectId { get; set; }
/// <summary>
/// End date of out of sample data
/// </summary>
public DateTime? OutOfSampleMaxEndDate { get; set; }
/// <summary>
/// Number of days of out of sample days
/// </summary>
public int? OutOfSampleDays { get; set; }
/// <summary>
/// Backtest analysis results.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<Analysis> Analysis { get; set; }
}
/// <summary>
/// Result object class for the List Backtest response from the API
/// </summary>
public class BacktestSummary : BasicBacktest
{
/// <summary>
/// Sharpe ratio with respect to risk free rate: measures excess of return per unit of risk
/// </summary>
public decimal? SharpeRatio { get; set; }
/// <summary>
/// Algorithm "Alpha" statistic - abnormal returns over the risk free rate and the relationshio (beta) with the benchmark returns
/// </summary>
public decimal? Alpha { get; set; }
/// <summary>
/// Algorithm "beta" statistic - the covariance between the algorithm and benchmark performance, divided by benchmark's variance
/// </summary>
public decimal? Beta { get; set; }
/// <summary>
/// Annual compounded returns statistic based on the final-starting capital and years
/// </summary>
public decimal? CompoundingAnnualReturn { get; set; }
/// <summary>
/// Drawdown maximum percentage
/// </summary>
public decimal? Drawdown { get; set; }
/// <summary>
/// The ratio of the number of trades with zero or negative profit loss to the total number of trades
/// </summary>
public decimal? LossRate { get; set; }
/// <summary>
/// Net profit percentage
/// </summary>
public decimal? NetProfit { get; set; }
/// <summary>
/// Number of parameters in the backtest
/// </summary>
public int? Parameters { get; set; }
/// <summary>
/// Price-to-sales ratio
/// </summary>
public decimal? Psr { get; set; }
/// <summary>
/// SecurityTypes present in the backtest
/// </summary>
public string? SecurityTypes { get; set; }
/// <summary>
/// Sortino ratio with respect to risk free rate: measures excess of return per unit of downside risk
/// </summary>
public decimal? SortinoRatio { get; set; }
/// <summary>
/// Number of trades in the backtest
/// </summary>
public int? Trades { get; set; }
/// <summary>
/// Treynor ratio statistic is a measurement of the returns earned in excess of that which could have been earned on an investment that has no diversifiable risk
/// </summary>
public decimal? TreynorRatio { get; set; }
/// <summary>
/// The ratio of the number of trades with positive profit loss to the total number of trades
/// </summary>
public decimal? WinRate { get; set; }
/// <summary>
/// Collection of tags for the backtest
/// </summary>
public List<string> Tags { get; set; }
}
/// <summary>
/// Wrapper class for Backtest/* endpoints JSON response
/// Currently used by Backtest/Read and Backtest/Create
/// </summary>
public class BacktestResponseWrapper : RestResponse
{
/// <summary>
/// Backtest Object
/// </summary>
public Backtest Backtest { get; set; }
/// <summary>
/// Indicates if the backtest is run under debugging mode
/// </summary>
public bool Debugging { get; set; }
}
/// <summary>
/// Collection container for a list of backtests for a project
/// </summary>
public class BacktestList : RestResponse
{
/// <summary>
/// Collection of summarized backtest objects
/// </summary>
public List<Backtest> Backtests { get; set; }
}
/// <summary>
/// Collection container for a list of backtest summaries for a project
/// </summary>
public class BacktestSummaryList : RestResponse
{
/// <summary>
/// Collection of summarized backtest summary objects
/// </summary>
public List<BacktestSummary> Backtests { get; set; }
/// <summary>
/// Number of backtest summaries retrieved in the response
/// </summary>
public int Count { get; set; }
}
/// <summary>
/// Collection container for a list of backtest tags
/// </summary>
public class BacktestTags : RestResponse
{
/// <summary>
/// Collection of tags for a backtest
/// </summary>
public List<string> Tags { get; set; }
}
}
+29
View File
@@ -0,0 +1,29 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Backtest Report Response wrapper
/// </summary>
public class BacktestReport : RestResponse
{
/// <summary>
/// HTML data of the report with embedded base64 images
/// </summary>
public string Report { get; set; }
}
}
+113
View File
@@ -0,0 +1,113 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Objectives;
using System.Collections.Generic;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Util;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
namespace QuantConnect.Api
{
/// <summary>
/// BaseOptimization item from the QuantConnect.com API.
/// </summary>
public class BaseOptimization : RestResponse
{
/// <summary>
/// Optimization ID
/// </summary>
public string OptimizationId { get; set; }
/// <summary>
/// Project ID of the project the optimization belongs to
/// </summary>
public int ProjectId { get; set; }
/// <summary>
/// Name of the optimization
/// </summary>
public string Name { get; set; }
/// <summary>
/// Status of the optimization
/// </summary>
[JsonConverter(typeof(StringEnumConverter), converterParameters: typeof(CamelCaseNamingStrategy))]
public OptimizationStatus Status { get; set; }
/// <summary>
/// Optimization node type
/// </summary>
/// <remarks><see cref="OptimizationNodes"/></remarks>
public string NodeType { get; set; }
/// <summary>
/// Number of days of out of sample days
/// </summary>
public int OutOfSampleDays { get; set; }
/// <summary>
/// End date of out of sample data
/// </summary>
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
public DateTime? OutOfSampleMaxEndDate { get; set; }
/// <summary>
/// Parameters used in this optimization
/// </summary>
public List<OptimizationParameter> Parameters { get; set; }
/// <summary>
/// Optimization statistical target
/// </summary>
public Target Criterion { get; set; }
}
/// <summary>
/// Optimization summary response for creating an optimization
/// </summary>
public class OptimizationSummary: BaseOptimization
{
/// <summary>
/// Date when this optimization was created
/// </summary>
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
public DateTime Created { get; set; }
/// <summary>
/// Price-sales ratio stastic
/// </summary>
public decimal? PSR { get; set; }
/// <summary>
/// Sharpe ratio statistic
/// </summary>
public decimal? SharpeRatio { get; set; }
/// <summary>
/// Number of trades
/// </summary>
public int? Trades { get; set; }
/// <summary>
/// ID of project, were this current project was originally cloned
/// </summary>
public int? CloneId { get; set; }
}
}
+58
View File
@@ -0,0 +1,58 @@
/*
* 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.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace QuantConnect.Api
{
/// <summary>
/// Response from the compiler on a build event
/// </summary>
public class Compile : RestResponse
{
/// <summary>
/// Compile Id for a sucessful build
/// </summary>
public string CompileId { get; set; }
/// <summary>
/// True on successful compile
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public CompileState State { get; set; }
/// <summary>
/// Logs of the compilation request
/// </summary>
public List<string> Logs { get; set; }
/// <summary>
/// Project Id we sent for compile
/// </summary>
public int ProjectId { get; set; }
/// <summary>
/// Signature key of compilation
/// </summary>
public string Signature { get; set; }
/// <summary>
/// Signature order of files to be compiled
/// </summary>
public List<string> SignatureOrder { get; set; }
}
}
+37
View File
@@ -0,0 +1,37 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace QuantConnect.Api
{
/// <summary>
/// State of the compilation request
/// </summary>
public enum CompileState
{
/// <summary>
/// Compile waiting in the queue to be processed.
/// </summary>
InQueue,
/// <summary>
/// Compile was built successfully
/// </summary>
BuildSuccess,
/// <summary>
/// Build error, check logs for more information
/// </summary>
BuildError
}
}
+151
View File
@@ -0,0 +1,151 @@
/*
* 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.Linq;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Text.RegularExpressions;
// Collection of response objects for Quantconnect Data/ endpoints
namespace QuantConnect.Api
{
/// <summary>
/// Data/Read response wrapper, contains link to requested data
/// </summary>
public class DataLink : RestResponse
{
/// <summary>
/// Url to the data requested
/// </summary>
public string Link { get; set; }
/// <summary>
/// Remaining QCC balance on account after this transaction
/// </summary>
public double Balance { get; set; }
/// <summary>
/// QCC Cost for this data link
/// </summary>
public double Cost { get; set; }
}
/// <summary>
/// Data/List response wrapper for available data
/// </summary>
public class DataList : RestResponse
{
/// <summary>
/// List of all available data from this request
/// </summary>
[JsonProperty(PropertyName = "objects")]
public List<string> AvailableData { get; set; }
}
/// <summary>
/// Data/Prices response wrapper for prices by vendor
/// </summary>
public class DataPricesList : RestResponse
{
/// <summary>
/// Collection of prices objects
/// </summary>
public List<PriceEntry> Prices { get; set; }
/// <summary>
/// The Agreement URL for this Organization
/// </summary>
[JsonProperty(PropertyName = "agreement")]
public string AgreementUrl { get; set; }
/// <summary>
/// Get the price in QCC for a given data file
/// </summary>
/// <param name="path">Lean data path of the file</param>
/// <returns>QCC price for data, -1 if no entry found</returns>
public int GetPrice(string path)
{
if (path == null)
{
return -1;
}
var entry = Prices.FirstOrDefault(x => x.RegEx.IsMatch(path));
return entry?.Price ?? -1;
}
}
/// <summary>
/// Prices entry for Data/Prices response
/// </summary>
public class PriceEntry
{
private Regex _regex;
/// <summary>
/// Vendor for this price
/// </summary>
[JsonProperty(PropertyName = "vendorName")]
public string Vendor { get; set; }
/// <summary>
/// Regex for this data price entry
/// Trims regex open, close, and multiline flag
/// because it won't match otherwise
/// </summary>
public Regex RegEx
{
get
{
if (_regex == null && RawRegEx != null)
{
_regex = new Regex(RawRegEx.TrimStart('/').TrimEnd('m').TrimEnd('/'), RegexOptions.Compiled);
}
return _regex;
}
}
/// <summary>
/// RegEx directly from response
/// </summary>
[JsonProperty(PropertyName = "regex")]
public string RawRegEx { get; set; }
/// <summary>
/// The price for this entry in QCC
/// </summary>
public int? Price { get; set; }
/// <summary>
/// The type associated to this price entry if any
/// </summary>
public string Type { get; set; }
/// <summary>
/// True if the user is subscribed
/// </summary>
public bool? Subscribed { get; set; }
/// <summary>
/// The associated product id
/// </summary>
public int ProductId { get; set; }
/// <summary>
/// The associated data paths
/// </summary>
public HashSet<string> Paths { get; set; }
}
}
+52
View File
@@ -0,0 +1,52 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Estimate response packet from the QuantConnect.com API.
/// </summary>
public class Estimate: StringRepresentation
{
/// <summary>
/// Estimate id
/// </summary>
public string EstimateId { get; set; }
/// <summary>
/// Estimate time in seconds
/// </summary>
public int Time { get; set; }
/// <summary>
/// Estimate balance in QCC
/// </summary>
public int Balance { get; set; }
}
/// <summary>
/// Wrapper class for Optimizations/* endpoints JSON response
/// Currently used by Optimizations/Estimate
/// </summary>
public class EstimateResponseWrapper : RestResponse
{
/// <summary>
/// Estimate object
/// </summary>
public Estimate Estimate { get; set; }
}
}
+36
View File
@@ -0,0 +1,36 @@
/*
* 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.Collections.Generic;
using QuantConnect.Algorithm.Framework.Alphas;
namespace QuantConnect.Api
{
/// <summary>
/// Class containing insights and the number of insights of the live algorithm in the request criteria
/// </summary>
public class InsightResponse: RestResponse
{
/// <summary>
/// Collection of insights
/// </summary>
public List<Insight> Insights { get; set; }
/// <summary>
/// Total number of returned insights
/// </summary>
public int Length { get; set; }
}
}
+108
View File
@@ -0,0 +1,108 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Class representing the REST response from QC API when creating or reading a live algorithm
/// </summary>
public class BaseLiveAlgorithm : RestResponse
{
/// <summary>
/// Project id for the live instance
/// </summary>
public int ProjectId { get; set; }
/// <summary>
/// Unique live algorithm deployment identifier (similar to a backtest id).
/// </summary>
public string DeployId { get; set; }
}
/// <summary>
/// Class representing the REST response from QC API when creating a live algorithm
/// </summary>
public class CreateLiveAlgorithmResponse : BaseLiveAlgorithm
{
/// <summary>
/// The version of the Lean used to run the algorithm
/// </summary>
public int VersionId { get; set; }
/// <summary>
/// Id of the node that will run the algorithm
/// </summary>
public string Source { get; set; }
/// <summary>
/// HTTP status response code
/// </summary>
public string ResponseCode { get; set; }
}
/// <summary>
/// Response from List Live Algorithms request to QuantConnect Rest API.
/// </summary>
public class LiveAlgorithmSummary : BaseLiveAlgorithm
{
/// <summary>
/// Algorithm status: running, stopped or runtime error.
/// </summary>
public AlgorithmStatus Status { get; set; }
/// <summary>
/// Datetime the algorithm was launched in UTC.
/// </summary>
public DateTime Launched { get; set; }
/// <summary>
/// Datetime the algorithm was stopped in UTC, null if its still running.
/// </summary>
public DateTime? Stopped { get; set; }
/// <summary>
/// Brokerage
/// </summary>
public string Brokerage { get; set; }
/// <summary>
/// Chart we're subscribed to
/// </summary>
/// <remarks>
/// Data limitations mean we can only stream one chart at a time to the consumer. See which chart you're watching here.
/// </remarks>
public string Subscription { get; set; }
/// <summary>
/// Live algorithm error message from a crash or algorithm runtime error.
/// </summary>
public string Error { get; set; }
}
/// <summary>
/// List of the live algorithms running which match the requested status
/// </summary>
public class LiveList : RestResponse
{
/// <summary>
/// Algorithm list matching the requested status.
/// </summary>
[JsonProperty(PropertyName = "live")]
public List<LiveAlgorithmSummary> Algorithms { get; set; }
}
}
+124
View File
@@ -0,0 +1,124 @@
/*
* 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 Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using QuantConnect.Packets;
using System;
using System.Collections.Generic;
namespace QuantConnect.Api
{
/// <summary>
/// Details a live algorithm from the "live/read" Api endpoint
/// </summary>
public class LiveAlgorithmResults : RestResponse
{
/// <summary>
/// Error message
/// </summary>
public string Message { get; set; }
/// <summary>
/// Indicates the status of the algorihtm, i.e. 'Running', 'Stopped'
/// </summary>
public string Status { get; set; }
/// <summary>
/// Algorithm deployment ID
/// </summary>
public string DeployId { get; set; }
/// <summary>
/// The snapshot project ID for cloning the live development's source code.
/// </summary>
public int CloneId { get; set; }
/// <summary>
/// Date the live algorithm was launched
/// </summary>
public DateTime Launched { get; set; }
/// <summary>
/// Date the live algorithm was stopped
/// </summary>
public DateTime? Stopped { get; set; }
/// <summary>
/// Brokerage used in the live algorithm
/// </summary>
public string Brokerage { get; set; }
/// <summary>
/// Security types present in the live algorithm
/// </summary>
public string SecurityTypes { get; set; }
/// <summary>
/// Name of the project the live algorithm is in
/// </summary>
public string ProjectName { get; set; }
/// <summary>
/// Name of the data center where the algorithm is physically located.
/// </summary>
public string Datacenter { get; set; }
/// <summary>
/// Indicates if the algorithm is being live shared
/// </summary>
public bool Public { get; set; }
/// <summary>
/// Files present in the project in which the algorithm is
/// </summary>
public List<ProjectFile> Files { get; set; }
/// <summary>
/// Runtime banner/updating statistics in the title banner of the live algorithm GUI.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, string> RuntimeStatistics { get; set; }
/// <summary>
/// Charts updates for the live algorithm since the last result packet
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, Chart> Charts { get; set; }
}
/// <summary>
/// Holds information about the state and operation of the live running algorithm
/// </summary>
public class LiveResultsData
{
/// <summary>
/// Results version
/// </summary>
public int Version { get; set; }
/// <summary>
/// Temporal resolution of the results returned from the Api
/// </summary>
[JsonProperty(PropertyName = "resolution"), JsonConverter(typeof(StringEnumConverter))]
public Resolution Resolution { get; set; }
/// <summary>
/// Class to represent the data groups results return from the Api
/// </summary>
[JsonProperty(PropertyName = "results")]
public LiveResult Results { get; set; }
}
}
@@ -0,0 +1,132 @@
/*
* 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.Linq;
using Newtonsoft.Json;
using QuantConnect.Util;
using QuantConnect.Orders;
using Newtonsoft.Json.Linq;
using QuantConnect.Packets;
using QuantConnect.Securities;
using System.Collections.Generic;
namespace QuantConnect.Api
{
/// <summary>
/// Custom JsonConverter for LiveResults data for live algorithms
/// </summary>
public class LiveAlgorithmResultsJsonConverter : JsonConverter
{
/// <summary>
/// Gets a value indicating whether this <see cref="T:Newtonsoft.Json.JsonConverter"/> can write JSON.
/// </summary>
/// <value>
/// <c>true</c> if this <see cref="T:Newtonsoft.Json.JsonConverter"/> can write JSON; otherwise, <c>false</c>.
/// </value>
public override bool CanWrite
{
get { return false; }
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter"/> to write to.</param><param name="value">The value.</param><param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("The LiveAlgorithmResultsJsonConverter does not implement a WriteJson method.");
}
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return typeof(LiveAlgorithmResults).IsAssignableFrom(objectType);
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param><param name="objectType">Type of the object.</param><param name="existingValue">The existing value of object being read.</param><param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
// We don't deserialize the json object directly since it contains properties such as `files` and `charts`
// that need to be deserialized in a different way
var liveAlgoResults = new LiveAlgorithmResults
{
Message = jObject["message"].Value<string>(),
Status = jObject["status"].Value<string>(),
DeployId = jObject["deployId"].Value<string>(),
CloneId = jObject["cloneId"].Value<int>(),
Launched = jObject["launched"].Value<DateTime>(),
Stopped = jObject["stopped"].Value<DateTime?>(),
Brokerage = jObject["brokerage"].Value<string>(),
SecurityTypes = jObject["securityTypes"].Value<string>(),
ProjectName = jObject["projectName"].Value<string>(),
Datacenter = jObject["datacenter"].Value<string>(),
Public = jObject["public"].Value<bool>(),
Success = jObject["success"].Value<bool>()
};
if (!liveAlgoResults.Success)
{
// Either there was an error in the running algorithm or the algorithm hasn't started
liveAlgoResults.Errors = jObject.Last.Children().Select(error => error.ToString()).ToList();
return liveAlgoResults;
}
// Deserialize charting data
var chartDictionary = new Dictionary<string, Chart>();
var charts = jObject["charts"] ?? jObject["Charts"];
if (charts != null)
{
var stringCharts = jObject["charts"]?.ToString() ?? jObject["Charts"].ToString();
if(!string.IsNullOrEmpty(stringCharts))
{
chartDictionary = JsonConvert.DeserializeObject<Dictionary<string, Chart>>(stringCharts);
}
}
// Deserialize files data
var projectFiles = new List<ProjectFile>();
var files = jObject["files"] ?? jObject["Files"];
if (files != null)
{
var stringFiles = jObject["files"]?.ToString() ?? jObject["Files"].ToString();
if (!string.IsNullOrEmpty(stringFiles))
{
projectFiles = JsonConvert.DeserializeObject<List<ProjectFile>>(stringFiles);
}
}
liveAlgoResults.Charts = chartDictionary;
liveAlgoResults.Files = projectFiles;
return liveAlgoResults;
}
}
}
+141
View File
@@ -0,0 +1,141 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using QuantConnect.Brokerages;
namespace QuantConnect.Api
{
/// <summary>
/// Helper class to put BaseLiveAlgorithmSettings in proper format.
/// </summary>
public class LiveAlgorithmApiSettingsWrapper
{
/// <summary>
/// Constructor for LiveAlgorithmApiSettingsWrapper
/// </summary>
/// <param name="projectId">Id of project from QuantConnect</param>
/// <param name="compileId">Id of compilation of project from QuantConnect</param>
/// <param name="nodeId">Server type to run live Algorithm</param>
/// <param name="settings">Dictionary with brokerage specific settings. Each brokerage requires certain specific credentials
/// in order to process the given orders. Each key in this dictionary represents a required field/credential
/// to provide to the brokerage API and its value represents the value of that field. For example: "brokerageSettings: {
/// "id": "Binance", "binance-api-secret": "123ABC", "binance-api-key": "ABC123"}. It is worth saying,
/// that this dictionary must always contain an entry whose key is "id" and its value is the name of the brokerage
/// (see <see cref="Brokerages.BrokerageName"/>)</param>
/// <param name="version">The version identifier</param>
/// <param name="dataProviders">Dictionary with data providers credentials. Each data provider requires certain credentials
/// in order to retrieve data from their API. Each key in this dictionary describes a data provider name
/// and its corresponding value is another dictionary with the required key-value pairs of credential
/// names and values. For example: "dataProviders: {InteractiveBrokersBrokerage : { "id": 12345, "environement" : "paper",
/// "username": "testUsername", "password": "testPassword"}}"</param>
/// <param name="parameters">Dictionary to specify the parameters for the live algorithm</param>
/// <param name="notification">Dictionary with the lists of events and targets</param>
public LiveAlgorithmApiSettingsWrapper(
int projectId,
string compileId,
string nodeId,
Dictionary<string, object> settings,
string version = "-1",
Dictionary<string, object> dataProviders = null,
Dictionary<string, string> parameters = null,
Dictionary<string, List<string>> notification = null)
{
VersionId = version;
ProjectId = projectId;
CompileId = compileId;
NodeId = nodeId;
Brokerage = settings;
var quantConnectDataProvider = new Dictionary<string, string>
{
{ "id", "QuantConnectBrokerage" },
};
DataProviders = dataProviders ?? new Dictionary<string, object>()
{
{ "QuantConnectBrokerage", quantConnectDataProvider },
};
Signature = CompileId.Split("-").LastOrDefault();
Parameters = parameters ?? new Dictionary<string, string>();
Notification = notification ?? new Dictionary<string, List<string>>();
AutomaticRedeploy = false;
}
/// <summary>
/// -1 is master
/// </summary>
[JsonProperty(PropertyName = "versionId")]
public string VersionId { get; set; }
/// <summary>
/// Project id for the live instance
/// </summary>
[JsonProperty(PropertyName = "projectId")]
public int ProjectId { get; private set; }
/// <summary>
/// Compile Id for the live algorithm
/// </summary>
[JsonProperty(PropertyName = "compileId")]
public string CompileId { get; private set; }
/// <summary>
/// Id of the node being used to run live algorithm
/// </summary>
[JsonProperty(PropertyName = "nodeId")]
public string NodeId { get; private set; }
/// <summary>
/// Signature of the live algorithm
/// </summary>
[JsonProperty(PropertyName = "signature")]
public string Signature { get; private set; }
/// <summary>
/// True to enable Automatic Re-Deploy of the live algorithm,
/// false otherwise
/// </summary>
[JsonProperty(PropertyName = "automaticRedeploy")]
public bool AutomaticRedeploy { get; private set; }
/// <summary>
/// The API expects the settings as part of a brokerage object
/// </summary>
[JsonProperty(PropertyName = "brokerage")]
public Dictionary<string, object> Brokerage { get; private set; }
/// <summary>
/// Dictionary with the data providers and their corresponding credentials
/// </summary>
[JsonProperty(PropertyName = "dataProviders")]
public Dictionary<string, object> DataProviders { get; private set; }
/// <summary>
/// Dictionary with the parameters to be used in the live algorithm
/// </summary>
[JsonProperty(PropertyName = "parameters")]
public Dictionary<string, string> Parameters { get; private set; }
/// <summary>
/// Dictionary with the lists of events and targets
/// </summary>
[JsonProperty(PropertyName = "notification")]
public Dictionary<string, List<string>> Notification { get; private set; }
}
}
+41
View File
@@ -0,0 +1,41 @@
/*
* 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.Collections.Generic;
using Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Logs from a live algorithm
/// </summary>
public class LiveLog : RestResponse
{
/// <summary>
/// List of logs from the live algorithm
/// </summary>
public List<string> Logs { get; set; }
/// <summary>
/// Total amount of rows in the logs
/// </summary>
public int Length { get; set; }
/// <summary>
/// Amount of log rows before the current deployment
/// </summary>
public int DeploymentOffset { get; set; }
}
}
+300
View File
@@ -0,0 +1,300 @@
/*
* 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.Collections.Generic;
using Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Node class built for API endpoints nodes/read and nodes/create.
/// Converts JSON properties from API response into data members for the class.
/// Contains all relevant information on a Node to interact through API endpoints.
/// </summary>
public class Node
{
/// <summary>
/// The nodes cpu clock speed in GHz.
/// </summary>
[JsonProperty(PropertyName = "speed")]
public decimal Speed { get; set; }
/// <summary>
/// The monthly and yearly prices of the node in US dollars,
/// see <see cref="NodePrices"/> for type.
/// </summary>
[JsonProperty(PropertyName = "price")]
public NodePrices Prices { get; set; }
/// <summary>
/// CPU core count of node.
/// </summary>
[JsonProperty(PropertyName = "cpu")]
public int CpuCount { get; set; }
/// <summary>
/// Indicate if the node has GPU (1) or not (0)
/// </summary>
[JsonProperty(PropertyName = "hasGpu")]
public int HasGPU { get; set; }
/// <summary>
/// Size of RAM in Gigabytes.
/// </summary>
[JsonProperty(PropertyName = "ram")]
public decimal Ram { get; set; }
/// <summary>
/// Name of the node.
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// Node type identifier for configuration.
/// </summary>
[JsonProperty(PropertyName = "sku")]
public string SKU { get; set; }
/// <summary>
/// Description of the node.
/// </summary>
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }
/// <summary>
/// User currently using the node.
/// </summary>
[JsonProperty(PropertyName = "usedBy")]
public string UsedBy { get; set; }
/// <summary>
/// URL of the user using the node
/// </summary>
[JsonProperty(PropertyName = "userProfile")]
public string UserProfile { get; set; }
/// <summary>
/// Project the node is being used for.
/// </summary>
[JsonProperty(PropertyName = "projectName")]
public string ProjectName { get; set; }
/// <summary>
/// Id of the project the node is being used for.
/// </summary>
[JsonProperty(PropertyName = "projectId")]
public int? ProjectId { get; set; }
/// <summary>
/// Indicates if the node is currently busy.
/// </summary>
[JsonProperty(PropertyName = "busy")]
public bool Busy { get; set; }
/// <summary>
/// Full ID of node.
/// </summary>
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
/// <summary>
/// Maximum number of assets recommended for this node.
/// </summary>
[JsonProperty(PropertyName = "assets")]
public int Assets { get; set; }
/// <summary>
/// Node host.
/// </summary>
[JsonProperty(PropertyName = "host")]
public string Host { get; set; }
/// <summary>
/// Indicate if this is the active node. The project will use this node if it's not busy.
/// </summary>
[JsonProperty(PropertyName = "active")]
public bool Active { get; set; }
}
/// <summary>
/// Collection of <see cref="Node"/> objects for each target environment.
/// </summary>
public class NodeList : RestResponse
{
/// <summary>
/// Collection of backtest nodes
/// </summary>
[JsonProperty(PropertyName = "backtest")]
public List<Node> BacktestNodes { get; set; }
/// <summary>
/// Collection of research nodes
/// </summary>
[JsonProperty(PropertyName = "research")]
public List<Node> ResearchNodes { get; set; }
/// <summary>
/// Collection of live nodes
/// </summary>
[JsonProperty(PropertyName = "live")]
public List<Node> LiveNodes { get; set; }
}
/// <summary>
/// Rest api response wrapper for node/create, reads in the nodes information into a
/// node object
/// </summary>
public class CreatedNode : RestResponse
{
/// <summary>
/// The created node from node/create
/// </summary>
[JsonProperty("node")]
public Node Node { get; set; }
}
/// <summary>
/// Class for generating a SKU for a node with a given configuration
/// Every SKU is made up of 3 variables:
/// - Target environment (L for live, B for Backtest, R for Research)
/// - CPU core count
/// - Dedicated RAM (GB)
/// </summary>
public class SKU
{
/// <summary>
/// The number of CPU cores in the node
/// </summary>
public int Cores { get; set; }
/// <summary>
/// Size of RAM in GB of the Node
/// </summary>
public int Memory { get; set; }
/// <summary>
/// Target environment for the node
/// </summary>
public NodeType Target { get; set; }
/// <summary>
/// Constructs a SKU object out of the provided node configuration
/// </summary>
/// <param name="cores">Number of cores</param>
/// <param name="memory">Size of RAM in GBs</param>
/// <param name="target">Target Environment Live/Backtest/Research</param>
public SKU(int cores, int memory, NodeType target)
{
Cores = cores;
Memory = memory;
Target = target;
}
/// <summary>
/// Generates the SKU string for API calls based on the specifications of the node
/// </summary>
/// <returns>String representation of the SKU</returns>
public override string ToString()
{
string result = "";
switch (Target)
{
case NodeType.Backtest:
result += "B";
break;
case NodeType.Research:
result += "R";
break;
case NodeType.Live:
result += "L";
break;
}
if (Cores == 0)
{
result += "-MICRO";
}
else
{
result += Cores + "-" + Memory;
}
return result;
}
}
/// <summary>
/// NodeTypes enum for all possible options of target environments
/// Used in conjuction with SKU class as a NodeType is a required parameter for SKU
/// </summary>
public enum NodeType
{
/// <summary>
/// A node for running backtests (0)
/// </summary>
Backtest,
/// <summary>
/// A node for running research (1)
/// </summary>
Research,
/// <summary>
/// A node for live trading (2)
/// </summary>
Live
}
/// <summary>
/// Class for deserializing node prices from node object
/// </summary>
public class NodePrices
{
/// <summary>
/// The monthly price of the node in US dollars
/// </summary>
[JsonProperty(PropertyName = "monthly")]
public int Monthly { get; set; }
/// <summary>
/// The yearly prices of the node in US dollars
/// </summary>
[JsonProperty(PropertyName = "yearly")]
public int Yearly { get; set; }
}
/// <summary>
/// Supported optimization nodes
/// </summary>
public static class OptimizationNodes
{
/// <summary>
/// 2 CPUs 8 GB ram
/// </summary>
public static string O2_8 => "O2-8";
/// <summary>
/// 4 CPUs 12 GB ram
/// </summary>
public static string O4_12 => "O4-12";
/// <summary>
/// 8 CPUs 16 GB ram
/// </summary>
public static string O8_16 => "O8-16";
}
}
+153
View File
@@ -0,0 +1,153 @@
/*
* 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 Newtonsoft.Json;
using System.Collections.Generic;
using System;
namespace QuantConnect.Api
{
/// <summary>
/// Response received when fetching Object Store
/// </summary>
public class GetObjectStoreResponse : RestResponse
{
/// <summary>
/// Job ID which can be used for querying state or packaging
/// </summary>
[JsonProperty("jobId")]
public string JobId { get; set; }
/// <summary>
/// The URL to download the object. This can also be null
/// </summary>
[JsonProperty("url")]
public string Url { get; set; }
}
/// <summary>
/// Class contining basic store properties present in the REST response from QC API
/// </summary>
public class BasicObjectStore
{
/// <summary>
/// Object store key
/// </summary>
[JsonProperty(PropertyName = "key")]
public string Key { get; set; }
/// <summary>
/// Last time it was modified
/// </summary>
[JsonProperty(PropertyName = "modified")]
public DateTime? Modified { get; set; }
/// <summary>
/// MIME type
/// </summary>
[JsonProperty(PropertyName = "mime")]
public string Mime { get; set; }
/// <summary>
/// File size
/// </summary>
[JsonProperty(PropertyName = "size")]
public decimal? Size { get; set; }
}
/// <summary>
/// Summary information of the Object Store
/// </summary>
public class SummaryObjectStore: BasicObjectStore
{
/// <summary>
/// File or folder name
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// True if it is a folder, false otherwise
/// </summary>
[JsonProperty(PropertyName = "isFolder")]
public bool IsFolder { get; set; }
}
/// <summary>
/// Object Store file properties
/// </summary>
public class PropertiesObjectStore: BasicObjectStore
{
/// <summary>
/// Date this object was created
/// </summary>
[JsonProperty(PropertyName = "created")]
public DateTime Created { get; set; }
/// <summary>
/// MD5 (hashing algorithm) hash authentication code
/// </summary>
[JsonProperty(PropertyName = "md5")]
public string Md5 { get; set; }
/// <summary>
/// Preview of the Object Store file content
/// </summary>
[JsonProperty(PropertyName = "preview")]
public string Preview { get; set; }
}
/// <summary>
/// Response received containing a list of stored objects metadata, as well as the total size of all of them.
/// </summary>
public class ListObjectStoreResponse : RestResponse
{
/// <summary>
/// Path to the files in the Object Store
/// </summary>
[JsonProperty(PropertyName = "path")]
public string Path { get; set; }
/// <summary>
/// List of objects stored
/// </summary>
[JsonProperty(PropertyName = "objects")]
public List<SummaryObjectStore> Objects { get; set; }
/// <summary>
/// Size of all objects stored in bytes
/// </summary>
[JsonProperty(PropertyName = "objectStorageUsed")]
public long ObjectStorageUsed { get; set; }
/// <summary>
/// Size of all the objects stored in human-readable format
/// </summary>
[JsonProperty(PropertyName = "objectStorageUsedHuman")]
public string ObjectStorageUsedHuman { get; set; }
}
/// <summary>
/// Response received containing the properties of the requested Object Store
/// </summary>
public class PropertiesObjectStoreResponse : RestResponse
{
/// <summary>
/// Object Store properties
/// </summary>
[JsonProperty(PropertyName = "metadata")]
public PropertiesObjectStore Properties { get; set; }
}
}
+112
View File
@@ -0,0 +1,112 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using QuantConnect.Optimizer;
using QuantConnect.Optimizer.Objectives;
using QuantConnect.Util;
namespace QuantConnect.Api
{
/// <summary>
/// Optimization response packet from the QuantConnect.com API.
/// </summary>
public class Optimization : BaseOptimization
{
/// <summary>
/// Snapshot ID of this optimization
/// </summary>
public int? SnapshotId { get; set; }
/// <summary>
/// Statistic to be optimized
/// </summary>
public string OptimizationTarget { get; set; }
/// <summary>
/// List with grid charts representing the grid layout
/// </summary>
public List<GridChart> GridLayout { get; set; }
/// <summary>
/// Runtime banner/updating statistics for the optimization
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, string> RuntimeStatistics { get; set; }
/// <summary>
/// Optimization constraints
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<Constraint> Constraints { get; set; }
/// <summary>
/// Number of parallel nodes for optimization
/// </summary>
public int ParallelNodes { get; set; }
/// <summary>
/// Optimization constraints
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, OptimizationBacktest> Backtests { get; set; }
/// <summary>
/// Optimization strategy
/// </summary>
public string Strategy { get; set; }
/// <summary>
/// Optimization requested date and time
/// </summary>
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
public DateTime Requested { get; set; }
/// <summary>
/// Aggregate diagnostic of the optimization; omitted when no analysis was produced.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public OptimizationAnalysis Analysis { get; set; }
}
/// <summary>
/// Wrapper class for Optimizations/Read endpoint JSON response
/// </summary>
public class OptimizationResponseWrapper : RestResponse
{
/// <summary>
/// Optimization object
/// </summary>
public Optimization Optimization { get; set; }
}
/// <summary>
/// Collection container for a list of summarized optimizations for a project
/// </summary>
public class OptimizationList : RestResponse
{
/// <summary>
/// Collection of summarized optimization objects
/// </summary>
public List<OptimizationSummary> Optimizations { get; set; }
/// <summary>
/// The optimization count
/// </summary>
public int Count => Optimizations?.Count ?? 0;
}
}
+102
View File
@@ -0,0 +1,102 @@
/*
* 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 Newtonsoft.Json;
using System.Collections.Generic;
using QuantConnect.Optimizer.Parameters;
namespace QuantConnect.Api
{
/// <summary>
/// OptimizationBacktest object from the QuantConnect.com API.
/// </summary>
[JsonConverter(typeof(OptimizationBacktestJsonConverter))]
public class OptimizationBacktest
{
/// <summary>
/// Progress of the backtest as a percentage from 0-1 based on the days lapsed from start-finish.
/// </summary>
public decimal Progress { get; set; }
/// <summary>
/// The backtest name
/// </summary>
public string Name { get; }
/// <summary>
/// The backtest host name
/// </summary>
public string HostName { get; set; }
/// <summary>
/// The backtest id
/// </summary>
public string BacktestId { get; }
/// <summary>
/// Represent a combination as key value of parameters, i.e. order doesn't matter
/// </summary>
public ParameterSet ParameterSet { get; }
/// <summary>
/// The backtest statistics results
/// </summary>
public IDictionary<string, string> Statistics { get; set; }
/// <summary>
/// The backtest equity chart series
/// </summary>
public CandlestickSeries Equity { get; set; }
/// <summary>
/// The exit code of this backtest
/// </summary>
public int ExitCode { get; set; }
/// <summary>
/// Backtest maximum end date
/// </summary>
public DateTime? OutOfSampleMaxEndDate { get; set; }
/// <summary>
/// The backtest out of sample day count
/// </summary>
public int OutOfSampleDays { get; set; }
/// <summary>
/// The backtest start date
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// The backtest end date
/// </summary>
public DateTime EndDate { get; set; }
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="parameterSet">The parameter set</param>
/// <param name="backtestId">The backtest id if any</param>
/// <param name="name">The backtest name</param>
public OptimizationBacktest(ParameterSet parameterSet, string backtestId, string name)
{
ParameterSet = parameterSet;
BacktestId = backtestId;
Name = name;
}
}
}
@@ -0,0 +1,256 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using QuantConnect.Optimizer.Parameters;
using QuantConnect.Statistics;
using QuantConnect.Util;
namespace QuantConnect.Api
{
/// <summary>
/// Json converter for <see cref="OptimizationBacktest"/> which creates a light weight easy to consume serialized version
/// </summary>
public class OptimizationBacktestJsonConverter : JsonConverter
{
private static Dictionary<string, int> StatisticsIndices = new()
{
{ PerformanceMetrics.Alpha, 0 },
{ PerformanceMetrics.AnnualStandardDeviation, 1 },
{ PerformanceMetrics.AnnualVariance, 2 },
{ PerformanceMetrics.AverageLoss, 3 },
{ PerformanceMetrics.AverageWin, 4 },
{ PerformanceMetrics.Beta, 5 },
{ PerformanceMetrics.CompoundingAnnualReturn, 6 },
{ PerformanceMetrics.Drawdown, 7 },
{ PerformanceMetrics.EstimatedStrategyCapacity, 8 },
{ PerformanceMetrics.Expectancy, 9 },
{ PerformanceMetrics.InformationRatio, 10 },
{ PerformanceMetrics.LossRate, 11 },
{ PerformanceMetrics.NetProfit, 12 },
{ PerformanceMetrics.ProbabilisticSharpeRatio, 13 },
{ PerformanceMetrics.ProfitLossRatio, 14 },
{ PerformanceMetrics.SharpeRatio, 15 },
{ PerformanceMetrics.TotalFees, 16 },
{ PerformanceMetrics.TotalOrders, 17 },
{ PerformanceMetrics.TrackingError, 18 },
{ PerformanceMetrics.TreynorRatio, 19 },
{ PerformanceMetrics.WinRate, 20 },
{ PerformanceMetrics.SortinoRatio, 21 },
{ PerformanceMetrics.StartEquity, 22 },
{ PerformanceMetrics.EndEquity, 23 },
{ PerformanceMetrics.DrawdownRecovery, 24 },
};
private static string[] StatisticNames { get; } = StatisticsIndices
.OrderBy(kvp => kvp.Value)
.Select(kvp => kvp.Key)
.ToArray();
// Only 21 Lean statistics where supported when the serialized statistics where a json array
private static int ArrayStatisticsCount = 21;
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(OptimizationBacktest);
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var optimizationBacktest = value as OptimizationBacktest;
if (ReferenceEquals(optimizationBacktest, null)) return;
writer.WriteStartObject();
if (!string.IsNullOrEmpty(optimizationBacktest.Name))
{
writer.WritePropertyName("name");
writer.WriteValue(optimizationBacktest.Name);
}
if (!string.IsNullOrEmpty(optimizationBacktest.BacktestId))
{
writer.WritePropertyName("id");
writer.WriteValue(optimizationBacktest.BacktestId);
writer.WritePropertyName("progress");
writer.WriteValue(optimizationBacktest.Progress);
writer.WritePropertyName("exitCode");
writer.WriteValue(optimizationBacktest.ExitCode);
}
if (optimizationBacktest.StartDate != default)
{
writer.WritePropertyName("startDate");
writer.WriteValue(optimizationBacktest.StartDate.ToStringInvariant(DateFormat.ISOShort));
}
if (optimizationBacktest.EndDate != default)
{
writer.WritePropertyName("endDate");
writer.WriteValue(optimizationBacktest.EndDate.ToStringInvariant(DateFormat.ISOShort));
}
if (optimizationBacktest.OutOfSampleMaxEndDate != null)
{
writer.WritePropertyName("outOfSampleMaxEndDate");
writer.WriteValue(optimizationBacktest.OutOfSampleMaxEndDate.ToStringInvariant(DateFormat.ISOShort));
writer.WritePropertyName("outOfSampleDays");
writer.WriteValue(optimizationBacktest.OutOfSampleDays);
}
if (!optimizationBacktest.Statistics.IsNullOrEmpty())
{
writer.WritePropertyName("statistics");
writer.WriteStartObject();
var customStatisticsNames = new HashSet<string>();
foreach (var (name, statisticValue, index) in optimizationBacktest.Statistics
.Select(kvp => (Name: kvp.Key, kvp.Value, Index: StatisticsIndices.TryGetValue(kvp.Key, out var index) ? index : int.MaxValue))
.OrderBy(t => t.Index)
.ThenByDescending(t => t.Name))
{
var statistic = statisticValue.Replace("%", string.Empty, StringComparison.InvariantCulture);
if (Currencies.TryParse(statistic, out var result))
{
writer.WritePropertyName(index < StatisticsIndices.Count ? index.ToStringInvariant() : name);
writer.WriteValue(result);
}
}
writer.WriteEndObject();
}
if (optimizationBacktest.ParameterSet != null)
{
writer.WritePropertyName("parameterSet");
serializer.Serialize(writer, optimizationBacktest.ParameterSet.Value);
}
if (optimizationBacktest.Equity != null)
{
writer.WritePropertyName("equity");
var equity = JsonConvert.SerializeObject(optimizationBacktest.Equity.Values);
writer.WriteRawValue(equity);
}
writer.WriteEndObject();
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var name = jObject["name"].Value<string>();
var hostName = jObject["hostName"]?.Value<string>();
var backtestId = jObject["id"].Value<string>();
var progress = jObject["progress"].Value<decimal>();
var exitCode = jObject["exitCode"].Value<int>();
var outOfSampleDays = jObject["outOfSampleDays"]?.Value<int>() ?? default;
var startDate = jObject["startDate"]?.Value<DateTime?>() ?? default;
var endDate = jObject["endDate"]?.Value<DateTime?>() ?? default;
var outOfSampleMaxEndDate = jObject["outOfSampleMaxEndDate"]?.Value<DateTime>();
var jStatistics = jObject["statistics"];
Dictionary<string, string> statistics = default;
if (jStatistics != null)
{
if (jStatistics.Type == JTokenType.Array)
{
var statsCount = Math.Min(ArrayStatisticsCount, (jStatistics as JArray).Count);
statistics = new Dictionary<string, string>(StatisticsIndices
.Where(kvp => kvp.Value < statsCount)
.Select(kvp => KeyValuePair.Create(kvp.Key, jStatistics[kvp.Value].Value<string>()))
.Where(kvp => kvp.Value != null));
}
else
{
statistics = new();
foreach (var statistic in jStatistics.Children<JProperty>())
{
var statisticName = TryConvertToLeanStatisticIndex(statistic.Name, out var index)
? StatisticNames[index]
: statistic.Name;
statistics[statisticName] = statistic.Value.Value<string>();
}
}
}
var parameterSet = serializer.Deserialize<ParameterSet>(jObject["parameterSet"].CreateReader());
var equity = new CandlestickSeries();
if (jObject["equity"] != null)
{
foreach (var point in JsonConvert.DeserializeObject<List<Candlestick>>(jObject["equity"].ToString()))
{
equity.AddPoint(point);
}
}
var optimizationBacktest = new OptimizationBacktest(parameterSet, backtestId, name)
{
HostName = hostName,
Progress = progress,
ExitCode = exitCode,
Statistics = statistics,
Equity = equity,
EndDate = endDate,
StartDate = startDate,
OutOfSampleDays = outOfSampleDays,
OutOfSampleMaxEndDate = outOfSampleMaxEndDate,
};
return optimizationBacktest;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryConvertToLeanStatisticIndex(string statistic, out int index)
{
return int.TryParse(statistic, out index) && index >= 0 && index < StatisticsIndices.Count;
}
}
}
+169
View File
@@ -0,0 +1,169 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using QuantConnect.Api.Serialization;
// Collection of response objects for QuantConnect Organization/ endpoints
namespace QuantConnect.Api
{
/// <summary>
/// Response wrapper for Organizations/Read
/// </summary>
public class OrganizationResponse : RestResponse
{
/// <summary>
/// Organization read from the response
/// </summary>
public Organization Organization { get; set; }
}
/// <summary>
/// Object representation of Organization from QuantConnect Api
/// </summary>
public class Organization: StringRepresentation
{
/// <summary>
/// Data Agreement information
/// </summary>
[JsonProperty(PropertyName = "data")]
public DataAgreement DataAgreement { get; set; }
/// <summary>
/// Organization Product Subscriptions
/// </summary>
public List<Product> Products { get; set; }
/// <summary>
/// Organization Credit Balance and Transactions
/// </summary>
public Credit Credit { get; set; }
}
/// <summary>
/// Organization Data Agreement
/// </summary>
public class DataAgreement
{
/// <summary>
/// Epoch time the Data Agreement was Signed
/// </summary>
[JsonProperty(PropertyName = "signedTime")]
public long? EpochSignedTime { get; set; }
/// <summary>
/// DateTime the agreement was signed.
/// Uses EpochSignedTime converted to a standard datetime.
/// </summary>
[JsonIgnore]
public DateTime? SignedTime => EpochSignedTime.HasValue ? DateTimeOffset.FromUnixTimeSeconds(EpochSignedTime.Value).DateTime : null;
/// <summary>
/// True/False if it is currently signed
/// </summary>
[JsonProperty(PropertyName = "current")]
public bool Signed { get; set; }
}
/// <summary>
/// Organization Credit Object
/// </summary>
public class Credit
{
/// <summary>
/// QCC Current Balance
/// </summary>
public decimal Balance { get; set; }
}
/// <summary>
/// QuantConnect Products
/// </summary>
[JsonConverter(typeof(ProductJsonConverter))]
public class Product
{
/// <summary>
/// Product Type
/// </summary>
public ProductType Type { get; set; }
/// <summary>
/// Collection of item subscriptions
/// Nodes/Data/Seats/etc
/// </summary>
public List<ProductItem> Items { get; set; }
}
/// <summary>
/// QuantConnect ProductItem
/// </summary>
public class ProductItem
{
/// <summary>
/// ID for this product
/// </summary>
[JsonProperty(PropertyName = "productId")]
public int Id { get; set; }
/// <summary>
/// Quantity for this product
/// </summary>
public int Quantity { get; set; }
}
/// <summary>
/// Product types offered by QuantConnect
/// Used by Product class
/// </summary>
public enum ProductType
{
/// <summary>
/// Professional Seats Subscriptions
/// </summary>
ProfessionalSeats,
/// <summary>
/// Backtest Nodes Subscriptions
/// </summary>
BacktestNode,
/// <summary>
/// Research Nodes Subscriptions
/// </summary>
ResearchNode,
/// <summary>
/// Live Trading Nodes Subscriptions
/// </summary>
LiveNode,
/// <summary>
/// Support Subscriptions
/// </summary>
Support,
/// <summary>
/// Data Subscriptions
/// </summary>
Data,
/// <summary>
/// Modules Subscriptions
/// </summary>
Modules
}
}
+93
View File
@@ -0,0 +1,93 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using QuantConnect.Optimizer.Parameters;
namespace QuantConnect.Api
{
/// <summary>
/// Json converter for <see cref="ParameterSet"/> which creates a light weight easy to consume serialized version
/// </summary>
public class ParameterSetJsonConverter : JsonConverter
{
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ParameterSet);
}
/// <summary>
/// Writes a JSON object from a Parameter set
/// </summary>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
var parameterSet = value as ParameterSet;
if (ReferenceEquals(parameterSet, null)) return;
writer.WriteStartObject();
if (parameterSet.Value != null)
{
writer.WritePropertyName("parameterSet");
serializer.Serialize(writer, parameterSet.Value);
}
writer.WriteEndObject();
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
if (JArray.Load(reader).Count == 0)
{
return new ParameterSet(-1, new Dictionary<string, string>());
}
}
else if (reader.TokenType == JsonToken.StartObject)
{
var jObject = JObject.Load(reader);
var value = jObject["parameterSet"] ?? jObject;
var parameterSet = new ParameterSet(-1, value.ToObject<Dictionary<string, string>>());
return parameterSet;
}
throw new ArgumentException($"Unexpected Tokentype {reader.TokenType}");
}
}
}
+47
View File
@@ -0,0 +1,47 @@
/*
* 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.Collections.Generic;
using QuantConnect.Securities;
namespace QuantConnect.Api
{
/// <summary>
/// Class containing the basic portfolio information of a live algorithm
/// </summary>
public class Portfolio
{
/// <summary>
/// Dictionary of algorithm holdings information
/// </summary>
public Dictionary<string, Holding> Holdings { get; set; }
/// <summary>
/// Dictionary of algorithm cash currencies information
/// </summary>
public Dictionary<string, Cash> Cash { get; set; }
}
/// <summary>
/// Response class for reading the portfolio of a live algorithm
/// </summary>
public class PortfolioResponse : RestResponse
{
/// <summary>
/// Object containing the basic portfolio information of a live algorithm
/// </summary>
public Portfolio Portfolio { get; set; }
}
}
+460
View File
@@ -0,0 +1,460 @@
/*
* 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 Newtonsoft.Json;
using System.Collections.Generic;
namespace QuantConnect.Api
{
/// <summary>
/// Collaborator responses
/// </summary>
public class Collaborator
{
/// <summary>
/// User ID
/// </summary>
[JsonProperty(PropertyName = "uid")]
public int? Uid { get; set; }
/// <summary>
/// Indicate if the user have live control
/// </summary>
[JsonProperty(PropertyName = "liveControl")]
public bool LiveControl { get; set; }
/// <summary>
/// The permission this user is given. Can be "read"
/// or "write"
/// </summary>
[JsonProperty(PropertyName = "permission")]
public string Permission { get; set; }
/// <summary>
/// The user public ID
/// </summary>
[JsonProperty(PropertyName = "publicId")]
public string PublicId { get; set; }
/// <summary>
/// The url of the user profile image
/// </summary>
[JsonProperty(PropertyName = "profileImage")]
public string ProfileImage { get; set; }
/// <summary>
/// The registered email of the user
/// </summary>
[JsonProperty(PropertyName = "email")]
public string Email { get; set; }
/// <summary>
/// The display name of the user
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// The biography of the user
/// </summary>
[JsonProperty(PropertyName = "bio")]
public string Bio { get; set; }
/// <summary>
/// Indicate if the user is the owner of the project
/// </summary>
[JsonProperty(PropertyName = "owner")]
public bool Owner { get; set; }
}
/// <summary>
/// Library response
/// </summary>
public class Library
{
/// <summary>
/// Project Id of the library project
/// </summary>
[JsonProperty(PropertyName = "projectId")]
public int Projectid { get; set; }
/// <summary>
/// Name of the library project
/// </summary>
[JsonProperty(PropertyName = "libraryName")]
public string LibraryName { get; set; }
/// <summary>
/// Name of the library project owner
/// </summary>
[JsonProperty(PropertyName = "ownerName")]
public string OwnerName { get; set; }
/// <summary>
/// Indicate if the library project can be accessed
/// </summary>
[JsonProperty(PropertyName = "access")]
public bool Access { get; set; }
}
/// <summary>
/// The chart display properties
/// </summary>
public class GridChart
{
/// <summary>
/// The chart name
/// </summary>
[JsonProperty(PropertyName = "chartName")]
public string ChartName { get; set; }
/// <summary>
/// Width of the chart
/// </summary>
[JsonProperty(PropertyName = "width")]
public int Width { get; set; }
/// <summary>
/// Height of the chart
/// </summary>
[JsonProperty(PropertyName = "height")]
public int Height { get; set; }
/// <summary>
/// Number of rows of the chart
/// </summary>
[JsonProperty(PropertyName = "row")]
public int Row { get; set; }
/// <summary>
/// Number of columns of the chart
/// </summary>
[JsonProperty(PropertyName = "column")]
public int Column { get; set; }
/// <summary>
/// Sort of the chart
/// </summary>
[JsonProperty(PropertyName = "sort")]
public int Sort { get; set; }
/// <summary>
/// Optionally related definition
/// </summary>
[JsonProperty(PropertyName = "definition")]
public List<string> Definition { get; set; }
}
/// <summary>
/// The grid arrangement of charts
/// </summary>
public class Grid
{
/// <summary>
/// List of chart in the xs (Extra small) position
/// </summary>
[JsonProperty(PropertyName = "xs")]
public List<GridChart> Xs { get; set; }
/// <summary>
/// List of chart in the sm (Small) position
/// </summary>
[JsonProperty(PropertyName = "sm")]
public List<GridChart> Sm { get; set; }
/// <summary>
/// List of chart in the md (Medium) position
/// </summary>
[JsonProperty(PropertyName = "md")]
public List<GridChart> Md { get; set; }
/// <summary>
/// List of chart in the lg (Large) position
/// </summary>
[JsonProperty(PropertyName = "lg")]
public List<GridChart> Lg { get; set; }
/// <summary>
/// List of chart in the xl (Extra large) position
/// </summary>
[JsonProperty(PropertyName = "xl")]
public List<GridChart> Xl { get; set; }
}
/// <summary>
/// Encryption key details
/// </summary>
public class EncryptionKey
{
/// <summary>
/// Encryption key id
/// </summary>
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
/// <summary>
/// Name of the encryption key
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
}
/// <summary>
/// Parameter set
/// </summary>
public class Parameter
{
/// <summary>
/// Name of parameter
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// Value of parameter
/// </summary>
[JsonProperty(PropertyName = "value")]
public string Value { get; set; }
}
/// <summary>
/// Response from reading a project by id.
/// </summary>
public class Project : RestResponse
{
/// <summary>
/// Project id
/// </summary>
[JsonProperty(PropertyName = "projectId")]
public int ProjectId { get; set; }
/// <summary>
/// Name of the project
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// Date the project was created
/// </summary>
[JsonProperty(PropertyName = "created")]
public DateTime Created { get; set; }
/// <summary>
/// Modified date for the project
/// </summary>
[JsonProperty(PropertyName = "modified")]
public DateTime Modified { get; set; }
/// <summary>
/// Programming language of the project
/// </summary>
[JsonProperty(PropertyName = "language")]
public Language Language { get; set; }
/// <summary>
/// The projects owner id
/// </summary>
[JsonProperty(PropertyName = "ownerId")]
public int OwnerId { get; set; }
/// <summary>
/// The organization ID
/// </summary>
[JsonProperty(PropertyName = "organizationId")]
public string OrganizationId { get; set; }
/// <summary>
/// List of collaborators
/// </summary>
[JsonProperty(PropertyName = "collaborators")]
public List<Collaborator> Collaborators { get; set; }
/// <summary>
/// The version of LEAN this project is running on
/// </summary>
[JsonProperty(PropertyName = "leanVersionId")]
public int LeanVersionId { get; set; }
/// <summary>
/// Indicate if the project is pinned to the master branch of LEAN
/// </summary>
[JsonProperty(PropertyName = "leanPinnedToMaster")]
public bool LeanPinnedToMaster { get; set; }
/// <summary>
/// Indicate if you are the owner of the project
/// </summary>
[JsonProperty(PropertyName = "owner")]
public bool Owner { get; set; }
/// <summary>
/// The project description
/// </summary>
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }
/// <summary>
/// Channel id
/// </summary>
[JsonProperty(PropertyName = "channelId")]
public string ChannelId { get; set; }
/// <summary>
/// Optimization parameters
/// </summary>
[JsonProperty(PropertyName = "parameters")]
public List<Parameter> Parameters { get; set; }
/// <summary>
/// The library projects
/// </summary>
[JsonProperty(PropertyName = "libraries")]
public List<Library> Libraries { get; set; }
/// <summary>
/// Configuration of the backtest view grid
/// </summary>
[JsonProperty(PropertyName = "grid")]
public Grid Grid { get; set; }
/// <summary>
/// Configuration of the live view grid
/// </summary>
[JsonProperty(PropertyName = "liveGrid")]
public Grid LiveGrid { get; set; }
/// <summary>
/// The equity value of the last paper trading instance
/// </summary>
[JsonProperty(PropertyName = "paperEquity")]
public decimal? PaperEquity { get; set; }
/// <summary>
/// The last live deployment active time
/// </summary>
[JsonProperty(PropertyName = "lastLiveDeployment")]
public DateTime? LastLiveDeployment { get; set; }
/// <summary>
/// The last live wizard content used
/// </summary>
[JsonProperty(PropertyName = "liveForm")]
public object LiveForm { get; set; }
/// <summary>
/// Indicates if the project is encrypted
/// </summary>
[JsonProperty(PropertyName = "encrypted")]
public bool? Encrypted { get; set; }
/// <summary>
/// Indicates if the project is running or not
/// </summary>
[JsonProperty(PropertyName = "codeRunning")]
public bool CodeRunning { get; set; }
/// <summary>
/// LEAN environment of the project running on
/// </summary>
[JsonProperty(PropertyName = "leanEnvironment")]
public int LeanEnvironment { get; set; }
/// <summary>
/// Text file with at least 32 characters to be used to encrypt the project
/// </summary>
[JsonProperty(PropertyName = "encryptionKey")]
public EncryptionKey EncryptionKey { get; set; }
}
/// <summary>
/// API response for version
/// </summary>
public class Version
{
/// <summary>
/// ID of the LEAN version
/// </summary>
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
/// <summary>
/// Date when this version was created
/// </summary>
[JsonProperty(PropertyName = "created")]
public DateTime? Created { get; set; }
/// <summary>
/// Description of the LEAN version
/// </summary>
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }
/// <summary>
/// Commit Hash in the LEAN repository
/// </summary>
[JsonProperty(PropertyName = "leanHash")]
public string LeanHash { get; set; }
/// <summary>
/// Commit Hash in the LEAN Cloud repository
/// </summary>
[JsonProperty(PropertyName = "leanCloudHash")]
public string LeanCloudHash { get; set; }
/// <summary>
/// Name of the branch where the commit is
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// Reference to the branch where the commit is
/// </summary>
[JsonProperty(PropertyName = "ref")]
public string Ref { get; set; }
/// <summary>
/// Indicates if the version is available for the public (1) or not (0)
/// </summary>
[JsonProperty(PropertyName = "public")]
public bool Public { get; set; }
}
/// <summary>
/// Read versions response
/// </summary>
public class VersionsResponse : RestResponse
{
/// <summary>
/// List of LEAN versions
/// </summary>
[JsonProperty(PropertyName = "versions")]
public List<Version> Versions { get; set; }
}
/// <summary>
/// Project list response
/// </summary>
public class ProjectResponse : VersionsResponse
{
/// <summary>
/// List of projects for the authenticated user
/// </summary>
[JsonProperty(PropertyName = "projects")]
public List<Project> Projects { get; set; }
}
}
+81
View File
@@ -0,0 +1,81 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// File for a project
/// </summary>
public class ProjectFile
{
/// <summary>
/// Name of a project file
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// Contents of the project file
/// </summary>
[JsonProperty(PropertyName = "content")]
public string Code { get; set; }
/// <summary>
/// DateTime project file was modified
/// </summary>
[JsonProperty(PropertyName = "modified")]
public DateTime DateModified{ get; set; }
/// <summary>
/// Indicates if the project file is a library or not
/// </summary>
[JsonProperty(PropertyName = "isLibrary")]
public bool IsLibrary { get; set; }
/// <summary>
/// Indicates if the project file is open or not
/// </summary>
[JsonProperty(PropertyName = "open")]
public bool Open { get; set; }
/// <summary>
/// ID of the project
/// </summary>
[JsonProperty(PropertyName = "projectId")]
public int ProjectId { get; set; }
/// <summary>
/// ID of the project file, can be null
/// </summary>
[JsonProperty(PropertyName = "id")]
public int? Id { get; set; }
}
/// <summary>
/// Response received when creating a file or reading one file or more in a project
/// </summary>
public class ProjectFilesResponse : RestResponse
{
/// <summary>
/// List of project file information
/// </summary>
[JsonProperty(PropertyName = "files")]
public List<ProjectFile> Files { get; set; }
}
}
+37
View File
@@ -0,0 +1,37 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Response received when reading or updating some nodes of a project
/// </summary>
public class ProjectNodesResponse : RestResponse
{
/// <summary>
/// List of project nodes.
/// </summary>
[JsonProperty(PropertyName = "nodes")]
public NodeList Nodes { get; set; }
/// <summary>
/// Indicate if the node is automatically selected
/// </summary>
[JsonProperty(PropertyName = "autoSelectNode")]
public bool AutoSelectNode { get; set; }
}
}
+31
View File
@@ -0,0 +1,31 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Class for wrapping Read Chart response
/// </summary>
public class ReadChartResponse: RestResponse
{
/// <summary>
/// Chart object from the ReadChart response
/// </summary>
[JsonProperty(PropertyName = "chart")]
public Chart Chart { get; set; }
}
}
+43
View File
@@ -0,0 +1,43 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Api
{
/// <summary>
/// Base API response class for the QuantConnect API.
/// </summary>
public class RestResponse: StringRepresentation
{
/// <summary>
/// JSON Constructor
/// </summary>
public RestResponse()
{
Success = false;
Errors = new List<string>();
}
/// <summary>
/// Indicate if the API request was successful.
/// </summary>
public bool Success { get; set; }
/// <summary>
/// List of errors with the API call.
/// </summary>
public List<string> Errors { get; set; }
}
}
@@ -0,0 +1,109 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace QuantConnect.Api.Serialization
{
/// <summary>
/// Provides an implementation of <see cref="JsonConverter"/> that can deserialize <see cref="Product"/>
/// </summary>
public class ProductJsonConverter : JsonConverter
{
private Dictionary<string, ProductType> _productTypeMap = new Dictionary<string, ProductType>()
{
{"Professional Seats", ProductType.ProfessionalSeats},
{"Backtest Node", ProductType.BacktestNode},
{"Research Node", ProductType.ResearchNode},
{"Live Trading Node", ProductType.LiveNode},
{"Support", ProductType.Support},
{"Data", ProductType.Data},
{"Modules", ProductType.Modules}
};
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
/// </summary>
/// <value>
/// <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.
/// </value>
public override bool CanWrite => false;
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Product);
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param><param name="value">The value.</param><param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("The OrderJsonConverter does not implement a WriteJson method;.");
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param><param name="objectType">Type of the object.</param><param name="existingValue">The existing value of object being read.</param><param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var result = CreateProductFromJObject(jObject);
return result;
}
/// <summary>
/// Create an order from a simple JObject
/// </summary>
/// <param name="jObject"></param>
/// <returns>Order Object</returns>
public Product CreateProductFromJObject(JObject jObject)
{
if (jObject == null)
{
return null;
}
var productTypeName = jObject["name"].Value<string>();
if (!_productTypeMap.ContainsKey(productTypeName))
{
return null;
}
return new Product
{
Type = _productTypeMap[productTypeName],
Items = jObject["items"].ToObject<List<ProductItem>>()
};
}
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Class to return the string representation of an API response class
/// </summary>
public class StringRepresentation
{
/// <summary>
/// Returns the string representation of this object
/// </summary>
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
}
+302
View File
@@ -0,0 +1,302 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using QuantConnect.Logging;
using QuantConnect.Util;
namespace QuantConnect
{
/// <summary>
/// Chart Series Object - Series data and properties for a chart:
/// </summary>
[JsonConverter(typeof(SeriesJsonConverter))]
public abstract class BaseSeries
{
/// The index of the last fetch update request to only retrieve the "delta" of the previous request.
private int _updatePosition;
/// <summary>
/// Name of the series.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Axis for the chart series.
/// </summary>
public string Unit { get; set; }
/// <summary>
/// Index/position of the series on the chart.
/// </summary>
public int Index { get; set; }
/// <summary>
/// Axis name for the chart series.
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string IndexName { get; set; }
/// <summary>
/// Defines the visual Z index of the series on the chart.
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public int? ZIndex { get; set; }
/// <summary>
/// Chart type for the series:
/// </summary>
public SeriesType SeriesType { get; set; }
/// <summary>
/// An optional tooltip template
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Tooltip { get; set; }
/// <summary>
/// The series list of values.
/// These values are assumed to be in ascending time order (first points earliest, last points latest)
/// </summary>
public List<ISeriesPoint> Values { get; set; }
/// <summary>
/// Default constructor for chart series
/// </summary>
protected BaseSeries()
{
Unit = "$";
Values = new List<ISeriesPoint>();
}
/// <summary>
/// Constructor method for Chart Series
/// </summary>
/// <param name="name">Name of the chart series</param>
/// <param name="type">Type of the series</param>
protected BaseSeries(string name, SeriesType type)
: this()
{
Name = name;
SeriesType = type;
}
/// <summary>
/// Foundational constructor on the series class
/// </summary>
/// <param name="name">Name of the series</param>
/// <param name="type">Type of the series</param>
/// <param name="index">Series index position on the chart</param>
protected BaseSeries(string name, SeriesType type, int index)
: this(name, type)
{
Index = index;
}
/// <summary>
/// Foundational constructor on the series class
/// </summary>
/// <param name="name">Name of the series</param>
/// <param name="type">Type of the series</param>
/// <param name="index">Series index position on the chart</param>
/// <param name="unit">Unit for the series axis</param>
protected BaseSeries(string name, SeriesType type, int index, string unit)
: this(name, type, index)
{
Unit = unit;
}
/// <summary>
/// Constructor method for Chart Series
/// </summary>
/// <param name="name">Name of the chart series</param>
/// <param name="type">Type of the chart series</param>
/// <param name="unit">Unit of the series</param>
protected BaseSeries(string name, SeriesType type, string unit)
: this(name, type, 0, unit)
{
}
/// <summary>
/// Add a new point to this series
/// </summary>
/// <param name="point">The data point to add</param>
public virtual void AddPoint(ISeriesPoint point)
{
if (Values.Count > 0 && Values[Values.Count - 1].Time == point.Time)
{
// duplicate points at the same time, overwrite the value
Values[Values.Count - 1] = point;
}
else
{
Values.Add(point);
}
}
/// <summary>
/// Add a new point to this series
/// </summary>
/// <param name="time">The time of the data point</param>
/// <param name="values">The values of the data point</param>
public abstract void AddPoint(DateTime time, List<decimal> values);
/// <summary>
/// Get the updates since the last call to this function.
/// </summary>
/// <returns>List of the updates from the series</returns>
public BaseSeries GetUpdates()
{
var copy = Clone(empty: true);
try
{
//Add the updates since the last
for (var i = _updatePosition; i < Values.Count; i++)
{
copy.Values.Add(Values[i]);
}
//Shuffle the update point to now:
_updatePosition = Values.Count;
}
catch (Exception err)
{
Log.Error(err);
}
return copy;
}
/// <summary>
/// Removes the data from this series and resets the update position to 0
/// </summary>
public void Purge()
{
Values.Clear();
_updatePosition = 0;
}
/// <summary>
/// Will sum up all chart points into a new single value, using the time of latest point
/// </summary>
/// <returns>The new chart point</returns>
public abstract ISeriesPoint ConsolidateChartPoints();
/// <summary>
/// Return a new instance clone of this object
/// </summary>
/// <returns></returns>
public abstract BaseSeries Clone(bool empty = false);
/// <summary>
/// Return a list of cloned values
/// </summary>
/// <returns></returns>
protected List<ISeriesPoint> CloneValues()
{
var clone = new List<ISeriesPoint>(Values.Count);
foreach (var point in Values)
{
clone.Add(point.Clone());
}
return clone;
}
/// <summary>
/// Returns an enumerable of the values of the series cast to the specified type
/// </summary>
/// <returns>An enumerable of the values of the series cast to the specified type</returns>
public IEnumerable<T> GetValues<T>()
where T : ISeriesPoint
{
return Values.Cast<T>();
}
/// <summary>
/// Creates a series according to the specified type.
/// </summary>
/// <param name="seriesType">The series type</param>
/// <param name="name">The name of the series</param>
/// <param name="index">Series index position on the chart</param>
/// <param name="unit">Unit for the series axis</param>
/// <returns>
/// A <see cref="CandlestickSeries"/> if <paramref name="seriesType"/> is <see cref="SeriesType.Candle"/>.
/// A <see cref="Series"/> otherwise.
/// </returns>
public static BaseSeries Create(SeriesType seriesType, string name, int index = 0, string unit = "$")
{
if (!Enum.IsDefined(typeof(SeriesType), seriesType))
{
throw new ArgumentOutOfRangeException(nameof(seriesType), "Series type out of range");
}
if (seriesType == SeriesType.Candle)
{
return new CandlestickSeries(name, index, unit);
}
return new Series(name, seriesType, index, unit);
}
}
/// <summary>
/// Available types of chart series
/// </summary>
public enum SeriesType
{
/// <summary>
/// Line Plot for Value Types (0)
/// </summary>
Line,
/// <summary>
/// Scatter Plot for Chart Distinct Types (1)
/// </summary>
Scatter,
/// <summary>
/// Charts (2)
/// </summary>
Candle,
/// <summary>
/// Bar chart (3)
/// </summary>
Bar,
/// <summary>
/// Flag indicators (4)
/// </summary>
Flag,
/// <summary>
/// 100% area chart showing relative proportions of series values at each time index (5)
/// </summary>
StackedArea,
/// <summary>
/// Pie chart (6)
/// </summary>
Pie,
/// <summary>
/// Treemap Plot (7)
/// </summary>
Treemap,
/// <summary>
/// Heatmap Plot (9) -- NOTE: 8 is reserved
/// </summary>
Heatmap = 9,
/// <summary>
/// Scatter 3D Plot (10)
/// </summary>
Scatter3d
}
}
+63
View File
@@ -0,0 +1,63 @@
/*
* 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 Python.Runtime;
namespace QuantConnect.Benchmarks
{
/// <summary>
/// Creates a benchmark defined by a function
/// </summary>
public class FuncBenchmark : IBenchmark
{
private readonly Func<DateTime, decimal> _benchmark;
/// <summary>
/// Initializes a new instance of the <see cref="FuncBenchmark"/> class
/// </summary>
/// <param name="benchmark">The functional benchmark implementation</param>
public FuncBenchmark(Func<DateTime, decimal> benchmark)
{
if (benchmark == null)
{
throw new ArgumentNullException(nameof(benchmark));
}
_benchmark = benchmark;
}
/// <summary>
/// Create a function benchmark from a Python function
/// </summary>
/// <param name="pyFunc"></param>
public FuncBenchmark(PyObject pyFunc)
{
if (!pyFunc.TrySafeAs(out _benchmark))
{
throw new ArgumentException($"FuncBenchmark(): {Messages.FuncBenchmark.UnableToConvertPythonFunctionToBenchmarkFunction}");
}
}
/// <summary>
/// Evaluates this benchmark at the specified time
/// </summary>
/// <param name="time">The time to evaluate the benchmark at</param>
/// <returns>The value of the benchmark at the specified time</returns>
public decimal Evaluate(DateTime time)
{
return _benchmark(time);
}
}
}
+32
View File
@@ -0,0 +1,32 @@
/*
* 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;
namespace QuantConnect.Benchmarks
{
/// <summary>
/// Specifies how to compute a benchmark for an algorithm
/// </summary>
public interface IBenchmark
{
/// <summary>
/// Evaluates this benchmark at the specified time
/// </summary>
/// <param name="time">The time to evaluate the benchmark at</param>
/// <returns>The value of the benchmark at the specified time</returns>
decimal Evaluate(DateTime time);
}
}
+64
View File
@@ -0,0 +1,64 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Securities;
namespace QuantConnect.Benchmarks
{
/// <summary>
/// Creates a benchmark defined by the closing price of a <see cref="Security"/> instance
/// </summary>
public class SecurityBenchmark : IBenchmark
{
/// <summary>
/// The benchmark security
/// </summary>
public Security Security { get; }
/// <summary>
/// Initializes a new instance of the <see cref="SecurityBenchmark"/> class
/// </summary>
public SecurityBenchmark(Security security)
{
Security = security;
}
/// <summary>
/// Evaluates this benchmark at the specified time in units of the account's currency.
/// </summary>
/// <param name="time">The time to evaluate the benchmark at</param>
/// <returns>The value of the benchmark at the specified time
/// in units of the account's currency.</returns>
public decimal Evaluate(DateTime time)
{
return Security.Price * Security.QuoteCurrency.ConversionRate;
}
/// <summary>
/// Helper function that will create a security with the given SecurityManager
/// for a specific symbol and then create a SecurityBenchmark for it
/// </summary>
/// <param name="securities">SecurityService to create the security</param>
/// <param name="symbol">The symbol to create a security benchmark with</param>
/// <returns>The new SecurityBenchmark</returns>
public static SecurityBenchmark CreateInstance(SecurityManager securities, Symbol symbol)
{
return new SecurityBenchmark(securities.CreateBenchmarkSecurity(symbol));
}
}
}
+199
View File
@@ -0,0 +1,199 @@
/*
* 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.Linq.Expressions;
using static QuantConnect.Util.ExpressionBuilder;
namespace QuantConnect
{
/// <summary>
/// Enumeration class defining binary comparisons and providing access to expressions and functions
/// capable of evaluating a particular comparison for any type. If a particular type does not implement
/// a binary comparison than an exception will be thrown.
/// </summary>
public class BinaryComparison
{
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.Equal"/>
/// </summary>
public static readonly BinaryComparison Equal = new BinaryComparison(ExpressionType.Equal);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.NotEqual"/>
/// </summary>
public static readonly BinaryComparison NotEqual = new BinaryComparison(ExpressionType.NotEqual);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.LessThan"/>
/// </summary>
public static readonly BinaryComparison LessThan = new BinaryComparison(ExpressionType.LessThan);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.GreaterThan"/>
/// </summary>
public static readonly BinaryComparison GreaterThan = new BinaryComparison(ExpressionType.GreaterThan);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.LessThanOrEqual"/>
/// </summary>
public static readonly BinaryComparison LessThanOrEqual = new BinaryComparison(ExpressionType.LessThanOrEqual);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.GreaterThanOrEqual"/>
/// </summary>
public static readonly BinaryComparison GreaterThanOrEqual = new BinaryComparison(ExpressionType.GreaterThanOrEqual);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> matching the provided <paramref name="type"/>
/// </summary>
public static BinaryComparison FromExpressionType(ExpressionType type)
{
switch (type)
{
case ExpressionType.Equal: return Equal;
case ExpressionType.NotEqual: return NotEqual;
case ExpressionType.LessThan: return LessThan;
case ExpressionType.LessThanOrEqual: return LessThanOrEqual;
case ExpressionType.GreaterThan: return GreaterThan;
case ExpressionType.GreaterThanOrEqual: return GreaterThanOrEqual;
default:
throw new InvalidOperationException($"The specified ExpressionType '{type}' is not a binary comparison.");
}
}
/// <summary>
/// Gets the expression type defining the binary comparison.
/// </summary>
public ExpressionType Type { get; }
private BinaryComparison(ExpressionType type)
{
Type = type;
}
/// <summary>
/// Evaluates the specified <paramref name="left"/> and <paramref name="right"/> according to this <see cref="BinaryComparison"/>
/// </summary>
public bool Evaluate<T>(T left, T right)
=> OfType<T>.GetFunc(Type)(left, right);
/// <summary>
/// Gets a function capable of performing this <see cref="BinaryComparison"/>
/// </summary>
public Func<T, T, bool> GetEvaluator<T>()
=> OfType<T>.GetFunc(Type);
/// <summary>
/// Gets an expression representing this <see cref="BinaryComparison"/>
/// </summary>
public Expression<Func<T, T, bool>> GetExpression<T>()
=> OfType<T>.GetExpression(Type);
/// <summary>
/// Flips the logic ordering of the comparison's operands. For example, <see cref="LessThan"/>
/// is converted into <see cref="GreaterThan"/>
/// </summary>
public BinaryComparison FlipOperands()
{
switch (Type)
{
case ExpressionType.Equal: return this;
case ExpressionType.NotEqual: return this;
case ExpressionType.LessThan: return GreaterThan;
case ExpressionType.LessThanOrEqual: return GreaterThanOrEqual;
case ExpressionType.GreaterThan: return LessThan;
case ExpressionType.GreaterThanOrEqual: return LessThanOrEqual;
default:
throw new Exception(
"The skies are falling and the oceans are rising! " +
"If you've made it here then this exception is the least of your worries! " +
$"ExpressionType: {Type}"
);
}
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return Type.ToString();
}
/// <summary>
/// Provides thread-safe lookups of expressions and functions for binary comparisons by type.
/// MUCH faster than using a concurrency dictionary, for example, as it's expanded at runtime
/// and hard-linked, no look-up is actually performed!
/// </summary>
private static class OfType<T>
{
private static readonly Expression<Func<T, T, bool>> EqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.Equal);
private static readonly Expression<Func<T, T, bool>> NotEqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.NotEqual);
private static readonly Expression<Func<T, T, bool>> LessThanExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.LessThan);
private static readonly Expression<Func<T, T, bool>> LessThanOrEqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.LessThanOrEqual);
private static readonly Expression<Func<T, T, bool>> GreaterThanExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.GreaterThan);
private static readonly Expression<Func<T, T, bool>> GreaterThanOrEqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.GreaterThanOrEqual);
public static Expression<Func<T, T, bool>> GetExpression(ExpressionType type)
{
switch (type)
{
case ExpressionType.Equal: return EqualExpr;
case ExpressionType.NotEqual: return NotEqualExpr;
case ExpressionType.LessThan: return LessThanExpr;
case ExpressionType.LessThanOrEqual: return LessThanOrEqualExpr;
case ExpressionType.GreaterThan: return GreaterThanExpr;
case ExpressionType.GreaterThanOrEqual: return GreaterThanOrEqualExpr;
default:
throw new InvalidOperationException($"The specified ExpressionType '{type}' is not a binary comparison.");
}
}
private static readonly Func<T, T, bool> EqualFunc = EqualExpr?.Compile();
private static readonly Func<T, T, bool> NotEqualFunc = NotEqualExpr?.Compile();
private static readonly Func<T, T, bool> LessThanFunc = LessThanExpr?.Compile();
private static readonly Func<T, T, bool> LessThanOrEqualFunc = LessThanOrEqualExpr?.Compile();
private static readonly Func<T, T, bool> GreaterThanFunc = GreaterThanExpr?.Compile();
private static readonly Func<T, T, bool> GreaterThanOrEqualFunc = GreaterThanOrEqualExpr?.Compile();
public static Func<T, T, bool> GetFunc(ExpressionType type)
{
switch (type)
{
case ExpressionType.Equal: return EqualFunc;
case ExpressionType.NotEqual: return NotEqualFunc;
case ExpressionType.LessThan: return LessThanFunc;
case ExpressionType.LessThanOrEqual: return LessThanOrEqualFunc;
case ExpressionType.GreaterThan: return GreaterThanFunc;
case ExpressionType.GreaterThanOrEqual: return GreaterThanOrEqualFunc;
default:
throw new InvalidOperationException($"The specified ExpressionType '{type}' is not a binary comparison.");
}
}
private static Expression<Func<T, T, bool>> MakeBinaryComparisonLambdaOrNull(ExpressionType type)
{
try
{
return MakeBinaryComparisonLambda<T>(type);
}
catch
{
return null;
}
}
}
}
}
+211
View File
@@ -0,0 +1,211 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq.Expressions;
namespace QuantConnect
{
/// <summary>
/// Provides convenience extension methods for applying a <see cref="BinaryComparison"/> to collections.
/// </summary>
public static class BinaryComparisonExtensions
{
/// <summary>
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
/// we call Filter(values, 42). We're looking for keys that are less than 42.
/// </summary>
public static TCollection Filter<T, TCollection>(
this BinaryComparison comparison,
TCollection values,
T reference
)
where TCollection : ICollection<T>, new()
{
var result = new TCollection();
var evaluator = comparison.GetEvaluator<T>();
foreach (var value in values)
{
if (evaluator(value, reference))
{
result.Add(value);
}
}
return result;
}
/// <summary>
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
/// we call Filter(values, 42). We're looking for keys that are less than 42.
/// </summary>
public static SortedDictionary<TKey, TValue> Filter<TKey, TValue>(
this BinaryComparison comparison,
SortedDictionary<TKey, TValue> values,
TKey reference
)
{
SortedDictionary<TKey, TValue> result;
if (comparison.Type == ExpressionType.NotEqual)
{
result = new SortedDictionary<TKey, TValue>(values);
result.Remove(reference);
return result;
}
result = new SortedDictionary<TKey, TValue>();
if (comparison.Type == ExpressionType.Equal)
{
TValue value;
if (values.TryGetValue(reference, out value))
{
result.Add(reference, value);
}
return result;
}
// since we're enumerating a sorted collection, once we receive
// a mismatch it means we'll never again receive a match
var breakAfterFailure =
comparison == BinaryComparison.LessThanOrEqual ||
comparison == BinaryComparison.LessThanOrEqual;
var evaluator = comparison.GetEvaluator<TKey>();
foreach (var kvp in values)
{
if (evaluator(kvp.Key, reference))
{
result.Add(kvp.Key, kvp.Value);
}
else if (breakAfterFailure)
{
break;
}
}
return result;
}
/// <summary>
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
/// we call Filter(values, 42). We're looking for keys that are less than 42.
/// </summary>
public static ImmutableSortedDictionary<TKey, TValue> Filter<TKey, TValue>(
this BinaryComparison comparison,
ImmutableSortedDictionary<TKey, TValue> values,
TKey reference
)
{
if (comparison.Type == ExpressionType.NotEqual)
{
return values.Remove(reference);
}
var result = ImmutableSortedDictionary<TKey, TValue>.Empty;
if (comparison.Type == ExpressionType.Equal)
{
TValue value;
if (values.TryGetValue(reference, out value))
{
result = result.Add(reference, value);
}
return result;
}
// since we're enumerating a sorted collection, once we receive
// a mismatch it means we'll never again receive a match
var breakAfterFailure =
comparison == BinaryComparison.LessThanOrEqual ||
comparison == BinaryComparison.LessThanOrEqual;
var evaluator = comparison.GetEvaluator<TKey>();
foreach (var kvp in values)
{
if (evaluator(kvp.Key, reference))
{
result = result.Add(kvp.Key, kvp.Value);
}
else if (breakAfterFailure)
{
break;
}
}
return result;
}
/// <summary>
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
/// we call Filter(values, 42). We're looking for keys that are less than 42.
/// </summary>
public static Tuple<ImmutableSortedDictionary<TKey, TValue>, ImmutableSortedDictionary<TKey, TValue>> SplitBy<TKey, TValue>(
this BinaryComparison comparison,
ImmutableSortedDictionary<TKey, TValue> values,
TKey reference
)
{
var matches = ImmutableSortedDictionary<TKey, TValue>.Empty;
var removed = ImmutableSortedDictionary<TKey, TValue>.Empty;
if (comparison.Type == ExpressionType.NotEqual)
{
var match = values.Remove(reference);
removed = BinaryComparison.Equal.Filter(values, reference);
return Tuple.Create(match, removed);
}
if (comparison.Type == ExpressionType.Equal)
{
TValue value;
if (values.TryGetValue(reference, out value))
{
matches = matches.Add(reference, value);
removed = BinaryComparison.NotEqual.Filter(values, reference);
return Tuple.Create(matches, removed);
}
// no matches
return Tuple.Create(ImmutableSortedDictionary<TKey, TValue>.Empty, values);
}
var evaluator = comparison.GetEvaluator<TKey>();
foreach (var kvp in values)
{
if (evaluator(kvp.Key, reference))
{
matches = matches.Add(kvp.Key, kvp.Value);
}
else
{
removed = removed.Add(kvp.Key, kvp.Value);
}
}
return Tuple.Create(matches, removed);
}
}
}
+148
View File
@@ -0,0 +1,148 @@
/*
* 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 QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Orders.Fees;
using System.Collections.Generic;
using QuantConnect.Orders.TimeInForces;
using System.Linq;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides an implementation of the <see cref="DefaultBrokerageModel"/> specific to Alpaca brokerage.
/// </summary>
public class AlpacaBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// The default start time of the <see cref="OrderType.MarketOnOpen"/> order submission window.
/// Example: 19:00 (7:00 PM).
/// </summary>
private static readonly TimeOnly _mooWindowStart = new(19, 0, 0);
/// <summary>
/// A dictionary that maps each supported <see cref="SecurityType"/> to an array of <see cref="OrderType"/> supported by Alpaca brokerage.
/// </summary>
private readonly Dictionary<SecurityType, HashSet<OrderType>> _supportOrderTypeBySecurityType = new()
{
{ SecurityType.Equity, new HashSet<OrderType> { OrderType.Market, OrderType.Limit, OrderType.StopMarket, OrderType.StopLimit,
OrderType.TrailingStop, OrderType.MarketOnOpen, OrderType.MarketOnClose } },
// Market and limit order types see https://docs.alpaca.markets/docs/options-trading-overview
{ SecurityType.Option, new HashSet<OrderType> { OrderType.Market, OrderType.Limit } },
{ SecurityType.Crypto, new HashSet<OrderType> { OrderType.Market, OrderType.Limit, OrderType.StopLimit }}
};
/// <summary>
/// Defines the default set of <see cref="SecurityType"/> values that support <see cref="OrderType.MarketOnOpen"/> orders.
/// </summary>
private readonly IReadOnlySet<SecurityType> _defaultMarketOnOpenSupportedSecurityTypes;
/// <summary>
/// Initializes a new instance of the <see cref="AlpacaBrokerageModel"/> class
/// </summary>
/// <remarks>All Alpaca accounts are set up as margin accounts</remarks>
public AlpacaBrokerageModel() : base(AccountType.Margin)
{
_defaultMarketOnOpenSupportedSecurityTypes = _supportOrderTypeBySecurityType.Where(x => x.Value.Contains(OrderType.MarketOnOpen)).Select(x => x.Key).ToHashSet();
}
/// <summary>
/// Gets a new fee model that represents this brokerage's fee structure
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new AlpacaFeeModel();
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security being ordered</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
if (!_supportOrderTypeBySecurityType.TryGetValue(security.Type, out var supportOrderTypes))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
if (!supportOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, supportOrderTypes));
return false;
}
var supportsOutsideTradingHours = (order.Properties as AlpacaOrderProperties)?.OutsideRegularTradingHours ?? false;
if (supportsOutsideTradingHours && (order.Type != OrderType.Limit || order.TimeInForce is not DayTimeInForce))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.AlpacaBrokerageModel.TradingOutsideRegularHoursNotSupported(this, order.Type, order.TimeInForce));
return false;
}
if (!BrokerageExtensions.ValidateCrossZeroOrder(this, security, order, out message))
{
return false;
}
if (!BrokerageExtensions.ValidateMarketOnOpenOrder(security, order, GetMarketOnOpenAllowedWindow, _defaultMarketOnOpenSupportedSecurityTypes, out message))
{
return false;
}
return base.CanSubmitOrder(security, order, out message);
}
/// <summary>
/// Returns true if the brokerage would allow updating the order as specified by the request
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested updated to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = null;
return true;
}
/// <summary>
/// Returns the allowed Market-on-Open submission window for Alpaca.
/// </summary>
/// <param name="marketHours">The market hours segment for the security.</param>
/// <returns>
/// A tuple with <c>MarketOnOpenWindowStart</c> (default 19:00 / 7:00 PM) and
/// <c>MarketOnOpenWindowEnd</c>, adjusted slightly before the market open to avoid rejection.
/// </returns>
private (TimeOnly MarketOnOpenWindowStart, TimeOnly MarketOnOpenWindowEnd) GetMarketOnOpenAllowedWindow(MarketHoursSegment marketHours)
{
return (_mooWindowStart, TimeOnly.FromTimeSpan(marketHours.Start.Add(-TimeSpan.FromMinutes(2))));
}
}
}
@@ -0,0 +1,80 @@
/*
* 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 QuantConnect.Orders.Fees;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides properties specific to Alpha Streams
/// </summary>
public class AlphaStreamsBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// Initializes a new instance of the <see cref="AlphaStreamsBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modeled, defaults to <see cref="AccountType.Margin"/> does not accept <see cref="AccountType.Cash"/>.</param>
public AlphaStreamsBrokerageModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
if (accountType == AccountType.Cash)
{
throw new ArgumentException(Messages.AlphaStreamsBrokerageModel.UnsupportedAccountType, nameof(accountType));
}
}
/// <summary>
/// Gets a new fee model that represents this brokerage's fee structure
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security) => new AlphaStreamsFeeModel();
/// <summary>
/// Gets a new slippage model that represents this brokerage's fill slippage behavior
/// </summary>
/// <param name="security">The security to get a slippage model for</param>
/// <returns>The new slippage model for this brokerage</returns>
public override ISlippageModel GetSlippageModel(Security security) => new AlphaStreamsSlippageModel();
/// <summary>
/// Gets the brokerage's leverage for the specified security
/// </summary>
/// <param name="security">The security's whose leverage we seek</param>
/// <returns>The leverage for the specified security</returns>
public override decimal GetLeverage(Security security)
{
switch (security.Type)
{
case SecurityType.Forex:
case SecurityType.Cfd:
return 10m;
default:
return 1m;
}
}
/// <summary>
/// Gets a new settlement model for the security
/// </summary>
/// <param name="security">The security to get a settlement model for</param>
/// <returns>The settlement model for this brokerage</returns>
public override ISettlementModel GetSettlementModel(Security security) => new ImmediateSettlementModel();
}
}
@@ -0,0 +1,151 @@
/*
* 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.Linq;
using System.Collections.Generic;
using QuantConnect.Benchmarks;
using QuantConnect.Data.Shortable;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.TimeInForces;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides the Axos clearing brokerage model specific properties
/// </summary>
public class AxosClearingBrokerageModel : DefaultBrokerageModel
{
private readonly HashSet<OrderType> _supportedOrderTypes = new()
{
OrderType.Limit,
OrderType.Market,
OrderType.MarketOnClose
};
/// <summary>
/// The default markets for Trading Technologies
/// </summary>
public new static readonly IReadOnlyDictionary<SecurityType, string> DefaultMarketMap = new Dictionary<SecurityType, string>
{
{SecurityType.Equity, Market.USA}
}.ToReadOnlyDictionary();
/// <summary>
/// Creates a new instance
/// </summary>
public AxosClearingBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType)
{
}
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets => DefaultMarketMap;
/// <summary>
/// Provides Axos fee model
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new AxosFeeModel();
}
/// <summary>
/// Gets the shortable provider
/// </summary>
/// <returns>Shortable provider</returns>
public override IShortableProvider GetShortableProvider(Security security)
{
if(security.Type == SecurityType.Equity)
{
return new LocalDiskShortableProvider("axos");
}
return base.GetShortableProvider(security);
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
// Equivalent to no benchmark
return new FuncBenchmark(x => 0);
}
/// <summary>
/// Returns true if the brokerage could accept this order.
/// </summary>
/// <param name="security">The security being ordered</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
message = null;
// validate security type
if (!DefaultMarketMap.ContainsKey(security.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
// validate order type
if (!_supportedOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
return false;
}
// validate orders quantity
if (order.AbsoluteQuantity % 1 != 0)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.AxosBrokerageModel.NonIntegerOrderQuantity(order));
return false;
}
return true;
}
/// <summary>
/// Returns true if the brokerage would allow updating the order as specified by the request
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = null;
return true;
}
}
}
+210
View File
@@ -0,0 +1,210 @@
/*
* 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.Linq;
using QuantConnect.Util;
using QuantConnect.Orders;
using QuantConnect.Benchmarks;
using QuantConnect.Securities;
using QuantConnect.Orders.Fees;
using System.Collections.Generic;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides Binance specific properties
/// </summary>
public class BinanceBrokerageModel : DefaultBrokerageModel
{
private const decimal _defaultLeverage = 3;
private const decimal _defaultFutureLeverage = 25;
/// <summary>
/// The base Binance API endpoint URL.
/// </summary>
protected virtual string BaseApiEndpoint => "https://api.binance.com/api/v3";
/// <summary>
/// Market name
/// </summary>
protected virtual string MarketName => Market.Binance;
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets(Market.Binance);
/// <summary>
/// Initializes a new instance of the <see cref="BinanceBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modeled, defaults to <see cref="AccountType.Cash"/></param>
public BinanceBrokerageModel(AccountType accountType = AccountType.Cash) : base(accountType)
{
}
/// <summary>
/// Binance global leverage rule
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override decimal GetLeverage(Security security)
{
if (AccountType == AccountType.Cash || security.IsInternalFeed() || security.Type == SecurityType.Base)
{
return 1m;
}
return security.Symbol.SecurityType == SecurityType.CryptoFuture ? _defaultFutureLeverage : _defaultLeverage;
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("BTCUSDC", SecurityType.Crypto, MarketName);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Provides Binance fee model
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override IFeeModel GetFeeModel(Security security)
{
return new BinanceFeeModel();
}
/// <summary>
/// Binance does not support update of orders
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>Binance does not support update of orders, so it will always return false</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, 0, Messages.DefaultBrokerageModel.OrderUpdateNotSupported);
return false;
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
message = null;
// Binance API provides minimum order size in quote currency
// and hence we have to check current order size using available price and order quantity
var quantityIsValid = true;
decimal price;
switch (order)
{
case LimitOrder limitOrder:
quantityIsValid &= IsOrderSizeLargeEnough(limitOrder.LimitPrice);
price = limitOrder.LimitPrice;
break;
case MarketOrder:
if (!security.HasData)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.NoDataForSymbol);
return false;
}
price = order.Direction == OrderDirection.Buy ? security.AskPrice : security.BidPrice;
quantityIsValid &= IsOrderSizeLargeEnough(price);
break;
case StopLimitOrder stopLimitOrder:
price = stopLimitOrder.LimitPrice;
quantityIsValid &= IsOrderSizeLargeEnough(stopLimitOrder.LimitPrice);
if (!quantityIsValid)
{
break;
}
// Binance Trading UI requires this check too...
quantityIsValid &= IsOrderSizeLargeEnough(stopLimitOrder.StopPrice);
price = stopLimitOrder.StopPrice;
break;
case StopMarketOrder stopMarketOrder:
if (security.Symbol.SecurityType != SecurityType.CryptoFuture)
{
// despite Binance API allows you to post STOP_LOSS and TAKE_PROFIT order types
// they always fails with the content
// {"code":-1013,"msg":"Take profit orders are not supported for this symbol."}
// currently no symbols supporting TAKE_PROFIT or STOP_LOSS orders
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.BinanceBrokerageModel.UnsupportedOrderTypeWithLinkToSupportedTypes(BaseApiEndpoint, order, security));
return false;
}
quantityIsValid &= IsOrderSizeLargeEnough(stopMarketOrder.StopPrice);
price = stopMarketOrder.StopPrice;
break;
default:
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, new[] { OrderType.StopMarket, OrderType.StopLimit, OrderType.Market, OrderType.Limit }));
return false;
}
if (!quantityIsValid)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.InvalidOrderSize(security, order.Quantity, price));
return false;
}
if (security.Type != SecurityType.Crypto && security.Type != SecurityType.CryptoFuture)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
return base.CanSubmitOrder(security, order, out message);
bool IsOrderSizeLargeEnough(decimal price) =>
// if we have a minimum order size we enforce it
!security.SymbolProperties.MinimumOrderSize.HasValue || order.AbsoluteQuantity * price > security.SymbolProperties.MinimumOrderSize;
}
/// <summary>
/// Returns a readonly dictionary of binance default markets
/// </summary>
protected static IReadOnlyDictionary<SecurityType, string> GetDefaultMarkets(string marketName)
{
var map = DefaultMarketMap.ToDictionary();
map[SecurityType.Crypto] = marketName;
return map.ToReadOnlyDictionary();
}
}
}
@@ -0,0 +1,66 @@
/*
* 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 QuantConnect.Benchmarks;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Securities.CryptoFuture;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides Binance Coin Futures specific properties
/// </summary>
public class BinanceCoinFuturesBrokerageModel : BinanceFuturesBrokerageModel
{
/// <summary>
/// Creates a new instance
/// </summary>
public BinanceCoinFuturesBrokerageModel(AccountType accountType) : base(accountType)
{
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("BTCUSD", SecurityType.CryptoFuture, MarketName);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Provides Binance Coin Futures fee model
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new BinanceCoinFuturesFeeModel();
}
/// <summary>
/// Creates the crypto future margin model for the given security
/// </summary>
/// <param name="security">The security to create the margin model for</param>
/// <returns>The margin model instance</returns>
protected override IBuyingPowerModel CreateCryptoFutureMarginModel(Security security)
{
return new CryptoFutureMarginModel(GetLeverage(security));
}
}
}
@@ -0,0 +1,103 @@
/*
* 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 QuantConnect.Benchmarks;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Securities.CryptoFuture;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides Binance Futures specific properties
/// </summary>
public class BinanceFuturesBrokerageModel : BinanceBrokerageModel
{
/// <summary>
/// Creates a new instance
/// </summary>
public BinanceFuturesBrokerageModel(AccountType accountType) : base(accountType)
{
if (accountType == AccountType.Cash)
{
throw new InvalidOperationException($"{SecurityType.CryptoFuture} can only be traded using a {AccountType.Margin} account type");
}
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("BTCUSDT", SecurityType.CryptoFuture, MarketName);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Provides Binance Futures fee model
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new BinanceFuturesFeeModel();
}
/// <summary>
/// Gets a new margin interest rate model for the security
/// </summary>
/// <param name="security">The security to get a margin interest rate model for</param>
/// <returns>The margin interest rate model for this brokerage</returns>
public override IMarginInterestRateModel GetMarginInterestRateModel(Security security)
{
// only applies for perpetual futures
if (security.Symbol.SecurityType == SecurityType.CryptoFuture && security.Symbol.ID.Date == SecurityIdentifier.DefaultDate)
{
return new BinanceFutureMarginInterestRateModel();
}
return base.GetMarginInterestRateModel(security);
}
/// <summary>
/// Gets a new buying power model for the security.
/// For <see cref="SecurityType.CryptoFuture"/>, returns a <see cref="BinanceCryptoFutureMarginModel"/>
/// that recognizes supplementary stable coin collateral (e.g. BNFCR for EU Credits Trading Mode).
/// </summary>
/// <param name="security">The security to get a buying power model for</param>
/// <returns>The buying power model for this brokerage/security</returns>
public override IBuyingPowerModel GetBuyingPowerModel(Security security)
{
if (security?.Type == SecurityType.CryptoFuture)
{
return CreateCryptoFutureMarginModel(security);
}
return base.GetBuyingPowerModel(security);
}
/// <summary>
/// Creates the crypto future margin model for the given security
/// </summary>
/// <param name="security">The security to create the margin model for</param>
/// <returns>The margin model instance</returns>
protected virtual IBuyingPowerModel CreateCryptoFutureMarginModel(Security security)
{
return new BinanceCryptoFutureMarginModel(GetLeverage(security));
}
}
}
@@ -0,0 +1,65 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides Binance.US specific properties
/// </summary>
public class BinanceUSBrokerageModel : BinanceBrokerageModel
{
/// <summary>
/// The base Binance Futures API endpoint URL.
/// </summary>
protected override string BaseApiEndpoint => "https://api.binance.us/api/v3";
/// <summary>
/// Market name
/// </summary>
protected override string MarketName => Market.BinanceUS;
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets(Market.BinanceUS);
/// <summary>
/// Initializes a new instance of the <see cref="BinanceBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modeled, defaults to <see cref="AccountType.Cash"/></param>
public BinanceUSBrokerageModel(AccountType accountType = AccountType.Cash) : base(accountType)
{
if (accountType == AccountType.Margin)
{
throw new ArgumentException(Messages.BinanceUSBrokerageModel.UnsupportedAccountType);
}
}
/// <summary>
/// Binance global leverage rule
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override decimal GetLeverage(Security security)
{
// margin trading is not currently supported by Binance.US
return 1m;
}
}
}
+165
View File
@@ -0,0 +1,165 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Benchmarks;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides Bitfinex specific properties
/// </summary>
public class BitfinexBrokerageModel : DefaultBrokerageModel
{
private const decimal _maxLeverage = 3.3m;
/// <summary>
/// Represents a set of order types supported by the current brokerage model.
/// </summary>
private readonly HashSet<OrderType> _supportedOrderTypes = new()
{
OrderType.Market,
OrderType.Limit,
OrderType.StopMarket,
OrderType.StopLimit,
};
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets();
/// <summary>
/// Initializes a new instance of the <see cref="BitfinexBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modeled, defaults to <see cref="AccountType.Margin"/></param>
public BitfinexBrokerageModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
}
/// <summary>
/// Bitfinex global leverage rule
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override decimal GetLeverage(Security security)
{
if (AccountType == AccountType.Cash || security.IsInternalFeed() || security.Type == SecurityType.Base)
{
return 1m;
}
if (security.Type == SecurityType.Crypto)
{
return _maxLeverage;
}
throw new ArgumentException(Messages.DefaultBrokerageModel.InvalidSecurityTypeForLeverage(security), nameof(security));
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Bitfinex);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Provides Bitfinex fee model
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override IFeeModel GetFeeModel(Security security)
{
return new BitfinexFeeModel();
}
/// <summary>
/// Checks whether an order can be updated or not in the Bitfinex brokerage model
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The update request</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the update requested quantity is valid, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
// If the requested quantity is null is going to be ignored by the moment ApplyUpdateOrderRequest() method is call
if (request.Quantity == null)
{
message = null;
return true;
}
// Check if the requested quantity is valid
var requestedQuantity = (decimal)request.Quantity;
return IsValidOrderSize(security, requestedQuantity, out message);
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
if (!IsValidOrderSize(security, order.Quantity, out message))
{
return false;
}
message = null;
if (security.Type != SecurityType.Crypto)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
if (!_supportedOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
return false;
}
return base.CanSubmitOrder(security, order, out message);
}
private static IReadOnlyDictionary<SecurityType, string> GetDefaultMarkets()
{
var map = DefaultMarketMap.ToDictionary();
map[SecurityType.Crypto] = Market.Bitfinex;
return map.ToReadOnlyDictionary();
}
}
}
@@ -0,0 +1,92 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides Bloomberg FixNet HUB specific properties.
/// </summary>
public class BloombergFixBrokerageModel : DefaultBrokerageModel
{
private readonly HashSet<SecurityType> _supportedSecurityTypes = new()
{
SecurityType.Equity,
SecurityType.Option,
SecurityType.IndexOption,
SecurityType.Future,
};
private readonly HashSet<OrderType> _supportedOrderTypes = new()
{
OrderType.Market,
OrderType.MarketOnOpen,
OrderType.Limit,
OrderType.StopMarket,
OrderType.StopLimit,
};
/// <summary>
/// Constructor for Bloomberg FIX brokerage model.
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public BloombergFixBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType)
{
if (accountType == AccountType.Cash)
{
throw new NotSupportedException($"Bloomberg FIX brokerage can only be used with a {AccountType.Margin} account type");
}
}
/// <summary>
/// Returns true if the brokerage could accept this order.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
if (!_supportedSecurityTypes.Contains(security.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
if (!_supportedOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
return false;
}
return base.CanSubmitOrder(security, order, out message);
}
/// <summary>
/// Bloomberg FixNet HUB supports cancel/replace (35=G) on order quantity, price, stop price,
/// order type and time in force.
/// </summary>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = null;
return true;
}
}
}
+188
View File
@@ -0,0 +1,188 @@
/*
* 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.Linq;
using QuantConnect.Orders;
using QuantConnect.Securities;
using System.Collections.Generic;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides extension methods for handling brokerage operations.
/// </summary>
public static class BrokerageExtensions
{
/// <summary>
/// The default set of order types that are not allowed to cross zero holdings.
/// This is used by <see cref="ValidateCrossZeroOrder"/> when no custom set is provided.
/// </summary>
private static readonly IReadOnlySet<OrderType> DefaultNotSupportedCrossZeroOrderTypes = new HashSet<OrderType>
{
OrderType.MarketOnOpen,
OrderType.MarketOnClose
};
/// <summary>
/// Determines if executing the specified order will cross the zero holdings threshold.
/// </summary>
/// <param name="holdingQuantity">The current quantity of holdings.</param>
/// <param name="orderQuantity">The quantity of the order to be evaluated.</param>
/// <returns>
/// <c>true</c> if the order will change the holdings from positive to negative or vice versa; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// This method checks if the order will result in a position change from positive to negative holdings or from negative to positive holdings.
/// </remarks>
public static bool OrderCrossesZero(decimal holdingQuantity, decimal orderQuantity)
{
//We're reducing position or flipping:
if (holdingQuantity > 0 && orderQuantity < 0)
{
if ((holdingQuantity + orderQuantity) < 0)
{
//We don't have enough holdings so will cross through zero:
return true;
}
}
else if (holdingQuantity < 0 && orderQuantity > 0)
{
if ((holdingQuantity + orderQuantity) > 0)
{
//Crossed zero: need to split into 2 orders:
return true;
}
}
return false;
}
/// <summary>
/// Determines whether an order that crosses zero holdings is permitted
/// for the specified brokerage model and order type.
/// </summary>
/// <param name="brokerageModel">The brokerage model performing the validation.</param>
/// <param name="security">The security associated with the order.</param>
/// <param name="order">The order to validate.</param>
/// <param name="notSupportedTypes">The set of order types that cannot cross zero holdings.</param>
/// <param name="message">
/// When the method returns <c>false</c>, contains a <see cref="BrokerageMessageEvent"/>
/// explaining why the order is not supported; otherwise <c>null</c>.
/// </param>
/// <returns>
/// <c>true</c> if the order is valid to submit; <c>false</c> if crossing zero is not supported
/// for the given order type.
/// </returns>
public static bool ValidateCrossZeroOrder(
IBrokerageModel brokerageModel,
Security security,
Order order,
out BrokerageMessageEvent message,
IReadOnlySet<OrderType> notSupportedTypes = null)
{
message = null;
notSupportedTypes ??= DefaultNotSupportedCrossZeroOrderTypes;
if (OrderCrossesZero(security.Holdings.Quantity, order.Quantity) && notSupportedTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(
BrokerageMessageType.Warning,
"NotSupported",
Messages.DefaultBrokerageModel.UnsupportedCrossZeroByOrderType(brokerageModel, order.Type)
);
return false;
}
return true;
}
/// <summary>
/// Validates whether a <see cref="OrderType.MarketOnOpen"/> order.
/// </summary>
/// <param name="security">The security associated with the order.</param>
/// <param name="order">The order to validate.</param>
/// <param name="getMarketOnOpenAllowedWindow">
/// A delegate that takes a <see cref="MarketHoursSegment"/> and returns the allowed
/// Market-on-Open submission window as a <see cref="TimeOnly"/> tuple (start, end).
/// </param>
/// <param name="supportedSecurityTypes"> The set of <see cref="SecurityType"/> values allowed for <see cref="OrderType.MarketOnOpen"/> orders.</param>
/// <param name="message">
/// An output <see cref="BrokerageMessageEvent"/> containing the reason
/// the order is invalid if the check fails; otherwise <c>null</c>.
/// </param>
/// <returns><c>true</c> if the order may be submitted within the given window; otherwise <c>false</c>.</returns>
public static bool ValidateMarketOnOpenOrder(
Security security,
Order order,
Func<MarketHoursSegment, (TimeOnly WindowStart, TimeOnly WindowEnd)> getMarketOnOpenAllowedWindow,
IReadOnlySet<SecurityType> supportedSecurityTypes,
out BrokerageMessageEvent message)
{
message = null;
if (order.Type != OrderType.MarketOnOpen)
{
return true;
}
if (!supportedSecurityTypes.Contains(security.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, $"UnsupportedSecurityType",
$"The broker does not support Market-on-Open orders for security type {security.Type}");
return false;
}
var targetTime = TimeOnly.FromDateTime(security.LocalTime);
var regularHours = security.Exchange.Hours.GetMarketHours(security.LocalTime).Segments.FirstOrDefault(x => x.State == MarketHoursState.Market);
var (windowStart, windowEnd) = (TimeOnly.MinValue, TimeOnly.MaxValue);
if (regularHours != null)
{
(windowStart, windowEnd) = getMarketOnOpenAllowedWindow(regularHours);
}
if (!targetTime.IsBetween(windowStart, windowEnd))
{
message = new BrokerageMessageEvent(
BrokerageMessageType.Warning,
"NotSupported",
Messages.DefaultBrokerageModel.UnsupportedMarketOnOpenOrderTime(windowStart, windowEnd)
);
return false;
}
return true;
}
/// <summary>
/// Gets the position that might result given the specified order direction and the current holdings quantity.
/// This is useful for brokerages that require more specific direction information than provided by the OrderDirection enum
/// (e.g. Tradier differentiates Buy/Sell and BuyToOpen/BuyToCover/SellShort/SellToClose)
/// </summary>
/// <param name="orderDirection">The order direction</param>
/// <param name="holdingsQuantity">The current holdings quantity</param>
/// <returns>The order position</returns>
public static OrderPosition GetOrderPosition(OrderDirection orderDirection, decimal holdingsQuantity)
{
return orderDirection switch
{
OrderDirection.Buy => holdingsQuantity >= 0 ? OrderPosition.BuyToOpen : OrderPosition.BuyToClose,
OrderDirection.Sell => holdingsQuantity <= 0 ? OrderPosition.SellToOpen : OrderPosition.SellToClose,
_ => throw new ArgumentOutOfRangeException(nameof(orderDirection), orderDirection, "Invalid order direction")
};
}
}
}
@@ -0,0 +1,40 @@
/*
* 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;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Represents the brokerage factory type required to load a data queue handler
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class BrokerageFactoryAttribute : Attribute
{
/// <summary>
/// The type of the brokerage factory
/// </summary>
public Type Type { get; set; }
/// <summary>
/// Creates a new instance of the <see cref="BrokerageFactoryAttribute"/> class
/// </summary>
/// <param name="type">The brokerage factory type</param>
public BrokerageFactoryAttribute(Type type)
{
Type = type;
}
}
}
@@ -0,0 +1,96 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace QuantConnect.Brokerages
{
/// <summary>
/// Represents a message received from a brokerage
/// </summary>
public class BrokerageMessageEvent
{
/// <summary>
/// Gets the type of brokerage message
/// </summary>
public BrokerageMessageType Type { get; private set; }
/// <summary>
/// Gets the brokerage specific code for this message, zero if no code was specified
/// </summary>
public string Code { get; private set; }
/// <summary>
/// Gets the message text received from the brokerage
/// </summary>
public string Message { get; private set; }
/// <summary>
/// Initializes a new instance of the BrokerageMessageEvent class
/// </summary>
/// <param name="type">The type of brokerage message</param>
/// <param name="code">The brokerage specific code</param>
/// <param name="message">The message text received from the brokerage</param>
public BrokerageMessageEvent(BrokerageMessageType type, int code, string message)
{
Type = type;
Code = code.ToStringInvariant();
Message = message;
}
/// <summary>
/// Initializes a new instance of the BrokerageMessageEvent class
/// </summary>
/// <param name="type">The type of brokerage message</param>
/// <param name="code">The brokerage specific code</param>
/// <param name="message">The message text received from the brokerage</param>
public BrokerageMessageEvent(BrokerageMessageType type, string code, string message)
{
Type = type;
Code = code;
Message = message;
}
/// <summary>
/// Creates a new <see cref="BrokerageMessageEvent"/> to represent a disconnect message
/// </summary>
/// <param name="message">The message from the brokerage</param>
/// <returns>A brokerage disconnect message</returns>
public static BrokerageMessageEvent Disconnected(string message)
{
return new BrokerageMessageEvent(BrokerageMessageType.Disconnect, Messages.BrokerageMessageEvent.DisconnectCode, message);
}
/// <summary>
/// Creates a new <see cref="BrokerageMessageEvent"/> to represent a reconnect message
/// </summary>
/// <param name="message">The message from the brokerage</param>
/// <returns>A brokerage reconnect message</returns>
public static BrokerageMessageEvent Reconnected(string message)
{
return new BrokerageMessageEvent(BrokerageMessageType.Reconnect, Messages.BrokerageMessageEvent.ReconnectCode, message);
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return Messages.BrokerageMessageEvent.ToString(this);
}
}
}
+53
View File
@@ -0,0 +1,53 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace QuantConnect.Brokerages
{
/// <summary>
/// Specifies the type of message received from an IBrokerage implementation
/// </summary>
public enum BrokerageMessageType
{
/// <summary>
/// Informational message (0)
/// </summary>
Information,
/// <summary>
/// Warning message (1)
/// </summary>
Warning,
/// <summary>
/// Fatal error message, the algo will be stopped (2)
/// </summary>
Error,
/// <summary>
/// Brokerage reconnected with remote server (3)
/// </summary>
Reconnect,
/// <summary>
/// Brokerage disconnected from remote server (4)
/// </summary>
Disconnect,
/// <summary>
/// Action required by the user (5)
/// </summary>
ActionRequired,
}
}
+212
View File
@@ -0,0 +1,212 @@
/*
* 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;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Specifices what transaction model and submit/execution rules to use
/// </summary>
public enum BrokerageName
{
/// <summary>
/// Transaction and submit/execution rules will be the default as initialized
/// </summary>
Default,
/// <summary>
/// Transaction and submit/execution rules will be the default as initialized
/// Alternate naming for default brokerage
/// </summary>
QuantConnectBrokerage = Default,
/// <summary>
/// Transaction and submit/execution rules will use interactive brokers models
/// </summary>
InteractiveBrokersBrokerage,
/// <summary>
/// Transaction and submit/execution rules will use tradier models
/// </summary>
TradierBrokerage,
/// <summary>
/// Transaction and submit/execution rules will use oanda models
/// </summary>
OandaBrokerage,
/// <summary>
/// Transaction and submit/execution rules will use fxcm models
/// </summary>
FxcmBrokerage,
/// <summary>
/// Transaction and submit/execution rules will use bitfinex models
/// </summary>
Bitfinex,
/// <summary>
/// Transaction and submit/execution rules will use binance models
/// </summary>
Binance,
/// <summary>
/// Transaction and submit/execution rules will use gdax models
/// </summary>
[Obsolete("GDAX brokerage name is deprecated. Use Coinbase instead.")]
GDAX = 12,
/// <summary>
/// Transaction and submit/execution rules will use alpaca models
/// </summary>
Alpaca,
/// <summary>
/// Transaction and submit/execution rules will use AlphaStream models
/// </summary>
AlphaStreams,
/// <summary>
/// Transaction and submit/execution rules will use Zerodha models
/// </summary>
Zerodha,
/// <summary>
/// Transaction and submit/execution rules will use Samco models
/// </summary>
Samco,
/// <summary>
/// Transaction and submit/execution rules will use atreyu models
/// </summary>
Atreyu,
/// <summary>
/// Transaction and submit/execution rules will use TradingTechnologies models
/// </summary>
TradingTechnologies,
/// <summary>
/// Transaction and submit/execution rules will use Kraken models
/// </summary>
Kraken,
/// <summary>
/// Transaction and submit/execution rules will use ftx models
/// </summary>
FTX,
/// <summary>
/// Transaction and submit/execution rules will use ftx us models
/// </summary>
FTXUS,
/// <summary>
/// Transaction and submit/execution rules will use Exante models
/// </summary>
Exante,
/// <summary>
/// Transaction and submit/execution rules will use Binance.US models
/// </summary>
BinanceUS,
/// <summary>
/// Transaction and submit/execution rules will use Wolverine models
/// </summary>
Wolverine,
/// <summary>
/// Transaction and submit/execution rules will use TDameritrade models
/// </summary>
TDAmeritrade,
/// <summary>
/// Binance Futures USDⓈ-Margined contracts are settled and collateralized in their quote cryptocurrency, USDT or BUSD
/// </summary>
BinanceFutures,
/// <summary>
/// Binance Futures COIN-Margined contracts are settled and collateralized in their based cryptocurrency.
/// </summary>
BinanceCoinFutures,
/// <summary>
/// Transaction and submit/execution rules will use RBI models
/// </summary>
RBI,
/// <summary>
/// Transaction and submit/execution rules will use Bybit models
/// </summary>
Bybit,
/// <summary>
/// Transaction and submit/execution rules will use Eze models
/// </summary>
Eze,
/// <summary>
/// Transaction and submit/execution rules will use Axos models
/// </summary>
Axos,
/// <summary>
/// Transaction and submit/execution rules will use Coinbase broker's model
/// </summary>
Coinbase,
/// <summary>
/// Transaction and submit/execution rules will use TradeStation models
/// </summary>
TradeStation,
/// <summary>
/// Transaction and submit/execution rules will use Terminal link models
/// </summary>
TerminalLink,
/// <summary>
/// Transaction and submit/execution rules will use Charles Schwab models
/// </summary>
CharlesSchwab,
/// <summary>
/// Transaction and submit/execution rules will use Tastytrade models
/// </summary>
Tastytrade,
/// <summary>
/// Transaction and submit/execution rules will use interactive brokers Fix models
/// </summary>
InteractiveBrokersFix,
/// <summary>
/// Transaction and submit/execution rules will use dYdX models
/// </summary>
DYDX,
/// <summary>
/// Transaction and submit/execution rules will use Webull models
/// </summary>
Webull,
/// <summary>
/// Transaction and submit/execution rules will use Public.com models
/// </summary>
Public
}
}
+218
View File
@@ -0,0 +1,218 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Benchmarks;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Securities.CryptoFuture;
using QuantConnect.Util;
namespace QuantConnect.Brokerages;
/// <summary>
/// Provides Bybit specific properties
/// </summary>
public class BybitBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// Market name
/// </summary>
protected virtual string MarketName => Market.Bybit;
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets(Market.Bybit);
/// <summary>
/// Initializes a new instance of the <see cref="BybitBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modeled, defaults to <see cref="AccountType.Cash"/></param>
public BybitBrokerageModel(AccountType accountType = AccountType.Cash) : base(accountType)
{
}
/// <summary>
/// Bybit global leverage rule
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override decimal GetLeverage(Security security)
{
if (AccountType == AccountType.Cash || security.IsInternalFeed() || security.Type == SecurityType.Base)
{
return 1m;
}
return 10;
}
/// <summary>
/// Provides Bybit fee model
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override IFeeModel GetFeeModel(Security security)
{
return security.Type switch
{
SecurityType.Crypto => new BybitFeeModel(),
SecurityType.CryptoFuture => new BybitFuturesFeeModel(),
SecurityType.Base => base.GetFeeModel(security),
_ => throw new ArgumentOutOfRangeException(nameof(security), security, $"Not supported security type {security.Type}")
};
}
/// <summary>
/// Gets a new margin interest rate model for the security
/// </summary>
/// <param name="security">The security to get a margin interest rate model for</param>
/// <returns>The margin interest rate model for this brokerage</returns>
public override IMarginInterestRateModel GetMarginInterestRateModel(Security security)
{
// only applies for perpetual futures
if (security.Type == SecurityType.CryptoFuture &&
security.Symbol.ID.Date == SecurityIdentifier.DefaultDate)
{
return new BybitFutureMarginInterestRateModel();
}
return base.GetMarginInterestRateModel(security);
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("BTCUSDC", SecurityType.Crypto, MarketName);
return SecurityBenchmark.CreateInstance(securities, symbol);
//todo default conversion?
}
/// <summary>
/// Returns true if the brokerage could accept this order update. This takes into account
/// order type, security type, and order size limits. Bybit can only update inverse, linear, and option orders
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage could update the order, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request,
out BrokerageMessageEvent message)
{
//can only update linear, inverse, and options
if (security.Type != SecurityType.CryptoFuture)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.OrderUpdateNotSupported);
return false;
}
if (order.Status is not (OrderStatus.New or OrderStatus.PartiallyFilled or OrderStatus.Submitted or OrderStatus.UpdateSubmitted))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
$"Order with status {order.Status} can't be modified");
return false;
}
if (request.Quantity.HasValue && !IsOrderSizeLargeEnough(security, Math.Abs(request.Quantity.Value)))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.InvalidOrderQuantity(security, request.Quantity.Value));
return false;
}
message = null;
return true;
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
if (security.Type != SecurityType.Crypto && security.Type != SecurityType.CryptoFuture && security.Type != SecurityType.Base)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
message = null;
bool quantityIsValid;
switch (order)
{
case StopLimitOrder:
case StopMarketOrder:
case LimitOrder:
case MarketOrder:
quantityIsValid = IsOrderSizeLargeEnough(security, Math.Abs(order.Quantity));
break;
default:
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order,
new[] { OrderType.StopMarket, OrderType.StopLimit, OrderType.Market, OrderType.Limit }));
return false;
}
if (!quantityIsValid)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.InvalidOrderQuantity(security, order.Quantity));
return false;
}
return base.CanSubmitOrder(security, order, out message);
}
/// <summary>
/// Returns true if the order size is large enough for the given security.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="orderQuantity">The order quantity</param>
/// <returns>True if the order size is large enough, false otherwise</returns>
protected virtual bool IsOrderSizeLargeEnough(Security security, decimal orderQuantity)
{
return !security.SymbolProperties.MinimumOrderSize.HasValue ||
orderQuantity >= security.SymbolProperties.MinimumOrderSize;
}
private static IReadOnlyDictionary<SecurityType, string> GetDefaultMarkets(string marketName)
{
var map = DefaultMarketMap.ToDictionary();
map[SecurityType.Crypto] = marketName;
map[SecurityType.CryptoFuture] = marketName;
return map.ToReadOnlyDictionary();
}
}
@@ -0,0 +1,106 @@
/*
* 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 QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Orders.Fees;
using System.Collections.Generic;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Represents a brokerage model specific to Charles Schwab.
/// </summary>
public class CharlesSchwabBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// HashSet containing the security types supported by TradeStation.
/// </summary>
private readonly HashSet<SecurityType> _supportSecurityTypes = new(
new[]
{
SecurityType.Equity,
SecurityType.Option,
SecurityType.IndexOption
});
/// <summary>
/// HashSet containing the order types supported by the <see cref="CanSubmitOrder"/> operation in TradeStation.
/// </summary>
private readonly HashSet<OrderType> _supportOrderTypes =
[
OrderType.Market,
OrderType.Limit,
OrderType.StopMarket,
OrderType.ComboMarket,
OrderType.ComboLimit,
OrderType.MarketOnClose,
OrderType.MarketOnOpen,
OrderType.StopLimit
];
/// <summary>
/// Constructor for Charles Schwab brokerage model
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public CharlesSchwabBrokerageModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
}
/// <summary>
/// Provides TradeStation fee model
/// </summary>
/// <param name="security">Security</param>
/// <returns>TradeStation fee model</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new CharlesSchwabFeeModel();
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
message = default;
if (!_supportSecurityTypes.Contains(security.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
if (!_supportOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportOrderTypes));
return false;
}
return base.CanSubmitOrder(security, order, out message);
}
}
}
+254
View File
@@ -0,0 +1,254 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014-2023 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.Linq;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Benchmarks;
using QuantConnect.Orders.Fees;
using System.Collections.Generic;
using QuantConnect.Util;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Represents a brokerage model for interacting with the Coinbase exchange.
/// This class extends the default brokerage model.
/// </summary>
public class CoinbaseBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// Marks the end of stop market order support on Coinbase Pro.
/// For backtesting purposes, this field '_stopMarketOrderSupportEndDate' specifies the date when the
/// market structure update was applied, affecting the handling of historical data or simulations
/// involving stop market orders. Details: https://blog.coinbase.com/coinbase-pro-market-structure-update-fbd9d49f43d7
/// </summary>
private readonly DateTime _stopMarketOrderSupportEndDate = new DateTime(2019, 3, 23, 1, 0, 0);
/// <summary>
/// Notifies users that order updates are not supported by the current brokerage model.
/// </summary>
private readonly BrokerageMessageEvent _message = new(BrokerageMessageType.Warning, 0, Messages.DefaultBrokerageModel.OrderUpdateNotSupported);
/// <summary>
/// Represents a set of order types supported by the current brokerage model.
/// </summary>
private readonly HashSet<OrderType> _supportedOrderTypes = new()
{
OrderType.Limit,
OrderType.Market,
OrderType.StopLimit,
OrderType.StopMarket
};
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets => GetDefaultMarkets(Market.Coinbase);
/// <summary>
/// Initializes a new instance of the <see cref="CoinbaseBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modelled, defaults to <see cref="AccountType.Cash"/></param>
public CoinbaseBrokerageModel(AccountType accountType = AccountType.Cash)
: base(accountType)
{
if (accountType == AccountType.Margin)
{
throw new ArgumentException(Messages.CoinbaseBrokerageModel.UnsupportedAccountType, nameof(accountType));
}
}
/// <summary>
/// Coinbase global leverage rule
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override decimal GetLeverage(Security security)
{
// margin trading is not currently supported by Coinbase
return 1m;
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Coinbase);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Provides Coinbase fee model
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override IFeeModel GetFeeModel(Security security)
{
return new CoinbaseFeeModel();
}
/// <summary>
/// Determines whether the brokerage supports updating an existing order for the specified security.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns><c>true</c> if the brokerage supports updating orders; otherwise, <c>false</c>.</returns>
/// <remarks>Coinbase: Only limit order types, with time in force type of good-till-cancelled can be edited.</remarks>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
if (order == null || security == null || request == null)
{
var parameter = order == null ? nameof(order) : nameof(security);
throw new ArgumentNullException(parameter, $"{parameter} parameter cannot be null. Please provide a valid {parameter} for submission.");
}
if (order.Type != OrderType.Limit)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
$"Order with type {order.Type} can't be modified, only LIMIT.");
return false;
}
if (order.TimeInForce != TimeInForce.GoodTilCanceled)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
$"Order's parameter 'TimeInForce' is not instance of Good Til Cancelled class.");
return false;
}
if (order.Status is not (OrderStatus.New or OrderStatus.PartiallyFilled or OrderStatus.Submitted or OrderStatus.UpdateSubmitted))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
$"Order with status {order.Status} can't be modified");
return false;
}
if (request.Quantity.HasValue && !IsOrderSizeLargeEnough(security, Math.Abs(request.Quantity.Value)))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.InvalidOrderQuantity(security, request.Quantity.Value));
return false;
}
message = null;
return true;
}
/// <summary>
/// Evaluates whether exchange will accept order. Will reject order update
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
if(order == null || security == null)
{
var parameter = order == null ? nameof(order) : nameof(security);
throw new ArgumentNullException(parameter, $"{parameter} parameter cannot be null. Please provide a valid {parameter} for submission.");
}
if (order.BrokerId != null && order.BrokerId.Any())
{
message = _message;
return false;
}
if (!IsValidOrderSize(security, order.Quantity, out message))
{
return false;
}
if (security.Type != SecurityType.Crypto)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
if (!_supportedOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
return false;
}
if (order.Type == OrderType.StopMarket && order.Time >= _stopMarketOrderSupportEndDate)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.CoinbaseBrokerageModel.StopMarketOrdersNoLongerSupported(_stopMarketOrderSupportEndDate));
return false;
}
if (!IsOrderSizeLargeEnough(security, Math.Abs(order.Quantity)))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.InvalidOrderQuantity(security, order.Quantity));
return false;
}
return base.CanSubmitOrder(security, order, out message);
}
/// <summary>
/// Gets a new buying power model for the security, returning the default model with the security's configured leverage.
/// For cash accounts, leverage = 1 is used.
/// </summary>
/// <param name="security">The security to get a buying power model for</param>
/// <returns>The buying power model for this brokerage/security</returns>
public override IBuyingPowerModel GetBuyingPowerModel(Security security)
{
// margin trading is not currently supported by Coinbase
return new CashBuyingPowerModel();
}
/// <summary>
/// Returns true if the order size is large enough for the given security.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="orderQuantity">The order quantity</param>
/// <returns>True if the order size is large enough, false otherwise</returns>
protected virtual bool IsOrderSizeLargeEnough(Security security, decimal orderQuantity)
{
#pragma warning disable CA1062
return !security!.SymbolProperties.MinimumOrderSize.HasValue ||
orderQuantity >= security.SymbolProperties.MinimumOrderSize;
#pragma warning restore CA1062
}
/// <summary>
/// Gets the default markets for different security types, with an option to override the market name for Crypto securities.
/// </summary>
/// <param name="marketName">The default market name for Crypto securities.</param>
/// <returns>
/// A read-only dictionary where the keys are <see cref="SecurityType"/> and the values are market names.
/// </returns>
protected static IReadOnlyDictionary<SecurityType, string> GetDefaultMarkets(string marketName)
{
var map = DefaultMarketMap.ToDictionary();
map[SecurityType.Crypto] = marketName;
return map.ToReadOnlyDictionary();
}
}
}
@@ -0,0 +1,217 @@
/*
* 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Logging;
using QuantConnect.Packets;
using QuantConnect.Util;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides a default implementation o <see cref="IBrokerageMessageHandler"/> that will forward
/// messages as follows:
/// Information -> IResultHandler.Debug
/// Warning -> IResultHandler.Error &amp;&amp; IApi.SendUserEmail
/// Error -> IResultHandler.Error &amp;&amp; IAlgorithm.RunTimeError
/// </summary>
public class DefaultBrokerageMessageHandler : IBrokerageMessageHandler
{
private static readonly TimeSpan DefaultOpenThreshold = TimeSpan.FromMinutes(5);
private static readonly TimeSpan DefaultInitialDelay = TimeSpan.FromMinutes(15);
private volatile bool _connected;
private readonly IAlgorithm _algorithm;
private readonly TimeSpan _openThreshold;
private readonly TimeSpan _initialDelay;
private CancellationTokenSource _cancellationTokenSource;
private bool _outsideLeanOrderWarningEmitted;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultBrokerageMessageHandler"/> class
/// </summary>
/// <param name="algorithm">The running algorithm</param>
/// <param name="initialDelay"></param>
/// <param name="openThreshold">Defines how long before market open to re-check for brokerage reconnect message</param>
public DefaultBrokerageMessageHandler(IAlgorithm algorithm, TimeSpan? initialDelay = null, TimeSpan? openThreshold = null)
: this(algorithm, null, null, initialDelay, openThreshold)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultBrokerageMessageHandler"/> class
/// </summary>
/// <param name="algorithm">The running algorithm</param>
/// <param name="job">The job that produced the algorithm</param>
/// <param name="api">The api for the algorithm</param>
/// <param name="initialDelay"></param>
/// <param name="openThreshold">Defines how long before market open to re-check for brokerage reconnect message</param>
public DefaultBrokerageMessageHandler(IAlgorithm algorithm, AlgorithmNodePacket job, IApi api, TimeSpan? initialDelay = null, TimeSpan? openThreshold = null)
{
_algorithm = algorithm;
_connected = true;
_openThreshold = openThreshold ?? DefaultOpenThreshold;
_initialDelay = initialDelay ?? DefaultInitialDelay;
}
/// <summary>
/// Handles the message
/// </summary>
/// <param name="message">The message to be handled</param>
public void HandleMessage(BrokerageMessageEvent message)
{
// based on message type dispatch to result handler
switch (message.Type)
{
case BrokerageMessageType.Information:
_algorithm.Debug(Messages.DefaultBrokerageMessageHandler.BrokerageInfo(message));
break;
case BrokerageMessageType.Warning:
_algorithm.Error(Messages.DefaultBrokerageMessageHandler.BrokerageWarning(message));
break;
case BrokerageMessageType.Error:
// unexpected error, we need to close down shop
_algorithm.SetRuntimeError(new Exception(message.Message),
Messages.DefaultBrokerageMessageHandler.BrokerageErrorContext);
break;
case BrokerageMessageType.Disconnect:
_connected = false;
Log.Trace(Messages.DefaultBrokerageMessageHandler.Disconnected);
// check to see if any non-custom security exchanges are open within the next x minutes
var open = (from kvp in _algorithm.Securities
let security = kvp.Value
where security.Type != SecurityType.Base
let exchange = security.Exchange
let localTime = _algorithm.UtcTime.ConvertFromUtc(exchange.TimeZone)
where exchange.IsOpenDuringBar(
localTime,
localTime + _openThreshold,
_algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(security.Symbol)
.IsExtendedMarketHours())
select security).Any();
// if any are open then we need to kill the algorithm
if (open)
{
Log.Trace(Messages.DefaultBrokerageMessageHandler.DisconnectedWhenExchangesAreOpen(_initialDelay));
// wait 15 minutes before killing algorithm
StartCheckReconnected(_initialDelay, message);
}
else
{
Log.Trace(Messages.DefaultBrokerageMessageHandler.DisconnectedWhenExchangesAreClosed);
// if they aren't open, we'll need to check again a little bit before markets open
DateTime nextMarketOpenUtc;
if (_algorithm.Securities.Count != 0)
{
nextMarketOpenUtc = (from kvp in _algorithm.Securities
let security = kvp.Value
where security.Type != SecurityType.Base
let exchange = security.Exchange
let localTime = _algorithm.UtcTime.ConvertFromUtc(exchange.TimeZone)
let marketOpen = exchange.Hours.GetNextMarketOpen(localTime,
_algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(security.Symbol)
.IsExtendedMarketHours())
let marketOpenUtc = marketOpen.ConvertToUtc(exchange.TimeZone)
select marketOpenUtc).Min();
}
else
{
// if we have no securities just make next market open an hour from now
nextMarketOpenUtc = DateTime.UtcNow.AddHours(1);
}
var timeUntilNextMarketOpen = nextMarketOpenUtc - DateTime.UtcNow - _openThreshold;
Log.Trace(Messages.DefaultBrokerageMessageHandler.TimeUntilNextMarketOpen(timeUntilNextMarketOpen));
// wake up 5 minutes before market open and check if we've reconnected
StartCheckReconnected(timeUntilNextMarketOpen, message);
}
break;
case BrokerageMessageType.Reconnect:
_connected = true;
Log.Trace(Messages.DefaultBrokerageMessageHandler.Reconnected);
if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
{
_cancellationTokenSource.Cancel();
}
break;
case BrokerageMessageType.ActionRequired:
// not supported atm
_algorithm.SetRuntimeError(new Exception("Brokerage requires user action"), Messages.DefaultBrokerageMessageHandler.BrokerageDisconnectedShutDownContext);
break;
}
}
/// <summary>
/// Handles a new order placed manually in the brokerage side
/// </summary>
/// <param name="eventArgs">The new order event</param>
/// <returns>Whether the order should be added to the transaction handler</returns>
public virtual bool HandleOrder(NewBrokerageOrderNotificationEventArgs eventArgs)
{
if (!_outsideLeanOrderWarningEmitted)
{
_outsideLeanOrderWarningEmitted = true;
_algorithm.Error(Messages.DefaultBrokerageMessageHandler.IgnoreUnrecognizedOrder(eventArgs.Order.BrokerId.FirstOrDefault()));
}
return false;
}
private void StartCheckReconnected(TimeSpan delay, BrokerageMessageEvent message)
{
_cancellationTokenSource.DisposeSafely();
_cancellationTokenSource = new CancellationTokenSource(delay);
Task.Run(() =>
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
Thread.Sleep(TimeSpan.FromMinutes(1));
}
CheckReconnected(message);
}, _cancellationTokenSource.Token);
}
private void CheckReconnected(BrokerageMessageEvent message)
{
if (!_connected)
{
Log.Error(Messages.DefaultBrokerageMessageHandler.StillDisconnected);
_algorithm.SetRuntimeError(new Exception(message.Message),
Messages.DefaultBrokerageMessageHandler.BrokerageDisconnectedShutDownContext);
}
}
}
}
+407
View File
@@ -0,0 +1,407 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using QuantConnect.Benchmarks;
using QuantConnect.Data.Market;
using QuantConnect.Data.Shortable;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities;
using QuantConnect.Securities.CryptoFuture;
using QuantConnect.Securities.Equity;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.Option;
using QuantConnect.Util;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides a default implementation of <see cref="IBrokerageModel"/> that allows all orders and uses
/// the default transaction models
/// </summary>
public class DefaultBrokerageModel : IBrokerageModel
{
/// <summary>
/// The default markets for the backtesting brokerage
/// </summary>
public static readonly IReadOnlyDictionary<SecurityType, string> DefaultMarketMap = new Dictionary<SecurityType, string>
{
{SecurityType.Base, Market.USA},
{SecurityType.Equity, Market.USA},
{SecurityType.Option, Market.USA},
{SecurityType.Future, Market.CME},
{SecurityType.FutureOption, Market.CME},
{SecurityType.Forex, Market.Oanda},
{SecurityType.Cfd, Market.Oanda},
{SecurityType.Crypto, Market.Coinbase},
{SecurityType.CryptoFuture, Market.Binance},
{SecurityType.Index, Market.USA},
{SecurityType.IndexOption, Market.USA}
}.ToReadOnlyDictionary();
/// <summary>
/// Gets or sets the account type used by this model
/// </summary>
public virtual AccountType AccountType
{
get;
private set;
}
/// <summary>
/// Gets the brokerages model percentage factor used to determine the required unused buying power for the account.
/// From 1 to 0. Example: 0 means no unused buying power is required. 0.5 means 50% of the buying power should be left unused.
/// </summary>
public virtual decimal RequiredFreeBuyingPowerPercent => 0m;
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public virtual IReadOnlyDictionary<SecurityType, string> DefaultMarkets
{
get { return DefaultMarketMap; }
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modelled, defaults to
/// <see cref="QuantConnect.AccountType.Margin"/></param>
public DefaultBrokerageModel(AccountType accountType = AccountType.Margin)
{
AccountType = accountType;
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security being ordered</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public virtual bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
if ((security.Type == SecurityType.Future || security.Type == SecurityType.FutureOption) && order.Type == OrderType.MarketOnOpen)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedMarketOnOpenOrdersForFuturesAndFutureOptions);
return false;
}
message = null;
return true;
}
/// <summary>
/// Returns true if the brokerage would allow updating the order as specified by the request
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
public virtual bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = null;
return true;
}
/// <summary>
/// Returns true if the brokerage would be able to execute this order at this time assuming
/// market prices are sufficient for the fill to take place. This is used to emulate the
/// brokerage fills in backtesting and paper trading. For example some brokerages may not perform
/// executions during extended market hours. This is not intended to be checking whether or not
/// the exchange is open, that is handled in the Security.Exchange property.
/// </summary>
/// <param name="security">The security being traded</param>
/// <param name="order">The order to test for execution</param>
/// <returns>True if the brokerage would be able to perform the execution, false otherwise</returns>
public virtual bool CanExecuteOrder(Security security, Order order)
{
return true;
}
/// <summary>
/// Applies the split to the specified order ticket
/// </summary>
/// <remarks>
/// This default implementation will update the orders to maintain a similar market value
/// </remarks>
/// <param name="tickets">The open tickets matching the split event</param>
/// <param name="split">The split event data</param>
public virtual void ApplySplit(List<OrderTicket> tickets, Split split)
{
// by default we'll just update the orders to have the same notional value
var splitFactor = split.SplitFactor;
tickets.ForEach(ticket => ticket.Update(new UpdateOrderFields
{
Quantity = (int?) (ticket.Quantity/splitFactor),
LimitPrice = ticket.OrderType.IsLimitOrder() ? ticket.Get(OrderField.LimitPrice)*splitFactor : (decimal?) null,
StopPrice = ticket.OrderType.IsStopOrder() ? ticket.Get(OrderField.StopPrice)*splitFactor : (decimal?) null,
TriggerPrice = ticket.OrderType == OrderType.LimitIfTouched ? ticket.Get(OrderField.TriggerPrice) * splitFactor : (decimal?) null,
TrailingAmount = ticket.OrderType == OrderType.TrailingStop && !ticket.Get<bool>(OrderField.TrailingAsPercentage) ? ticket.Get(OrderField.TrailingAmount) * splitFactor : (decimal?) null
}));
}
/// <summary>
/// Gets the brokerage's leverage for the specified security
/// </summary>
/// <param name="security">The security's whose leverage we seek</param>
/// <returns>The leverage for the specified security</returns>
public virtual decimal GetLeverage(Security security)
{
if (AccountType == AccountType.Cash)
{
return 1m;
}
switch (security.Type)
{
case SecurityType.CryptoFuture:
return 25m;
case SecurityType.Equity:
return 2m;
case SecurityType.Forex:
case SecurityType.Cfd:
return 50m;
case SecurityType.Crypto:
return 1m;
case SecurityType.Base:
case SecurityType.Commodity:
case SecurityType.Option:
case SecurityType.FutureOption:
case SecurityType.Future:
case SecurityType.Index:
case SecurityType.IndexOption:
default:
return 1m;
}
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public virtual IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("SPY", SecurityType.Equity, Market.USA);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Gets a new fill model that represents this brokerage's fill behavior
/// </summary>
/// <param name="security">The security to get fill model for</param>
/// <returns>The new fill model for this brokerage</returns>
public virtual IFillModel GetFillModel(Security security)
{
switch (security.Type)
{
case SecurityType.Equity:
return new EquityFillModel();
case SecurityType.FutureOption:
return new FutureOptionFillModel();
case SecurityType.Future:
return new FutureFillModel();
case SecurityType.Base:
case SecurityType.Option:
case SecurityType.Commodity:
case SecurityType.Forex:
case SecurityType.Cfd:
case SecurityType.Crypto:
case SecurityType.CryptoFuture:
case SecurityType.Index:
case SecurityType.IndexOption:
return new ImmediateFillModel();
default:
throw new ArgumentOutOfRangeException(Messages.DefaultBrokerageModel.InvalidSecurityTypeToGetFillModel(this, security));
}
}
/// <summary>
/// Gets a new fee model that represents this brokerage's fee structure
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public virtual IFeeModel GetFeeModel(Security security)
{
switch (security.Type)
{
case SecurityType.Base:
case SecurityType.Forex:
case SecurityType.Cfd:
case SecurityType.Crypto:
case SecurityType.CryptoFuture:
case SecurityType.Index:
return new ConstantFeeModel(0m);
case SecurityType.Equity:
case SecurityType.Option:
case SecurityType.Future:
case SecurityType.FutureOption:
return new InteractiveBrokersFeeModel();
case SecurityType.Commodity:
default:
return new ConstantFeeModel(0m);
}
}
/// <summary>
/// Gets a new slippage model that represents this brokerage's fill slippage behavior
/// </summary>
/// <param name="security">The security to get a slippage model for</param>
/// <returns>The new slippage model for this brokerage</returns>
public virtual ISlippageModel GetSlippageModel(Security security)
{
return NullSlippageModel.Instance;
}
/// <summary>
/// Gets a new settlement model for the security
/// </summary>
/// <param name="security">The security to get a settlement model for</param>
/// <returns>The settlement model for this brokerage</returns>
public virtual ISettlementModel GetSettlementModel(Security security)
{
if (AccountType == AccountType.Cash)
{
switch (security.Type)
{
case SecurityType.Equity:
return new DelayedSettlementModel(Equity.DefaultSettlementDays, Equity.DefaultSettlementTime);
case SecurityType.Option:
return new DelayedSettlementModel(Option.DefaultSettlementDays, Option.DefaultSettlementTime);
}
}
if(security.Symbol.SecurityType == SecurityType.Future)
{
return new FutureSettlementModel();
}
return new ImmediateSettlementModel();
}
/// <summary>
/// Gets a new settlement model for the security
/// </summary>
/// <param name="security">The security to get a settlement model for</param>
/// <param name="accountType">The account type</param>
/// <returns>The settlement model for this brokerage</returns>
[Obsolete("Flagged deprecated and will remove December 1st 2018")]
public ISettlementModel GetSettlementModel(Security security, AccountType accountType)
{
return GetSettlementModel(security);
}
/// <summary>
/// Gets a new buying power model for the security, returning the default model with the security's configured leverage.
/// For cash accounts, leverage = 1 is used.
/// </summary>
/// <param name="security">The security to get a buying power model for</param>
/// <returns>The buying power model for this brokerage/security</returns>
public virtual IBuyingPowerModel GetBuyingPowerModel(Security security)
{
IBuyingPowerModel getCurrencyBuyingPowerModel() =>
AccountType == AccountType.Cash
? new CashBuyingPowerModel()
: new SecurityMarginModel(GetLeverage(security), RequiredFreeBuyingPowerPercent);
return security?.Type switch
{
SecurityType.Crypto => getCurrencyBuyingPowerModel(),
SecurityType.Forex => getCurrencyBuyingPowerModel(),
SecurityType.CryptoFuture => new CryptoFutureMarginModel(GetLeverage(security)),
SecurityType.Future => new FutureMarginModel(RequiredFreeBuyingPowerPercent, security),
SecurityType.FutureOption => new FuturesOptionsMarginModel(RequiredFreeBuyingPowerPercent, (Option)security),
SecurityType.IndexOption => new OptionMarginModel(RequiredFreeBuyingPowerPercent),
SecurityType.Option => new OptionMarginModel(RequiredFreeBuyingPowerPercent),
_ => new SecurityMarginModel(GetLeverage(security), RequiredFreeBuyingPowerPercent)
};
}
/// <summary>
/// Gets the shortable provider
/// </summary>
/// <returns>Shortable provider</returns>
public virtual IShortableProvider GetShortableProvider(Security security)
{
// Shortable provider, responsible for loading the data that indicates how much
// quantity we can short for a given asset. The NullShortableProvider default will
// allow for infinite quantities of any asset to be shorted.
return NullShortableProvider.Instance;
}
/// <summary>
/// Gets a new margin interest rate model for the security
/// </summary>
/// <param name="security">The security to get a margin interest rate model for</param>
/// <returns>The margin interest rate model for this brokerage</returns>
public virtual IMarginInterestRateModel GetMarginInterestRateModel(Security security)
{
return MarginInterestRateModel.Null;
}
/// <summary>
/// Gets a new buying power model for the security
/// </summary>
/// <param name="security">The security to get a buying power model for</param>
/// <param name="accountType">The account type</param>
/// <returns>The buying power model for this brokerage/security</returns>
[Obsolete("Flagged deprecated and will remove December 1st 2018")]
public IBuyingPowerModel GetBuyingPowerModel(Security security, AccountType accountType)
{
return GetBuyingPowerModel(security);
}
/// <summary>
/// Checks if the order quantity is valid, it means, the order size is bigger than the minimum size allowed
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="orderQuantity">The quantity of the order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may be invalid</param>
/// <returns>True if the order quantity is bigger than the minimum allowed, false otherwise</returns>
public static bool IsValidOrderSize(Security security, decimal orderQuantity, out BrokerageMessageEvent message)
{
var minimumOrderSize = security.SymbolProperties.MinimumOrderSize;
if (minimumOrderSize != null && Math.Abs(orderQuantity) < minimumOrderSize)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.InvalidOrderQuantity(security, orderQuantity));
return false;
}
message = null;
return true;
}
}
}
@@ -0,0 +1,47 @@
/*
* 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 QuantConnect.Interfaces;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Event arguments class for the <see cref="IBrokerage.DelistingNotification"/> event
/// </summary>
public class DelistingNotificationEventArgs
{
/// <summary>
/// Gets the option symbol which has received a notification
/// </summary>
public Symbol Symbol { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DelistingNotificationEventArgs"/> class
/// </summary>
/// <param name="symbol">The symbol</param>
public DelistingNotificationEventArgs(Symbol symbol)
{
Symbol = symbol;
}
/// <summary>
/// Returns a string describing the delisting notification.
/// </summary>
public override string ToString()
{
return $"Symbol: {Symbol}";
}
}
}
@@ -0,0 +1,65 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Linq;
using System.Collections.Generic;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides an implementation of <see cref="IBrokerageMessageHandler"/> that converts specified error codes into warnings
/// </summary>
public class DowngradeErrorCodeToWarningBrokerageMessageHandler : IBrokerageMessageHandler
{
private readonly HashSet<string> _errorCodesToIgnore;
private readonly IBrokerageMessageHandler _brokerageMessageHandler;
/// <summary>
/// Initializes a new instance of the <see cref="DowngradeErrorCodeToWarningBrokerageMessageHandler"/> class
/// </summary>
/// <param name="brokerageMessageHandler">The brokerage message handler to be wrapped</param>
/// <param name="errorCodesToIgnore">The error codes to convert to warning messages</param>
public DowngradeErrorCodeToWarningBrokerageMessageHandler(IBrokerageMessageHandler brokerageMessageHandler, string[] errorCodesToIgnore)
{
_brokerageMessageHandler = brokerageMessageHandler;
_errorCodesToIgnore = errorCodesToIgnore.ToHashSet();
}
/// <summary>
/// Handles the message
/// </summary>
/// <param name="message">The message to be handled</param>
public void HandleMessage(BrokerageMessageEvent message)
{
if (message.Type == BrokerageMessageType.Error && _errorCodesToIgnore.Contains(message.Code))
{
// rewrite the ignored message as a warning message
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, message.Code, message.Message);
}
_brokerageMessageHandler.HandleMessage(message);
}
/// <summary>
/// Handles a new order placed manually in the brokerage side
/// </summary>
/// <param name="eventArgs">The new order event</param>
/// <returns>Whether the order should be added to the transaction handler</returns>
public bool HandleOrder(NewBrokerageOrderNotificationEventArgs eventArgs)
{
return _brokerageMessageHandler.HandleOrder(eventArgs);
}
}
}
+122
View File
@@ -0,0 +1,122 @@
/*
* 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 QuantConnect.Benchmarks;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using static QuantConnect.Util.SecurityExtensions;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Exante Brokerage Model Implementation for Back Testing.
/// </summary>
public class ExanteBrokerageModel : DefaultBrokerageModel
{
private const decimal EquityLeverage = 1.2m;
/// <summary>
/// Constructor for Exante brokerage model
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public ExanteBrokerageModel(AccountType accountType = AccountType.Cash)
: base(accountType)
{
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("SPY", SecurityType.Equity, Market.USA);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security being ordered</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
message = null;
if (order == null)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", Messages.ExanteBrokerageModel.NullOrder);
return false;
}
if (order.Price == 0m)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", Messages.ExanteBrokerageModel.PriceNotSet);
return false;
}
if (security.Type != SecurityType.Forex &&
security.Type != SecurityType.Equity &&
security.Type != SecurityType.Index &&
security.Type != SecurityType.Option &&
security.Type != SecurityType.Future &&
security.Type != SecurityType.Cfd &&
security.Type != SecurityType.Crypto &&
security.Type != SecurityType.Index)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
return true;
}
/// <summary>
/// Gets a new fee model that represents this brokerage's fee structure
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security) => new ExanteFeeModel();
/// <summary>
/// Exante global leverage rule
/// </summary>
/// <param name="security">The security's whose leverage we seek</param>
/// <returns>The leverage for the specified security</returns>
public override decimal GetLeverage(Security security)
{
if (AccountType == AccountType.Cash || security.IsInternalFeed() || security.Type == SecurityType.Base)
{
return 1m;
}
return security.Type switch
{
SecurityType.Forex => 1.05m,
SecurityType.Equity => EquityLeverage,
_ => 1.0m,
};
}
}
}
+142
View File
@@ -0,0 +1,142 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2023 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 QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides Eze specific properties
/// </summary>
public class EzeBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// Array's Eze supports security types
/// </summary>
private readonly HashSet<SecurityType> _supportSecurityTypes = new(
new[]
{
SecurityType.Equity,
SecurityType.Option,
SecurityType.Future,
SecurityType.FutureOption,
SecurityType.Index,
SecurityType.IndexOption
});
/// <summary>
/// Array's Eze supports order types
/// </summary>
private readonly HashSet<OrderType> _supportOrderTypes = new(
new[]
{
OrderType.Market,
OrderType.Limit,
OrderType.StopMarket,
OrderType.StopLimit,
OrderType.MarketOnOpen,
OrderType.MarketOnClose,
});
/// <summary>
/// Constructor for Eze brokerage model
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public EzeBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType)
{
if (accountType == AccountType.Cash)
{
throw new NotSupportedException($"Eze brokerage can only be used with a {AccountType.Margin} account type");
}
}
/// <summary>
/// Provides Eze fee model
/// </summary>
/// <param name="security">Security</param>
/// <returns>Eze Fee model</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new EzeFeeModel();
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">>If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
if (!_supportSecurityTypes.Contains(security.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
if (!_supportOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportOrderTypes));
return false;
}
if (order.AbsoluteQuantity % 1 != 0)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
$"Order Quantity must be Integer, but provided {order.AbsoluteQuantity}.");
return false;
}
return base.CanSubmitOrder(security, order, out message);
}
/// <summary>
/// Returns true if the brokerage could accept this order update. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage could update the order, false otherwise</returns>
/// <remarks>
/// The Eze supports update:
/// - quantity <see cref="Order.Quantity"/>
/// - LimitPrice <see cref="LimitOrder.LimitPrice"/>
/// - StopPrice <see cref="StopLimitOrder.StopPrice"/>
/// - OrderType <seealso cref="OrderType"/>
/// - Time In Force <see cref="Order.TimeInForce"/>
/// </remarks>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = null;
return true;
}
}
}
+199
View File
@@ -0,0 +1,199 @@
/*
* 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 QuantConnect.Benchmarks;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Util;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Brokerages
{
/// <summary>
/// FTX Brokerage model
/// </summary>
public class FTXBrokerageModel : DefaultBrokerageModel
{
private readonly HashSet<OrderType> _supportedOrderTypes = new HashSet<OrderType>
{
OrderType.Market,
OrderType.Limit,
OrderType.StopMarket,
OrderType.StopLimit
};
private const decimal _defaultLeverage = 3m;
/// <summary>
/// market name
/// </summary>
protected virtual string MarketName => Market.FTX;
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets(Market.FTX);
/// <summary>
/// Creates an instance of <see cref="FTXBrokerageModel"/> class
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public FTXBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType)
{
}
/// <summary>
/// Gets the brokerage's leverage for the specified security
/// </summary>
/// <param name="security">The security's whose leverage we seek</param>
/// <returns>The leverage for the specified security</returns>
public override decimal GetLeverage(Security security)
{
if (AccountType == AccountType.Cash)
{
return 1m;
}
return _defaultLeverage;
}
/// <summary>
/// Provides FTX fee model
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
=> new FTXFeeModel();
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("BTCUSD", SecurityType.Crypto, MarketName);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
if (!IsValidOrderSize(security, order.Quantity, out message))
{
return false;
}
message = null;
// validate order type
if (!_supportedOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
return false;
}
if (order.Type is OrderType.StopMarket or OrderType.StopLimit)
{
if (!security.HasData)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.NoDataForSymbol);
return false;
}
var stopPrice = (order as StopMarketOrder)?.StopPrice;
if (!stopPrice.HasValue)
{
stopPrice = (order as StopLimitOrder)?.StopPrice;
}
switch (order.Direction)
{
case OrderDirection.Sell:
if (stopPrice > security.BidPrice)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.FTXBrokerageModel.TriggerPriceTooHigh);
}
break;
case OrderDirection.Buy:
if (stopPrice < security.AskPrice)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.FTXBrokerageModel.TriggerPriceTooLow);
}
break;
}
if (message != null)
{
return false;
}
}
if (security.Type != SecurityType.Crypto)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
return base.CanSubmitOrder(security, order, out message);
}
/// <summary>
/// Please note that the order's queue priority will be reset, and the order ID of the modified order will be different from that of the original order.
/// Also note: this is implemented as cancelling and replacing your order.
/// There's a chance that the order meant to be cancelled gets filled and its replacement still gets placed.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, 0, Messages.DefaultBrokerageModel.OrderUpdateNotSupported);
return false;
}
/// <summary>
/// Returns a readonly dictionary of FTX default markets
/// </summary>
protected static IReadOnlyDictionary<SecurityType, string> GetDefaultMarkets(string market)
{
var map = DefaultMarketMap.ToDictionary();
map[SecurityType.Crypto] = market;
return map.ToReadOnlyDictionary();
}
}
}
+53
View File
@@ -0,0 +1,53 @@
/*
* 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.Collections.Generic;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
namespace QuantConnect.Brokerages
{
/// <summary>
/// FTX.US Brokerage model
/// </summary>
public class FTXUSBrokerageModel : FTXBrokerageModel
{
/// <summary>
/// Market name
/// </summary>
protected override string MarketName => Market.FTXUS;
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets(Market.FTXUS);
/// <summary>
/// Creates an instance of <see cref="FTXUSBrokerageModel"/> class
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public FTXUSBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType)
{
}
/// <summary>
/// Provides FTX.US fee model
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
=> new FTXUSFeeModel();
}
}
+246
View File
@@ -0,0 +1,246 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using QuantConnect.Benchmarks;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides FXCM specific properties
/// </summary>
public class FxcmBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// The default markets for the fxcm brokerage
/// </summary>
public new static readonly IReadOnlyDictionary<SecurityType, string> DefaultMarketMap = new Dictionary<SecurityType, string>
{
{SecurityType.Base, Market.USA},
{SecurityType.Equity, Market.USA},
{SecurityType.Option, Market.USA},
{SecurityType.Forex, Market.FXCM},
{SecurityType.Cfd, Market.FXCM}
}.ToReadOnlyDictionary();
private readonly HashSet<OrderType> _supportedOrderTypes = new()
{
OrderType.Limit,
OrderType.Market,
OrderType.StopMarket
};
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets => DefaultMarketMap;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modelled, defaults to
/// <see cref="AccountType.Margin"/></param>
public FxcmBrokerageModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security"></param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
message = null;
// validate security type
if (security.Type != SecurityType.Forex && security.Type != SecurityType.Cfd)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
// validate order type
if (!_supportedOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
return false;
}
// validate order quantity
if (order.Quantity % security.SymbolProperties.LotSize != 0)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.FxcmBrokerageModel.InvalidOrderQuantityForLotSize(security));
return false;
}
// validate stop/limit orders prices
var limit = order as LimitOrder;
if (limit != null)
{
return IsValidOrderPrices(security, OrderType.Limit, limit.Direction, security.Price, limit.LimitPrice, ref message);
}
var stopMarket = order as StopMarketOrder;
if (stopMarket != null)
{
return IsValidOrderPrices(security, OrderType.StopMarket, stopMarket.Direction, stopMarket.StopPrice, security.Price, ref message);
}
// validate time in force
if (order.TimeInForce != TimeInForce.GoodTilCanceled)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedTimeInForce(this, order));
return false;
}
return true;
}
/// <summary>
/// Returns true if the brokerage would allow updating the order as specified by the request
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = null;
// validate order quantity
if (request.Quantity != null && request.Quantity % security.SymbolProperties.LotSize != 0)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.FxcmBrokerageModel.InvalidOrderQuantityForLotSize(security));
return false;
}
// determine direction via the new, updated quantity
var newQuantity = request.Quantity ?? order.Quantity;
var direction = newQuantity > 0 ? OrderDirection.Buy : OrderDirection.Sell;
// use security.Price if null, allows to pass checks
var stopPrice = request.StopPrice ?? security.Price;
var limitPrice = request.LimitPrice ?? security.Price;
return IsValidOrderPrices(security, order.Type, direction, stopPrice, limitPrice, ref message);
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Gets a new fee model that represents this brokerage's fee structure
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new FxcmFeeModel();
}
/// <summary>
/// Gets a new settlement model for the security
/// </summary>
/// <param name="security">The security to get a settlement model for</param>
/// <returns>The settlement model for this brokerage</returns>
public override ISettlementModel GetSettlementModel(Security security)
{
return security.Type == SecurityType.Cfd
? new AccountCurrencyImmediateSettlementModel() :
(ISettlementModel)new ImmediateSettlementModel();
}
/// <summary>
/// Validates limit/stopmarket order prices, pass security.Price for limit/stop if n/a
/// </summary>
private static bool IsValidOrderPrices(Security security, OrderType orderType, OrderDirection orderDirection, decimal stopPrice, decimal limitPrice, ref BrokerageMessageEvent message)
{
// validate order price
var invalidPrice = orderType == OrderType.Limit && orderDirection == OrderDirection.Buy && limitPrice > security.Price ||
orderType == OrderType.Limit && orderDirection == OrderDirection.Sell && limitPrice < security.Price ||
orderType == OrderType.StopMarket && orderDirection == OrderDirection.Buy && stopPrice < security.Price ||
orderType == OrderType.StopMarket && orderDirection == OrderDirection.Sell && stopPrice > security.Price;
if (invalidPrice)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.FxcmBrokerageModel.InvalidOrderPrice);
return false;
}
// Validate FXCM maximum distance for limit and stop orders:
// there are two different Max Limits, 15000 pips and 50% rule,
// whichever comes first (for most pairs, 50% rule comes first)
var maxDistance = Math.Min(
// MinimumPriceVariation is 1/10th of a pip
security.SymbolProperties.MinimumPriceVariation * 10 * 15000,
security.Price / 2);
var currentPrice = security.Price;
var minPrice = currentPrice - maxDistance;
var maxPrice = currentPrice + maxDistance;
var outOfRangePrice = orderType == OrderType.Limit && orderDirection == OrderDirection.Buy && limitPrice < minPrice ||
orderType == OrderType.Limit && orderDirection == OrderDirection.Sell && limitPrice > maxPrice ||
orderType == OrderType.StopMarket && orderDirection == OrderDirection.Buy && stopPrice > maxPrice ||
orderType == OrderType.StopMarket && orderDirection == OrderDirection.Sell && stopPrice < minPrice;
if (outOfRangePrice)
{
var orderPrice = orderType == OrderType.Limit ? limitPrice : stopPrice;
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.FxcmBrokerageModel.PriceOutOfRange(orderType, orderDirection, orderPrice, currentPrice));
return false;
}
return true;
}
}
}
+35
View File
@@ -0,0 +1,35 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014-2023 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;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides GDAX specific properties
/// </summary>
[Obsolete("GDAXBrokerageModel is deprecated. Use CoinbaseBrokerageModel instead.")]
public class GDAXBrokerageModel : CoinbaseBrokerageModel
{
/// <summary>
/// Initializes a new instance of the <see cref="GDAXBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modelled, defaults to
/// <see cref="AccountType.Cash"/></param>
public GDAXBrokerageModel(AccountType accountType = AccountType.Cash)
: base(accountType)
{ }
}
}
@@ -0,0 +1,37 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides an plugin point to allow algorithms to directly handle the messages
/// that come from their brokerage
/// </summary>
public interface IBrokerageMessageHandler
{
/// <summary>
/// Handles the message
/// </summary>
/// <param name="message">The message to be handled</param>
void HandleMessage(BrokerageMessageEvent message);
/// <summary>
/// Handles a new order placed manually in the brokerage side
/// </summary>
/// <param name="eventArgs">The new order event</param>
/// <returns>Whether the order should be added to the transaction handler</returns>
bool HandleOrder(NewBrokerageOrderNotificationEventArgs eventArgs);
}
}
+419
View File
@@ -0,0 +1,419 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
using System.Collections.Generic;
using QuantConnect.Benchmarks;
using QuantConnect.Data.Market;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities;
using QuantConnect.Python;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Models brokerage transactions, fees, and order
/// </summary>
public interface IBrokerageModel
{
/// <summary>
/// Gets the account type used by this model
/// </summary>
AccountType AccountType
{
get;
}
/// <summary>
/// Gets the brokerages model percentage factor used to determine the required unused buying power for the account.
/// From 1 to 0. Example: 0 means no unused buying power is required. 0.5 means 50% of the buying power should be left unused.
/// </summary>
decimal RequiredFreeBuyingPowerPercent
{
get;
}
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; }
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security being ordered</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message);
/// <summary>
/// Returns true if the brokerage would allow updating the order as specified by the request
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested updated to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message);
/// <summary>
/// Returns true if the brokerage would be able to execute this order at this time assuming
/// market prices are sufficient for the fill to take place. This is used to emulate the
/// brokerage fills in backtesting and paper trading. For example some brokerages may not perform
/// executions during extended market hours. This is not intended to be checking whether or not
/// the exchange is open, that is handled in the Security.Exchange property.
/// </summary>
/// <param name="security">The security being ordered</param>
/// <param name="order">The order to test for execution</param>
/// <returns>True if the brokerage would be able to perform the execution, false otherwise</returns>
bool CanExecuteOrder(Security security, Order order);
/// <summary>
/// Applies the split to the specified order ticket
/// </summary>
/// <param name="tickets">The open tickets matching the split event</param>
/// <param name="split">The split event data</param>
void ApplySplit(List<OrderTicket> tickets, Split split);
/// <summary>
/// Gets the brokerage's leverage for the specified security
/// </summary>
/// <param name="security">The security's whose leverage we seek</param>
/// <returns>The leverage for the specified security</returns>
decimal GetLeverage(Security security);
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
IBenchmark GetBenchmark(SecurityManager securities);
/// <summary>
/// Gets a new fill model that represents this brokerage's fill behavior
/// </summary>
/// <param name="security">The security to get fill model for</param>
/// <returns>The new fill model for this brokerage</returns>
IFillModel GetFillModel(Security security);
/// <summary>
/// Gets a new fee model that represents this brokerage's fee structure
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
IFeeModel GetFeeModel(Security security);
/// <summary>
/// Gets a new slippage model that represents this brokerage's fill slippage behavior
/// </summary>
/// <param name="security">The security to get a slippage model for</param>
/// <returns>The new slippage model for this brokerage</returns>
ISlippageModel GetSlippageModel(Security security);
/// <summary>
/// Gets a new settlement model for the security
/// </summary>
/// <param name="security">The security to get a settlement model for</param>
/// <returns>The settlement model for this brokerage</returns>
ISettlementModel GetSettlementModel(Security security);
/// <summary>
/// Gets a new margin interest rate model for the security
/// </summary>
/// <param name="security">The security to get a margin interest rate model for</param>
/// <returns>The margin interest rate model for this brokerage</returns>
IMarginInterestRateModel GetMarginInterestRateModel(Security security);
/// <summary>
/// Gets a new settlement model for the security
/// </summary>
/// <param name="security">The security to get a settlement model for</param>
/// <param name="accountType">The account type</param>
/// <returns>The settlement model for this brokerage</returns>
[Obsolete("Flagged deprecated and will remove December 1st 2018")]
ISettlementModel GetSettlementModel(Security security, AccountType accountType);
/// <summary>
/// Gets a new buying power model for the security
/// </summary>
/// <param name="security">The security to get a buying power model for</param>
/// <returns>The buying power model for this brokerage/security</returns>
IBuyingPowerModel GetBuyingPowerModel(Security security);
/// <summary>
/// Gets a new buying power model for the security
/// </summary>
/// <param name="security">The security to get a buying power model for</param>
/// <param name="accountType">The account type</param>
/// <returns>The buying power model for this brokerage/security</returns>
[Obsolete("Flagged deprecated and will remove December 1st 2018")]
IBuyingPowerModel GetBuyingPowerModel(Security security, AccountType accountType);
/// <summary>
/// Gets the shortable provider
/// </summary>
/// <returns>Shortable provider</returns>
IShortableProvider GetShortableProvider(Security security);
}
/// <summary>
/// Provides factory method for creating an <see cref="IBrokerageModel"/> from the <see cref="BrokerageName"/> enum
/// </summary>
public static class BrokerageModel
{
/// <summary>
/// Creates a new <see cref="IBrokerageModel"/> for the specified <see cref="BrokerageName"/>
/// </summary>
/// <param name="orderProvider">The order provider</param>
/// <param name="brokerage">The name of the brokerage</param>
/// <param name="accountType">The account type</param>
/// <returns>The model for the specified brokerage</returns>
public static IBrokerageModel Create(IOrderProvider orderProvider, BrokerageName brokerage, AccountType accountType)
{
switch (brokerage)
{
case BrokerageName.Default:
return new DefaultBrokerageModel(accountType);
case BrokerageName.TerminalLink:
return new TerminalLinkBrokerageModel(accountType);
case BrokerageName.Alpaca:
return new AlpacaBrokerageModel();
case BrokerageName.InteractiveBrokersBrokerage:
return new InteractiveBrokersBrokerageModel(accountType);
case BrokerageName.InteractiveBrokersFix:
return new InteractiveBrokersFixModel(accountType);
case BrokerageName.TradierBrokerage:
return new TradierBrokerageModel(accountType);
case BrokerageName.OandaBrokerage:
return new OandaBrokerageModel(accountType);
case BrokerageName.FxcmBrokerage:
return new FxcmBrokerageModel(accountType);
case BrokerageName.Bitfinex:
return new BitfinexBrokerageModel(accountType);
case BrokerageName.BinanceFutures:
return new BinanceFuturesBrokerageModel(accountType);
case BrokerageName.BinanceCoinFutures:
return new BinanceCoinFuturesBrokerageModel(accountType);
case BrokerageName.Binance:
return new BinanceBrokerageModel(accountType);
case BrokerageName.BinanceUS:
return new BinanceUSBrokerageModel(accountType);
case BrokerageName.GDAX:
return new GDAXBrokerageModel(accountType);
case BrokerageName.Coinbase:
return new CoinbaseBrokerageModel(accountType);
case BrokerageName.AlphaStreams:
return new AlphaStreamsBrokerageModel(accountType);
case BrokerageName.Zerodha:
return new ZerodhaBrokerageModel(accountType);
case BrokerageName.Axos:
return new AxosClearingBrokerageModel(accountType);
case BrokerageName.TradingTechnologies:
return new TradingTechnologiesBrokerageModel(accountType);
case BrokerageName.Samco:
return new SamcoBrokerageModel(accountType);
case BrokerageName.Kraken:
return new KrakenBrokerageModel(accountType);
case BrokerageName.Exante:
return new ExanteBrokerageModel(accountType);
case BrokerageName.FTX:
return new FTXBrokerageModel(accountType);
case BrokerageName.FTXUS:
return new FTXUSBrokerageModel(accountType);
case BrokerageName.Wolverine:
return new WolverineBrokerageModel(accountType);
case BrokerageName.TDAmeritrade:
return new TDAmeritradeBrokerageModel(accountType);
case BrokerageName.RBI:
return new RBIBrokerageModel(accountType);
case BrokerageName.Bybit:
return new BybitBrokerageModel(accountType);
case BrokerageName.Eze:
return new EzeBrokerageModel(accountType);
case BrokerageName.TradeStation:
return new TradeStationBrokerageModel(accountType);
case BrokerageName.CharlesSchwab:
return new CharlesSchwabBrokerageModel(accountType);
case BrokerageName.Tastytrade:
return new TastytradeBrokerageModel(accountType);
case BrokerageName.DYDX:
return new dYdXBrokerageModel(accountType);
case BrokerageName.Webull:
return new WebullBrokerageModel(accountType);
case BrokerageName.Public:
return new PublicBrokerageModel(accountType);
default:
throw new ArgumentOutOfRangeException(nameof(brokerage), brokerage, null);
}
}
/// <summary>
/// Gets the corresponding <see cref="BrokerageName"/> for the specified <see cref="IBrokerageModel"/>
/// </summary>
/// <param name="brokerageModel">The brokerage model</param>
/// <returns>The <see cref="BrokerageName"/> for the specified brokerage model</returns>
public static BrokerageName GetBrokerageName(IBrokerageModel brokerageModel)
{
var model = brokerageModel;
if (brokerageModel is BrokerageModelPythonWrapper)
{
model = (brokerageModel as BrokerageModelPythonWrapper).GetModel();
}
// Case order matters to ensure we get the correct brokerage name from the inheritance chain
switch (model)
{
case AlpacaBrokerageModel:
return BrokerageName.Alpaca;
case InteractiveBrokersBrokerageModel _:
return BrokerageName.InteractiveBrokersBrokerage;
case TradierBrokerageModel _:
return BrokerageName.TradierBrokerage;
case OandaBrokerageModel _:
return BrokerageName.OandaBrokerage;
case FxcmBrokerageModel _:
return BrokerageName.FxcmBrokerage;
case BitfinexBrokerageModel _:
return BrokerageName.Bitfinex;
case BinanceUSBrokerageModel _:
return BrokerageName.BinanceUS;
case BinanceBrokerageModel _:
return BrokerageName.Binance;
case GDAXBrokerageModel _:
return BrokerageName.GDAX;
case CoinbaseBrokerageModel _:
return BrokerageName.Coinbase;
case AlphaStreamsBrokerageModel _:
return BrokerageName.AlphaStreams;
case ZerodhaBrokerageModel _:
return BrokerageName.Zerodha;
case AxosClearingBrokerageModel _:
return BrokerageName.Axos;
case TradingTechnologiesBrokerageModel _:
return BrokerageName.TradingTechnologies;
case SamcoBrokerageModel _:
return BrokerageName.Samco;
case KrakenBrokerageModel _:
return BrokerageName.Kraken;
case ExanteBrokerageModel _:
return BrokerageName.Exante;
case FTXUSBrokerageModel _:
return BrokerageName.FTXUS;
case FTXBrokerageModel _:
return BrokerageName.FTX;
case WolverineBrokerageModel _:
return BrokerageName.Wolverine;
case TDAmeritradeBrokerageModel _:
return BrokerageName.TDAmeritrade;
case RBIBrokerageModel _:
return BrokerageName.RBI;
case BybitBrokerageModel _:
return BrokerageName.Bybit;
case EzeBrokerageModel _:
return BrokerageName.Eze;
case TradeStationBrokerageModel _:
return BrokerageName.TradeStation;
case CharlesSchwabBrokerageModel:
return BrokerageName.CharlesSchwab;
case TastytradeBrokerageModel:
return BrokerageName.Tastytrade;
case WebullBrokerageModel:
return BrokerageName.Webull;
case PublicBrokerageModel:
return BrokerageName.Public;
case DefaultBrokerageModel _:
return BrokerageName.Default;
default:
throw new ArgumentOutOfRangeException(nameof(brokerageModel), brokerageModel, null);
}
}
}
}
@@ -0,0 +1,357 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Util;
using QuantConnect.Benchmarks;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.TimeInForces;
using QuantConnect.Securities;
using QuantConnect.Securities.Forex;
using QuantConnect.Securities.Option;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides properties specific to interactive brokers
/// </summary>
public class InteractiveBrokersBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// Defines the default set of <see cref="SecurityType"/> values that support <see cref="OrderType.MarketOnOpen"/> orders.
/// </summary>
private static readonly IReadOnlySet<SecurityType> _defaultMarketOnOpenSupportedSecurityTypes = new HashSet<SecurityType>
{
SecurityType.Cfd,
SecurityType.Equity,
SecurityType.Option,
SecurityType.FutureOption,
SecurityType.IndexOption
};
/// <summary>
/// The default markets for the IB brokerage
/// </summary>
public new static readonly IReadOnlyDictionary<SecurityType, string> DefaultMarketMap = new Dictionary<SecurityType, string>
{
{SecurityType.Base, Market.USA},
{SecurityType.Equity, Market.USA},
{SecurityType.Index, Market.USA},
{SecurityType.Option, Market.USA},
{SecurityType.IndexOption, Market.USA},
{SecurityType.Future, Market.CME},
{SecurityType.FutureOption, Market.CME},
{SecurityType.Forex, Market.Oanda},
{SecurityType.Cfd, Market.InteractiveBrokers}
}.ToReadOnlyDictionary();
/// <summary>
/// Supported time in force
/// </summary>
protected virtual Type[] SupportedTimeInForces { get; } =
{
typeof(GoodTilCanceledTimeInForce),
typeof(DayTimeInForce),
typeof(GoodTilDateTimeInForce)
};
/// <summary>
/// Supported order types
/// </summary>
protected virtual HashSet<OrderType> SupportedOrderTypes { get; } = new HashSet<OrderType>
{
OrderType.Market,
OrderType.MarketOnOpen,
OrderType.MarketOnClose,
OrderType.Limit,
OrderType.StopMarket,
OrderType.StopLimit,
OrderType.TrailingStop,
OrderType.LimitIfTouched,
OrderType.ComboMarket,
OrderType.ComboLimit,
OrderType.ComboLegLimit,
OrderType.OptionExercise
};
/// <summary>
/// Initializes a new instance of the <see cref="InteractiveBrokersBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modelled, defaults to
/// <see cref="AccountType.Margin"/></param>
public InteractiveBrokersBrokerageModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
}
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets => DefaultMarketMap;
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
// Equivalent to no benchmark
return new FuncBenchmark(x => 0);
}
/// <summary>
/// Gets a new fee model that represents this brokerage's fee structure
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new InteractiveBrokersFeeModel();
}
/// <summary>
/// Gets the brokerage's leverage for the specified security
/// </summary>
/// <param name="security">The security's whose leverage we seek</param>
/// <returns>The leverage for the specified security</returns>
public override decimal GetLeverage(Security security)
{
if (AccountType == AccountType.Cash)
{
return 1m;
}
return security.Type == SecurityType.Cfd ? 10m : base.GetLeverage(security);
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security being ordered</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
message = null;
// validate order type
if (!SupportedOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, SupportedOrderTypes));
return false;
}
else if (order.Type == OrderType.MarketOnClose && security.Type != SecurityType.Future && security.Type != SecurityType.Equity && security.Type != SecurityType.Cfd)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, $"Unsupported order type for {security.Type} security type",
"InteractiveBrokers does not support Market-on-Close orders for other security types different than Future and Equity.");
return false;
}
else if (!BrokerageExtensions.ValidateMarketOnOpenOrder(security, order, GetMarketOnOpenAllowedWindow, _defaultMarketOnOpenSupportedSecurityTypes, out message))
{
return false;
}
if (order.Type == OrderType.ComboLegLimit && order.GroupOrderManager?.Count >= 4)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.InteractiveBrokersBrokerageModel.UnsupportedFourLegComboLegLimitOrders(this));
return false;
}
// validate security type
if (security.Type != SecurityType.Equity &&
security.Type != SecurityType.Forex &&
security.Type != SecurityType.Option &&
security.Type != SecurityType.Future &&
security.Type != SecurityType.FutureOption &&
security.Type != SecurityType.Index &&
security.Type != SecurityType.IndexOption &&
security.Type != SecurityType.Cfd)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
// validate order quantity
//https://www.interactivebrokers.com/en/?f=%2Fen%2Ftrading%2FforexOrderSize.php
if (security.Type == SecurityType.Forex &&
!IsForexWithinOrderSizeLimits(order.Symbol.Value, order.Quantity, out message))
{
return false;
}
// validate time in force
if (!SupportedTimeInForces.Contains(order.TimeInForce.GetType()))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedTimeInForce(this, order));
return false;
}
// IB doesn't support index options and cash-settled options exercise
if (order.Type == OrderType.OptionExercise &&
(security.Type == SecurityType.IndexOption ||
(security.Type == SecurityType.Option && (security as Option).ExerciseSettlement == SettlementType.Cash)))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.InteractiveBrokersBrokerageModel.UnsupportedExerciseForIndexAndCashSettledOptions(this, order));
return false;
}
return true;
}
/// <summary>
/// Returns true if the brokerage would allow updating the order as specified by the request
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = null;
if (order.SecurityType == SecurityType.Forex && request.Quantity != null)
{
return IsForexWithinOrderSizeLimits(order.Symbol.Value, request.Quantity.Value, out message);
}
return true;
}
/// <summary>
/// Returns true if the brokerage would be able to execute this order at this time assuming
/// market prices are sufficient for the fill to take place. This is used to emulate the
/// brokerage fills in backtesting and paper trading. For example some brokerages may not perform
/// executions during extended market hours. This is not intended to be checking whether or not
/// the exchange is open, that is handled in the Security.Exchange property.
/// </summary>
/// <param name="security"></param>
/// <param name="order">The order to test for execution</param>
/// <returns>True if the brokerage would be able to perform the execution, false otherwise</returns>
public override bool CanExecuteOrder(Security security, Order order)
{
return order.SecurityType != SecurityType.Base;
}
/// <summary>
/// Returns true if the specified order is within IB's order size limits
/// </summary>
private bool IsForexWithinOrderSizeLimits(string currencyPair, decimal quantity, out BrokerageMessageEvent message)
{
/* https://www.interactivebrokers.com/en/trading/forexOrderSize.php
Currency Currency Description Minimum Order Size Maximum Order Size
USD US Dollar 25,000 7,000,000
AUD Australian Dollar 25,000 6,000,000
CAD Canadian Dollar 25,000 6,000,000
CHF Swiss Franc 25,000 6,000,000
CNH China Renminbi (offshore) 150,000 40,000,000
CZK Czech Koruna USD 25,000(1) USD 7,000,000(1)
DKK Danish Krone 150,000 35,000,000
EUR Euro 20,000 6,000,000
GBP British Pound Sterling 20,000 5,000,000
HKD Hong Kong Dollar 200,000 50,000,000
HUF Hungarian Forint USD 25,000(1) USD 7,000,000(1)
ILS Israeli Shekel USD 25,000(1) USD 7,000,000(1)
KRW Korean Won 0 200,000,000
JPY Japanese Yen 2,500,000 550,000,000
MXN Mexican Peso 300,000 70,000,000
NOK Norwegian Krone 150,000 35,000,000
NZD New Zealand Dollar 35,000 8,000,000
PLN Polish Zloty USD 25,000(1) USD 7,000,000(1)
RUB Russian Ruble 750,000 30,000,000
SEK Swedish Krona 175,000 40,000,000
SGD Singapore Dollar 35,000 8,000,000
ZAR South African Rand 350,000 100,000,000
*/
message = null;
// switch on the currency being bought
Forex.DecomposeCurrencyPair(currencyPair, out var baseCurrency, out _);
ForexCurrencyLimits.TryGetValue(baseCurrency, out var limits);
var min = limits?.Item1 ?? 0m;
var max = limits?.Item2 ?? 0m;
var absoluteQuantity = Math.Abs(quantity);
var orderIsWithinForexSizeLimits = ((min == 0 && absoluteQuantity > min) || (min > 0 && absoluteQuantity >= min)) && absoluteQuantity <= max;
if (!orderIsWithinForexSizeLimits)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "OrderSizeLimit",
Messages.InteractiveBrokersBrokerageModel.InvalidForexOrderSize(min, max, baseCurrency));
}
return orderIsWithinForexSizeLimits;
}
// currency -> (min, max)
private static readonly IReadOnlyDictionary<string, Tuple<decimal, decimal>> ForexCurrencyLimits =
new Dictionary<string, Tuple<decimal, decimal>>()
{
{"USD", Tuple.Create(25000m, 7000000m)},
{"AUD", Tuple.Create(25000m, 6000000m)},
{"CAD", Tuple.Create(25000m, 6000000m)},
{"CHF", Tuple.Create(25000m, 6000000m)},
{"CNH", Tuple.Create(150000m, 40000000m)},
{"CZK", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
{"DKK", Tuple.Create(150000m, 35000000m)},
{"EUR", Tuple.Create(20000m, 6000000m)},
{"GBP", Tuple.Create(20000m, 5000000m)},
{"HKD", Tuple.Create(200000m, 50000000m)},
{"HUF", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
{"ILS", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
{"KRW", Tuple.Create(0m, 200000000m)},
{"JPY", Tuple.Create(2500000m, 550000000m)},
{"MXN", Tuple.Create(300000m, 70000000m)},
{"NOK", Tuple.Create(150000m, 35000000m)},
{"NZD", Tuple.Create(35000m, 8000000m)},
{"PLN", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
{"RUB", Tuple.Create(750000m, 30000000m)},
{"SEK", Tuple.Create(175000m, 40000000m)},
{"SGD", Tuple.Create(35000m, 8000000m)},
{"ZAR", Tuple.Create(350000m, 100000000m)}
};
/// <summary>
/// Returns the allowed Market-on-Open submission window for a <see cref="MarketHoursSegment"/>.
/// </summary>
/// <param name="marketHours">The market hours segment for the security.</param>
/// <returns>
/// A tuple with <c>MarketOnOpenWindowStart</c> and <c>MarketOnOpenWindowEnd</c>,
/// adjusted to avoid IB order rejections at exact market boundaries.
/// </returns>
private (TimeOnly MarketOnOpenWindowStart, TimeOnly MarketOnOpenWindowEnd) GetMarketOnOpenAllowedWindow(MarketHoursSegment marketHours)
{
return (TimeOnly.FromTimeSpan(marketHours.End), TimeOnly.FromTimeSpan(marketHours.Start.Add(-TimeSpan.FromMinutes(2))));
}
}
}
@@ -0,0 +1,99 @@
/*
* 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.Linq;
using QuantConnect.Orders;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Orders.TimeInForces;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Provides properties specific to interactive brokers
/// </summary>
public class InteractiveBrokersFixModel : InteractiveBrokersBrokerageModel
{
/// <summary>
/// Supported time in force
/// </summary>
protected override Type[] SupportedTimeInForces { get; } =
{
typeof(GoodTilCanceledTimeInForce),
typeof(DayTimeInForce),
};
/// <summary>
/// Supported order types
/// </summary>
protected override HashSet<OrderType> SupportedOrderTypes { get; } = new HashSet<OrderType>
{
OrderType.Market,
OrderType.MarketOnOpen,
OrderType.MarketOnClose,
OrderType.Limit,
OrderType.StopMarket,
OrderType.StopLimit,
OrderType.TrailingStop,
OrderType.ComboMarket,
OrderType.ComboLimit
};
private readonly GroupOrderCacheManager _groupOrderCacheManager = new();
/// <summary>
/// Initializes a new instance of the <see cref="InteractiveBrokersFixModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modelled, defaults to
/// <see cref="AccountType.Margin"/></param>
public InteractiveBrokersFixModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security being ordered</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
// only check supported combo order types
if (order is ComboOrder && order.GroupOrderManager != null && SupportedOrderTypes.Contains(order.Type))
{
if (_groupOrderCacheManager.TryGetGroupCachedOrders(order, out var orders))
{
// reject combos that mix FutureOption and Future legs
if (orders.Any(o => o.SecurityType == SecurityType.FutureOption) &&
orders.Any(o => o.SecurityType == SecurityType.Future))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.InteractiveBrokersFixModel.UnsupportedFopFutureComboOrders(this, order));
return false;
}
}
}
return base.CanSubmitOrder(security, order, out message);
}
}
}
+201
View File
@@ -0,0 +1,201 @@
/*
* 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.Collections.Generic;
using System.Linq;
using QuantConnect.Benchmarks;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Kraken Brokerage model
/// </summary>
public class KrakenBrokerageModel : DefaultBrokerageModel
{
private readonly List<string> _fiatsAvailableMargin = new() {"USD", "EUR"};
private readonly List<string> _onlyFiatsAvailableMargin = new() {"BTC", "USDT", "USDC"};
private readonly List<string> _ethAvailableMargin = new() {"REP", "XTZ", "ADA", "EOS", "TRX", "LINK" };
private readonly HashSet<OrderType> _supportedOrderTypes = new()
{
OrderType.Limit,
OrderType.Market,
OrderType.StopMarket,
OrderType.StopLimit,
OrderType.LimitIfTouched
};
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } = GetDefaultMarkets();
/// <summary>
/// Leverage map of different coins
/// </summary>
public IReadOnlyDictionary<string, decimal> CoinLeverage { get; } = new Dictionary<string, decimal>
{
{"BTC", 5}, // only with fiats
{"ETH", 5},
{"USDT", 2}, // only with fiats
{"XMR", 2},
{"REP", 2}, // eth available
{"XRP", 3},
{"BCH", 2},
{"XTZ", 2}, // eth available
{"LTC", 3},
{"ADA", 3}, // eth available
{"EOS", 3}, // eth available
{"DASH", 3},
{"TRX", 3}, // eth available
{"LINK", 3}, // eth available
{"USDC", 3}, // only with fiats
};
/// <summary>
/// Constructor for Kraken brokerage model
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public KrakenBrokerageModel(AccountType accountType = AccountType.Cash) : base(accountType)
{
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
if (!IsValidOrderSize(security, order.Quantity, out message))
{
return false;
}
message = null;
if (security.Type != SecurityType.Crypto)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
if (!_supportedOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
return false;
}
return base.CanSubmitOrder(security, order, out message);
}
/// <summary>
/// Kraken does not support update of orders
/// </summary>
/// <param name="security">Security</param>
/// <param name="order">Order that should be updated</param>
/// <param name="request">Update request</param>
/// <param name="message">Outgoing message</param>
/// <returns>Always false as Kraken does not support update of orders</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, 0, Messages.DefaultBrokerageModel.OrderUpdateNotSupported);
return false;
}
/// <summary>
/// Provides Kraken fee model
/// </summary>
/// <param name="security">Security</param>
/// <returns>Kraken fee model</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new KrakenFeeModel();
}
/// <summary>
/// Kraken global leverage rule
/// </summary>
/// <param name="security"></param>
/// <returns></returns>
public override decimal GetLeverage(Security security)
{
if (AccountType == AccountType.Cash)
{
return 1m;
}
// first check whether this security support margin only with fiats.
foreach (var coin in _onlyFiatsAvailableMargin.Where(coin => security.Symbol.ID.Symbol.StartsWith(coin)).Where(coin => _fiatsAvailableMargin.Any(rightFiat => security.Symbol.Value.EndsWith(rightFiat))))
{
return CoinLeverage[coin];
}
List<string> extendedCoinArray = new() {"BTC", "ETH"};
extendedCoinArray.AddRange(_fiatsAvailableMargin);
// Then check whether this security support margin with ETH.
foreach (var coin in _ethAvailableMargin.Where(coin => security.Symbol.ID.Symbol.StartsWith(coin)).Where(coin => extendedCoinArray.Any(rightFiat => security.Symbol.Value.EndsWith(rightFiat))))
{
return CoinLeverage[coin];
}
extendedCoinArray.Remove("ETH");
// At the end check all others.
foreach (var coin in CoinLeverage.Keys.Where(coin => security.Symbol.ID.Symbol.StartsWith(coin)).Where(coin => extendedCoinArray.Any(rightFiat => security.Symbol.Value.EndsWith(rightFiat))))
{
return CoinLeverage[coin];
}
return 1m;
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Kraken);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Get default markets and specify Kraken as crypto market
/// </summary>
/// <returns>default markets</returns>
private static IReadOnlyDictionary<SecurityType, string> GetDefaultMarkets()
{
var map = DefaultMarketMap.ToDictionary();
map[SecurityType.Crypto] = Market.Kraken;
return map.ToReadOnlyDictionary();
}
}
}
@@ -0,0 +1,47 @@
/*
* 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 QuantConnect.Orders;
using QuantConnect.Interfaces;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Event arguments class for the <see cref="IBrokerage.NewBrokerageOrderNotification"/> event
/// </summary>
public class NewBrokerageOrderNotificationEventArgs
{
/// <summary>
/// The new brokerage side generated order
/// </summary>
public Order Order { get; set; }
/// <summary>
/// Creates a new instance
/// </summary>
public NewBrokerageOrderNotificationEventArgs(Order order)
{
Order = order;
}
/// <summary>
/// Returns a string describing the new brokerage order notification.
/// </summary>
override public string ToString()
{
return Order.ToString();
}
}
}
+149
View File
@@ -0,0 +1,149 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using QuantConnect.Benchmarks;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Oanda Brokerage Model Implementation for Back Testing.
/// </summary>
public class OandaBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// The default markets for the fxcm brokerage
/// </summary>
public new static readonly IReadOnlyDictionary<SecurityType, string> DefaultMarketMap = new Dictionary<SecurityType, string>
{
{SecurityType.Base, Market.USA},
{SecurityType.Equity, Market.USA},
{SecurityType.Option, Market.USA},
{SecurityType.Forex, Market.Oanda},
{SecurityType.Cfd, Market.Oanda}
}.ToReadOnlyDictionary();
private readonly HashSet<OrderType> _supportedOrderTypes = new()
{
OrderType.Limit,
OrderType.Market,
OrderType.StopMarket,
OrderType.StopLimit
};
/// <summary>
/// Gets a map of the default markets to be used for each security type
/// </summary>
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets => DefaultMarketMap;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultBrokerageModel"/> class
/// </summary>
/// <param name="accountType">The type of account to be modelled, defaults to
/// <see cref="AccountType.Margin"/></param>
public OandaBrokerageModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
if (accountType == AccountType.Cash)
{
throw new InvalidOperationException($"Oanda brokerage can only be used with a {AccountType.Margin} account type");
}
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security"></param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
message = null;
// validate security type
if (security.Type != SecurityType.Forex && security.Type != SecurityType.Cfd)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
// validate order type
if (!_supportedOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
return false;
}
// validate time in force
if (order.TimeInForce != TimeInForce.GoodTilCanceled)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedTimeInForce(this, order));
return false;
}
return true;
}
/// <summary>
/// Get the benchmark for this model
/// </summary>
/// <param name="securities">SecurityService to create the security with if needed</param>
/// <returns>The benchmark for this brokerage</returns>
public override IBenchmark GetBenchmark(SecurityManager securities)
{
var symbol = Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda);
return SecurityBenchmark.CreateInstance(securities, symbol);
}
/// <summary>
/// Gets a new fee model that represents this brokerage's fee structure
/// </summary>
/// <param name="security">The security to get a fee model for</param>
/// <returns>The new fee model for this brokerage</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new ConstantFeeModel(0m);
}
/// <summary>
/// Gets a new settlement model for the security
/// </summary>
/// <param name="security">The security to get a settlement model for</param>
/// <returns>The settlement model for this brokerage</returns>
public override ISettlementModel GetSettlementModel(Security security)
{
return security.Type == SecurityType.Cfd
? new AccountCurrencyImmediateSettlementModel() :
(ISettlementModel)new ImmediateSettlementModel();
}
}
}
@@ -0,0 +1,78 @@
/*
* 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 QuantConnect.Interfaces;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Event arguments class for the <see cref="IBrokerage.OptionNotification"/> event
/// </summary>
public sealed class OptionNotificationEventArgs : EventArgs
{
/// <summary>
/// Gets the option symbol which has received a notification
/// </summary>
public Symbol Symbol { get; }
/// <summary>
/// Gets the new option position (positive for long, zero for flat, negative for short)
/// </summary>
public decimal Position { get; }
/// <summary>
/// The tag that will be used in the order
/// </summary>
public string Tag { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionNotificationEventArgs"/> class
/// </summary>
/// <param name="symbol">The symbol</param>
/// <param name="position">The new option position</param>
public OptionNotificationEventArgs(Symbol symbol, decimal position)
{
Symbol = symbol;
Position = position;
}
/// <summary>
/// Initializes a new instance of the <see cref="OptionNotificationEventArgs"/> class
/// </summary>
/// <param name="symbol">The symbol</param>
/// <param name="position">The new option position</param>
/// <param name="tag">The tag to be used for the order</param>
public OptionNotificationEventArgs(Symbol symbol, decimal position, string tag)
: this(symbol, position)
{
Tag = tag;
}
/// <summary>
/// Returns the string representation of this event
/// </summary>
public override string ToString()
{
var str = $"{Symbol} position: {Position}";
if (!string.IsNullOrEmpty(Tag))
{
str += $", tag: {Tag}";
}
return str;
}
}
}
+140
View File
@@ -0,0 +1,140 @@
/*
* 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 QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using System.Collections.Generic;
namespace QuantConnect.Brokerages
{
/// <summary>
/// Represents a brokerage model specific to Public.com.
/// </summary>
public class PublicBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// The security types supported by Public.com.
/// </summary>
private readonly HashSet<SecurityType> _supportSecurityTypes = new(
new[]
{
SecurityType.Equity,
SecurityType.Option,
SecurityType.IndexOption,
SecurityType.Crypto
});
/// <summary>
/// The order types supported by the <see cref="CanSubmitOrder"/> operation in Public.com.
/// Multi-leg combos are limit only.
/// </summary>
private readonly HashSet<OrderType> _supportOrderTypes = new(
new[]
{
OrderType.Market,
OrderType.Limit,
OrderType.StopMarket,
OrderType.StopLimit,
OrderType.ComboLimit
});
/// <summary>
/// Constructor for Public.com brokerage model
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public PublicBrokerageModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
}
/// <summary>
/// Provides the Public.com fee model
/// </summary>
/// <param name="security">Security</param>
/// <returns>Public.com fee model</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new PublicFeeModel();
}
/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account order type, security type.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
{
message = default;
if (!_supportSecurityTypes.Contains(security.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
return false;
}
if (!_supportOrderTypes.Contains(order.Type))
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportOrderTypes));
return false;
}
// Public.com only accepts Limit orders in the extended (outside regular trading hours) session.
if (order.Properties is PublicOrderProperties { OutsideRegularTradingHours: true } && order.Type != OrderType.Limit)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.PublicBrokerageModel.ExtendedMarketOrderMustBeLimit(order));
return false;
}
if (order.Properties is PublicOrderProperties publicOrderProperties)
{
// A cash account has no margin buying power, so margin is always off there.
// On a margin account, keep an explicit choice and otherwise use margin by default.
publicOrderProperties.UseMargin = AccountType != AccountType.Cash && (publicOrderProperties.UseMargin ?? true);
}
// Public.com handles crossing a zero position natively, so the order is not split or rejected here.
return base.CanSubmitOrder(security, order, out message);
}
/// <summary>
/// Returns true if the brokerage would allow updating the order as specified by the request.
/// Public.com has no multi-leg replace endpoint, so combo orders cannot be updated.
/// </summary>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be updated</param>
/// <param name="request">The requested update to be made to the order</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
if (order.GroupOrderManager != null)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
"Public.com does not support updating combo (multi-leg) orders.");
return false;
}
message = null;
return true;
}
}
}

Some files were not shown because too many files have changed in this diff Show More