Overall Statistics
Total Orders
1580
Average Win
3.36%
Average Loss
-2.38%
Compounding Annual Return
284.614%
Drawdown
56.900%
Expectancy
0.131
Start Equity
100000
End Equity
541442.65
Net Profit
441.443%
Sharpe Ratio
2.988
Sortino Ratio
3.165
Probabilistic Sharpe Ratio
73.851%
Loss Rate
53%
Win Rate
47%
Profit-Loss Ratio
1.41
Alpha
2.159
Beta
4.143
Annual Standard Deviation
0.929
Annual Variance
0.864
Information Ratio
2.969
Tracking Error
0.885
Treynor Ratio
0.67
Total Fees
$42954.85
Estimated Strategy Capacity
$320000000.00
Lowest Capacity Asset
NQ YGT6HGVF2SQP
Portfolio Turnover
5175.76%
# region imports
from AlgorithmImports import *
from datetime import timedelta
import numpy as np
from sklearn.linear_model import LinearRegression
# endregion

class Volumeprofile(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2024, 6, 1)
        self.set_cash(100000)
        self.future_chains = None

        # Set the symbol of the asset we want to trade
        future = self.add_future(Futures.Indices.NASDAQ_100_E_MINI, Resolution.Minute)
        future.SetFilter(timedelta(0), timedelta(182))
        self.symbol = future.Symbol

        # Volume Profile indicator settings
        self.profile_period = 120 # 2 hours
        self.value_area_percentage = 0.4
        self.volume_profile = VolumeProfile("Volume Profile", self.profile_period, self.value_area_percentage)

        # Rolling window to store past prices
        self.past_prices_period = 20
        self.past_prices = RollingWindow[TradeBar](self.past_prices_period)

        # Long or short position
        self.is_long = True

        # Consolidate data
        self.Consolidate(self.symbol, timedelta(minutes=1), self.on_data_consolidated)
        self.register_indicator(self.symbol, self.volume_profile, timedelta(hours = 2))

        # Setting stoploss
        self.stop_loss_len = 100
        self.stop_loss_indicator = self.MIN(self.symbol, self.stop_loss_len, Resolution.MINUTE)
        self.lowest_low = 0
        self.stop_loss = 0
        self.start_stop_loss = False

        # Warm up period
        self.set_warm_up(timedelta(hours = 2))

        # Free portfolio setting
        self.settings.free_portfolio_value = 0.3
    
    def on_data_consolidated(self, data: Slice):
        # Store the past prices of the future contract
        self.past_prices.Add(data)

    def on_data(self, data: Slice):

        # Check if the strategy warm up period is over and indicators are ready
        if self.is_warming_up and not self.volume_profile.is_ready:
            return
        
        # Find the future contract
        for chain in data.FutureChains:
            self.popular_contracts = [contract for contract in chain.value if contract.open_interest > 1000]
            if len(self.popular_contracts) == 0:
                continue
            
            sorted_bt_o_i_contracts = sorted(self.popular_contracts, key=lambda k: k.open_interest, reverse=True)
            self.future_contract = sorted_bt_o_i_contracts[0]

        if self.past_prices.is_ready and self.volume_profile.is_ready:
            past_prices = [x.Close for x in self.past_prices if x is not None]
            slope = self.compute_slope(past_prices)
            # Check if price is moving towards the value area
            current_price = self.past_prices[0].Close

            # Entry
            if not self.portfolio.invested:
                if (self.volume_profile.value_area_low <= current_price <= self.volume_profile.value_area_high):
                    # Long condition
                    if slope < -0.5:
                        # self.log("Price is moving up towards the value area")
                        self.set_holdings(self.future_contract.symbol, 1)
                        self.stop_loss = self.stop_loss_indicator.Current.Value
                        self.start_stop_loss = True
                        # self.is_long = True

        #             elif slope < 0:
        #                 self.log("Price is moving down towards the value area")
        #                 self.set_holdings(self.symbol, -1)
        #                 self.take_profit = self.volume_profile.profile_low
        #                 self.stop_loss = self.volume_profile.poc_price
        #                 self.is_long = False
            
            # Exit
            if self.portfolio.invested and self.start_stop_loss:
                # Register stop loss and take profit levels
                if self.past_prices.IsReady:
                    if self.past_prices[0].Close > self.past_prices[1].Close:
                        self.stop_loss += (self.past_prices[0].Close - self.past_prices[1].Close)
                            # Stop loss
                # if data.Close < self.stop_loss or not self.Time.hour in range(9, 16):
                if current_price < self.stop_loss:
                    self.log(f"Stop loss at {current_price}")
                    self.liquidate(self.future_contract.symbol)
                    self.start_stop_loss = False
            # elif self.portfolio.invested and not self.is_long:
            #     if current_price <= self.take_profit or current_price >= self.stop_loss:
            #         self.log("Closing short position")
            #         self.Liquidate()
        #     # Plotting the data
        #     # self.plot("VolumeProfile","vp", self.volume_profile.current.value)
        #     self.plot("VolumeProfile","profile_high", self.volume_profile.profile_high)
        #     self.plot("VolumeProfile","profile_low", self.volume_profile.profile_low)
        #     self.plot("VolumeProfile","poc_price", self.volume_profile.poc_price)
        #     # self.plot("VolumeProfile","poc_volume", self.volume_profile.poc_volume)
        #     # self.plot("VolumeProfile","value_area_volume", self.volume_profile.value_area_volume)
        #     self.plot("VolumeProfile","value_area_high", self.volume_profile.value_area_high)
        #     self.plot("VolumeProfile","value_area_low", self.volume_profile.value_area_low)
        #     self.plot("VolumeProfile","current_price", self.past_prices[0].Close)
    
    def compute_slope(self, prices: list) -> float:
        # Convert list to numpy array and reshape to 2D for sklearn
        prices_array = np.array(prices).reshape(-1, 1)
        
        # Create an array of indices representing time
        times = np.array(range(len(prices))).reshape(-1, 1)
        
        # Fit a linear regression model
        model = LinearRegression().fit(times, prices_array)
        
        # Return the slope of the regression line
        return model.coef_[0][0]