Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000.0
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino 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
-1.46
Tracking Error
0.648
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
# region imports
from AlgorithmImports import *
import requests
import json
import time
# endregion

from typing import List

class CoinGeckoUniverse:
    def __init__(self, algorithm, categories):
        self.algorithm = algorithm
        self.categories = categories

    def fetch_symbols(self):
        all_symbols = []
        for category in self.categories:
            url = f"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&category={category}" 
            retry_attempts = 3
            for attempt in range(retry_attempts):
                try:
                    response = self.algorithm.download(url)
                    if response:
                        data = json.loads(response)
                        symbols = [coin['symbol'].upper() + 'USD' for coin in data if 'symbol' in coin]
                        all_symbols.extend(symbols)
                        self.algorithm.debug(f"Fetching Symbols Successful")
                        break
                    else:
                        print("Failed to fetch symbols for category:", category)
                        break
                except Exception as e:
                    print(f"Attempt {attempt + 1} failed: {e}")
                    self.algorithm.debug(f"Attempt {attempt + 1} failed: {e}")
                    self.algorithm.sleep(2000)  # Wait before retrying (milliseconds)
        return all_symbols
# region imports
from AlgorithmImports import *
from datetime import timedelta
# endregion

class Criteria(object):
    def __init__(self, algorithm, crypto, ema_period, std_period):
        self.algorithm = algorithm
        self._symbol = crypto.symbol
        self._value = self._symbol.value
        self._volume_in_usd = crypto.volume_in_usd
        self._ema = ExponentialMovingAverage(ema_period)
        self._std = StandardDeviation(std_period)
        self.ema_value = 0
        self.percentage_volatility = 0
        self.ema_period = ema_period
        self.lookback_period = self.algorithm.look_back
        self.criteria_met = False
      


    def update(self, time, crypto_close, crypto_symbol):

        if self._ema.update(time, crypto_close) and self._std.update(time, crypto_close):
            self.ema_value = self._ema.current.value
            self.percentage_volatility = self._std.current.value / crypto_close * 100
            self.criteria_met = crypto_close > self.ema_value


    def ema_criteria(self, symbol):
        # Fetch the historical EMA data
        ema_history = self.algorithm.indicator_history(self._ema, symbol, timedelta(days=self.lookback_period))
        ema_values = ema_history.data_frame['current'] 

        # Fetch the historical price data
        history = self.algorithm.history(symbol, self.lookback_period, Resolution.DAILY)

        # Ensure we have enough data
        if len(history) < self.lookback_period:
            self.algorithm.log(f"Not enough data for {symbol}. History length: {len(history)})")
            return False

        # Track the index of the last closing price below the EMA
        last_below_ema_index = None

        # Iterate through historical data to find the last close below EMA
        for index in range(len(history) - 1, -1, -1):
            close_price = history.iloc[index]['close']
            ema_value = ema_values.iloc[index]
            if close_price < ema_value:
                last_below_ema_index = index
                break

        # If no closing price below EMA was found, return False
        if last_below_ema_index is None:
            return False

        # Check if any candle after the last_below_ema_index opened and closed above the EMA
        for index in range(last_below_ema_index + 1, len(history)):
            open_price = history.iloc[index]['open']
            close_price = history.iloc[index]['close']
            ema_value = ema_values.iloc[index]
            if open_price > ema_value and close_price > ema_value:
                return True

        return False

from AlgorithmImports import *
from CoinGeckoUniverse import CoinGeckoUniverse
from Criteria import Criteria

class UniverseSelectionModel:
    def __init__(self, algorithm):
        self.algorithm = algorithm
        self.categories = ['layer-1', 'depin', 'proof-of-work-pow', 'proof-of-stake-pos', 'meme-token', 'dog-themed-coins', 
                           'eth-2-0-staking', 'non-fungible-tokens-nft', 'governance', 'artificial-intelligence', 
                           'infrastructure', 'layer-2', 'zero-knowledge-zk', 'storage', 'oracle', 'bitcoin-fork', 
                           'restaking', 'rollup', 'metaverse', 'privacy-coins', 'layer-0-l0', 'solana-meme-coins', 
                           'data-availabilit', 'internet-of-things-iot', 'frog-themed-coins', 'ai-agents', 
                           'superchain-ecosystem', 'bitcoin-layer-2',  'bridge-governance-tokens', 'modular-blockchain', 
                           'cat-themed-coins', 'cross-chain-communication', 'analytics', 'identity', 'wallets', 
                           'masternodes'] 
        self.coin_universe = CoinGeckoUniverse(self.algorithm, self.categories)
        self.tickers = self.coin_universe.fetch_symbols()
        self.count = 10
        self.std_period = 20
        self.ema_period = 20
        self.criteria_by_symbol = {}

    def coarse_filters(self, coarse):
        for crypto in coarse:
            symbol = crypto.symbol
            
            if symbol.value in self.tickers:
                if symbol not in self.criteria_by_symbol:
                    self.criteria_by_symbol[symbol] = Criteria(self.algorithm, crypto, self.ema_period, self.std_period)
                self.criteria_by_symbol[symbol].update(crypto.end_time, crypto.close, crypto.symbol)

        if self.algorithm.is_warming_up:
            return Universe.UNCHANGED

        ### filter logic using self.criteria_by_symbol
        filtered = [x for x in self.criteria_by_symbol.values() if x._volume_in_usd > 10000 and x.criteria_met] 
        self.algorithm.Debug(f"First Symbol in Coarse Filtered: {filtered[0]._volume_in_usd if filtered else 'None'}")

        filtered.sort(key=lambda x: x.percentage_volatility, reverse=True)
        self.algorithm.Debug(f"Sorting Successful: {filtered[0]._value if filtered else 'None'}")

        for x in filtered[:self.count]:
            self.algorithm.debug('symbol: ' + str(x._value) + '  Volume: ' + str(x._volume_in_usd) + "$")

        self.algorithm.Debug(f"Length of criteria_by_symbol: {len(self.criteria_by_symbol)}")
        
        ### return symbols in self.criteria_by_symbol.keys() that pass the filter
        return [f._symbol for f in filtered]

from AlgorithmImports import *
from CryptoUniverseSelection import UniverseSelectionModel

class CryptoTradingAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2020, 1, 1)
        self.set_brokerage_model(BrokerageName.KRAKEN, AccountType.CASH)
        self.btcusd = self.add_crypto("BTCUSD", Resolution.HOUR, Market.KRAKEN).symbol
        self.set_benchmark("BTCUSD")
        self.look_back = 35
        self.set_warm_up(self.look_back, Resolution.DAILY)

        # Create an instance of the universe selection model
        self.universe_model = UniverseSelectionModel(self)
        
        # Define the universe
        # Add Kraken universe with a selection function
        self.add_universe(CryptoUniverse.kraken(self.universe_model.coarse_filters))
        

        self.alpha_counter = 2

        # self.schedule.on(self.date_rules.month_start("SPY"),
        #  self.time_rules.midnight,
        #   self.add_universe(CryptoUniverse.kraken(self.universe_model.coarse_filters)))

    def OnData(self, data):
        # for symbol in self.Securities.Keys:
        #     self.SetHoldings(symbol, 0.01)
        pass