| Overall Statistics |
|
Total Trades 106 Average Win 3.70% Average Loss -0.90% Compounding Annual Return 100.687% Drawdown 23.300% Expectancy 1.552 Net Profit 71.602% Sharpe Ratio 1.642 Loss Rate 50% Win Rate 50% Profit-Loss Ratio 4.10 Alpha 0.545 Beta -0.096 Annual Standard Deviation 0.325 Annual Variance 0.106 Information Ratio 1.256 Tracking Error 0.331 Treynor Ratio -5.542 Total Fees $0.00 |
using Newtonsoft.Json;
namespace QuantConnect.Algorithm.CSharp.Algo
{
/// <summary>
/// This is a truncated version of the algorithm made for the Trading Strategies Based on Genetic Algorithms project
/// for the QuantConnect platform.
/// The main difference is that the rules are hard coded in the TradingRule class, instead if being dynamically
/// generated as in the original.
/// </summary>
/// <seealso cref="QuantConnect.Algorithm.QCAlgorithm" />
public partial class TradingStrategiesBasedOnGeneticAlgorithmsQCVersion : QCAlgorithm
{
private int _entryIndicatorSignalCount = 6;
private int _exitIndicatorSignalCount = 6;
private List<TradingRuleQCVersion> _entryradingRule;
private List<TradingRuleQCVersion> _exitTradingRule;
private double accountsize = 10000;
public string[] Symbols;
FxRiskManagment RiskManager;
//[Parameter]
public string accessToken;
//[Parameter]
public string timeZone = "America/New_York";
//[Parameter]
private const decimal _leverage = 50m;
// How much of the total strategy equity can be at risk as maximum in all trades.
private const decimal _maxExposure = 0.8m;
// How much of the total strategy equity can be at risk in a single trade.
private const decimal _maxExposurePerTrade = 0.25m;
// The max strategy equity proportion to put at risk in a single operation.
private decimal _riskPerTrade = 0.01m;
private decimal takeProfit = 0.10m;
private Resolution _resolution = Resolution.Minute;
// Smallest lot
private LotSize _lotSize = LotSize.Nano;
private int ExitSMAFastPeriod = 50;
private int ExitSMASlowPeriod = 200;
private int EntrySMAFastPeriod = 50;
private int EntrySMASlowPeriod = 200;
private int EntryEMAFastPeriod = 50;
private int EntryEMASlowPeriod = 200;
private int EntryMACDFastPeriod = 12;
private int EntryMACDSlowPeriod = 26;
private int EntryMACDSignalPeriod = 9;
private int EntryStochasticPeriod = 14;
private int EntryRSIPeriod = 14;
private int EntryCCIPeriod = 14;
private int EntryMomentumPeriod = 60;
private int EntryWILRPeriod = 14;
private int EntryBBPeriod = 20;
//[Parameter]
public bool isLive = false;
public bool verbose = false;
public string[] OandaFXSymbols = {
"AUDCAD","AUDCHF","AUDHKD","AUDJPY","AUDNZD","AUDSGD","AUDUSD","CADCHF","CADHKD","CADJPY",
"CADSGD","CHFHKD","CHFJPY","CHFZAR","EURAUD","EURCAD","EURCHF","EURCZK","EURDKK","EURGBP",
"EURHKD","EURHUF","EURJPY","EURNOK","EURNZD","EURPLN","EURSEK","EURSGD","EURTRY","EURUSD",
"EURZAR","GBPAUD","GBPCAD","GBPCHF","GBPHKD","GBPJPY","GBPNZD","GBPPLN","GBPSGD","GBPUSD",
"GBPZAR","HKDJPY","NZDCAD","NZDCHF","NZDHKD","NZDJPY","NZDSGD","NZDUSD","SGDCHF","SGDHKD",
"SGDJPY","TRYJPY","USDCAD","USDCHF","USDCNH","USDCZK","USDDKK","USDHKD","USDHUF","USDINR",
"USDJPY","USDMXN","USDNOK","USDPLN","USDSAR","USDSEK","USDSGD","USDTHB","USDTRY","USDZAR",
"ZARJPY" };
public string[] OandaFXMajors2 = {
"AUDJPY","AUDUSD","EURAUD","EURCHF","EURGBP","EURJPY","EURUSD","GBPCHF","GBPJPY","GBPUSD",
"NZDUSD","USDCAD","USDCHF","USDJPY" };
public string[] OandaFXMajors1 = {
"EURCHF","EURGBP","EURJPY","EURUSD","GBPCHF","GBPJPY","GBPUSD","USDCHF","USDJPY" };
public string[] OandaFXMajors = {
"EURCHF","EURGBP","EURJPY","EURUSD" };
public string[] OandaFXMajors0 = {
"EURUSD" };
/// <summary>
/// Here are the parameters of the individual with the best in-sample fitness.
/// </summary>
private readonly Dictionary<string, string> parametersToBacktest = new Dictionary<string, string>
{
{"EntryIndicator1", "8"},
{"EntryIndicator2", "2"},
{"EntryIndicator3", "3"},
{"EntryIndicator4", "7"},
{"EntryIndicator5", "5"},
{"EntryIndicator6", "0"},
{"EntryIndicator1Direction", "1"},//0
{"EntryIndicator2Direction", "1"},//0
{"EntryIndicator3Direction", "1"}, //1
{"EntryIndicator4Direction", "1"}, //1
{"EntryIndicator5Direction", "1"},//0
{"EntryIndicator6Direction", "1"},//0
{"EntryIndicator1Operator", "0"},//0//0
{"EntryIndicator2Operator", "0"},//0//1
{"EntryIndicator3Operator", "0"},//1
{"EntryIndicator4Operator", "0"},//1
{"EntryIndicator5Operator", "0"},//0
{"ExitIndicator1", "4"},
{"ExitIndicator2", "0"},
{"ExitIndicator3", "1"},
{"ExitIndicator4", "2"},
{"ExitIndicator5", "7"},
{"ExitIndicator6", "8"},
{"ExitIndicator1Direction", "0"},//1
{"ExitIndicator2Direction", "0"},
{"ExitIndicator3Direction", "0"},
{"ExitIndicator4Direction", "0"},
{"ExitIndicator5Direction", "0"},//0
{"ExitIndicator6Direction", "0"},//0
{"ExitIndicator1Operator", "1"},//0//1
{"ExitIndicator2Operator", "1"},//0//0
{"ExitIndicator3Operator", "0"},//1
{"ExitIndicator4Operator", "1"},//0//0
{"ExitIndicator5Operator", "1"},//0
{"EntrySMAFastPeriod", "20"},
{"EntrySMASlowPeriod", "200"},
{"ExitSMAFastPeriod", "20"},
{"ExitSMASlowPeriod", "220"},
{"EntryIndicatorSignalCount", "6"},
{"ExitIndicatorSignalCount", "6"}
};
public override void Initialize()
{
SetCash(startingCash: accountsize);
var startDate = GetGeneDateTimeFromKey("startDate", new DateTime(2017, 1, 1));
//SetStartDate(startDate);
SetStartDate(startDate);
SetEndDate(GetGeneDateTimeFromKey("endDate", new DateTime(2017, 10, 10)));
//SetEndDate(startDate.AddMonths(months: 20));
takeProfit = GetGeneDecimalFromKey("takeProfit", 0.05m);
_riskPerTrade = GetGeneDecimalFromKey("riskPerTrade", 0.01m);
_entryIndicatorSignalCount = GetGeneIntFromKey("EntryIndicatorSignalCount", 2);
_exitIndicatorSignalCount = GetGeneIntFromKey("ExitIndicatorSignalCount", 2);
List<string> list = new List<string>();
list.AddRange(OandaFXMajors);
Symbols = list.ToArray();
foreach (var symbol in Symbols)
{
AddSecurity(SecurityType.Forex, symbol, _resolution, Market.Oanda, true, _leverage, false);
SetBrokerageModel(BrokerageName.OandaBrokerage);
}
SetParameters(parametersToBacktest);
SetUpRules();
}
public void OnData(QuoteBars data)
{
/*
foreach (var symbol in Symbols)
{
decimal smaVal = sma[symbol].AddSample(data[symbol].Close);
decimal stdVal = std[symbol].AddSample(data[symbol].Close, sma[symbol]);
if(sma[symbol].Ready) {
decimal upband = smaVal + stdVal;
decimal downband = smaVal - stdVal;
}
}
*/
foreach (var entry in _entryradingRule)
{
if (entry.IsReady)
{
EntrySignal(data, entry);
}
}
foreach (var entry in _exitTradingRule)
{
if (entry.IsReady)
{
ExitSignal(entry);
}
}
RiskManager.UpdateTrailingStopOrders(data);
var openStopLossOrders = Portfolio.Transactions.GetOrderTickets(o => o.OrderType == OrderType.StopMarket && o.Status == OrderStatus.Submitted);
if (verbose)
{
foreach (var ticket in openStopLossOrders)
{
Log(String.Format("{0} {1} {2} {3}", ticket.OrderId, ticket.Symbol, ticket.AverageFillPrice, ticket.Quantity));
}
}
}
public void ExitSignal(TradingRuleQCVersion signal)
{
if (verbose && signal.TradeRuleSignal)
{
Log(string.Format("signal symbol:: {0}", signal.Symbol));
}
if (Portfolio[signal.Symbol].Invested && signal.TradeRuleSignal)
{
Liquidate(signal.Symbol);
}
else if (Portfolio[signal.Symbol].Invested && Portfolio[signal.Symbol].UnrealizedProfitPercent > takeProfit)
{
//safeguard profits,
//liquidate half
MarketOrder(signal.Symbol, Portfolio[signal.Symbol].Quantity/2);
}
// Log("MarketOrder:: " +activeSignal.Symbol + " :: "+ signal.meta.direction);
}
public void EntrySignal(QuoteBars data, TradingRuleQCVersion signal)
{
if (verbose && signal.TradeRuleSignal)
{
Log(string.Format("signal symbol:: {0}", signal.Symbol));
}
if (!Portfolio[signal.Symbol].Invested)
{
if (signal.TradeRuleSignal)
{
var openPrice = Securities[signal.Symbol].Price;
int size = (int)_lotSize;
var actualAction = AgentAction.GoLong;
var entryValues = RiskManager.CalculateEntryOrders(data, signal.Symbol, actualAction);
if (entryValues.Item1 != 0)
{
var ticket = MarketOrder(signal.Symbol, entryValues.Item1);
StopMarketOrder(signal.Symbol, -entryValues.Item1, entryValues.Item2, tag: entryValues.Item3.ToString("0.000000"));
if (verbose)
{
Log(string.Format("MarketOrder:: {0} {1}", signal.Symbol, size));
}
}
//MarketOrder(signal.Symbol, size, false, "");
}
}
// Log("MarketOrder:: " +activeSignal.Symbol + " :: "+ signal.meta.direction);
}
/// <summary>
/// Sets up indicator signal. This method is where the Technical indicator rules are defined.
/// </summary>
/// <param name="pair">The pair.</param>
/// <param name="indicatorN">The number if indicator.</param>
/// <param name="ruleAction">
/// The rule action. Should be 'Entry' or 'Exit' and is only used to differentiate the genes for
/// entry and exit
/// </param>
/// <returns></returns>
/// <exception cref="System.NotImplementedException">WIP</exception>
public ITechnicalIndicatorSignal SetUpIndicatorSignal(Symbol pair, int indicatorN, string ruleAction)
{
var oscillatorThresholds = new OscillatorThresholds { Lower = 20, Upper = 80 };
var key = ruleAction + "Indicator" + indicatorN + "Direction";
var intDirection = GetGeneIntFromKey(key);
var direction = (TradeRuleDirection)intDirection;
key = ruleAction + "Indicator" + indicatorN;
var indicatorId = GetGeneIntFromKey(key);
var indicator = (TechicalIndicators)indicatorId;
ITechnicalIndicatorSignal technicalIndicator = null;
switch (indicator)
{
case TechicalIndicators.SimpleMovingAverage:
// Canonical cross moving average parameters.
var fast = SMA(pair, period: GetGeneIntFromKey(ruleAction + "SMAFastPeriod"));
var slow = SMA(pair, period: GetGeneIntFromKey(ruleAction + "SMASlowPeriod"));
technicalIndicator = new CrossingMovingAverages(fast, slow, direction);
break;
case TechicalIndicators.ExponentialMovingAverage:
// Canonical cross moving average parameters.
var fastema = EMA(pair, period: 50);
var slowema = EMA(pair, period: 200);
technicalIndicator = new CrossingMovingAverages(fastema, slowema, direction);
break;
case TechicalIndicators.MovingAverageConvergenceDivergence:
var macd = MACD(pair, fastPeriod: 12, slowPeriod: 26, signalPeriod: 9);
technicalIndicator = new CrossingMovingAverages(macd, macd.Signal, direction);
break;
case TechicalIndicators.Stochastic:
var sto = STO(pair, period: 14);
oscillatorThresholds.Lower = 20;
oscillatorThresholds.Upper = 80;
technicalIndicator = new OscillatorSignal(sto.StochD, oscillatorThresholds, direction, this);
break;
case TechicalIndicators.RelativeStrengthIndex:
var rsi = RSI(pair, period: 14);
oscillatorThresholds.Lower = 30;
oscillatorThresholds.Upper = 70;
technicalIndicator = new OscillatorSignal(rsi, oscillatorThresholds, direction, this);
break;
case TechicalIndicators.CommodityChannelIndex:
var cci = CCI(pair, period: 20);
oscillatorThresholds.Lower = -100;
oscillatorThresholds.Upper = 100;
technicalIndicator = new OscillatorSignal(cci, oscillatorThresholds, direction, this);
break;
case TechicalIndicators.MomentumPercent:
var pm = MOMP(pair, period: 60);
//in percentage, above 5% means bull, lower than -5% bear
oscillatorThresholds.Lower = -5;
oscillatorThresholds.Upper = 5;
technicalIndicator = new OscillatorSignal(pm, oscillatorThresholds, direction, this);
break;
case TechicalIndicators.WilliamsPercentR:
var wr = WILR(pair, period: 14);
oscillatorThresholds.Lower = -80;
oscillatorThresholds.Upper = -20;
technicalIndicator = new OscillatorSignal(wr, oscillatorThresholds, direction, this);
break;
case TechicalIndicators.PercentagePriceOscillator:
var ppo = MACD(pair, fastPeriod: 12, slowPeriod: 26, signalPeriod: 9).Over(EMA(pair, period: 26))
.Plus(constant: 100m);
var signal = new SimpleMovingAverage(period: 9).Of(ppo);
technicalIndicator = new CrossingMovingAverages(ppo, signal, direction);
break;
case TechicalIndicators.BollingerBands:
//Log("setting BB");
var signalb = BB(pair, period: 20, k: 2);
//technicalIndicator = new BolingerBandsIndicator(signalb, this, direction);
technicalIndicator = new BBOscillatorSignal(signalb, direction, this);
//throw new NotImplementedException("WIP");
break;
case TechicalIndicators.ClenowBreakout:
//Set up Indicators:
var _atr = ATR(pair, 100, MovingAverageType.Simple, Resolution.Daily);
var _max = MAX(pair, 50, Resolution.Daily);
var _min = MIN(pair, 50, Resolution.Daily);
var _maxC = MAX(pair, 25, Resolution.Daily);
var _minC = MIN(pair, 25, Resolution.Daily);
//Log("setting BB");
var signalt = BB(pair, period: 20, k: 2);
technicalIndicator = new BolingerBandsIndicator(signalt, this, direction);
//throw new NotImplementedException("WIP");
break;
}
return technicalIndicator;
}
/// <summary>
/// Gets the gene int from key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException">The gene " + key + " is not present either as Config or as Parameter</exception>
/// <remarks>
/// This method makes the algorithm working with the genes defined from the Config (as in the Lean Optimization) and
/// from the Parameters (as the Lean Caller).
/// </remarks>
private int GetGeneIntFromKey(string key)
{
// I'll keep this line as in the original code.
//var gene = Config.GetInt(key, int.MinValue);
var gene = int.MinValue;
if (gene == int.MinValue)//not found in config, then get from parameter
{
try
{
gene = int.Parse(GetParameter(key));
if (verbose)
{
Log(string.Format("Parameter {0} set to {1}", key, gene));
}
}
#pragma warning disable CS0168 // Variable is declared but never used
catch (ArgumentNullException e)
#pragma warning restore CS0168 // Variable is declared but never used
{
throw new ArgumentNullException(key,
"The gene " + key + " is not present either as Config or as Parameter");
}
}
return gene;
}
/// <summary>
/// Gets the gene int from key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException">The gene " + key + " is not present either as Config or as Parameter</exception>
/// <remarks>
/// This method makes the algorithm working with the genes defined from the Config (as in the Lean Optimization) and
/// from the Parameters (as the Lean Caller).
/// </remarks>
private int GetGeneIntFromKey(string key, int def)
{
// I'll keep this line as in the original code.
//var gene = Config.GetInt(key, int.MinValue);
var gene = int.MinValue;
if (gene == int.MinValue)//not found in config, then get from parameter
{
try
{
gene = int.Parse(GetParameter(key));
if (verbose)
{
Log(string.Format("Parameter {0} set to {1}", key, gene));
}
}
#pragma warning disable CS0168 // Variable is declared but never used
catch (ArgumentNullException e)
#pragma warning restore CS0168 // Variable is declared but never used
{
return def;
}
}
return gene;
}
/// <summary>
/// Gets the gene int from key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException">The gene " + key + " is not present either as Config or as Parameter</exception>
/// <remarks>
/// This method makes the algorithm working with the genes defined from the Config (as in the Lean Optimization) and
/// from the Parameters (as the Lean Caller).
/// </remarks>
private decimal GetGeneDecimalFromKey(string key, decimal def)
{
// I'll keep this line as in the original code.
//var gene = Config.GetValue<decimal>(key);
var gene = decimal.MinValue;
if (gene == decimal.MinValue)//not found in config, then get from parameter
{
try
{
gene = decimal.Parse(GetParameter(key));
if (verbose)
{
Log(string.Format("Parameter {0} set to {1}", key, gene));
}
}
#pragma warning disable CS0168 // Variable is declared but never used
catch (ArgumentNullException e)
#pragma warning restore CS0168 // Variable is declared but never used
{
return def;
}
}
return gene;
}
/// <summary>
/// Gets the gene int from key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException">The gene " + key + " is not present either as Config or as Parameter</exception>
/// <remarks>
/// This method makes the algorithm working with the genes defined from the Config (as in the Lean Optimization) and
/// from the Parameters (as the Lean Caller).
/// </remarks>
private DateTime GetGeneDateTimeFromKey(string key, DateTime def)
{
// I'll keep this line as in the original code.
//var gene = Config.GetValue<DateTime>(key, null);
var gene = DateTime.MinValue;
if (gene == DateTime.MinValue)//not found in config, then get from parameter
{
try
{
gene = DateTime.Parse(GetParameter(key));
if (verbose)
{
Log(string.Format("Parameter {0} set to {1}", key, gene));
}
}
#pragma warning disable CS0168 // Variable is declared but never used
catch (ArgumentNullException e)
#pragma warning restore CS0168 // Variable is declared but never used
{
return def;
}
}
return gene;
}
private void SetUpRules()
{
bool[] bools = { true, false };
_entryradingRule = new List<TradingRuleQCVersion>();
_exitTradingRule = new List<TradingRuleQCVersion>();
RiskManager = new FxRiskManagment(Portfolio, _riskPerTrade, _maxExposurePerTrade, _maxExposure, _lotSize);
foreach (var symbol in Symbols)
{
Securities[symbol].VolatilityModel = new ThreeSigmaVolatilityModel(STD(symbol: symbol, period: 12 * 60, resolution: _resolution), 20.0m);
foreach (var isEntryRule in bools)
{
var technicalIndicatorSignals = new List<ITechnicalIndicatorSignal>();
var ruleOperators = new List<RuleOperators>();
var ruleAction = isEntryRule ? "Entry" : "Exit";
var indicatorSignalCount = isEntryRule ? _entryIndicatorSignalCount : _exitIndicatorSignalCount;
for (var i = 1; i <= indicatorSignalCount; i++)
{
var indicatorSignal = SetUpIndicatorSignal(symbol, i, ruleAction);
technicalIndicatorSignals.Add(indicatorSignal);
}
for (var i = 1; i < indicatorSignalCount; i++)
{
var key = ruleAction + "Indicator" + i + "Operator";
ruleOperators.Add((RuleOperators)GetGeneIntFromKey(key));
}
if (isEntryRule)
{
_entryradingRule.Add(new TradingRuleQCVersion(technicalIndicatorSignals.ToArray(), ruleOperators.ToArray(), isEntryRule, symbol));
}
else
{
_exitTradingRule.Add(new TradingRuleQCVersion(technicalIndicatorSignals.ToArray(), ruleOperators.ToArray(), isEntryRule, symbol));
}
}
}
}
}
}namespace QuantConnect
{
/// <summary>
/// Interface used by the <see cref="TradingRule" /> class to flag technical indicator signals as crossing moving
/// averages or oscillators crossing its thresholds.
/// </summary>
public interface ITechnicalIndicatorSignal
{
/// <summary>
/// Gets a value indicating whether this instance is ready.
/// </summary>
/// <value>
/// <c>true</c> if this instance is ready; otherwise, <c>false</c>.
/// </value>
bool IsReady { get; }
/// <summary>
/// Gets the signal. Only used if the instance will be part of a <see cref="TradingRule" /> class.
/// </summary>
/// <returns>
/// <c>true</c> if the actual <see cref="Signal" /> correspond with the instance <see cref="TradeRuleDirection" />.
/// <c>false</c>
/// otherwise.
/// </returns>
bool HasSignal();
ActiveSignal GetSignal();
TradeRuleDirection GetDirection();
}
/// <summary>
/// The <see cref="TradingStrategiesBasedOnGeneticAlgorithms" /> implementation requires a direction for every
/// technical indicator.
/// </summary>
public enum TradeRuleDirection
{
LongOnly = 1,
ShortOnly = 0,
Any = -2
}
/// <summary>
/// List of the technical indicator implemented... well not really, Bollinger bands wasn't implemented.
/// </summary>
public enum TechicalIndicators
{
SimpleMovingAverage = 0,
MovingAverageConvergenceDivergence = 1,
Stochastic = 2,
RelativeStrengthIndex = 3,
CommodityChannelIndex = 4,
MomentumPercent = 5,
WilliamsPercentR = 6,
PercentagePriceOscillator = 7,
BollingerBands = 8,
ExponentialMovingAverage = 9,
ClenowBreakout = 10
}
}namespace QuantConnect
{
/// <summary>
/// Possibles states of two moving averages.
/// </summary>
public enum CrossingMovingAveragesSignals
{
Bullish = 1,
FastCrossSlowFromAbove = -2,
Bearish = -1,
FastCrossSlowFromBelow = 2
}
/// <summary>
/// This class keeps track of two crossing moving averages and updates a <see cref="CrossingMovingAveragesSignals" />
/// for each given state.
/// </summary>
/// <seealso cref="QuantConnect.Algorithm.CSharp.ITechnicalIndicatorSignal" />
public class CrossingMovingAverages : SignalsBase
{
private readonly CompositeIndicator<IndicatorDataPoint> _moving_average_difference;
private readonly TradeRuleDirection _tradeRuleDirection;
private int _lastSignal;
/// <summary>
/// Initializes a new instance of the <see cref="CrossingMovingAverages" /> class.
/// </summary>
/// <param name="fast_moving_average">The fast moving average.</param>
/// <param name="slow_moving_average">The slow moving average.</param>
/// <param name="tradeRuleDirection">
/// The trade rule direction. Only used if the instance will be part of a
/// <see cref="TradingRule" /> class
/// </param>
/// <remarks>
/// Both Moving Averages must be registered BEFORE being used by this constructor.
/// </remarks>
public CrossingMovingAverages(IndicatorBase<IndicatorDataPoint> fast_moving_average,
IndicatorBase<IndicatorDataPoint> slow_moving_average, TradeRuleDirection? tradeRuleDirection = null)
{
_moving_average_difference = fast_moving_average.Minus(slow_moving_average);
_moving_average_difference.Updated += ma_Updated;
if (tradeRuleDirection != null) _tradeRuleDirection = (TradeRuleDirection)tradeRuleDirection;
}
/// <summary>
/// Gets a value indicating whether this instance is ready.
/// </summary>
/// <value>
/// <c>true</c> if this instance is ready; otherwise, <c>false</c>.
/// </value>
public override bool IsReady
{
get { return _moving_average_difference.IsReady; }
}
public override TradeRuleDirection GetDirection()
{
return _tradeRuleDirection;
}
/// <summary>
/// Gets the actual state of both moving averages.
/// </summary>
public CrossingMovingAveragesSignals Signal { get; private set; }
/// <summary>
/// Gets the signal. Only used if the instance will be part of a <see cref="TradingRule" /> class.
/// </summary>
/// <returns>
/// <c>true</c> if the actual <see cref="Signal" /> correspond with the instance <see cref="TradeRuleDirection" />.
/// <c>false</c>
/// otherwise.
/// </returns>
public override bool HasSignal()
{
var signal = false;
if (IsReady)
{
switch (_tradeRuleDirection)
{
case TradeRuleDirection.LongOnly:
signal = Signal == CrossingMovingAveragesSignals.FastCrossSlowFromBelow;
break;
case TradeRuleDirection.ShortOnly:
signal = Signal == CrossingMovingAveragesSignals.FastCrossSlowFromAbove;
break;
case TradeRuleDirection.Any:
signal = Signal == CrossingMovingAveragesSignals.FastCrossSlowFromAbove ||
Signal == CrossingMovingAveragesSignals.FastCrossSlowFromBelow;
break;
}
}
return signal;
}
private void ma_Updated(object sender, IndicatorDataPoint updated)
{
if (!IsReady)
{
return;
}
var actualSignal = Math.Sign(_moving_average_difference);
if (actualSignal == _lastSignal || _lastSignal == 0)
{
Signal = (CrossingMovingAveragesSignals)actualSignal;
}
else if (_lastSignal == -1 && actualSignal == 1)
{
Signal = CrossingMovingAveragesSignals.FastCrossSlowFromBelow;
}
else if (_lastSignal == 1 && actualSignal == -1)
{
Signal = CrossingMovingAveragesSignals.FastCrossSlowFromAbove;
}
_lastSignal = actualSignal;
}
}
}namespace QuantConnect.Algorithm.CSharp.Algo
{
/// <summary>
/// Possibles states of an oscillator respect to its thresholds.
/// </summary>
public enum OscillatorSignals
{
CrossLowerThresholdFromAbove = -3,
BellowLowerThreshold = -2,
CrossLowerThresholdFromBelow = -1,
BetweenThresholds = 0,
CrossUpperThresholdFromBelow = 3,
AboveUpperThreshold = 2,
CrossUpperThresholdFromAbove = 1
}
public struct OscillatorThresholds
{
public decimal Lower;
public decimal Upper;
}
/// <summary>
/// This class keeps track of an oscillator respect to its thresholds and updates an <see cref="OscillatorSignal" />
/// for each given state.
/// </summary>
/// <seealso cref="QuantConnect.Algorithm.CSharp.ITechnicalIndicatorSignal" />
public class OscillatorSignal : SignalsBase
{
private decimal _previousIndicatorValue;
private OscillatorSignals _previousSignal;
private OscillatorThresholds _thresholds;
private TradeRuleDirection _tradeRuleDirection;
QCAlgorithm _log;
/// <summary>
/// Initializes a new instance of the <see cref="OscillatorSignal" /> class.
/// </summary>
/// <param name="indicator">The indicator.</param>
/// <param name="thresholds">The thresholds.</param>
/// <param name="tradeRuleDirection">
/// The trade rule direction. Only used if the instance will be part of a
/// <see cref="TradingRule" /> class
/// </param>
/// <remarks>The oscillator must be registered BEFORE being used by this constructor.</remarks>
public OscillatorSignal(dynamic indicator, OscillatorThresholds thresholds,
TradeRuleDirection tradeRuleDirection, QCAlgorithm log)
{
SetUpClass(ref indicator, ref thresholds, tradeRuleDirection);
_log = log;
}
/// <summary>
/// Initializes a new instance of the <see cref="OscillatorSignal" /> class.
/// </summary>
/// <param name="indicator">The indicator.</param>
/// <remarks>The oscillator must be registered BEFORE being used by this constructor.</remarks>
public OscillatorSignal(dynamic indicator, TradeRuleDirection tradeRuleDirection, QCAlgorithm log)
{
var defaultThresholds = new OscillatorThresholds { Lower = 20, Upper = 80 };
_log = log;
SetUpClass(ref indicator, ref defaultThresholds, tradeRuleDirection);
}
/// <summary>
/// The underlying indicator, must be an oscillator.
/// </summary>
public dynamic Indicator { get; private set; }
/// <summary>
/// Gets the actual state of the oscillator.
/// </summary>
public OscillatorSignals Signal { get; private set; }
/// <summary>
/// Gets a value indicating whether this instance is ready.
/// </summary>
/// <value>
/// <c>true</c> if this instance is ready; otherwise, <c>false</c>.
/// </value>
public override bool IsReady
{
get { return Indicator.IsReady; }
}
public override TradeRuleDirection GetDirection()
{
return _tradeRuleDirection;
}
/// <summary>
/// Gets the signal. Only used if the instance will be part of a <see cref="TradingRule" /> class.
/// </summary>
/// <returns>
/// <c>true</c> if the actual <see cref="Signal" /> correspond with the instance <see cref="TradeRuleDirection" />.
/// <c>false</c>
/// otherwise.
/// </returns>
public override bool HasSignal()
{
var signal = false;
if (IsReady)
{
switch (_tradeRuleDirection)
{
case TradeRuleDirection.LongOnly:
signal = Signal == OscillatorSignals.CrossLowerThresholdFromBelow;
break;
case TradeRuleDirection.ShortOnly:
signal = Signal == OscillatorSignals.CrossUpperThresholdFromAbove;
break;
case TradeRuleDirection.Any:
signal = Signal == OscillatorSignals.CrossUpperThresholdFromAbove ||
Signal == OscillatorSignals.CrossLowerThresholdFromBelow;
break;
}
}
return signal;
}
/// <summary>
/// Updates the <see cref="Signal" /> status.
/// </summary>
private void Indicator_Updated(object sender, IndicatorDataPoint updated)
{
var actualPositionSignal = GetActualPositionSignal(updated);
if (!Indicator.IsReady)
{
_previousIndicatorValue = updated.Value;
_previousSignal = actualPositionSignal;
Signal = _previousSignal;
return;
}
var actualSignal = GetActualSignal(_previousSignal, actualPositionSignal);
Signal = actualSignal;
_previousIndicatorValue = updated.Value;
_previousSignal = actualSignal;
}
/// <summary>
/// Gets the actual position respect to the thresholds.
/// </summary>
/// <param name="indicatorCurrentValue">The indicator current value.</param>
/// <returns></returns>
protected virtual OscillatorSignals GetActualPositionSignal(decimal indicatorCurrentValue)
{
var positionSignal = OscillatorSignals.BetweenThresholds;
if (indicatorCurrentValue > _thresholds.Upper)
{
positionSignal = OscillatorSignals.AboveUpperThreshold;
}
else if (indicatorCurrentValue < _thresholds.Lower)
{
positionSignal = OscillatorSignals.BellowLowerThreshold;
}
return positionSignal;
}
/// <summary>
/// Gets the actual signal from the actual position respect to the thresholds and the last signal.
/// </summary>
/// <param name="previousSignal">The previous signal.</param>
/// <param name="actualPositionSignal">The actual position signal.</param>
/// <returns></returns>
private OscillatorSignals GetActualSignal(OscillatorSignals previousSignal,
OscillatorSignals actualPositionSignal)
{
OscillatorSignals actualSignal;
var previousSignalInt = (int)previousSignal;
var actualPositionSignalInt = (int)actualPositionSignal;
if (actualPositionSignalInt == 0)
{
if (Math.Abs(previousSignalInt) > 1)
{
actualSignal = (OscillatorSignals)Math.Sign(previousSignalInt);
}
else
{
actualSignal = OscillatorSignals.BetweenThresholds;
}
}
else
{
if (previousSignalInt * actualPositionSignalInt <= 0 ||
Math.Abs(previousSignalInt + actualPositionSignalInt) == 3)
{
actualSignal = (OscillatorSignals)(Math.Sign(actualPositionSignalInt) * 3);
}
else
{
actualSignal = (OscillatorSignals)(Math.Sign(actualPositionSignalInt) * 2);
}
}
return actualSignal;
}
/// <summary>
/// Sets up class.
/// </summary>
/// <param name="indicator">The indicator.</param>
/// <param name="thresholds">The thresholds.</param>
/// <param name="tradeRuleDirection">The trade rule direction.</param>
private void SetUpClass(ref dynamic indicator, ref OscillatorThresholds thresholds,
TradeRuleDirection? tradeRuleDirection = null)
{
_thresholds = thresholds;
Indicator = indicator;
indicator.Updated += new IndicatorUpdatedHandler(Indicator_Updated);
if (tradeRuleDirection != null) _tradeRuleDirection = (TradeRuleDirection)tradeRuleDirection;
}
}
}namespace QuantConnect
{
public enum LotSize
{
Standard = 100000,
Mini = 10000,
Micro = 1000,
Nano = 100,
}
public enum AgentAction
{
GoShort = -1,
DoNothing = 0,
GoLong = 1
}
public class FxRiskManagment
{
// Maximum equity proportion to put at risk in a single operation.
private decimal _riskPerTrade;
// Maximum equity proportion at risk in open positions in a given time.
private decimal _maxExposure;
// Maximum equity proportion at risk in a single trade.
private decimal _maxExposurePerTrade;
private int _lotSize;
private int _minQuantity;
private SecurityPortfolioManager _portfolio;
/// <summary>
/// Initializes a new instance of the <see cref="FxRiskManagment"/> class.
/// </summary>
/// <param name="portfolio">The QCAlgorithm Portfolio.</param>
/// <param name="riskPerTrade">The max risk per trade.</param>
/// <param name="maxExposurePerTrade">The maximum exposure per trade.</param>
/// <param name="maxExposure">The maximum exposure in all trades.</param>
/// <param name="lotsize">The minimum quantity to trade.</param>
/// <exception cref="System.NotImplementedException">The pairs should be added to the algorithm before initialize the risk manager.</exception>
public FxRiskManagment(SecurityPortfolioManager portfolio, decimal riskPerTrade, decimal maxExposurePerTrade,
decimal maxExposure, LotSize lotsize = LotSize.Micro, int minQuantity = 5)
{
_portfolio = portfolio;
if (_portfolio.Securities.Count == 0)
{
throw new NotImplementedException("The pairs should be added to the algorithm before initialize the risk manager.");
}
this._riskPerTrade = riskPerTrade;
_maxExposurePerTrade = maxExposurePerTrade;
this._maxExposure = maxExposure;
_lotSize = (int)lotsize;
_minQuantity = minQuantity;
}
/// <summary>
/// Calculates the entry orders and stop-loss price.
/// </summary>
/// <param name="pair">The Forex pair Symbol.</param>
/// <param name="action">The order direction.</param>
/// <returns>a Tuple with the quantity as Item1 and the stop-loss price as Item2. If quantity is zero, then means that no trade must be done.</returns>
public Tuple<int, decimal, decimal> CalculateEntryOrders(QuoteBars data, Symbol pair, AgentAction action)
{
// If exposure is greater than the max exposure, then return zero.
if (_portfolio.TotalMarginUsed > _portfolio.TotalPortfolioValue * _maxExposure)
{
return Tuple.Create(0, 0m, 0m);
}
var closePrice = _portfolio.Securities[pair].Price;
var leverage = _portfolio.Securities[pair].Leverage;
var exchangeRate = _portfolio.Securities[pair].QuoteCurrency.ConversionRate;
var volatility = _portfolio.Securities[pair].VolatilityModel.Volatility;
// Estimate the maximum entry order quantity given the risk per trade.
var moneyAtRisk = _portfolio.TotalPortfolioValue * _riskPerTrade;
var maxQuantitybyRisk = moneyAtRisk / (volatility * exchangeRate);
// Estimate the maximum entry order quantity given the exposure per trade.
var maxBuySize = Math.Min(_portfolio.MarginRemaining, _portfolio.TotalPortfolioValue * _maxExposurePerTrade) * leverage;
var maxQuantitybyExposure = maxBuySize / (closePrice * exchangeRate);
// The final quantity is the lowest of both.
var quantity = (int)(Math.Round(Math.Min(maxQuantitybyRisk, maxQuantitybyExposure) / _lotSize, 0) * _lotSize);
// If the final quantity is lower than the minimum quantity of the given lot size, then return zero.
if (quantity < _lotSize * _minQuantity) return Tuple.Create(0, 0m, 0m);
quantity = action == AgentAction.GoLong ? quantity : -quantity;
var stopLossPrice = closePrice + (action == AgentAction.GoLong ? -volatility : volatility);
return Tuple.Create(quantity, stopLossPrice, action == AgentAction.GoLong ? data[pair].Ask.Close : data[pair].Bid.Close);
}
/// <summary>
/// Updates the stop-loss price of all open StopMarketOrders.
/// </summary>
public void UpdateTrailingStopOrders(QuoteBars data)
{
// Get all the spot-loss orders.
var openStopLossOrders = _portfolio.Transactions.GetOrderTickets(o => o.OrderType == OrderType.StopMarket && o.Status == OrderStatus.Submitted);
foreach (var ticket in openStopLossOrders)
{
var stopLossPrice = ticket.SubmitRequest.StopPrice;
var volatility = _portfolio.Securities[ticket.Symbol].VolatilityModel.Volatility;
var actualPrice = _portfolio.Securities[ticket.Symbol].Price;
// The StopLossOrder has the opposite direction of the original order.
var originalOrderDirection = ticket.Quantity > 0 ? OrderDirection.Sell : OrderDirection.Buy;
if (originalOrderDirection == OrderDirection.Sell)
{
actualPrice = data[ticket.Symbol].Ask.Close;
}
if (originalOrderDirection == OrderDirection.Buy)
{
actualPrice = data[ticket.Symbol].Bid.Close;
}
var newStopLossPrice = actualPrice + (volatility * (originalOrderDirection == OrderDirection.Buy ? -1 : 1));
if ((originalOrderDirection == OrderDirection.Buy && newStopLossPrice > stopLossPrice)
|| (originalOrderDirection == OrderDirection.Sell && newStopLossPrice < stopLossPrice))
{
/*
if (originalOrderDirection == OrderDirection.Sell && data[ticket.Symbol].Ask.Close < System.Convert.ToDecimal(ticket.SubmitRequest.Tag))
{
if(newStopLossPrice>System.Convert.ToDecimal(ticket.SubmitRequest.Tag))
{
newStopLossPrice = System.Convert.ToDecimal(ticket.SubmitRequest.Tag);
}
}
if (originalOrderDirection == OrderDirection.Buy && data[ticket.Symbol].Bid.Close > System.Convert.ToDecimal(ticket.SubmitRequest.Tag))
{
//price is on right direction
if(newStopLossPrice<System.Convert.ToDecimal(ticket.SubmitRequest.Tag))
{
newStopLossPrice = System.Convert.ToDecimal(ticket.SubmitRequest.Tag);
}
}
*/
ticket.Update(new UpdateOrderFields { Quantity = _portfolio[ticket.Symbol].Quantity, StopPrice = newStopLossPrice });
}
}
}
}
}namespace QuantConnect
{
/// <summary>
/// Provides an implementation of <see cref="IVolatilityModel"/> that computes the
/// relative standard deviation as the volatility of the security
/// </summary>
public class ThreeSigmaVolatilityModel : IVolatilityModel
{
private readonly TimeSpan _periodSpan;
private StandardDeviation _standardDeviation;
private decimal _percentage;
/// <summary>
/// Gets the volatility of the security as a percentage
/// </summary>
public decimal Volatility
{
get { return _standardDeviation * _percentage; }
}
/// <summary>
/// Initializes a new instance of the <see cref="QuantConnect.Securities.RelativeStandardDeviationVolatilityModel"/> class
/// </summary>
/// <param name="periodSpan">The time span representing one 'period' length</param>
/// <param name="periods">The nuber of 'period' lengths to wait until updating the value</param>
public ThreeSigmaVolatilityModel(StandardDeviation standardDeviation, decimal percentage = 2.5m)
{
_standardDeviation = standardDeviation;
_percentage = percentage;
_periodSpan = TimeSpan.FromMinutes(standardDeviation.Period);
}
/// <summary>
/// Updates this model using the new price information in
/// the specified security instance
/// </summary>
/// <param name="security">The security to calculate volatility for</param>
/// <param name="data"></param>
public void Update(Security security, BaseData data)
{
}
public IEnumerable<HistoryRequest> GetHistoryRequirements(Security security, DateTime utcTime)
{
return Enumerable.Empty<HistoryRequest>();
}
}
}namespace QuantConnect
{
/// <summary>
/// Possibles states of two moving averages.
/// </summary>
public enum BolingerBandsSignals
{
Bullish = 1,
Bearish = -1,
Middle = 0
}
/// <summary>
/// This class keeps track of two crossing moving averages and updates a <see cref="CrossingMovingAveragesSignals" />
/// for each given state.
/// </summary>
/// <seealso cref="QuantConnect.Algorithm.CSharp.ITechnicalIndicatorSignal" />
public class BolingerBandsIndicator : SignalsBase
{
private BollingerBands _bb;
private TradeRuleDirection _tradeRuleDirection;
private int _lastSignal;
QCAlgorithm _log;
/// <summary>
/// Initializes a new instance of the <see cref="CrossingMovingAverages" /> class.
/// </summary>
/// <param name="fast_moving_average">The fast moving average.</param>
/// <param name="slow_moving_average">The slow moving average.</param>
/// <param name="tradeRuleDirection">
/// The trade rule direction. Only used if the instance will be part of a
/// <see cref="TradingRule" /> class
/// </param>
/// <remarks>
/// Both Moving Averages must be registered BEFORE being used by this constructor.
/// </remarks>
public BolingerBandsIndicator(BollingerBands bb, QCAlgorithm log,
TradeRuleDirection? tradeRuleDirection = null)
{
_bb = bb;
// _bb.Updated += new IndicatorUpdatedHandler(ma_Updated);
_log = log;
SetUpClass(ref bb, tradeRuleDirection);
//if (tradeRuleDirection != null) _tradeRuleDirection = (TradeRuleDirection)tradeRuleDirection;
}
private void Indicator_Updated(object sender, IndicatorDataPoint updated)
{
if (!IsReady)
{
return;
}
var actualSignal = GetActualPositionSignal(updated);
_lastSignal = (int)actualSignal;
}
private void SetUpClass(ref BollingerBands indicator,
TradeRuleDirection? tradeRuleDirection = null)
{
_bb = indicator;
indicator.Updated += new IndicatorUpdatedHandler(Indicator_Updated);
if (tradeRuleDirection != null) _tradeRuleDirection = (TradeRuleDirection) tradeRuleDirection;
}
/// <summary>
/// Gets a value indicating whether this instance is ready.
/// </summary>
/// <value>
/// <c>true</c> if this instance is ready; otherwise, <c>false</c>.
/// </value>
public override bool IsReady
{
get { return _bb.IsReady; }
}
public override TradeRuleDirection GetDirection()
{
return _tradeRuleDirection;
}
/// <summary>
/// Gets the actual state of both moving averages.
/// </summary>
public BolingerBandsSignals Signal { get; private set; }
/// <summary>
/// Gets the signal. Only used if the instance will be part of a <see cref="TradingRule" /> class.
/// </summary>
/// <returns>
/// <c>true</c> if the actual <see cref="Signal" /> correspond with the instance <see cref="TradeRuleDirection" />.
/// <c>false</c>
/// otherwise.
/// </returns>
public override bool HasSignal()
{
var signal = false;
if (IsReady)
{
switch (_tradeRuleDirection)
{
case TradeRuleDirection.LongOnly:
signal = _lastSignal == (int)BolingerBandsSignals.Bullish;
break;
case TradeRuleDirection.ShortOnly:
signal = _lastSignal == (int)BolingerBandsSignals.Bearish;
break;
case TradeRuleDirection.Any:
signal = _lastSignal == (int)BolingerBandsSignals.Bearish ||
_lastSignal == (int)BolingerBandsSignals.Bullish;
break;
}
}
return signal;
}
/// <summary>
/// Gets the actual position respect to the thresholds.
/// </summary>
/// <param name="indicatorCurrentValue">The indicator current value.</param>
/// <returns></returns>
private BolingerBandsSignals GetActualPositionSignal(decimal indicatorCurrentValue)
{
var positionSignal = BolingerBandsSignals.Middle;
if (indicatorCurrentValue > _bb.UpperBand)
{
_lastActiveSignal = new ActiveSignal();
_lastActiveSignal.IsShort = true;
positionSignal = BolingerBandsSignals.Bearish;//Bearish
}
else if (indicatorCurrentValue < _bb.LowerBand)
{
_lastActiveSignal = new ActiveSignal();
_lastActiveSignal.IsLong = true;
positionSignal = BolingerBandsSignals.Bullish;//Bullish
}
/*
if(positionSignal!=BolingerBandsSignals.Middle)
{
_log.Log("indicatorCurrentValue:: " + indicatorCurrentValue);
_log.Log("UpperBand:: "+_bb.UpperBand);
_log.Log("LowerBand:: "+_bb.LowerBand);
_log.Log("positionSignal:: "+positionSignal);
}
*/
return positionSignal;
}
}
}namespace QuantConnect
{
public enum RuleOperators
{
AND = 1,
OR = 0
}
public class TradingRuleQCVersion
{
private readonly ITechnicalIndicatorSignal[] _technicalIndicatorSignals;
private readonly RuleOperators[] _ruleOperators;
private readonly bool _isEntryRule;
private Symbol _symbol;
public TradingRuleQCVersion(ITechnicalIndicatorSignal[] technicalIndicatorSignals, RuleOperators[] ruleOperators, bool isEntryRule, Symbol symbol)
{
_technicalIndicatorSignals = technicalIndicatorSignals;
_isEntryRule = isEntryRule;
_symbol = symbol;
_ruleOperators = ruleOperators;
}
public bool IsReady
{
get
{
var isReady = true;
foreach (var indicator in _technicalIndicatorSignals)
{
isReady = indicator.IsReady && isReady;
}
return isReady;
}
}
public Symbol Symbol
{
get { return _symbol; }
}
public bool TradeRuleSignal
{
get { return GetTradeRuleSignal(); }
}
/// <summary>
/// Gets the trade rule signal for the best in-sample performance individual.
/// </summary>
/// <returns></returns>
private bool GetTradeRuleSignal()
{
var signal = false;
//if (_isEntryRule)
{
signal =_technicalIndicatorSignals[0].HasSignal();
for (var i = 1; i < _technicalIndicatorSignals.Length; i++)
{
signal = op(i, signal, i-1);
}
/*&& // Long SimpleMovingAverage
_technicalIndicatorSignals[1].HasSignal() || // Long Stochastic
_technicalIndicatorSignals[2].HasSignal() && // Long RelativeStrengthIndex
_technicalIndicatorSignals[3].HasSignal() || // Short MovingAverageConvergenceDivergence
_technicalIndicatorSignals[4].HasSignal() )|| // Short MomentumPercent
_technicalIndicatorSignals[5].HasSignal(); // Long BollingerBands
signal = signal || _technicalIndicatorSignals[5].HasSignal(); // Long BollingerBands
*/
}
/*
else
{
signal = _technicalIndicatorSignals[0].HasSignal() || // Short CommodityChannelIndex
_technicalIndicatorSignals[1].HasSignal() && // Long RelativeStrengthIndex
_technicalIndicatorSignals[2].HasSignal() || // Short Stochastic
_technicalIndicatorSignals[3].HasSignal() && // Long MovingAverageConvergenceDivergence
_technicalIndicatorSignals[4].HasSignal();
//|| // Long Stochastic
//_technicalIndicatorSignals[5].GetSignal(); // Short BollingerBands
}
*/
return signal;
}
private bool op(int ind1, bool ind2, int op)
{
if(_ruleOperators[op]==RuleOperators.AND)
{
return _technicalIndicatorSignals[ind1].HasSignal() && ind2;
}
return _technicalIndicatorSignals[ind1].HasSignal() || ind2;
}
}
}namespace QuantConnect
{
public abstract class SignalsBase : ITechnicalIndicatorSignal
{
protected ActiveSignal _lastActiveSignal;
protected SignalsBase()
{
}
public abstract bool IsReady { get; }
public abstract TradeRuleDirection GetDirection();
public ActiveSignal GetSignal()
{
return _lastActiveSignal;
}
public abstract bool HasSignal();
}
}using Newtonsoft.Json;
namespace QuantConnect
{
public class ActiveSignal
{
public int Id { get; set; }
public bool IsCompleted { get; set; }
public bool IsLong { get; set; }
public bool IsShort { get; set; }
public string Symbol { get; set; }
public DateTime Expiration { get; set; }
public DateTime SetupDate { get; set; }
public decimal Forecast { get; set; }
public string GenSignalTag()
{
var orderTag = new OrderTag
{
SignalId = Id,
Expiration = Expiration,
Target = Forecast
};
return JsonConvert.SerializeObject(orderTag);
}
}
}namespace QuantConnect
{
public class OrderTag
{
public int SignalId { get; set; } = 0;
public DateTime Expiration { get; set; } = DateTime.MaxValue;
public decimal Target { get; set; } = 0;
}
}namespace QuantConnect.Algorithm.CSharp.Algo
{
/// <summary>
/// This class keeps track of an oscillator respect to its thresholds and updates an <see cref="BBOscillatorSignal" />
/// for each given state.
/// </summary>
/// <seealso cref="QuantConnect.Algorithm.CSharp.ITechnicalIndicatorSignal" />
public class BBOscillatorSignal : OscillatorSignal
{
private BollingerBands _bb;
/// <summary>
/// Initializes a new instance of the <see cref="BBOscillatorSignal" /> class.
/// </summary>
/// <param name="indicator">The indicator.</param>
/// <remarks>The oscillator must be registered BEFORE being used by this constructor.</remarks>
public BBOscillatorSignal(BollingerBands indicator, TradeRuleDirection tradeRuleDirection, QCAlgorithm log) : base(indicator, tradeRuleDirection, log)
{
_bb = indicator;
}
/// <summary>
/// Gets the actual position respect to the thresholds.
/// </summary>
/// <param name="indicatorCurrentValue">The indicator current value.</param>
/// <returns></returns>
protected override OscillatorSignals GetActualPositionSignal(decimal indicatorCurrentValue)
{
var positionSignal = OscillatorSignals.BetweenThresholds;
if (indicatorCurrentValue > _bb.UpperBand)
{
positionSignal = OscillatorSignals.AboveUpperThreshold;
}
else if (indicatorCurrentValue < _bb.LowerBand)
{
positionSignal = OscillatorSignals.BellowLowerThreshold;
}
return positionSignal;
}
}
}