Overall Statistics
Total Orders
1107
Average Win
0.00%
Average Loss
-0.01%
Compounding Annual Return
36.377%
Drawdown
7.300%
Expectancy
-0.359
Start Equity
100000.00
End Equity
108137.80
Net Profit
8.138%
Sharpe Ratio
2.093
Sortino Ratio
2.551
Probabilistic Sharpe Ratio
79.725%
Loss Rate
55%
Win Rate
45%
Profit-Loss Ratio
0.43
Alpha
-0.047
Beta
0.775
Annual Standard Deviation
0.092
Annual Variance
0.008
Information Ratio
-2.208
Tracking Error
0.053
Treynor Ratio
0.248
Total Fees
$1005.79
Estimated Strategy Capacity
$9500000.00
Lowest Capacity Asset
PCS TRX10524LYZP
Portfolio Turnover
47.12%
# region imports
from AlgorithmImports import *
# endregion

# Your New Python File
class AssetArbitrageStrategy:
    def __init__(self, algorithm, symbol):
        self.algorithm = algorithm
        self.symbol = symbol
        self.long_trade_size = 0.05  # 5% of the portfolio for long trades
        self.short_trade_size = 0.03  # 3% of the portfolio for short trades
        self.long_stop_loss = 0.05  # 5% stop-loss for long trades
        self.short_stop_loss = 0.03  # 3% stop-loss for short trades
        self.max_portfolio_exposure = 0.80  # Maximum 80% portfolio exposure

    def Execute(self, indicators):
        contrarian_bands = indicators["contrarian_bands"]
        rsi = indicators["rsi"]
        trend = indicators["trend"]

        if not contrarian_bands.HasSignal() or not rsi.HasSignal():
            return

        price = self.algorithm.Securities[self.symbol].Price
        holdings = self.algorithm.Portfolio[self.symbol].Quantity
        average_price = self.algorithm.Portfolio[self.symbol].AveragePrice

        if price is None or price <= 0:
            self.algorithm.Debug(f"Skipping {self.symbol}: Invalid price {price}")
            return

        # Portfolio Exposure Check
        portfolio_exposure = sum([holding.HoldingsValue for holding in self.algorithm.Portfolio.Values]) / self.algorithm.Portfolio.TotalPortfolioValue
        if portfolio_exposure > self.max_portfolio_exposure:
            self.algorithm.Debug(f"Skipping trade for {self.symbol}: Portfolio exposure exceeds limit ({portfolio_exposure:.2%})")
            return

        # Long Entry
        if holdings == 0 and price < contrarian_bands.bbands.LowerBand.Current.Value and rsi.rsi.Current.Value < 30 and trend.IsUptrend():
            self.algorithm.SetHoldings(self.symbol, self.long_trade_size)

        # Short Entry
        elif holdings == 0 and price > contrarian_bands.bbands.UpperBand.Current.Value and rsi.rsi.Current.Value > 70 and trend.IsDowntrend():
            self.algorithm.SetHoldings(self.symbol, -self.short_trade_size)

        # Stop-Loss for Long Positions
        if holdings > 0 and price < average_price * (1 - self.long_stop_loss):
            self.algorithm.Debug(f"Stop-loss triggered for long {self.symbol} at price {price}")
            self.algorithm.Liquidate(self.symbol)

        # Stop-Loss for Short Positions
        if holdings < 0 and price > average_price * (1 + self.short_stop_loss):
            self.algorithm.Debug(f"Stop-loss triggered for short {self.symbol} at price {price}")
            self.algorithm.Liquidate(self.symbol)

        # Long Exit
        if holdings > 0 and price >= contrarian_bands.bbands.MiddleBand.Current.Value:
            self.algorithm.Liquidate(self.symbol)

        # Short Exit
        if holdings < 0 and price <= contrarian_bands.bbands.MiddleBand.Current.Value:
            self.algorithm.Liquidate(self.symbol)
# region imports
from AlgorithmImports import *
# endregion

# Your New Python File
class CustomBollingerBands:
    def __init__(self, algorithm, symbol, period=20, deviations=2):
        self.algorithm = algorithm
        self.symbol = symbol
        self.bbands = algorithm.BB(symbol, period, deviations, MovingAverageType.Simple, Resolution.Daily)

    def Update(self, data):
        if self.symbol in data and data[self.symbol] is not None:
            self.bbands.Update(data[self.symbol].EndTime, data[self.symbol].Close)

    def HasSignal(self):
        return self.bbands.IsReady


class RSIIndicator:
    def __init__(self, algorithm, symbol, period=14):
        self.algorithm = algorithm
        self.symbol = symbol
        self.rsi = algorithm.RSI(symbol, period, MovingAverageType.Simple, Resolution.Daily)

    def Update(self, data):
        if self.symbol in data and data[self.symbol] is not None:
            self.rsi.Update(data[self.symbol].EndTime, data[self.symbol].Close)

    def HasSignal(self):
        return self.rsi.IsReady


class HistoricalVolatility:
    def __init__(self, algorithm, symbol, period=20):
        self.algorithm = algorithm
        self.symbol = symbol
        self.returns = RollingWindow[float](period)

    def Update(self, data):
        if self.symbol in data and data[self.symbol] is not None:
            price = data[self.symbol].Close
            if self.returns.Count > 0 and self.returns[0] != 0:
                self.returns.Add(np.log(price / self.returns[0]))
            else:
                self.returns.Add(0)

    def GetVolatility(self):
        if self.returns.IsReady:
            return np.std(list(self.returns))
        return None


class TrendFilter:
    def __init__(self, algorithm, symbol, sma_period=50):
        self.algorithm = algorithm
        self.symbol = symbol
        self.sma = algorithm.SMA(symbol, sma_period, Resolution.Daily)

    def Update(self, data):
        if self.symbol in data and data[self.symbol] is not None:
            self.sma.Update(data[self.symbol].EndTime, data[self.symbol].Close)

    def IsUptrend(self):
        if self.sma.IsReady:
            price = self.algorithm.Securities[self.symbol].Price
            return price > self.sma.Current.Value
        return False

    def IsDowntrend(self):
        if self.sma.IsReady:
            price = self.algorithm.Securities[self.symbol].Price
            return price < self.sma.Current.Value
        return False
from AlgorithmImports import *
from Indicators import CustomBollingerBands, RSIIndicator, HistoricalVolatility, TrendFilter
from AssetStrategy import AssetArbitrageStrategy

class VolatilityArbitrage(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2023, 10, 1)
        self.SetEndDate(2023, 12, 31)
        self.SetCash(100000)

        # Add cryptocurrencies
        self.crypto_symbols = ["BTCUSD", "ETHUSD", "SOLUSD"]
        for crypto in self.crypto_symbols:
            self.AddCrypto(crypto, Resolution.Daily)

        # Universe selection for 100 stocks
        self.AddUniverse(self.CoarseSelectionFunction)

        # Extended warm-up period for better historical volatility data
        self.SetWarmUp(timedelta(days=60))

        self.symbol_data = {}

    def CoarseSelectionFunction(self, coarse):
        # Select liquid equities with price > $20 and sort by dollar volume in descending order
        filtered = [x for x in coarse if x.Price > 20 and x.DollarVolume > 1e7]
        sorted_by_volume = sorted(filtered, key=lambda x: x.DollarVolume, reverse=True)
        selected = [x.Symbol for x in sorted_by_volume[:100]]  # Top 100 stocks
        return selected + self.crypto_symbols  # Add cryptos to the universe

    def OnSecuritiesChanged(self, changes):
        for added in changes.AddedSecurities:
            symbol = added.Symbol
            if symbol not in self.symbol_data:
                self.symbol_data[symbol] = {
                    "indicators": {
                        "contrarian_bands": CustomBollingerBands(self, symbol),
                        "rsi": RSIIndicator(self, symbol),
                        "hist_vol": HistoricalVolatility(self, symbol),
                        "trend": TrendFilter(self, symbol)
                    },
                    "strategy": AssetArbitrageStrategy(self, symbol)
                }

        for removed in changes.RemovedSecurities:
            symbol = removed.Symbol
            if symbol in self.symbol_data:
                del self.symbol_data[symbol]

    def OnData(self, data):
        if self.IsWarmingUp:
            return

        for symbol, data_set in self.symbol_data.items():
            if symbol in data:
                indicators = data_set["indicators"]
                indicators["contrarian_bands"].Update(data)
                indicators["rsi"].Update(data)
                indicators["hist_vol"].Update(data)
                indicators["trend"].Update(data)
                data_set["strategy"].Execute(indicators)