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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user