chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>>()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 && IApi.SendUserEmail
|
||||
/// Error -> IResultHandler.Error && 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user