Overall Statistics
Total Trades
481
Average Win
1.79%
Average Loss
-3.81%
Compounding Annual Return
111.371%
Drawdown
44.500%
Expectancy
0.203
Net Profit
550.161%
Sharpe Ratio
1.825
Probabilistic Sharpe Ratio
73.297%
Loss Rate
18%
Win Rate
82%
Profit-Loss Ratio
0.47
Alpha
0.762
Beta
0.192
Annual Standard Deviation
0.481
Annual Variance
0.231
Information Ratio
0.405
Tracking Error
0.687
Treynor Ratio
4.58
Total Fees
$213323.56
Estimated Strategy Capacity
$240000.00
Lowest Capacity Asset
ETHUSD 10B
Portfolio Turnover
19.63%
from AlgorithmImports import *

class PriceActionAlpha(AlphaModel):
    def __init__(self, algorithm, symbol):
        self.algorithm = algorithm
        self.symbol = symbol

        self.hour = -1
        self.selltrig = None
        self.buytrig = None
        self.currentopen = None

        self.consolidator = TradeBarConsolidator(timedelta(1))
        self.consolidator.DataConsolidated += self.OnConsolidated
        self.window = RollingWindow[TradeBar](4)

        history = algorithm.History[TradeBar](self.symbol, 4*24*60, Resolution.Minute)
        for bar in history:
            self.consolidator.Update(bar)

        algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator)

    def OnConsolidated(self, sender, bar):
        self.window.Add(bar)
        self.currentopen = bar.Open

        if not self.window.IsReady: return

        df = self.algorithm.PandasConverter.GetDataFrame[TradeBar](self.window)    
        k1 = 0.5
        k2 = 0.5
        
        HH, HC, LC, LL = max(df['high']), max(df['close']), min(df['close']), min(df['low'])
        if (HH - LC) >= (HC - LL):
            signalrange = HH - LC
        else:
            signalrange = HC - LL
        
        self.selltrig = self.currentopen - k2 * signalrange
        self.buytrig = self.currentopen + k1 * signalrange    
    
    def Update(self, algorithm, data):        
        # We only use hourly signals
        if not data.ContainsKey(self.symbol) or not self.window.IsReady:
            return []

        if self.hour == algorithm.Time.hour:
            return []
        self.hour = algorithm.Time.hour
        
        price = data[self.symbol].Price
        
        if algorithm.LiveMode:
            algorithm.Log(f'Buy Trigger {self.buytrig} > Price {price} > {self.selltrig}')
        
        if price >= self.buytrig:
            # self.algorithm.pcm.maxcap = (price - self.buytrig) / self.buytrig * self.algorithm.multiplier
            return [Insight(self.symbol, timedelta(days=365), InsightType.Price, InsightDirection.Up)]
                                                # magnitude = size,
                                                # confidence = size,
                                                # weight = size)]
        elif price < self.selltrig:
            algorithm.Insights.Cancel([self.symbol])
        
        return []
from AlgorithmImports import *
from alpha import PriceActionAlpha
from pcm import EqualWeightingMaxCapPortfolioConstructionModel

# BTCUSD Long Only Dual Thrust Algorithm 
# Originated by Michael Vitucci
class DualThrustAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 1)
        self.SetEndDate(2022, 7, 1)
        self.SetCash(100000)
        self.SetBrokerageModel(BrokerageName.Kraken, AccountType.Margin)

        # self.stopLoss = self.GetParameter('stopLoss', 0.04)
        # self.takeProfit = self.GetParameter('takeProfit', 0.25)
        # self.lookback = self.GetParameter('lookback', 4)
        # self.maxcap = self.GetParameter('maxcap', 1.0)
        # self.multiplier = self.GetParameter('multiplier', 1)

        tickers = ['ETHUSD']

        for ticker in tickers:
            symbol = self.AddCrypto(ticker, Resolution.Minute).Symbol
            self.AddAlpha(PriceActionAlpha(self, symbol))

        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(lambda time: None))
        # self.pcm = EqualWeightingMaxCapPortfolioConstructionModel()
        # self.SetPortfolioConstruction(self.pcm)
        # self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel())
        # self.SetPortfolioConstruction(AccumulativeInsightPortfolioConstructionModel(percent = 0.64))
        self.SetExecution(ImmediateExecutionModel())
        # execution_model = VolumeWeightedAveragePriceExecutionModel()
        # execution_model.MaximumOrderQuantityPercentVolume = 0.05
        # self.SetExecution(execution_model)
        # self.AddRiskManagement(MaximumDrawdownPercentPortfolio(self.stopLoss, isTrailing=True))
        # self.AddRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(self.takeProfit))
#region imports
from AlgorithmImports import *
#endregion


# 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 EqualWeightingMaxCapPortfolioConstructionModel(PortfolioConstructionModel):
    '''Provides an implementation of IPortfolioConstructionModel that gives equal weighting to all securities.
    The target percent holdings of each security is 1/N where N is the number of securities.
    For insights of direction InsightDirection.Up, long targets are returned and
    for insights of direction InsightDirection.Down, short targets are returned.'''

    def __init__(self, rebalance = Resolution.Daily, portfolioBias = PortfolioBias.LongShort, maxcap = 1.0):
        '''Initialize a new instance of EqualWeightingPortfolioConstructionModel
        Args:
            rebalance: Rebalancing parameter. If it is a timedelta, date rules or Resolution, it will be converted into a function.
                              If None will be ignored.
                              The function returns the next expected rebalance time for a given algorithm UTC DateTime.
                              The function returns null if unknown, in which case the function will be called again in the
                              next loop. Returning current time will trigger rebalance.
            portfolioBias: Specifies the bias of the portfolio (Short, Long/Short, Long)'''
        super().__init__()
        self.portfolioBias = portfolioBias
        self.maxcap = maxcap

        # If the argument is an instance of Resolution or Timedelta
        # Redefine rebalancingFunc
        rebalancingFunc = rebalance
        if isinstance(rebalance, int):
            rebalance = Extensions.ToTimeSpan(rebalance)
        if isinstance(rebalance, timedelta):
            rebalancingFunc = lambda dt: dt + rebalance
        if rebalancingFunc:
            self.SetRebalancingFunc(rebalancingFunc)

    def DetermineTargetPercent(self, activeInsights):
        '''Will determine the target percent for each insight
        Args:
            activeInsights: The active insights to generate a target for'''
        result = {}

        # give equal weighting to each security
        count = sum(x.Direction != InsightDirection.Flat and self.RespectPortfolioBias(x) for x in activeInsights)
        percent = 0 if count == 0 else self.maxcap / count
        for insight in activeInsights:
            result[insight] = (insight.Direction if self.RespectPortfolioBias(insight) else InsightDirection.Flat) * percent
        return result

    def RespectPortfolioBias(self, insight):
        '''Method that will determine if a given insight respects the portfolio bias
        Args:
            insight: The insight to create a target for
        '''
        return self.portfolioBias == PortfolioBias.LongShort or insight.Direction == self.portfolioBias