| Overall Statistics |
|
Total Trades 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Net Profit 0% Sharpe Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -0.016 Tracking Error 0.101 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset |
using System;
namespace GoldenCross
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
/*
* Copyright (C) 2022 by Lanikai Studios, Inc. - All Rights Reserved
*
* This code is not to be distributed to others, it is confidential information of Lanikai Studios.
*
* This code was built from open source Python code written by Aaron Eller - www.excelintrading.com
*/
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Brokerages;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Orders;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using static Lanikai.GlobalSettings;
namespace Lanikai
{
/// <summary>
/// The framework for the Lanikai algo.
/// </summary>
public class LanikaiQCAlgorithm : QCAlgorithm
{
/// <summary>
/// The benchmark for comparison. Normally set to SPY.
/// </summary>
private Symbol bm;
/// <summary>
/// Used for benchmark plotting code.
/// </summary>
private decimal? bm_first_price;
/// <summary>
/// Used for benchmark plotting code.
/// </summary>
private int bm_plot_counter = 0;
/// <summary>
/// The data for each symbol we are following.
/// </summary>
private IDictionary<Symbol, SymbolData> symbol_data;
/// <summary>
/// true when we need to update the universe.
/// </summary>
private bool update_universe;
/// <summary>
/// The max number of positions allowed.
/// </summary>
private int maxPositions;
/// <summary>
/// The maximum percentage of the budget to spend on any one stock.
/// </summary>
private float maxPctPerPosition;
/// <summary>
/// Long exit when RSI crosses below this value
/// </summary>
public decimal RsiExit1Value { get; private set; }
/// <summary>
/// Long exit when RSI crosses below this value
/// </summary>
public decimal RsiExit2Value { get; private set; }
// Initialize algorithm.
public override void Initialize()
{
// Set backtest details
SetBacktestDetails();
// Add instrument data to the algo
AddInstrumentData();
// Schedule functions
ScheduleFunctions();
// initialize each security with today's prices
SetSecurityInitializer(Initialize);
// Warm up the indicators prior to the start date
// this.SetWarmUp()
// This doesn't work with universe filters
// Instead we'll use History() to warm up indicators when SymbolData class objects get created
var strParam = GetParameter("MAX_POSITIONS");
maxPositions = strParam == null ? MAX_POSITIONS : Convert.ToInt32(strParam);
maxPctPerPosition = 1.0f / maxPositions;
var strRsiExit1Value = GetParameter("RSI_EXIT_1_VALUE");
RsiExit1Value = strRsiExit1Value == null ? RSI_EXIT_1_VALUE : Convert.ToInt32(strRsiExit1Value);
var strRsiExit2Value = GetParameter("RSI_EXIT_2_VALUE_ADD");
RsiExit2Value = strRsiExit2Value == null
? RSI_EXIT_2_VALUE
: RsiExit1Value - Convert.ToInt32(strRsiExit2Value);
if (PRINT_SETTINGS)
{
Log($"maxPositions = {maxPositions}, maxPctPerPosition = {maxPctPerPosition}");
Log($"rsiExit1Value = {RsiExit1Value}.");
Log($"rsiExit2Value = {RsiExit2Value}.");
Log($"Stop loss: {SL_PCT * 100}%");
}
}
private void Initialize (Security security) {
security.SetMarketPrice(GetLastKnownPrice(security));
}
/// <summary>
/// End of algorithm run event handler. This method is called at the end of a backtest or
/// live trading operation. Intended for closing out logs.
/// </summary>
public override void OnEndOfAlgorithm()
{
// Check if we want to plot the benchmark
if (PLOT_BENCHMARK_ON_STRATEGY_EQUITY_CHART)
PlotBenchmarkOnEquityCurve(true);
if (PRINT_VERBOSE || PRINT_ORDERS)
Log($"End of backtest at {Time}");
if (PRINT_ORDERS) {
Log("Portfolio:");
var positions = (from symbol in Portfolio.Keys where Portfolio[symbol].Invested select symbol).ToList();
foreach (var sym in positions)
Log($"{sym} holding {Portfolio[sym].Quantity} shares at {Portfolio[sym].Price:C}");
}
}
/// <summary>
/// Set the backtest details.
/// </summary>
public void SetBacktestDetails()
{
SetStartDate(START_DATE.Year, START_DATE.Month, START_DATE.Day);
SetEndDate(END_DATE.Year, END_DATE.Month, END_DATE.Day);
SetCash(CASH);
SetTimeZone(TIMEZONE.Id);
// Setup trading framework
// Transaction and submit/execution rules will use IB models
// brokerages: https://github.com/QuantConnect/Lean/blob/master/Common/Brokerages/BrokerageName.cs
// account types: AccountType.Margin, AccountType.Cash
SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash);
// Configure all universe securities
// This sets the data normalization mode
// You can also set custom fee, slippage, fill, and buying power models
// see if SubscriptionManager.SubscriptionDataConfigService has SetDataNormalizationMode() if the root class is cast to Equity
this.SetSecurityInitializer(new MySecurityInitializer());
// Adjust the cash buffer from the default 2.5% to custom setting
Settings.FreePortfolioValuePercentage = (decimal)FREE_PORTFOLIO_VALUE_PCT;
}
// Add instrument data to the algo.
public void AddInstrumentData()
{
// Set data resolution based on input
Resolution resolution;
switch (DATA_RESOLUTION)
{
case "SECOND":
resolution = Resolution.Second;
break;
case "MINUTE":
resolution = Resolution.Minute;
break;
case "HOUR":
resolution = Resolution.Hour;
break;
default:
throw new ApplicationException($"Must set resolution to SECOND, MINUTE, or HOUR. Is set to {DATA_RESOLUTION}.");
}
// Define the desired universe
this.AddUniverse(this.CoarseSelectionFunction, this.FineSelectionFunction);
// Set universe data properties desired
UniverseSettings.Resolution = resolution;
UniverseSettings.ExtendedMarketHours = false;
UniverseSettings.DataNormalizationMode = DataNormalizationMode.Adjusted;
UniverseSettings.MinimumTimeInUniverse = new TimeSpan(MIN_TIME_IN_UNIVERSE, 0, 0, 0);
// Add data for the benchmark and set benchmark
// Always use minute data for the benchmark
bm = AddEquity(BENCHMARK, Resolution.Minute).Symbol;
SetBenchmark(BENCHMARK);
// Create a dictionary to hold SymbolData class objects
symbol_data = new Dictionary<Symbol, SymbolData>();
// Create a variable to tell the algo when to update the universe
update_universe = true;
}
/// <summary>
/// Scheduling the functions required by the algo.
/// </summary>
public void ScheduleFunctions()
{
// For live trading, universe selection occurs approximately 04:00-07:00am EST on Tue-Sat.
// For backtesting, universe selection occurs at 00:00am EST (midnight)
// We need to update the self.update_universe variable
// before both of these scenarios are triggered
// Desired order of events:
// 1. Update the self.update_universe variable True
// end of week/month, 5 min after market close
// 2. Coarse/Fine universe filters run and update universe
// run everyday at either 00:00 or 04:00 EST
// Update self.update_universe variable True when desired
IDateRule date_rules;
switch (UNIVERSE_FREQUENCY)
{
case "daily":
date_rules = DateRules.EveryDay(this.bm);
break;
case "weekly":
// Want to schedule at end of the week, so actual update on start
// of the next week
date_rules = DateRules.WeekEnd(this.bm);
break;
case "monthly":
// Want to schedule at end of the month, so actual update on
// first day of the next month
date_rules = DateRules.MonthEnd(this.bm);
break;
default:
throw new ApplicationException($"UNIVERSE_FREQUENCY needs to be set to daily, weekly, or monthly. Is set to {UNIVERSE_FREQUENCY}.");
}
// Timing is after the market closes
Schedule.On(date_rules, TimeRules.BeforeMarketClose(this.bm, -5), this.UpdateUniverse);
// Calling -5 minutes "BeforeMarketClose" schedules the function 5
// minutes "after market close"
// Calling -5 minutes "AfterMarketOpen" schedules the function 5
// minutes "before market open"
// Now the coarse/fine universe filters will run automatically either at
// 00:00(midnight) EST for backtesting or
// 04:00am EST for live trading
// Check for new signals SIGNAL_CHECK_MINUTES before the market open
Schedule.On(DateRules.EveryDay(this.bm), TimeRules.AfterMarketOpen(this.bm, -SIGNAL_CHECK_MINUTES),
this.CheckForSignals);
// Check if end of day exit is desired
if (EOD_EXIT)
{
Utils.Trap();
// Schedule function to liquidate the portfolio EOD_EXIT_MINUTES
// before the market close
Schedule.On(DateRules.EveryDay(this.bm), TimeRules.BeforeMarketClose(this.bm, EOD_EXIT_MINUTES),
this.LiquidatePortfolio);
}
// Check if we want to plot the benchmark on the equity curve
if (PLOT_BENCHMARK_ON_STRATEGY_EQUITY_CHART)
// Schedule benchmark end of day event 5 minutes after the close
Schedule.On(DateRules.EveryDay(this.bm), TimeRules.BeforeMarketClose(this.bm, -5),
this.BenchmarkOnEndOfDay);
else Utils.Trap();
}
/// <summary>
/// Event called when re-balancing is desired.
/// </summary>
public void UpdateUniverse()
{
// Update variable to trigger the universe to be updated
update_universe = true;
}
//
// Perform coarse filters on universe.
// Called once per day.
// Returns all stocks meeting the desired criteria.
//
// Attributes available:
// .AdjustedPrice
// .DollarVolume
// .HasFundamentalData
// .Price -> always the raw price!
// .Volume
//
/// <summary>
/// Perform coarse filters on universe. Called once per day.
///
/// Attributes available:
/// .AdjustedPrice
/// .DollarVolume
/// .HasFundamentalData
/// .Price -> always the raw price!
/// .Volume
/// </summary>
/// <param name="coarse">Summary information for each stock.</param>
/// <returns>All stocks meeting the desired criteria.</returns>
public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
{
// # Testing - catch specific symbol
// for x in coarse:
// # if str(x.Symbol).split(" ")[0] in ['AAPL']:
// if x.Symbol.ID.Symbol in ['AAPL']:
// # Stop and debug below
// print(x)
// Check if the universe doesn't need to be updated
if (!update_universe)
// Return unchanged universe
return Universe.Unchanged;
// Otherwise update the universe based on the desired filters
// Filter all securities with appropriate price and volume
var filteredCoarse = (from x in coarse
where x.Price >= MIN_PRICE && x.Price <= MAX_PRICE && x.Volume >= MIN_DAILY_VOLUME &&
x.DollarVolume >= MIN_DAILY_DOLLAR_VOLUME
select x).ToList();
// Check if fundamental data is required
if (REQUIRE_FUNDAMENTAL_DATA)
filteredCoarse = (from x in filteredCoarse where x.HasFundamentalData select x).ToList();
// Return the symbol objects
List<Symbol> universe = (from x in filteredCoarse select x.Symbol).ToList();
// Print universe details when desired
if (PRINT_COARSE)
Log($"Coarse filter returned {universe.Count} stocks.");
return universe;
}
/// <summary>
/// Perform fine filters on universe. Called once per day.
///
/// Attributes available:
/// .AssetClassification
/// .CompanyProfile
/// .CompanyReference
/// .EarningRatios
/// .EarningReports
/// .FinancialStatements
/// .MarketCap
/// .OperationRatios
/// .Price -> always the raw price!
/// .ValuationRatios
/// </summary>
/// <param name="fine">Summary information for each stock.</param>
/// <returns>All stocks meeting the desired criteria.</returns>
public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
{
// # Testing - catch specific symbol
// for x in coarse:
// # if str(x.Symbol).split(" ")[0] in ['AAPL']:
// if x.Symbol.ID.Symbol in ['AAPL']:
// # Stop and debug below
// print(x)
// Check if the universe doesn't need to be updated
if (!update_universe)
// Return unchanged universe
return Universe.Unchanged;
// Otherwise update the universe based on the desired filters
// Filter by allowed exchange and market cap
var symbols = (from x in fine
where ALLOWED_EXCHANGE.Contains(x.SecurityReference.ExchangeId) && x.MarketCap >= MIN_MARKET_CAP &&
x.MarketCap <= MAX_MARKET_CAP
select x).ToList();
// Filter stocks based on primary share class
if (PRIMARY_SHARES)
symbols = (from x in symbols where x.SecurityReference.IsPrimaryShare select x).ToList();
// Filter stocks based on disallowed sectors
if (SECTORS_NOT_ALLOWED.Count > 0)
{
Utils.Trap();
symbols = (from x in symbols
where !SECTORS_NOT_ALLOWED.Contains(x.AssetClassification.MorningstarSectorCode)
select x).ToList();
}
// Filter stocks based on disallowed industry groups
if (GROUPS_NOT_ALLOWED.Length > 0)
{
Utils.Trap();
symbols = (from x in symbols
where !GROUPS_NOT_ALLOWED.Contains(x.AssetClassification.MorningstarIndustryGroupCode)
select x).ToList();
}
// Filter stocks based on disallowed industries
if (INDUSTRIES_NOT_ALLOWED.Length > 0)
{
Utils.Trap();
symbols = (from x in symbols
where !INDUSTRIES_NOT_ALLOWED.Contains(x.AssetClassification.MorningstarIndustryCode)
select x).ToList();
}
// Return the symbol objects we want in our universe.
IList<Symbol> universe = (from x in symbols select x.Symbol).ToList();
if (PRINT_FINE)
Log($"Fine filter returned {universe.Count} stocks.");
this.update_universe = false;
return universe;
}
// Event handler for changes to our universe.
/// <summary>
/// Event fired each time the we add/remove securities from the data feed.
/// </summary>
/// <param name="changes">Security additions/removals for this time step.</param>
public override void OnSecuritiesChanged(SecurityChanges changes)
{
// Loop through securities added to the universe
foreach (var security in changes.AddedSecurities)
{
// Skip if BENCHMARK - we cannot trade this!
if (security.Symbol.ID.Symbol == BENCHMARK)
continue;
// Create a new symbol_data object for the security
symbol_data.Add(security.Symbol, new SymbolData(this, security.Symbol));
}
// Loop through securities removed from the universe
foreach (var security in changes.RemovedSecurities)
{
// Liquidate removed securities
if (security.Invested)
{
if (PRINT_VERBOSE)
Log($"{security.Symbol.ID.Symbol} removed from the universe, so closing open position.");
Liquidate(security.Symbol);
}
// Remove from symbol_data dictionary
if (symbol_data.ContainsKey(security.Symbol))
{
Utils.Trap();
// Remove desired bar consolidator for this security
TradeBarConsolidator consolidator = symbol_data[security.Symbol].Consolidator;
SubscriptionManager.RemoveConsolidator(security.Symbol, consolidator);
// Remove symbol from symbol data
if (PRINT_VERBOSE)
Log($"{security.Symbol.ID.Symbol} removed from symbol_data.");
symbol_data.Remove(security.Symbol);
}
}
}
/// <summary>
/// Liquidate the entire portfolio. Also cancel any pending orders.
/// </summary>
public void LiquidatePortfolio()
{
Utils.Trap();
if (Portfolio.Invested && PRINT_ORDERS)
Log("Time for end of day exit. Liquidating the portfolio.");
Liquidate();
}
/// <summary>
/// Event called when signal checks are desired.
/// </summary>
public void CheckForSignals()
{
if (PRINT_VERBOSE)
Log($"CheckForSignals() Time={Time}");
// Check for exit signals
CheckForExits();
// Check for entry signals
CheckForEntries();
}
/// <summary>
/// Check for exit signals.
/// </summary>
public void CheckForExits()
{
// Get list of current positions
var positions = (from symbol in Portfolio.Keys where Portfolio[symbol].Invested select symbol).ToList();
// Loop through positions
foreach (var sym in positions)
// Check for long position
if (Portfolio[sym].Quantity > 0)
// long - Check for long exit signal
symbol_data[sym].LongExitSignalChecks();
}
// Check for entry signals.
public void CheckForEntries()
{
// Get list of current positions
List<Symbol> positions = (from symbol in Portfolio.Keys where Portfolio[symbol].Invested select symbol).ToList();
// Get number of new entries allowed
var newEntryNum = maxPositions - positions.Count;
// Return if no new positions are allowed
if (newEntryNum == 0)
return;
// Create a list of long symbol
var longSymbols = new List<SymbolData>();
// Loop through the SymbolData class objects
foreach (SymbolData symbolData in symbol_data.Values)
{
// Skip if already invested
if (Securities[symbolData.Symbol].Invested)
continue;
// Check if indicators are ready - can be false for new stocks & sparsely traded ones
if (symbolData.IndicatorsReady)
// Check for long entry signal
if (symbolData.LongEntrySignal)
longSymbols.Add(symbolData);
}
// Check if the number of new entries exceeds limit
if (longSymbols.Count > newEntryNum)
{
if (PRINT_ORDERS)
Log($"{positions.Count} existing + {longSymbols.Count} additional orders > {maxPositions} maximum positions");
// Sort the entry_tuples list of tuples by largest pct_change
// pct_change is the second element of the tuple
// reverse=True for descending order (highest to lowest)
longSymbols = longSymbols.OrderByDescending(sd => sd.FastSlowPctDifference).ToList();
// Only keep the top newEntryNum
longSymbols = longSymbols.Take(newEntryNum).ToList();
}
// Print entry signal summary when desired
if (PRINT_ENTRIES && longSymbols.Count > 0)
{
string symbols = string.Join(", ", longSymbols.Select(sd => sd.Symbol.Value));
Log($"{longSymbols.Count} LONG entry signal(s): {symbols}");
}
// Place entry orders
foreach (SymbolData symbolData in longSymbols)
SetHoldings(symbolData.Symbol, maxPctPerPosition, tag: "initial buy");
}
/// <summary>
/// Order fill event handler. On an order fill update the resulting information is passed to this method.
/// </summary>
/// <param name="orderEvent">Order event details containing details of the evemts.</param>
public override void OnOrderEvent(OrderEvent orderEvent)
{
// Skip if not filled
if (orderEvent.Status != OrderStatus.Filled)
return;
// Get the order's symbol
if (PRINT_VERBOSE)
Log($"{orderEvent.Symbol.Value} OnOrderEvent({orderEvent})");
// this can come in after we removed a security
if (!symbol_data.ContainsKey(orderEvent.Symbol))
{
Utils.Trap();
if (PRINT_VERBOSE)
Log($"{orderEvent.Symbol.Value} not in self.symbol_data");
return;
}
// Call on_order_event for the symbol's SymbolData class
this.symbol_data[orderEvent.Symbol].OnOrderEvent(orderEvent);
}
/// <summary>
/// Event handler for end of trading day for the benchmark.
/// </summary>
public void BenchmarkOnEndOfDay()
{
PlotBenchmarkOnEquityCurve();
}
/// <summary>
/// Plot the benchmark buy & hold value on the strategy equity chart.
/// </summary>
/// <param name="force_plot"></param>
public void PlotBenchmarkOnEquityCurve(bool force_plot = false)
{
// Initially set percent change to zero
float pct_change = 0;
// Get today's daily prices
// history algo on QC github shows different formats that can be used:
// https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/HistoryAlgorithm.py
IEnumerable<Slice> history = this.History(new List<Symbol> { bm }, new TimeSpan(1, 0, 0, 0), Resolution.Daily);
var listHistory = history.ToList();
decimal? price;
if (listHistory.Count > 0)
{
TradeBar tradebar = listHistory[0].Bars[bm];
// We have not created the first price variable yet
// Get today's open and save as the first price
if (bm_first_price == null) {
bm_first_price = tradebar.Open;
Log($"Benchmark first price = {bm_first_price}");
}
// Get today's closing price
price = tradebar.Close;
// Calculate the percent change since the first price
pct_change = (float) ((price - bm_first_price) / bm_first_price);
}
else
price = null;
// Calculate today's ending value if we have the % change from the start
if (pct_change != 0)
{
var bm_value = Math.Round(CASH * (decimal)(1 + pct_change), 2);
// Plot every PLOT_EVERY_DAYS days
bm_plot_counter += 1;
if ((this.bm_plot_counter >= PLOT_EVERY_DAYS) || force_plot)
{
// Plot the benchmark's value to the Strategy Equity chart
// Plot function requires passing the chart name, series name,
// then the value to plot
this.Plot("Strategy Equity", "Benchmark", bm_value);
// Plot the account leverage
var account_leverage = Portfolio.TotalHoldingsValue / Portfolio.TotalPortfolioValue;
Plot("Leverage", "Leverge", account_leverage);
// Reset counter to 0
bm_plot_counter = 0;
// Log benchmark's ending price for reference
if (force_plot)
{
Log($"Benchmark's first price = {bm_first_price}");
Log($"Benchmark's final price = {price}");
Log($"Benchmark buy & hold value = {bm_value}");
}
}
}
}
/// <summary>
/// Define models to be used for securities as they are added to the algorithm's universe.
/// </summary>
private class MySecurityInitializer : ISecurityInitializer
{
public void Initialize(Security security)
{
// Define the data normalization mode
security.SetDataNormalizationMode(DataNormalizationMode.Adjusted);
// Define the fee model to use for the security
// security.SetFeeModel()
// Define the slippage model to use for the security
// security.SetSlippageModel()
// Define the fill model to use for the security
// security.SetFillModel()
// Define the buying power model to use for the security
}
}
}
}
/*
* Copyright (C) 2022 by Lanikai Studios, Inc. - All Rights Reserved
*
* This code is not to be distributed to others, it is confidential information of Lanikai Studios.
*/
using System;
using System.Diagnostics;
using QuantConnect.Data.Fundamental;
namespace Lanikai
{
/// <summary>
/// Basic utilities for the project.
///
/// Created by: David Thielen
/// Version: 1.0
/// </summary>
public static class Utils
{
/// <summary>
/// Break into the debugger if running in DEBUG mode.
/// </summary>
public static void Trap()
{
//#if DEBUG
// Debugger.Break();
Console.Out.WriteLine($"Trap() Stack = {Stack}");
//#endif
}
/// <summary>
/// Break into the debugger if running in DEBUG mode and the parameter is true..
/// </summary>
/// <param name="breakIfTrue">Break into the debugger if true, do nothing if false.</param>
public static void Trap(bool breakIfTrue)
{
//#if DEBUG
if (breakIfTrue) {
// Debugger.Break();
Console.Out.WriteLine($"Trap(true) Stack = {Stack}");
}
//#endif
}
private static string Stack
{
get
{
string stack = Environment.StackTrace;
int start = stack.IndexOf("Trap");
if (start == -1)
return stack.Trim();
start = stack.IndexOf('\n', start) + 1;
int end = stack.IndexOf("QuantConnect");
if (end == -1)
return stack.Substring(start).Trim().Replace('\n', ' ');
end = stack.LastIndexOf('\n', end);
if (end == -1)
return stack.Substring(start).Trim().Replace('\n', ' ');
return stack.Substring(start, end - start).Trim().Replace('\n', ' ');
}
}
}
}
/*
* Copyright (C) 2022 by Lanikai Studios, Inc. - All Rights Reserved
*
* This code is not to be distributed to others, it is confidential information of Lanikai Studios.
*
* This code was built from open source Python code written by Aaron Eller - www.excelintrading.com
*/
using System;
using System.Collections.Generic;
using System.Linq;
namespace Lanikai
{
/// <summary>
/// Moving Average Cross Universe Strategy
/// Version 1.1.0
///
/// Revision Notes:
/// 1.0.0 (01/17/2021) - Initial.Started from "Universe Strategy_v103.py". Also
/// copied SymbolData logic from
/// "Moving Average Crossover_v107.py".
/// 1.1.0 (01/26/2021) - converted to C#
///
/// References:
/// -QC(Lean) Class List https://lean-api-docs.netlify.app/annotated.html
/// -OrderTicket properties https://lean-api-docs.netlify.app/classQuantConnect_1_1Orders_1_1OrderTicket.html
/// -QC Universe https://www.quantconnect.com/docs/algorithm-reference/universes
/// -QC Universe Settings https://www.quantconnect.com/docs/algorithm-reference/universes#Universes-Universe-Settings
/// -QC Universe Fundamentals https://www.quantconnect.com/docs/data-library/fundamentals
/// -Speeding up QC Universe https://www.quantconnect.com/forum/discussion/7875/speeding-up-universe-selection/p1
/// </summary>
public static class GlobalSettings {
////////////////////////////////////////////////////
// BACKTEST INPUTS
/// <summary>Everything runs on New York time (not UTC). Therefore the market open/close is constant</summary>
public static readonly TimeZoneInfo TIMEZONE;
/// <summary>Backtest and Optimize start on this day (exclusive - because QC needs to run to overnight to pupulate equity pricing).</summary>
private static readonly DateTime StartDate = new DateTime(2020, 1, 1);
public static readonly DateTimeOffset START_DATE;
/// <summary>Backtest and Optimize end on this day (inclusive).</summary>
private static readonly DateTime EndDate = new DateTime(2020, 2, 1);
public static readonly DateTimeOffset END_DATE;
/// <summary>Starting portfolio value</summary>
public static decimal CASH = 500000;
////////////////////////////////////////////////////
// DATA INPUTS
/// <summary>
/// Define the data resolution to be fed to the algorithm
/// Must be "SECOND", "MINUTE", or "HOUR"
/// </summary>
public static string DATA_RESOLUTION = "HOUR";
/// <summary>
/// How often to update the universe?
/// Options: 'daily', 'weekly', or 'monthly'
/// </summary>
public static string UNIVERSE_FREQUENCY = "monthly";
////////////////////////////////////////////////////
// COARSE UNIVERSE SELECTION INPUTS
/// <summary>if True, stocks only / no etfs e.g.</summary>
public static bool REQUIRE_FUNDAMENTAL_DATA = true;
/// <summary>Selected stocks must be this price per share, or more. Set to 0 to disable.</summary>
public static decimal MIN_PRICE = 10.0m;
/// <summary> Selected stocks must be this price per share, or less. Set to 1e6 to disable.</summary>
public static decimal MAX_PRICE = 1000000.0m;
/// <summary>Daily volume of trades in this stock must be this amount, or more. Set to 0 to disable.</summary>
public static int MIN_DAILY_VOLUME = 0;
/// <summary>Daily total price of trades in this stock must be this amount, or more. Set to 1e6 to disable.</summary>
public static decimal MIN_DAILY_DOLLAR_VOLUME = 10000000.0m;
////////////////////////////////////////////////////
// FINE UNIVERSE SELECTION INPUTS
/// <summary>Minimum market cap. e6=million/e9=billion/e12=trillion</summary>
public static decimal MIN_MARKET_CAP = 10E9m;
/// <summary>Maximum market cap. e6=million/e9=billion/e12=trillion</summary>
public static decimal MAX_MARKET_CAP = 10E12m;
// Turn on/off specific exchanges allowed
/// <summary>Archipelago Electronic Communications Network.</summary>
public static bool ARCX = false;
/// <summary>American Stock Exchange</summary>
public static bool ASE = false;
/// <summary>Better Alternative Trading System</summary>
public static bool BATS = false;
/// <summary>Nasdaq Stock Exchange</summary>
public static bool NAS = true;
/// <summary>New York Stock Exchange</summary>
public static bool NYS = true;
/// <summary>Only allow a stock's primary shares</summary>
public static bool PRIMARY_SHARES = true;
// Turn on/off specific sectors allowed
public static bool BASIC_MATERIALS = true;
public static bool CONSUMER_CYCLICAL = true;
public static bool FINANCIAL_SERVICES = true;
public static bool REAL_ESTATE = true;
public static bool CONSUMER_DEFENSIVE = true;
public static bool HEALTHCARE = true;
public static bool UTILITIES = true;
public static bool COMMUNICATION_SERVICES = true;
public static bool ENERGY = true;
public static bool INDUSTRIALS = true;
public static bool TECHNOLOGY = true;
/// <summary>
/// Set Morningstar Industry Groups not allowed
/// Use Industry Group Code from:
/// https://www.quantconnect.com/docs/data-library/fundamentals#Fundamentals-Asset-Classification
/// example: MorningstarIndustryGroupCode.Banks (10320)
/// </summary>
public static int[] GROUPS_NOT_ALLOWED = new int [0];
/// <summary>
/// Set Morningstar Industries not allowed
/// Use Industry Codes from:
/// https://www.quantconnect.com/docs/data-library/fundamentals#Fundamentals-Asset-Classification
/// example: MorningstarIndustryCode.Gambling (10218038)
/// </summary>
public static int[] INDUSTRIES_NOT_ALLOWED = new int[0];
/// <summary>
/// Set the minimum number of days to leave a stock in a universe.
/// This helps with making the universe output more stable.
/// </summary>
public static int MIN_TIME_IN_UNIVERSE = 65;
/// <summary>
/// Set the minimum number of days with historical data
/// </summary>
public static int MIN_TRADING_DAYS = 200;
////////////////////////////////////////////////////
// ENTRY SIGNAL INPUTS
/// <summary>
/// The fast EMA period (number of trading days).
/// </summary>
public static int EMA_FAST_PERIOD = 50;
/// <summary>
/// The slow EMA period (number of trading days).
/// </summary>
public static int EMA_SLOW_PERIOD = 200;
/// <summary>
/// How many minutes prior to the open to check for new signals?
/// Ideally this is called AFTER the universe filters run!
/// </summary>
public static int SIGNAL_CHECK_MINUTES = 30;
////////////////////////////////////////////////////
// POSITION SIZING INPUTS
/// <summary>
/// Set the percentage of the portfolio to free up to avoid buying power issues.
/// decimal percent, e.g. 0.025=2.5% (default).
/// </summary>
public static float FREE_PORTFOLIO_VALUE_PCT = 0.025f;
/// <summary>
/// Set the max number of positions allowed
/// </summary>
public static int MAX_POSITIONS = 4;
/// <summary>
/// Calculate max % of portfolio per position
/// </summary>
public static float MAX_PCT_PER_POSITION = 1.0f / MAX_POSITIONS;
////////////////////////////////////////////////////
// EXIT SIGNAL INPUTS - stop loss
/// <summary>
/// Turn on/off stop loss
/// </summary>
public static bool STOP_LOSS = true;
/// <summary>
/// Set stop loss percentage as a decimal percent, e.g. 0.02 == 2.0%
/// </summary>
public static float SL_PCT = 0.05f;
/// <summary>
/// Turn on/off trailing stop. Starts and trails based on SL_PCT
/// </summary>
public static bool TRAILING_STOP = true;
/// <summary>
/// Turn on/off end of day exit.
/// </summary>
public static bool EOD_EXIT = false;
/// <summary>
/// When end of exit exit is desired, how many minutes prior to the market close should positions be liquidated?
/// </summary>
public static int EOD_EXIT_MINUTES = 15;
////////////////////////////////////////////////////
// EXIT SIGNAL INPUTS - Death Cross
/// <summary>
/// Turn on/off exiting on fast EMA crossing under the slow EMA
/// </summary>
public static bool EMA_CROSSUNDER_EXIT = true;
////////////////////////////////////////////////////
// EXIT SIGNAL INPUTS - RSI
/// <summary>
/// Set the RSI period (trading days).
/// </summary>
public static int RSI_PERIOD = 14;
/// <summary>
/// Turn on/off RSI exit
/// </summary>
public static bool RSI_EXIT = true;
/// <summary>
/// Long exit when RSI crosses below this value
/// </summary>
public static decimal RSI_EXIT_1_VALUE = 62m;
/// <summary>
/// decimal percent of initial position, 0.50=50.0%
/// </summary>
public static float RSI_EXIT_1_PCT = 0.5f;
/// <summary>
/// Long exit when RSI crosses below this value
/// </summary>
public static decimal RSI_EXIT_2_VALUE = 52m;
////////////////////////////////////////////////////
// EXIT SIGNAL INPUTS - days held
/// <summary>
/// Turn on/off days held exit
/// </summary>
public static bool DAYS_HELD_EXIT = false;
/// <summary>
/// How many days held until selling exit #1
/// </summary>
public static int DAYS_HELD_EXIT_1_VALUE = 2;
/// <summary>
/// What percentage of the initial order to sell on this exit. decimal percent, e.g. 0.25=25.0%
/// </summary>
public static float DAYS_HELD_EXIT_1_PCT = 0.25f;
/// <summary>
/// How many days held until selling exit #2
/// </summary>
public static int DAYS_HELD_EXIT_2_VALUE = 4;
/// <summary>
/// What percentage of the initial order to sell on this exit. decimal percent, e.g. 0.25=25.0%
/// </summary>
public static float DAYS_HELD_EXIT_2_PCT = 0.25f;
/// <summary>
/// How many days held until selling exit #3
/// </summary>
public static int DAYS_HELD_EXIT_3_VALUE = 6;
/// <summary>
/// What percentage of the initial order to sell on this exit. decimal percent, e.g. 0.25=25.0%
/// </summary>
public static float DAYS_HELD_EXIT_3_PCT = 0.25f;
/// <summary>
/// How many days held until selling exit #4
/// </summary>
public static int DAYS_HELD_EXIT_4_VALUE = 8;
////////////////////////////////////////////////////
// EXIT SIGNAL INPUTS - percent increase
/// <summary>
/// Turn on/off profit target
/// </summary>
public static bool PROFIT_TARGET = false;
/// <summary>
/// Set profit target percentage as a decimal percent, e.g. 0.05=5.0%
/// </summary>
public static float PT1_PCT = 0.05f;
/// <summary>
/// Set profit target order percentage as a decimal percent, e.g. 0.50=50.0%
/// </summary>
public static float PT1_ORDER_PCT = 0.5f;
/// <summary>
/// Set profit target percentage as a decimal percent, e.g. 0.07=7.0%
/// </summary>
public static float PT2_PCT = 0.07f;
/// <summary>
/// Set profit target order percentage as a decimal percent, e.g. 0.25=25.0%
/// </summary>
public static float PT2_ORDER_PCT = 0.25f;
/// <summary>
/// Set profit target percentage as a decimal percent, e.g. 0.09=9.0%
/// </summary>
public static float PT3_PCT = 0.09f;
////////////////////////////////////////////////////
// ROLLING WINDOWS, BENCHMARK DETAILS
/// <summary>
/// How many trading days to keep in the rolling windows. Set to how far back want to look at historical data.
/// </summary>
public static int INDICATOR_WINDOW_LENGTH = 10;
/// <summary>
/// Turn on/off using the custom benchmark plot on the strategy equity chart
/// </summary>
public static bool PLOT_BENCHMARK_ON_STRATEGY_EQUITY_CHART = true;
/// <summary>
/// Define benchmark equity. Currently set to not be able to trade the benchmark!
/// Also used for scheduling functions, <b>so make sure it has same trading hours as instruments traded.</b>
/// </summary>
public static string BENCHMARK = "SPY";
////////////////////////////////////////////////////
// LOGGING DETAILS (minimize logging due to QC limits!)
/// <summary>
/// print summary of coarse universe selection
/// </summary>
public static bool PRINT_COARSE = false;
/// <summary>
/// print summary of fine universe selection
/// </summary>
public static bool PRINT_FINE = false;
/// <summary>
/// print summary of daily entry signals
/// </summary>
public static bool PRINT_ENTRIES = true;
/// <summary>
/// print exit signals triggered
/// </summary>
public static bool PRINT_EXITS = false;
/// <summary>
/// print stats for new orders
/// </summary>
public static bool PRINT_ORDER_STATS = true;
/// <summary>
/// print new orders
/// </summary>
public static bool PRINT_ORDERS = false;
/// <summary>
/// print changes in stop loss, etc. orders
/// </summary>
public static bool PRINT_UPDATES = false;
/// <summary>
/// print settings for the run
/// </summary>
public static bool PRINT_SETTINGS = false;
/// <summary>
/// print equity data initialization
/// </summary>
public static bool PRINT_EQUITY_INIT = false;
/// <summary>
/// print verbose info
/// </summary>
public static bool PRINT_VERBOSE = false;
/// <summary>
/// print verbose info for only these stocks.
/// </summary>
public static string[] VERBOSE_SYMBOL = {"BKR"};
////////////////////////////////////////////////////
// END OF ALL USER INPUTS
// VALIDATE USER INPUTS - DO NOT CHANGE BELOW!!!
////////////////////////////////////////////////////
/// <summary>
/// List of the allowed exchanges
/// </summary>
public static List<string> ALLOWED_EXCHANGE;
/// <summary>
/// List of the sectors NOT allowed
/// </summary>
public static List<int> SECTORS_NOT_ALLOWED;
private static readonly string[] resolutions = {"SECOND", "MINUTE", "HOUR"};
private static string[] frequencies = { "daily", "weekly", "monthly" };
/// <summary>
/// The number of market warmup days required.
/// </summary>
public static int MARKET_WARMUP_DAYS;
/// <summary>
/// The number of calendar days required to match MARKET_WARMUP_DAYS. This number will be >= to MARKET_WARMUP_DAYS
/// as it uses rough additions to account for holidays.
/// </summary>
public static int CALENDAR_WARMUP_DAYS;
/// <summary>
/// Calculate the frequency of days that we can create a new plot.
/// </summary>
public static int PLOT_EVERY_DAYS;
/// <summary>
/// Initialize global lists, validate settings
/// </summary>
static GlobalSettings()
{
// first try the Linux name
TIMEZONE = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
// if that fails - the Windows name
if (TIMEZONE == null)
TIMEZONE = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
START_DATE = new DateTimeOffset(StartDate, TIMEZONE.GetUtcOffset(StartDate));
END_DATE = new DateTimeOffset(EndDate, TIMEZONE.GetUtcOffset(EndDate));
ALLOWED_EXCHANGE = new List<string>();
if (ARCX)
ALLOWED_EXCHANGE.Add("ARCX");
if (ASE)
ALLOWED_EXCHANGE.Add("ASE");
if (BATS)
ALLOWED_EXCHANGE.Add("BATS");
if (NAS)
ALLOWED_EXCHANGE.Add("NAS");
if (NYS)
ALLOWED_EXCHANGE.Add("NYS");
SECTORS_NOT_ALLOWED = new List<int>();
if (! BASIC_MATERIALS)
SECTORS_NOT_ALLOWED.Add(101);
if (! CONSUMER_CYCLICAL)
SECTORS_NOT_ALLOWED.Add(102);
if (! FINANCIAL_SERVICES)
SECTORS_NOT_ALLOWED.Add(103);
if (! REAL_ESTATE)
SECTORS_NOT_ALLOWED.Add(104);
if (! CONSUMER_DEFENSIVE)
SECTORS_NOT_ALLOWED.Add(205);
if (! HEALTHCARE)
SECTORS_NOT_ALLOWED.Add(206);
if (! UTILITIES)
SECTORS_NOT_ALLOWED.Add(207);
if (! COMMUNICATION_SERVICES)
SECTORS_NOT_ALLOWED.Add(308);
if (! ENERGY)
SECTORS_NOT_ALLOWED.Add(309);
if (! INDUSTRIALS)
SECTORS_NOT_ALLOWED.Add(310);
if (! TECHNOLOGY)
SECTORS_NOT_ALLOWED.Add(311);
DATA_RESOLUTION = DATA_RESOLUTION.ToUpper();
if (! resolutions.Contains(DATA_RESOLUTION))
throw new ApplicationException($"DATA_RESOLUTION = {DATA_RESOLUTION} is an invalid value.");
UNIVERSE_FREQUENCY = UNIVERSE_FREQUENCY.ToLower();
if (!frequencies.Contains(UNIVERSE_FREQUENCY))
throw new ApplicationException($"UNIVERSE_FREQUENCY = {UNIVERSE_FREQUENCY} is an invalid value.");
// For the benchmark plotting
int PLOT_LIMIT = 4000;
int BT_DAYS = (END_DATE - START_DATE).Days;
BT_DAYS = (int)Math.Ceiling(((float)BT_DAYS * 252f) / 365f);
PLOT_EVERY_DAYS = Math.Max(1, (int) Math.Ceiling((float)BT_DAYS / (float)PLOT_LIMIT));
// Calculate the number of days in the backtest
// Calculate the period to warm up the data
int BARS_PER_DAY = 1;
// Get the minimum number of bars required to fill all indicators
int MIN_BARS = Math.Max(Math.Max(EMA_FAST_PERIOD, EMA_SLOW_PERIOD), RSI_PERIOD) + INDICATOR_WINDOW_LENGTH;
MARKET_WARMUP_DAYS = (int) Math.Ceiling((float)MIN_BARS / (float)BARS_PER_DAY);
// Assume 252 market days per calendar year (or 365 calendar days), add a 10% and +2 buffer for holidays
CALENDAR_WARMUP_DAYS = (int) ((MARKET_WARMUP_DAYS * 1.1f) * 365f / 252f + 2f);
}
}
}
/*
* Copyright (C) 2022 by Lanikai Studios, Inc. - All Rights Reserved
*
* This code is not to be distributed to others, it is confidential information of Lanikai Studios.
*
* This code was built from open source Python code written by Aaron Eller - www.excelintrading.com
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using MathNet.Numerics;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using QuantConnect.Orders;
using QuantConnect.Securities;
using static Lanikai.GlobalSettings;
namespace Lanikai
{
/// <summary>
/// Class to store data for a specific symbol.
/// </summary>
public class SymbolData
{
/// <summary>The parent algorithm that owns this object.</summary>
private readonly LanikaiQCAlgorithm algo;
/// <summary>
/// The symbol this object operates on.
/// The underlying text symbol is symbol.ID.Symbol
/// </summary>
public Symbol Symbol { get; }
/// <summary>The desired daily bar consolidator for the symbol.</summary>
public TradeBarConsolidator Consolidator { get; private set; }
/// <summary>The fast EMA indicator. Used to find when we hit the Golden Cross.</summary>
private ExponentialMovingAverage ema_fast;
/// <summary>The slow EMA indicator. Used to find when we hit the Golden Cross.</summary>
private ExponentialMovingAverage ema_slow;
/// <summary>The RSI indicator. Used optionally to determine when to sell.</summary>
private RelativeStrengthIndex rsi;
// bugbug - correct naming of vars.
/// <summary>A boolean for each day true if the fast EMA isn > the slow EMA, false otherwise.
/// Used to determine when to buy.</summary>
private RollingWindow<bool> fast_ema_gt_slow_ema;
/// <summary>Create a rolling window of the last closing prices.
/// Optionally used to take profits based on the curve of recent closes.</summary>
private RollingWindow<decimal> window_closes;
/// <summary>Create a rolling window of the recent fast EMA values.
/// Optionally used to take profits based on the curve of recent EMA values.</summary>
public RollingWindow<decimal> window_ema_fast;
/// <summary>Create a rolling window of the recent slow EMA values.
/// Optionally used to take profits based on the curve of recent EMA values.</summary>
public RollingWindow<decimal> window_ema_slow;
/// <summary>
/// Create a rolling window of the recent RSI values.
/// Optionally used to take profits based on the curve of recent RSI values.
/// RSI has a value of 0 ... 100. Moving up is bullish, moving down bearish.
/// </summary>
private RollingWindow<double> window_rsi;
/// <summary>Create a rolling window of the recent trade bars (1 per day).
/// Optionally used to take profits based on the curve of recent trade bar values.</summary>
private RollingWindow<TradeBar> window_bar;
/// <summary>All of the indicators and rolling windows.
/// This is weird as there is no common base class, but both do have an IsReady property.</summary>
private List<object> indicators;
/// <summary>The exchange hours for this security.</summary>
private SecurityExchangeHours exchangeHours;
/// <summary>The time the market opens for this stock when the market is open for the full day.</summary>
private DateTime mktOpenTime;
/// <summary>The time the market closes for this stock when the market is open for the full day.</summary>
private DateTime mktCloseTime;
private decimal? cost_basis;
///<summary> The low price today. Used for adjusting the stop loss.</summary>
private decimal? todaysLow;
private OrderTicket sl_order;
private decimal? sl_price;
private OrderTicket pt_order1;
private OrderTicket pt_order2;
private OrderTicket pt_order3;
/// <summary>When selling part (profit taking) this is set to that percentage to only do that once. Set to 0 when first purchasing.</summary>
private float percentSold;
private int days_held;
public SymbolData(LanikaiQCAlgorithm algo, Symbol symbol)
{
// Save the references
this.algo = algo;
this.Symbol = symbol;
// Get the symbol's exchange market info
GetExchangeInfo();
// Initialize strategy specific variables
InitializeStrategyVariables();
// Add the bars and indicators required
AddBarsIndicators();
}
/// <summary>
/// Get the security's exchange info.
/// </summary>
public void GetExchangeInfo()
{
exchangeHours = algo.Securities[Symbol].Exchange.Hours;
// using a date we know the market was open the standard hours
// every market day should be these times OR LESS.
DateTime jan4 = new DateTime(2021, 1, 4);
DateTimeOffset date = new DateTimeOffset(jan4, TIMEZONE.GetUtcOffset(jan4));
// Save the full day market open and close times
mktOpenTime = exchangeHours.GetNextMarketOpen(date.DateTime, false);
mktCloseTime = exchangeHours.GetNextMarketClose(date.DateTime, false);
}
/// <summary>
/// Initialize the strategy variables.
/// </summary>
/// <returns></returns>
public void InitializeStrategyVariables()
{
// Initialize order variables
ResetOrderVariables();
}
/// <summary>
/// Reset order variables for the strategy.
/// </summary>
public void ResetOrderVariables()
{
cost_basis = null;
todaysLow = null;
sl_order = null;
sl_price = null;
pt_order1 = null;
pt_order2 = null;
pt_order3 = null;
days_held = 0;
}
/// <summary>
/// Set up the consolidators, indicators, & rolling windows.
/// </summary>
public void AddBarsIndicators()
{
// Create the desired daily bar consolidator for the symbol
Consolidator = new TradeBarConsolidator(DailyUSEquityCalendar);
// Create an event handler to be called on each new consolidated bar
Consolidator.DataConsolidated += OnDataConsolidated;
// Link the consolidator with our symbol and add it to the algo manager
algo.SubscriptionManager.AddConsolidator(Symbol, Consolidator);
// Create indicators to be based on the desired consolidated bars
ema_fast = new ExponentialMovingAverage(EMA_FAST_PERIOD);
ema_slow = new ExponentialMovingAverage(EMA_SLOW_PERIOD);
rsi = new RelativeStrengthIndex(RSI_PERIOD);
// Create rolling windows of whether the fast EMA is > or < slow EMA
// format: RollingWindow[object type](length)
fast_ema_gt_slow_ema = new RollingWindow<bool>(2);
// Create a rolling window of the last closing prices
// used to make sure there is enough data to start trading
window_closes = new RollingWindow<decimal>(MIN_TRADING_DAYS);
// Create rolling windows for desired data
window_ema_fast = new RollingWindow<decimal>(INDICATOR_WINDOW_LENGTH);
window_ema_slow = new RollingWindow<decimal>(INDICATOR_WINDOW_LENGTH);
window_rsi = new RollingWindow<double>(INDICATOR_WINDOW_LENGTH);
// we need EMA_SLOW_PERIOD because we rebuild the indicators each day.
window_bar = new RollingWindow<TradeBar>(EMA_SLOW_PERIOD);
// Keep a list of all indicators & rolling windows - for indicators.IsReady property
indicators = new List<object>
{
ema_fast,
ema_slow,
rsi,
fast_ema_gt_slow_ema,
window_closes,
window_ema_fast,
window_ema_slow,
window_rsi,
window_bar
};
// Warm up the indicators with historical data
WarmupIndicators();
}
private CalendarInfo DailyUSEquityCalendar(DateTime dateTime) {
// Set up daily consolidator calendar info for the US equity market.
// This should return a start datetime object that is timezone unaware
// with a valid date/time for the desired securities' exchange's time zone.
// Create a datetime.datetime object to represent the market open
DateTime start = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day,
mktOpenTime.Hour, mktOpenTime.Minute,
0, 0, dateTime.Kind);
// Get today's end time from the SecurityExchangeHours Class object
// exchange_class = self.algo.Securities[self.symbol].Exchange
// exchange_hrs_class = self.algo.Securities[self.symbol].Exchange.Hours
// which is saved as self.exchange_hours
DateTime end = exchangeHours.GetNextMarketClose(start, false);
// Return the start datetime and the consolidation period
return new CalendarInfo(start, end-start);
}
/// <summary>
/// Warm up indicators using historical data.
/// </summary>
public void WarmupIndicators()
{
// Get historical data
IEnumerable<Slice> history = algo.History(new List<Symbol> {Symbol}, MARKET_WARMUP_DAYS, Resolution.Daily);
int num = 0;
foreach (Slice slice in history)
{
// bar must exist
TradeBar bar = slice.Bars[Symbol];
if (bar == null)
continue;
UpdateIndicators(bar);
num++;
}
if (PRINT_EQUITY_INIT && ((! IndicatorsReady) || VERBOSE_SYMBOL.Contains(Symbol.Value)))
algo.Log($"{Symbol.Value} indicators " + (IndicatorsReady ? "" : "not ") + $"ready {this}, history.Count = {num}");
}
/// <summary>
/// Event handler that fires when a new piece of data is produced.
/// </summary>
/// <param name="sender"></param>
/// <param name="bar">The newly consolidated data.</param>
private void OnDataConsolidated(object sender, TradeBar bar)
{
// Manually update all of the indicators
UpdateIndicators(bar);
}
/// <summary>
/// true if all of the indicators used are ready (warmed up).
/// Once true, should never revert to false.
/// </summary>
public bool IndicatorsReady
{
get
{
// Return False if any indicator is not ready
foreach (var indicator in indicators)
{
// can be an indicator or a rolling window - so use reflection
PropertyInfo prop = indicator.GetType().GetProperty("IsReady");
if ((prop != null) && !((bool) prop.GetValue(indicator)))
return false;
}
// Otherwise all indicators are ready, so return True
return true;
}
}
/// <summary>
/// true if there is an EMA cross-over today.
/// </summary>
public bool EmaCrossOver
{
get
{
// Need latest value true (fast > slow ema)
// and previous one false (fast <= slow ema)
return fast_ema_gt_slow_ema[0] && !fast_ema_gt_slow_ema[1];
}
}
/// <summary>
/// true if there is an EMA cross-under today.
/// </summary>
public bool EmaCrossUnder
{
get
{
// Need latest value false (fast < slow ema)
// and previous one true (fast >= slow ema)
return (!fast_ema_gt_slow_ema[0]) && fast_ema_gt_slow_ema[1];
}
}
/// <summary>
/// The percent difference between the fast and slow EMAs.
/// </summary>
public decimal FastSlowPctDifference
{
get
{
return (ema_fast.Current.Value - ema_slow.Current.Value) / ema_slow.Current.Value;
}
}
/// <summary>
/// The current quantity held in the portfolio.
/// </summary>
public decimal CurrentQuantity
{
get
{
return algo.Portfolio[Symbol].Quantity;
}
}
/// <summary>
/// true if there is a valid long entry signal.
/// </summary>
public bool LongEntrySignal
{
get {
if (EmaCrossOver && (! RSILiquidate))
{
if (PRINT_ENTRIES)
algo.Log($"{Symbol.Value} LONG ENTRY SIGNAL: EMA CROSSOVER; EMA fast: {RollingWindowToString(window_ema_fast, FastEMASlope)}; EMA slow: {RollingWindowToString(window_ema_slow, SlowEMASlope)}; RSI: {RSIRollingWindowToString(window_rsi, RSISlope)}");
return true;
}
return false;
}
}
/// <summary>
/// Check if there are any valid long exit signals.
/// </summary>
public void LongExitSignalChecks()
{
// make a switch
// Trigger on an EMA cross-under
if (EMA_CROSSUNDER_EXIT && EmaCrossUnder)
{
if (PRINT_EXITS)
algo.Log($"{Symbol.Value} LONG EXIT SIGNAL: EMA CROSSUNDER");
algo.Log($"***** fast_ema_gt_slow_ema[0] = {fast_ema_gt_slow_ema[0]}; EndTime={window_bar[0].EndTime}");
algo.Log($"***** fast_ema_gt_slow_ema[1] = {fast_ema_gt_slow_ema[1]}; EndTime={window_bar[1].EndTime}");
algo.Log($"***** RSI={RSIRollingWindowToString(window_rsi, RSISlope)}");
algo.Log($"***** EMA fast={ToString(ema_fast)}");
algo.Log($"***** EMA slow={ToString(ema_slow)}");
algo.Liquidate(Symbol);
return;
}
// Check for RSI exits
if (RSI_EXIT)
{
// if the RSI is falling, then we exit if fallen below the exit values
if (RSISlope <= 0) {
if (PRINT_VERBOSE)
algo.Log($"{Symbol.Value} RSI is falling {RSIRollingWindowToString(window_rsi, RSISlope)}");
// Check for RSI below the RSI_EXIT_1_VALUE
if (rsi.Current.Value < algo.RsiExit1Value && percentSold < RSI_EXIT_1_PCT)
{
percentSold = RSI_EXIT_1_PCT;
if (PRINT_EXITS)
algo.Log($"{Symbol.Value} LONG EXIT SIGNAL: RSI ({rsi.Current.Value.ToString("F2")}) < {algo.RsiExit1Value}. Now closing {RSI_EXIT_1_PCT * 100.0}% of the position.");
var exitQty = (int) (-CurrentQuantity * (decimal) RSI_EXIT_1_PCT);
algo.MarketOrder(Symbol, exitQty, tag: $"sell {RSI_EXIT_1_PCT * 100}%, dropped below RSI_EXIT_1_VALUE");
}
// Check for RSI below the RSI_EXIT_2_VALUE
if (rsi.Current.Value < algo.RsiExit2Value && percentSold < 1)
{
percentSold = 1;
if (PRINT_EXITS)
algo.Log($"{Symbol.Value} LONG EXIT SIGNAL: RSI ({rsi.Current.Value.ToString("F2")}) < {algo.RsiExit2Value}. Now closing 100% of the position.");
algo.Liquidate(Symbol, tag: $"liquidate, dropped below RSI_EXIT_2_VALUE");
}
} else
if (PRINT_VERBOSE)
algo.Log($"{Symbol.Value} RSI is climbing {RSIRollingWindowToString(window_rsi, RSISlope)}");
}
// Check for days held exits
if (DAYS_HELD_EXIT)
{
Utils.Trap();
if (days_held >= DAYS_HELD_EXIT_1_VALUE && percentSold < DAYS_HELD_EXIT_1_PCT)
{
Utils.Trap();
percentSold = DAYS_HELD_EXIT_1_PCT;
if (PRINT_EXITS)
algo.Log(
$"{Symbol.Value} LONG EXIT SIGNAL: Days held ({days_held}) = {DAYS_HELD_EXIT_1_VALUE}. Now closing {DAYS_HELD_EXIT_1_PCT * 100.0}% of the position.");
var exitQty = (int) (-CurrentQuantity * (decimal) DAYS_HELD_EXIT_1_PCT);
algo.MarketOrder(Symbol, exitQty);
}
if (days_held >= DAYS_HELD_EXIT_2_VALUE && percentSold < DAYS_HELD_EXIT_2_PCT)
{
Utils.Trap();
percentSold = DAYS_HELD_EXIT_2_PCT;
if (PRINT_EXITS)
algo.Log(
$"{Symbol.Value} LONG EXIT SIGNAL: Days held ({days_held}) = {DAYS_HELD_EXIT_2_VALUE}. Now closing {DAYS_HELD_EXIT_2_PCT * 100.0}% of the position.");
var exitQty = (int) (-CurrentQuantity * (decimal) DAYS_HELD_EXIT_2_PCT);
algo.MarketOrder(Symbol, exitQty);
}
if (days_held == DAYS_HELD_EXIT_3_VALUE && percentSold < 1)
{
Utils.Trap();
percentSold = 1;
if (PRINT_EXITS)
algo.Log(
$"{Symbol.Value} LONG EXIT SIGNAL: Days held ({days_held}) = {DAYS_HELD_EXIT_3_VALUE}. Now closing 100% of the position.");
algo.Liquidate(Symbol);
}
}
}
/// <summary>
/// Manually update all of the symbol's indicators.
/// </summary>
/// <param name="bar">The trade bar for this symbol for the period just ended.</param>
public void UpdateIndicators(TradeBar bar)
{
// need this first - to recalc the indicators on the latest data
window_bar.Add(bar);
// Update the EMAs and RSI
// need to recalc if the indicators are not discarding the oldest value when Update() is called
// no need if warming up
// if window_bar is not fully populated (new stock) then can't do this.
if ((! algo.IsWarmingUp) && window_bar.IsReady) {
ema_fast.Reset();
for (int index=EMA_FAST_PERIOD-1; index>=0; index--) {
TradeBar _bar = window_bar[index];
ema_fast.Update(_bar.EndTime, _bar.Close);
}
ema_slow.Reset();
for (int index=EMA_SLOW_PERIOD-1; index>=0; index--) {
TradeBar _bar = window_bar[index];
ema_slow.Update(_bar.EndTime, _bar.Close);
}
rsi.Reset();
for (int index=RSI_PERIOD-1; index>=0; index--) {
TradeBar _bar = window_bar[index];
rsi.Update(_bar.EndTime, _bar.Close);
}
}
else
algo.Log($"***** {Symbol.Value} can't calculate indicators @ {bar.EndTime}. IsWarmingUp={algo.IsWarmingUp}, IsReady={window_bar.IsReady}");
// Update rolling windows
window_ema_fast.Add(ema_fast.Current.Value);
window_ema_slow.Add(ema_slow.Current.Value);
fast_ema_gt_slow_ema.Add(ema_fast.Current.Value > ema_slow.Current.Value);
window_rsi.Add((float)rsi.Current.Value);
window_closes.Add(bar.Close);
// Update the trades best price if trailing stop is used
if (TRAILING_STOP)
UpdateTradeBestPrice(bar);
// Increment days held counter if there is a position
if (CurrentQuantity != 0) {
days_held += 1;
// track the best price
algo.Log($"***** {Symbol.Value} update highest @ {bar.EndTime}");
highestHigh = highestHigh == null ? bar.High : Math.Max(bar.High, highestHigh.Value);
highestOpen = highestOpen == null ? bar.Open : Math.Max(bar.Open, highestOpen.Value);
}
}
/// <summary>
/// Update the trade's best price if there is an open position.
/// </summary>
/// <param name="bar">The trade bar for this symbol for the period just ended.</param>
public void UpdateTradeBestPrice(TradeBar bar)
{
// Check if there is an open position
if (CurrentQuantity <= 0)
return;
// Update the trade best price when appropriate
if (todaysLow == null)
{
Utils.Trap();
// Should be set, so raise error to debug
// throw new ApplicationException("what's going on here - bugbug");
algo.Log("***** what's going on here?");
return;
}
// we only move the stop loss up.
if (bar.Low > todaysLow)
{
todaysLow = bar.Low;
// Get the current stop loss price
decimal? slPrice = StopPrice;
// Check for increase in stop loss price
if ((slPrice != null) && slPrice > this.sl_price)
{
// Update the stop loss order's price
UpdateOrderStopPrice(sl_order, slPrice.Value);
sl_price = slPrice;
}
}
}
/// <summary>
/// The current stop loss price. This is SL_PCT below the lowest price from today's trades.
/// The caller of this property needs to determine if this is a higher value than the present
/// stop loss and only set to this if it's an increase (or decrease for shorting).
/// </summary>
public decimal? StopPrice
{
get
{
if (todaysLow == null)
return null;
// long
if (CurrentQuantity > 0)
return Math.Round(todaysLow.Value * (decimal) (1 - SL_PCT), 2);
// short
if (CurrentQuantity < 0)
return Math.Round(todaysLow.Value * (decimal) (1 + SL_PCT), 2);
Utils.Trap();
return null;
}
}
/// <summary>
/// Cancel any open exit orders.
/// </summary>
public void CancelExitOrders()
{
// Cancel open profit target order #1, if one
if (pt_order1 != null)
{
Utils.Trap();
if (PRINT_ORDERS)
algo.Log($"Cancelling {Symbol.Value} open profit target #1 order {pt_order1}.");
try
{
pt_order1.Cancel();
pt_order1 = null;
}
catch
{
algo.Log($"Error trying to cancel {Symbol.Value} profit target #1 order {pt_order1}.");
}
}
// Cancel open profit target order #2, if one
if (pt_order2 != null)
{
Utils.Trap();
if (PRINT_ORDERS)
algo.Log($"Cancelling {Symbol.Value} open profit target #2 order {pt_order2}.");
try
{
pt_order2.Cancel();
pt_order2 = null;
}
catch
{
algo.Log($"Error trying to cancel {Symbol.Value} profit target #2 order {pt_order2}.");
}
}
// Cancel open profit target order #3, if one
if (pt_order3 != null)
{
Utils.Trap();
if (PRINT_ORDERS)
algo.Log($"Cancelling {Symbol.Value} open profit target #3 order {pt_order3}.");
try
{
pt_order3.Cancel();
pt_order3 = null;
}
catch
{
algo.Log($"Error trying to cancel {Symbol.Value} profit target #3 order {pt_order3}.");
}
// Cancel open stop order, if one
if (sl_order != null)
{
Utils.Trap();
if (PRINT_ORDERS)
algo.Log($"Cancelling {Symbol.Value} open stop order {sl_order}.");
try
{
sl_order.Cancel();
sl_order = null;
}
catch
{
algo.Log($"Error trying to cancel {Symbol.Value} stop loss order {sl_order}.");
}
}
}
// Reset order variables
ResetOrderVariables();
}
/// <summary>
/// Get the profit target exit quantities for orders #1, #2, #3.
/// </summary>
/// <param name="initial">true if first time calculating the order quantity. false if updating the quantity.</param>
/// <returns>The profit taking order quantities for profit taking level 1, level 2, & level 3.</returns>
public Tuple<int, int, int> GetPtExitQuantities(bool initial = false)
{
// need as float to multiply times the percentages.
float exitQty = (float) -CurrentQuantity;
// Initialize each to 0
int pt1ExitQty = 0;
int pt2ExitQty = 0;
int pt3ExitQty = 0;
// Get PT1, PT2, PT3 exit quantities
if ((initial && PROFIT_TARGET) || (pt_order1 != null))
pt1ExitQty = Convert.ToInt32(exitQty * PT1_ORDER_PCT);
if ((initial && PROFIT_TARGET) || (pt_order2 != null))
pt2ExitQty = Convert.ToInt32(exitQty * PT2_ORDER_PCT);
if ((initial && PROFIT_TARGET) || (pt_order3 != null))
pt3ExitQty = (int) (exitQty - (pt1ExitQty + pt2ExitQty));
// Return the quantities
return Tuple.Create(pt1ExitQty, pt2ExitQty, pt3ExitQty);
}
/// <summary>
/// Update any open exit orders.
/// </summary>
public void UpdateExitOrders()
{
// Get the desired profit target exit order quantities
var exitQuantities = GetPtExitQuantities();
// Update open profit target order #1, if one
if (pt_order1 != null)
{
Utils.Trap();
if (PRINT_ORDERS)
algo.Log($"Updating {Symbol.Value} open profit target #1 order qty to {exitQuantities.Item1}.");
UpdateOrderQty(pt_order1, exitQuantities.Item1, $"updating profit taking quantity to {exitQuantities.Item1}");
}
// Update open profit target order #2, if one
if (pt_order2 != null)
{
Utils.Trap();
if (PRINT_ORDERS)
algo.Log($"Updating {Symbol.Value} open profit target #2 order qty to {exitQuantities.Item2}.");
UpdateOrderQty(pt_order2, exitQuantities.Item2, $"updating profit taking quantity to {exitQuantities.Item2}");
}
// Update open profit target order #3, if one
if (pt_order3 != null)
{
Utils.Trap();
if (PRINT_ORDERS)
algo.Log($"Updating {Symbol.Value} open profit target #3 order qty to {exitQuantities.Item3}.");
UpdateOrderQty(pt_order3, exitQuantities.Item3, $"updating profit taking quantity to {exitQuantities.Item3}");
}
// Update open stop loss order, if one
if (sl_order != null)
{
if (PRINT_ORDERS)
algo.Log($"Updating {Symbol.Value} open stop loss order qty to {-CurrentQuantity}.");
UpdateOrderQty(sl_order, -CurrentQuantity, $"updating stop loss quantity to {-CurrentQuantity}");
}
}
/// <summary>
/// Update the desired order ticket's qty.
/// </summary>
/// <param name="ticket">The ticket to update.</param>
/// <param name="qty">The quantity to change it to.</param>
public void UpdateOrderQty(OrderTicket ticket, decimal qty, string tag)
{
if (PRINT_UPDATES)
algo.Log($"{Symbol.Value} {tag}");
ticket.UpdateQuantity(qty, tag: tag);
}
/// <summary>
/// Update the desired order ticket's stop price.
/// </summary>
/// <param name="ticket">The ticket to update.</param>
/// <param name="price">The new price for the ticket.</param>
public void UpdateOrderStopPrice(OrderTicket ticket, decimal price)
{
if (PRINT_UPDATES)
algo.Log($"{Symbol.Value} updating stop price to {price}");
ticket.UpdateStopPrice(price, tag: $"updating stop price to {price}");
}
/// <summary>
/// Update the desired order ticket's limit price.
/// </summary>
/// <param name="ticket">The ticket to update.</param>
/// <param name="price">The new price for the ticket.</param>
public void UpdateOrderLimitPrice(OrderTicket ticket, decimal price)
{
Utils.Trap();
if (PRINT_UPDATES)
algo.Log($"{Symbol.Value} updating limit price to {price}");
ticket.UpdateLimitPrice(price, tag: $"updating limit price to {price}");
}
/// <summary>
/// Place the desired stop loss order. This assumes that StopPrice will not return a null.
/// </summary>
public void PlaceStopLossOrder()
{
// Save and place the stop loss order
sl_price = StopPrice;
sl_order = algo.StopMarketOrder(Symbol, (int) -CurrentQuantity, sl_price.Value, tag: $"initial stop price of {sl_price.Value}");
if (PRINT_ORDERS)
algo.Log($"{Symbol.Value} stop order placed at {sl_price.Value}");
}
/// <summary>
/// Place the desired profit target orders.
/// </summary>
public void PlaceProfitTakingOrders()
{
// Get the desired profit target exit order quantities
var exitQuantities = GetPtExitQuantities(initial: true);
if (PROFIT_TARGET && cost_basis != null && CurrentQuantity != 0)
{
Utils.Trap();
// Check for placing a profit target order #1
// Calculate the profit target price
decimal ptPrice = 0;
// long
if (CurrentQuantity > 0)
ptPrice = Math.Round(cost_basis.Value * (decimal) (1 + PT1_PCT), 2);
// short
else if (CurrentQuantity < 0)
ptPrice = Math.Round(cost_basis.Value * (decimal) (1 - PT1_PCT), 2);
// Place and save the stop loss order
pt_order1 = algo.LimitOrder(Symbol, exitQuantities.Item1, ptPrice);
if (PRINT_ORDERS)
algo.Log($"{Symbol.Value} profit target #1 order: {exitQuantities.Item1} at {ptPrice}");
// Check for placing a profit target order #2
// Calculate the profit target price
ptPrice = 0;
// long
if (CurrentQuantity > 0)
ptPrice = Math.Round(cost_basis.Value * (decimal) (1 + PT2_PCT), 2);
// short
else if (CurrentQuantity < 0)
ptPrice = Math.Round(cost_basis.Value * (decimal) (1 - PT2_PCT), 2);
// Place and save the stop loss order
pt_order2 = algo.LimitOrder(Symbol, exitQuantities.Item2, ptPrice);
if (PRINT_ORDERS)
algo.Log($"{Symbol.Value} profit target #2 order: {exitQuantities.Item2} at {ptPrice}");
// Check for placing a profit target order #3
// Calculate the profit target price
ptPrice = 0;
// long
if (CurrentQuantity > 0)
ptPrice = Math.Round(cost_basis.Value * (decimal) (1 + PT3_PCT), 2);
// short
else if (CurrentQuantity < 0)
ptPrice = Math.Round(cost_basis.Value * (decimal) (1 - PT3_PCT), 2);
// Place and save the stop loss order
pt_order3 = algo.LimitOrder(Symbol, exitQuantities.Item3, ptPrice);
if (PRINT_ORDERS)
algo.Log($"{Symbol.Value} profit target #3 order: {exitQuantities.Item3} at {ptPrice}");
}
}
// when order placed
private double fastSlope;
private double slowSlope;
private double rsiSlope;
private decimal purchasePrice;
private decimal? highestOpen;
private decimal? highestHigh;
/// <summary>
/// Order fill event handler. On an order fill update the resulting information is passed to this method.
/// </summary>
/// <param name="orderEvent">Order event details containing details of the evemts</param>
public void OnOrderEvent(OrderEvent orderEvent)
{
// Get the order details
var order = algo.Transactions.GetOrderById(orderEvent.OrderId);
var order_qty = Convert.ToInt32(order.Quantity);
var avg_fill = orderEvent.FillPrice;
// Get current qty of symbol
var qty = CurrentQuantity;
// Check for entry order - Entry order filled
if (order_qty == qty)
{
// reset stats for new purchase
fastSlope = FastEMASlope;
slowSlope = SlowEMASlope;
rsiSlope = RSISlope;
purchasePrice = avg_fill;
highestOpen = highestHigh = null;
// new purchase so reset
percentSold = 0;
if (PRINT_ORDERS || PRINT_ORDER_STATS)
algo.Log($"{Symbol.Value} entry order filled: {order_qty} at {avg_fill}");
if (PRINT_ORDER_STATS)
algo.Log(ToString());
// Save the cost basis and trade best price
cost_basis = avg_fill;
// use fill until next call where we can get Low.
todaysLow = avg_fill;
// Place a stop loss order when desired
if (STOP_LOSS || TRAILING_STOP)
PlaceStopLossOrder();
else
Utils.Trap();
// Place profit target orders when desired
PlaceProfitTakingOrders();
// Done with event, so return
return;
}
if (PRINT_ORDER_STATS) {
algo.Log($"{Symbol.Value} " + (avg_fill > purchasePrice ? "profit" : "LOSS") + $" of {avg_fill - purchasePrice}, price={avg_fill}. Held: HighestOpen={highestOpen}, HighestHigh={highestHigh}. Purchase: price={purchasePrice}, fastEMAslope={fastSlope}, slowEMAslope={slowSlope}, RSIslope={rsiSlope}");
algo.Log(ToString());
}
// Check for stop order
if (sl_order != null)
{
// Check for matching order ids
if (orderEvent.OrderId == sl_order.OrderId)
{
// Stop order filled
if (PRINT_ORDERS || PRINT_ORDER_STATS)
algo.Log($"{Symbol.Value} stop order filled: {order_qty} at {avg_fill}");
// Cancel open exit orders
CancelExitOrders();
// Done with event, so return
return;
}
}
// Check for profit target order #1
if (pt_order1 != null)
{
Utils.Trap();
// Check for matching order ids - Profit target order filled
if (orderEvent.OrderId == pt_order1.OrderId)
{
Utils.Trap();
if (PRINT_ORDERS || PRINT_ORDER_STATS)
algo.Log($"{Symbol.Value} profit target order #1 filled: {order_qty} at {avg_fill}");
// Check if the position is still open
if (qty != 0)
{
Utils.Trap();
// Update open exit orders
UpdateExitOrders();
}
else
{
Utils.Trap();
// Cancel open exit orders
CancelExitOrders();
}
// Set order to None
pt_order1 = null;
// Done with event, so return
return;
}
Utils.Trap();
}
// Check for profit target order #2
if (pt_order2 != null)
{
Utils.Trap();
// Check for matching order ids - Profit target order filled
if (orderEvent.OrderId == pt_order2.OrderId)
{
Utils.Trap();
if (PRINT_ORDERS || PRINT_ORDER_STATS)
algo.Log("${Symbol.Value} profit target order #2 filled: {order_qty} at {avg_fill}");
// Check if the position is still open
if (qty != 0)
{
Utils.Trap();
// Update open exit orders
UpdateExitOrders();
}
else
{
Utils.Trap();
// Cancel open exit orders
CancelExitOrders();
}
// Set order to None
pt_order2 = null;
// Done with event, so return
return;
}
}
// Check for profit target order #3
if (pt_order3 != null)
{
Utils.Trap();
// Check for matching order ids - Profit target order filled
if (orderEvent.OrderId == pt_order3.OrderId)
{
Utils.Trap();
if (PRINT_ORDERS || PRINT_ORDER_STATS)
algo.Log($"{Symbol.Value} profit target order #3 filled: {order_qty} at {avg_fill}");
// Check if the position is still open
if (qty != 0)
{
Utils.Trap();
// Update open exit orders
UpdateExitOrders();
}
else
{
Utils.Trap();
// Cancel open exit orders
CancelExitOrders();
}
// Set order to None
pt_order3 = null;
// Done with event, so return
return;
}
}
// Check for full exit order - Exit order filled
if (qty == 0)
{
// liquidated so reset
percentSold = 0;
if (PRINT_ORDERS || PRINT_ORDER_STATS)
algo.Log($"{Symbol.Value} full exit order filled: {order_qty} at {avg_fill}");
// Cancel open exit orders
CancelExitOrders();
// Done with event, so return
return;
}
// Check for pyramid entry order (qty and order_qty have the same signs)
if (qty * order_qty > 0)
{
Utils.Trap();
// This strategy doesn't have pyramid entries, so raise error
// throw new ApplicationException("pyramid entry order");
algo.Log($"***** pyramid entry order for {Symbol.Value}, qty={qty}, order_qty={order_qty}");
algo.Liquidate(this.Symbol);
return;
}
// Otherwise a partial exit order - Partial exit order filled
if (PRINT_ORDERS || PRINT_ORDER_STATS)
algo.Log($"{Symbol.Value} partial exit order filled: {order_qty} at {avg_fill}");
// Update open exit orders
UpdateExitOrders();
}
/// <summary>
/// Returns the slope of the fast EMA over the last 5 days.
/// </summary>
public double FastEMASlope {
get {
double [] xData = {1, 2, 3, 4, 5};
double [] yData = Array.ConvertAll(window_ema_fast.Take(5).Reverse().ToArray(), x => (double)x);
Tuple<double, double> line = Fit.Line(xData, yData);
return line.Item2;
}
}
/// <summary>
/// Returns the slope of the slow EMA over the last 5 days.
/// </summary>
public double SlowEMASlope {
get {
double [] xData = {1, 2, 3, 4, 5};
double [] yData = Array.ConvertAll(window_ema_slow.Take(5).Reverse().ToArray(), x => (double)x);
Tuple<double, double> line = Fit.Line(xData, yData);
return line.Item2;
}
}
/// <summary>
/// Returns the slope of the RSI over the last 5 days.
/// </summary>
public double RSISlope {
get {
double [] xData = {1, 2, 3, 4, 5};
double [] yData = window_rsi.Take(5).Reverse().ToArray();
Tuple<double, double> line = Fit.Line(xData, yData);
return line.Item2;
}
}
///<summary>
/// Returns true if the RSI values are set to liquidate (falling and below RSI_EXIT_2).
///</summary>
public bool RSILiquidate {
get {
return RSISlope <= 0 && rsi.Current.Value < algo.RsiExit2Value;
}
}
public override string ToString()
{
return $"Symbol:{Symbol.Value}; EMAFast:{ToString(ema_fast)}; EMASlow:{ToString(ema_slow)}; RSI:{ToString(rsi)}; FastGTSlow={ToString(fast_ema_gt_slow_ema)}; " +
$" winCloses:{ToString(window_closes)}; winEmaFast:{ToString(window_ema_fast)}; winEmaSlow:{ToString(window_ema_slow)}; winRsi:{ToString(window_rsi)}; winBar:{ToString(window_bar)}";
}
private string ToString(Indicator indicator) {
return $"[IsReady:{indicator.IsReady}; Value:{indicator}";
}
private string ToString<T>(RollingWindow<T> window) {
return $"[IsReady:{window.IsReady}; Count:{window.Count}/{window.Size}" + (window.Count == 0 ? "" : $"[0]: {window[0]}") + "]";
}
private string ToString(RollingWindow<decimal> window) {
return $"[IsReady:{window.IsReady}; Count:{window.Count}/{window.Size}" + (window.Count == 0 ? "" : $"[0]: {window[0].ToString("C")}") + "]";
}
private string ToString(RollingWindow<double> window) {
return $"[IsReady:{window.IsReady}; Count:{window.Count}/{window.Size}" + (window.Count == 0 ? "" : $"[0]: {window[0].ToString("F2")}") + "]";
}
public static string RollingWindowToString(RollingWindow<decimal> window, double? slope) {
StringBuilder buf = new StringBuilder("[");
for (int index=window.Size-1; index>=0; index--)
buf.Append($"{window[index]:C}, ");
buf.Remove(buf.Length - 2, 2);
if (slope != null)
buf.Append($" = {slope:F4}");
buf.Append("]");
return buf.ToString();
}
private static string RSIRollingWindowToString(RollingWindow<double> window, double? slope) {
StringBuilder buf = new StringBuilder("[");
for (int index=window.Size-1; index>=0; index--)
buf.Append($"{window[index]:F2}, ");
buf.Remove(buf.Length - 2, 2);
if (slope != null)
buf.Append($" = {slope:F4}");
buf.Append("]");
return buf.ToString();
}
}
}