Overall Statistics
Total Orders
1
Average Win
0%
Average Loss
0%
Compounding Annual Return
15.694%
Drawdown
30.400%
Expectancy
0
Start Equity
10000
End Equity
49104.96
Net Profit
391.050%
Sharpe Ratio
0.609
Sortino Ratio
0.629
Probabilistic Sharpe Ratio
13.897%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0.014
Beta
1.015
Annual Standard Deviation
0.155
Annual Variance
0.024
Information Ratio
0.418
Tracking Error
0.037
Treynor Ratio
0.093
Total Fees
$4.12
Estimated Strategy Capacity
$310000.00
Lowest Capacity Asset
XLG T8IX1UPGD279
Portfolio Turnover
0.03%
Drawdown Recovery
685
# region imports
from AlgorithmImports import *
from utils import Utils
# endregion

class WellDressedApricotRat(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2015, 1, 1)

        self.init_cash = 10000
        self.set_cash(self.init_cash)

        self.symbol = "XLG"
        self.add_equity(self.symbol , Resolution.DAILY)
        self.set_name(self.symbol)

        self._symbol = self.add_equity("VOO", Resolution.DAILY).Symbol

        self.utils = Utils(self, self._symbol)
        self.schedule.on(self.date_rules.every_day(), self.time_rules.before_market_close(self._symbol, 0), self.utils.plot)


    def on_data(self, data: Slice):
        if not self.portfolio.invested:
            self.set_holdings(self.symbol, 1)
from AlgorithmImports import *

from Newtonsoft.Json import JsonConvert
import System
import psutil

class Utils():

    def __init__(self, algo, ticker):
        self.algo = algo
        self.ticker = ticker
        self.mkt = []
        self.insights_key = f"{self.algo.project_id}/Live_{self.algo.live_mode}_insights"
        self.algo.set_benchmark(ticker)

        self._initial_portfolio_value = self.algo.init_cash
        self._initial_benchmark_price = 0
        self._portfolio_high_watermark = 0

        self.init_chart()

    def init_chart(self):
        chart_name = "Strategy Performance"
        chart = Chart(chart_name)

        strategy_series = Series("Strategy", SeriesType.LINE, 0, "$")
        strategy_series.color = Color.ORANGE
        chart.add_series(strategy_series)

        benchmark_series = Series("Benchmark", SeriesType.LINE, 0, "$")
        benchmark_series.color = Color.LIGHT_GRAY
        chart.add_series(benchmark_series)

        drawdown_series = Series("Drawdown", SeriesType.LINE, 1, "%")
        drawdown_series.color = Color.INDIAN_RED
        chart.add_series(drawdown_series)
        
        self.algo.add_chart(chart)

    def plot(self):
        if self.algo.live_mode or self.algo.is_warming_up:
            return


        # Capture initial reference values
        if self._initial_portfolio_value == 0.0:
            self._initial_portfolio_value = float(self.algo.portfolio.total_portfolio_value)

        benchmark_price = float(self.algo.securities[self.algo._symbol].price)
        if self._initial_benchmark_price == 0.0 and benchmark_price > 0.0:
            self._initial_benchmark_price = benchmark_price

        # Ensure both initial values are set
        if self._initial_portfolio_value == 0.0 or self._initial_benchmark_price == 0.0:
            return

        # Current values
        current_portfolio_value = float(self.algo.portfolio.total_portfolio_value)

        # Defensive check (avoid division by zero)
        if self._initial_portfolio_value == 0.0 or self._initial_benchmark_price == 0.0:
            return

        # Normalize (start at 1.0)
        normalized_portfolio = current_portfolio_value / self._initial_portfolio_value
        normalized_benchmark = benchmark_price / self._initial_benchmark_price

        current_value = self.algo.portfolio.total_portfolio_value

        if current_value > self._portfolio_high_watermark:
            self._portfolio_high_watermark = current_value

        drawdown = 0.0

        if self._portfolio_high_watermark != 0.0:
            drawdown = (current_value - self._portfolio_high_watermark) / self._portfolio_high_watermark * 100.0

        chart_name = "Strategy Performance"
        self.algo.plot(chart_name, "Drawdown", drawdown)
        self.algo.plot(chart_name, "Strategy", normalized_portfolio*self.algo.init_cash)
        self.algo.plot(chart_name, "Benchmark", normalized_benchmark*self.algo.init_cash)

        self.algo.plot('Strategy Equity', self.ticker, normalized_benchmark*self.algo.init_cash)


    def pctc(no1, no2):
        return((float(str(no2))-float(str(no1)))/float(str(no1)))


    def stats(self):
        df = None
        trades = self.algo.trade_builder.closed_trades
        for trade in trades:
            data = {
                'symbol': trade.symbol,
                'time': trade.entry_time,
                'entry_price': trade.entry_price,
                'exit_price': trade.exit_price,
                'pnl': trade.profit_loss,
                'pnl_pct': (trade.exit_price - trade.entry_price)/trade.entry_price,
            }
            df = pd.concat([pd.DataFrame(data=data, index=[0]), df])
        
        if df is not None:
            profit = df.query('pnl >= 0')['pnl'].sum()
            loss = df.query('pnl < 0')['pnl'].sum()

            avgWinPercentPerWin = "{0:.2%}".format(df.query('pnl >= 0')['pnl_pct'].mean())
            avgLostPercentPerLost = "{0:.2%}".format(df.query('pnl < 0')['pnl_pct'].mean())
            maxLost = "{0:.2%}".format(df.query('pnl < 0')['pnl_pct'].min())
            maxWin = "{0:.2%}".format(df.query('pnl > 0')['pnl_pct'].max())
            
            self.algo.set_summary_statistic("*Profit Ratio", round(profit / abs(loss),2))
            self.algo.set_summary_statistic("Avg. Win% Per Winner", avgWinPercentPerWin)
            self.algo.set_summary_statistic("Avg. Lost% Per Losser", avgLostPercentPerLost)
            self.algo.set_summary_statistic("Max Loss%", maxLost)
            self.algo.set_summary_statistic("Max Win%", maxWin)


    def read_insight(self):
        if self.algo.object_store.contains_key(self.insights_key) and self.algo.live_mode:
            insights = self.algo.object_store.read_json[System.Collections.Generic.List[Insight]](self.insights_key)
            self.algo.log.debug(f"Read {len(insights)} insight(s) from the Object Store")
            self.algo.insights.add_range(insights)
            
            #self.algo.object_store.delete(self.insights_key)

    def store_insight(self):
        if self.algo.live_mode:
            insights = self.algo.insights.get_insights(lambda x: x.is_active(self.algo.utc_time))
            # If we want to save all insights (expired and active), we can use
            # insights = self.insights.get_insights(lambda x: True)

            self.algo.log.debug(f"Save {len(insights)} insight(s) to the Object Store.")
            content = ','.join([JsonConvert.SerializeObject(x) for x in insights])
            self.algo.object_store.save(self.insights_key, f'[{content}]')

    def get_near_expiry_insights(self, sourceModel, symbol):
        insights = self.algo.insights.get_insights(lambda insight: (insight.close_time_utc) == (self.algo.utc_time + timedelta(hours=1)) and insight.Symbol == symbol and insight.SourceModel == sourceModel and insight.is_active(self.algo.utc_time))

        return len(insights) > 0
    
    def trace_memory(self, name):
        self.algo.log.debug(f"[{name}] RAM memory % used: {psutil.virtual_memory()[2]} / RAM Used (GB): {round(psutil.virtual_memory()[3]/1000000000,2)}")