Overall Statistics
Total Trades
140
Average Win
0.16%
Average Loss
-0.22%
Compounding Annual Return
-6.542%
Drawdown
6.300%
Expectancy
-0.231
Net Profit
-3.962%
Sharpe Ratio
-0.766
Probabilistic Sharpe Ratio
5.910%
Loss Rate
56%
Win Rate
44%
Profit-Loss Ratio
0.74
Alpha
-0.048
Beta
-0.024
Annual Standard Deviation
0.066
Annual Variance
0.004
Information Ratio
-0.476
Tracking Error
0.391
Treynor Ratio
2.131
Total Fees
$0.00
from clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")

from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Data.Consolidators import *
from datetime import timedelta

class MACDForexAlphaModel(AlphaModel):
    
    def __init__(self,
                 fastPeriod = 12,
                 slowPeriod = 26,
                 signalPeriod = 9,
                 trendPeriod = 200,
                 movingAverageType = MovingAverageType.Exponential,
                 resolution = Resolution.Minute,
                 difference = 0):
                     
        self.fastPeriod = fastPeriod
        self.slowPeriod = slowPeriod
        self.signalPeriod = signalPeriod
        self.trendPeriod = trendPeriod
        self.movingAverageType = movingAverageType
        self.resolution = resolution
        self.insightPeriod = timedelta(days=10)
        self.bounceThresholdPercent = 0.000
        self.differenceThreshold = difference
        self.symbolData = {}
        
    def Update(self, algorithm, data):
        ''' Determines an insight for each security based on it's current MACD signal
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
        insights = []

        for key, sd in self.symbolData.items():
            if sd.Security.Price == 0:
                continue

            #if not sd.MACD.IsReady:# or not sd.Trend.IsReady:
            #    continue
            
            direction = InsightDirection.Flat
            
            if sd.MACD.Current.Value > sd.MACD.Signal.Current.Value * (1 + self.bounceThresholdPercent):# and sd.Security.Price > sd.Trend.Current.Value:# and sd.MACD.Current.Value < (0 + self.differenceThreshold):
                direction = InsightDirection.Up
                
            elif sd.MACD.Current.Value < sd.MACD.Signal.Current.Value * (1 - self.bounceThresholdPercent):# and sd.Security.Price < sd.Trend.Current.Value:# and sd.MACD.Current.Value > (0 + self.differenceThreshold):
                direction = InsightDirection.Down
            
            if direction == sd.PreviousDirection:
                continue
        
            algorithm.Log(f"time: {algorithm.Time}, close: {data.QuoteBars[key].Close}, open: {data.QuoteBars[key].Open}, difference:{sd.MACD.Current.Value - sd.MACD.Signal.Current.Value} ")

            insight = Insight.Price(sd.Security.Symbol, self.insightPeriod, direction)
            sd.PreviousDirection = insight.Direction
            insights.append(insight)
            
            algorithm.Plot('Price', 'Price', data.QuoteBars[key].Close)
            algorithm.Plot('MACD', 'MACD', sd.MACD.Current.Value - sd.MACD.Signal.Current.Value)

        return insights
        
    def OnSecuritiesChanged(self, algorithm, changes):
        '''Event fired each time the we add/remove securities from the data feed.
        This initializes the MACD for each added security and cleans up the indicator for each removed security.
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''
        for added in changes.AddedSecurities:
            self.symbolData[added.Symbol] = SymbolData(algorithm, added, self.fastPeriod, self.slowPeriod, self.signalPeriod, self.trendPeriod, self.movingAverageType, self.resolution)

        for removed in changes.RemovedSecurities:
            data = self.symbolData.pop(removed.Symbol, None)
            if data is not None:
                # clean up our consolidator
                algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator)    
        
class SymbolData:
    
    def __init__(self, algorithm, security, fastPeriod, slowPeriod, signalPeriod, trendPeriod, movingAverageType, resolution):
        self.Security = security
        self.MACD = MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType)
       # self.Trend = ExponentialMovingAverage(trendPeriod)

        #self.Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
        #resolveconsolidator vs addconsolidator
        self.Consolidator = QuoteBarConsolidator(timedelta(days=1))

    # Register consolidator to get automatically updated with minute data
        #algorithm.SubscriptionManager.AddConsolidator(self.Security.Symbol, self.Consolidator)
        
        algorithm.RegisterIndicator(security.Symbol, self.MACD, self.Consolidator)
      #  algorithm.RegisterIndicator(security.Symbol, self.Trend, self.Consolidator)
        
        self.PreviousDirection = None
        return    

        history = algorithm.History(security.Symbol, 200, Resolution.Daily)
        
        for bar in history.itertuples():
            time = bar.Index[1]
            close = bar.close
            self.MACD.Update(time, close)
          #  self.Trend.Update(time, close)
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
from G10CurrencySelectionModel import G10CurrencySelectionModel
from MACDForexModel import MACDForexAlphaModel
from RiskManagementModule import ModifiedTrailingStopRiskManagementModel 

class MACDTrendFollower(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 1)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        # self.AddEquity("SPY", Resolution.Minute)
        self.SetExecution(ImmediateExecutionModel())

        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
        
        self.AddAlpha(MACDForexAlphaModel())

        # self.SetRiskManagement(ModifiedTrailingStopRiskManagementModel(0.02, .04))
        
        self.AddUniverseSelection( G10CurrencySelectionModel())
        
        self.SetBrokerageModel(BrokerageName.OandaBrokerage, AccountType.Margin)
        
        self.UniverseSettings.Leverage = 1
        self.UniverseSettings.Resolution = Resolution.Minute
        
        self.pricePlot = Chart('Price')
        self.macdPlot = Chart('MACD')
        self.pricePlot.AddSeries(Series('Price', SeriesType.Line, 0))
        self.macdPlot.AddSeries(Series('Difference', SeriesType.Line, 0))
from QuantConnect import *
from Selection.ManualUniverseSelectionModel import ManualUniverseSelectionModel

class G10CurrencySelectionModel(ManualUniverseSelectionModel):
    def __init__(self):
        super().__init__([Symbol.Create(x, SecurityType.Forex, Market.Oanda) for x in [ "EURJPY"]])
       # super().__init__([Symbol.Create(x, SecurityType.Forex, Market.Oanda) for x in [ "EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "NZDUSD","USDCAD", "USDCHF", "USDNOK", "USDSEK"]])
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel

class ModifiedTrailingStopRiskManagementModel(RiskManagementModel):
    '''Provides an implementation of IRiskManagementModel that limits the maximum possible loss
    measured from the highest unrealized profit'''
    def __init__(self, maximumDrawdownPercent = 0.01, takeProfitPercent = 0.01):
        '''Initializes a new instance of the TrailingStopRiskManagementModel class
        Args:
            maximumDrawdownPercent: The maximum percentage drawdown allowed for algorithm portfolio compared with the highest unrealized profit, defaults to 5% drawdown'''
        self.maximumDrawdownPercent = -abs(maximumDrawdownPercent)
        self.takeProfitPercent = takeProfitPercent
        self.trailingHighs = dict()

    def ManageRisk(self, algorithm, targets):
        '''Manages the algorithm's risk at each time step
        Args:
            algorithm: The algorithm instance
            targets: The current portfolio targets to be assessed for risk'''
        riskAdjustedTargets = list()

        for kvp in algorithm.Portfolio:
            symbol = kvp.Key
            security = kvp.Value

            # Remove if not invested
            if not security.Invested:
                self.trailingHighs.pop(symbol, None)
                continue

            # Add newly invested securities
            if symbol not in self.trailingHighs:
                self.trailingHighs[symbol] = security.HoldingsCost   # Set to average holding cost
                continue
            
            if security.UnrealizedProfitPercent >= self.takeProfitPercent:
                algorithm.Debug(f"{algorithm.Time}: 'take profit'")
                riskAdjustedTargets.append(PortfolioTarget(symbol, 0))
                continue
            
            bidPrice = algorithm.Securities[symbol].BidPrice
            askPrice = algorithm.Securities[symbol].AskPrice
            
            if security.IsLong:
                # Check for new highs and update - set to tradebar high
                algorithm.Debug(f"{algorithm.Time}: {symbol}: 'long'")
                if self.trailingHighs[symbol] < askPrice:
                    self.trailingHighs[symbol] = askPrice
                    continue

                # Check for securities past the drawdown limit
                securityHigh = self.trailingHighs[symbol]
                drawdown = (bidPrice / securityHigh) - 1
                
            if security.IsShort:
                # Check for new lows and update - set to tradebar Low
                algorithm.Debug(f"{algorithm.Time}: {symbol}: 'short'")
                if self.trailingHighs[symbol] > bidPrice:
                    self.trailingHighs[symbol] = bidPrice
                    continue

                # Check for securities past the drawdown limit
                securityHigh = self.trailingHighs[symbol]
                drawdown = (securityHigh / askPrice) - 1

            if drawdown < self.maximumDrawdownPercent:
                # liquidate
                algorithm.Debug(f"{algorithm.Time}: loss liquidate")
                riskAdjustedTargets.append(PortfolioTarget(symbol, 0))
                

        return riskAdjustedTargets