Overall Statistics
Total Trades
473
Average Win
0.02%
Average Loss
-0.02%
Compounding Annual Return
-0.565%
Drawdown
1.200%
Expectancy
-0.141
Net Profit
-0.845%
Sharpe Ratio
-0.707
Probabilistic Sharpe Ratio
0.482%
Loss Rate
56%
Win Rate
44%
Profit-Loss Ratio
0.95
Alpha
-0.003
Beta
0.021
Annual Standard Deviation
0.006
Annual Variance
0
Information Ratio
0.557
Tracking Error
0.073
Treynor Ratio
-0.185
Total Fees
$0.00
Estimated Strategy Capacity
$12000000000.00
Lowest Capacity Asset
USDZAR 8G
Portfolio Turnover
1.95%
#region imports
from AlgorithmImports import *

from collections import deque
#endregion

class RecordIndicator(PythonIndicator):
    '''
    This custom indicator was created to manage rolling indicators mainly.
    It takes an indicator, saves the amount of data required to perform a correct 
    computation (passed the warm-up period), and every time it is updated it will use that saved data.
    The IntradayUpdate method does not store values, the normal Update does.
    '''
    def __init__(self, name, indicator, update_method='bar'):
        '''
        Inputs:
            - Name [String]: Name of the indicator.
            - indicator [Indicator Object]: Underlying indicator
            - update_method [str]: 'bar' reference to a Bar object, 
                                    other reference to (datetime, decimal) object.
                                    The two conventions on QC.
        '''
        self.Name = name
        self.Time = datetime.min # Last time update.
        self.Value = 0 # For this case It does not vary.

        self.indicator = indicator
        self.LastValues = deque(maxlen=self.indicator.WarmUpPeriod) # Stores the value

        self.update_method = update_method 

    def SelectUpdate(self,input):
        # Perform the specified update method
        if self.update_method == 'bar':
            self.indicator.Update(input)
        else:
            self.indicator.Update(input.EndTime, input.Close)

    def get_current(self):
        # Reset the indicator to use the daily values
        self.indicator.Reset()
        for d in self.LastValues:
            self.SelectUpdate(d)
        return self.indicator

    def IntradayUpdate(self,input):
        # Update the indicator with intraday data. It do not store data
        self.get_current()
        self.SelectUpdate(input)
        return self.indicator

    def Update(self, input):
        # Reset the indicator, store and update the indicator with the daily data.
        self.get_current()
        self.LastValues.append(input)
        self.SelectUpdate(input)

        return len(self.LastValues) == self.LastValues.maxlen

        
#region imports
from AlgorithmImports import *
#endregion


# Your New Python File
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from AlgorithmImports import *

class CustomRiskManagementModel(RiskManagementModel):
    '''Provides an implementation of IRiskManagementModel that limits the maximum possible loss
    measured from the highest unrealized profit'''
    def __init__(self, main, maximumProfit = 3, remainingProfitPercent=0,
                     maximumDrawdown = 2, remainingStopLossPercent=0):
        '''Initializes a new instance of the TrailingStopRiskManagementModel class
        Args:
            maximumDrawdown: The maximum percentage drawdown allowed for the algorithm portfolio compared with the highest unrealized profit,
                             defaults to a 5% drawdown
            maximumProfit: Profit percentages generated over security that will trigger the Take Profit.
            remainingProfitPercent: The percentage of the actual holding values to maintain.
            remainingStopLossPercent: The percentage of the actual holding values to maintain.
        '''
            
        self.main = main
        self.maximumProfit = abs(maximumProfit)
        self.remainingProfitPercent = abs(remainingProfitPercent)

        self.remainingStopLossPercent = abs(remainingStopLossPercent)
        self.maximumDrawdown = abs(maximumDrawdown)
        self.trailingPriceState = 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 are to be assessed for risk'''
        riskAdjustedTargets = list()
        
        for kvp in algorithm.Securities:
            symbol = kvp.Key
            security = kvp.Value

            # Remove if not invested
            if not security.Invested: # For positions closed outside the risk management model
                self.trailingPriceState.pop(symbol, None) # remove from dictionary
                continue

            tracker = self.main.SecuritiesTracker[symbol]
            # Current ATR Indicator
            currentAtr = tracker.Atr.Current.Value
            
            quantity = algorithm.Portfolio[symbol].Quantity
            # Get position side
            position = PositionSide.Long if security.Holdings.IsLong else PositionSide.Short
            # Recorded Holdings Value
            trailingPriceState = self.trailingPriceState.get(symbol)

            # Add newly invested security (if doesn't exist) or reset holdings state (if position changed)
            if trailingPriceState is None or position != trailingPriceState.position:
                order = tracker.Order
                # Filled Average Price
                price = order.AverageFillPrice
                # Create a HoldingsState object if not existing or reset it if the position direction changed
                self.trailingPriceState[symbol] = trailingPriceState = PriceState(position, price)

            CurrentPrice = security.Price
            initialPrice = self.trailingPriceState[symbol].initialPrice
            # If the profits reach the trigger
            if CurrentPrice > ((self.maximumProfit * currentAtr) + initialPrice):
                # Update position
                riskAdjustedTargets.append(PortfolioTarget(symbol, int(quantity*self.remainingProfitPercent)))
                # Pop the symbol from the dictionary since the holdings state of the security has been changed
                self.trailingPriceState.pop(symbol, None) # remove from dictionary
                continue
            elif CurrentPrice < (initialPrice - (self.maximumDrawdown * currentAtr)):
                # liquidate
                riskAdjustedTargets.append(PortfolioTarget(symbol, int(quantity*self.remainingStopLossPercent)))
                # Pop the symbol from the dictionary since the holdings state of the security has been changed
                self.trailingPriceState.pop(symbol, None) # remove from dictionary

        return riskAdjustedTargets

class PriceState:
    def __init__(self, position, initialPrice):
        self.position = position
        self.initialPrice = initialPrice
#region imports
from AlgorithmImports import *
#endregion


# Your New Python File
#region imports
from AlgorithmImports import *

from collections import deque
#endregion

class SymbolData:
    # Object to Keep track of the securities

## INITIALIZATION
    def __init__(self,symbol, security, time,
                order_creator,
                atr, fast_ma, slow_ma):

        '''
        Inputs:
            - Symbol [QC Symbol]: Reference to the underlying security.
            - Security [QC Security]: Reference to the security object to access data.
            - time [Main Algo function]: This function returns the time of the main algorithm.
            - Indicator objects: Atr, Fast_MA, Slow_MA
        '''
        self.Symbol = symbol
        self.Security = security
        self.get_Time = time
        self.OrderCreator = order_creator

        self.CustomAtr = atr
        self.Atr = atr.indicator

        self.fast_ma = fast_ma
        self.slow_ma = slow_ma

        self.CreateConsolidator()

        self.FastIsOverSlow = False

        self.SetOrder(None)
        
    @property
    def SlowIsOverFast(self):
        return not self.FastIsOverSlow

    @property
    def Time(self):
        # Allow access to the Time object directly
        return  self.get_Time()

    @property
    def IsReady(self):
        # Tells if all the indicators assciated are ready
        return self.Atr.IsReady and self.fast_ma.IsReady and self.slow_ma

    def CreateConsolidator(self):
        self.MyConsolidator = QuoteBarConsolidator(self.DefineConsolidator)
        self.MyConsolidator.DataConsolidated += self.ConsolidatorHandler

## INDICATORS
    def CheckOpenMarket(self, dt):
        '''Check market times'''
        last_open = self.Security.Exchange.Hours.GetPreviousMarketOpen(dt, False)
        next_close = self.Security.Exchange.Hours.GetNextMarketClose(dt, False)
        return (last_open < dt and next_close > dt)

    def DefineConsolidator(self, dt):
        next_close = self.Security.Exchange.Hours.GetNextMarketClose(dt, False) -  timedelta(minutes=10)
        if self.Security.Exchange.Hours.IsDateOpen(dt):
            last_open = self.Security.Exchange.Hours.GetPreviousMarketOpen(dt, False)
            return CalendarInfo(last_open, next_close - last_open)
        else:
            next_open = self.Security.Exchange.Hours.GetNextMarketOpen(dt,False)
            return CalendarInfo(next_open, next_close - next_open)

    def ConsolidatorHandler(self, sender: object, consolidated_bar: TradeBar) -> None:
        self.slow_ma.Update(consolidated_bar.EndTime, consolidated_bar.Close)
        self.fast_ma.Update(consolidated_bar.EndTime, consolidated_bar.Close)
        self.CustomAtr.Update(consolidated_bar)
        
        target, direction = self.CreatePositionEvent()

        self.OrderCreator(self.Symbol, target, direction)
        self.FastIsOverSlow = self.fast_ma > self.slow_ma


## OPEN POSITION LOGIC: Logic specified to open a position associated to the tracked Equity.
    def CreatePositionEvent(self):
        if self.FastIsOverSlow:
            if self.slow_ma > self.fast_ma:
                if self.Order:
                    return - self.Order.QuantityFilled, OrderDirection.Sell
                else:
                    return 0, OrderDirection.Sell
        elif self.SlowIsOverFast:
            if self.fast_ma > self.slow_ma:
                if self.Order:
                    return - self.Order.QuantityFilled, OrderDirection.Buy
                else:
                    return 0, OrderDirection.Buy
        return 0, None


## MANEGE POSITIONS
    def SetOrder(self, order):
        '''Add associated order.'''
        self.Order = order
# region imports
from AlgorithmImports import *

import SymbolData
import CustomIndicators as ci
from CustomRiskManagementModel import CustomRiskManagementModel
# endregion

class CryingYellowGreenBadger(QCAlgorithm):
    TICKERS = [
        "USDAUD",
        "USDCAD",
        "USDCNY",
        "USDEUR",
        "USDINR",
        "USDJPY",
        "USDMXN",
        "USDTRY",
        "USDZAR",
    ]
    INVESTMENT_PCT = 1/len(TICKERS)

## INITIALIZE
    def Initialize(self):
        self.SetStartDate(2021, 9, 10)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        
        # Init Universe settings
        self.MyUniverseInitializer()

        self.AddRiskManagement(CustomRiskManagementModel(self))

        self.SecuritiesTracker = {} # Initilize tracker parameters
    
    def MyUniverseInitializer(self):
        # Set the resolution for universal use
        # Even though this is a daily trading strategy, we set the resolution to
        # minute to have a constant flow of data. We feed the data to the algorithm with
        # a minute resolution if not, the update of the state would be too long. 
        self.UniverseSettings.Resolution = Resolution.Minute

        # Set the data normalization raw, more like the real thing
        # self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw
        
        # Adding securities
        # We add the securities as a Universe Selection model so future implementations 
        # can have the adaptability to any security that gets into the Universe.
        symbols = [Symbol.Create(t, SecurityType.Forex, Market.Oanda) for t in self.TICKERS]
        self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
        
        # Add the Benchmark
        self.bench = self.AddForex('EURUSD').Symbol
        self.SetBenchmark(self.bench) # Set it

## SECURITIES LOGIC: CREATION, INDICATORS, UPDATE, TACKING
    def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
        # Gets an object with the changes in the universe
        # For the added securities we create a SymbolData object that allows us to 
        # track the orders associated and the indicators created for it.
        for security in changes.AddedSecurities:
            if self.SecuritiesTracker.get(security.Symbol) is None:
                atr,fast_sma,slow_sma = self.InitIndicators(security.Symbol)
                # Pass a reference to the symbol, security object, algorithm time and indicators
                self.SecuritiesTracker[security.Symbol] = SymbolData.SymbolData(security.Symbol,self.Securities[security.Symbol],self.get_Time,
                                                                                self.CreateOrder,
                                                                                atr,fast_sma,slow_sma)
                self.SubscriptionManager.AddConsolidator(security.Symbol, self.SecuritiesTracker[security.Symbol].MyConsolidator)

        # The removed securities are liquidated and removed from the security tracker.
        for security in changes.RemovedSecurities:
            if self.Portfolio[security.Symbol].Invested:
                self.SetHoldings(security.Symbol,0)
            # Remove Consolidator
            self.SubscriptionManager.RemoveConsolidator(security.Symbol, self.SecuritiesTracker[security.Symbol].MyConsolidator)
            self.SecuritiesTracker.pop(security.Symbol, None)

    def InitIndicators(self,symbol):
        '''
        Receive an equity symbol to track and create the indicators required from the strategy logic.

        The ATR required intraday updates without losing the past daily information,
        the use of the ci.RecordIndicator (also a created custom indicator) allows this functionality.
        Input:
            - symbol [QC Symbol object]: Reference to the security to feed the indicators.

        Returns: Returns the indicators objects it selfs
            - Atr, Fast_MA, Slow_MA
        '''
        # This procets repeat itself per security
        atr = AverageTrueRange('ATR '+symbol.Value, 14) # Create indicator
        custom_atr =  ci.RecordIndicator('Custom ATR '+symbol.Value, atr) # If required: Use ci.RecordIndicator for intraday update
        # self.RegisterIndicator(symbol, custom_atr, Resolution.Daily) # Associate the indicator to a ticker and a update resolution 
                                                                    # (resolution has to be equal or lower than security resolution)
                                                                    # Here you could pass the consolidator as resolution as well

        fast_sma = SimpleMovingAverage('Fast SMA '+symbol.Value,7)

        slow_sma = SimpleMovingAverage('Slow SMA '+symbol.Value,20)

        return custom_atr, fast_sma, slow_sma

    def IntradayUpdate(self, data, symbol, tracker):
        '''
        The OnData method will call this function every minute (set resolution), 
        and the tracker will call the indicators associated with the symbol information to update
        them without saving or updating the daily data.
        Inputs:
            - data [Slice QC Object]: Slice QC Object with the information of the securities in the universe.
            - symbol [Symbol QC object]: QC Symbol identifier of the securities.
            - tracker [SymbolData object]: Tracker created for the specific symbol.
        Returns:
        None
        '''
        if data.ContainsKey(symbol) and data[symbol] is not None and tracker.IsReady:
            tracker.CustomAtr.IntradayUpdate(data[symbol])

## CHECK FOR BUYING POWER: This are functions that I usually apply to avoid sending orders wiout margin

    def CheckBuyingPower(self,symbol, order_direction):
        '''
        Check for enough buying power.
        If the buying power for the target quantity is not enough,
        It will return the quantity for which the buying power is enough.
        '''
        # Get the buying power depending of the order direction and symbol
        buy_power = self.Portfolio.GetBuyingPower(symbol, order_direction)
        # Compute possible quantity
        q_t = abs(buy_power) / self.Securities[symbol].Price
        # Select minimum quantity
        return round(min(abs(quantity),q_t),8)*np.sign(quantity)

    def CheckOrdeQuatity(self,symbol, quantity):
        '''Check that the quantity of shares computed meets the minimum requirments'''
        q = abs(quantity)
        # There are requirements for the minimum or maximum that can be purchased per security.
        if q > self.Settings.MinAbsolutePortfolioTargetPercentage and q < self.Settings.MaxAbsolutePortfolioTargetPercentage:
            symbol_properties = self.Securities[symbol].SymbolProperties
            if symbol_properties.MinimumOrderSize is None or q > symbol_properties.MinimumOrderSize:
                return True
        return False

    def ComputeOrderQuantity(self, price):
        # Compute desired quantity of shares based on the remaining margin (buying capacity).
        return (self.Portfolio.MarginRemaining * self.INVESTMENT_PCT) / price

## POSITION MANAGEMENT

    def CreateOrder(self, symbol, target, direction):
        if direction is None:
            return # No action
        sign = -1 if direction == OrderDirection.Sell else 1
        quantity = target + self.ComputeOrderQuantity(self.Portfolio[symbol].Price)
        if self.CheckOrdeQuatity(symbol, quantity):
            self.SecuritiesTracker[symbol].SetOrder(self.MarketOrder(symbol, quantity))
        return

    def OnData(self, data: Slice):
        for symbol,tracker in self.SecuritiesTracker.items(): # Iterate Over the securities on track
            self.IntradayUpdate(data, symbol, tracker)