Overall Statistics
Total Orders
345
Average Win
1.70%
Average Loss
-1.35%
Compounding Annual Return
1.866%
Drawdown
10.400%
Expectancy
0.137
Start Equity
100000
End Equity
137850.30
Net Profit
37.850%
Sharpe Ratio
-0.099
Sortino Ratio
-0.116
Probabilistic Sharpe Ratio
0.047%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
1.25
Alpha
-0.003
Beta
-0.007
Annual Standard Deviation
0.04
Annual Variance
0.002
Information Ratio
-0.406
Tracking Error
0.171
Treynor Ratio
0.572
Total Fees
$593.30
Estimated Strategy Capacity
$170000000.00
Lowest Capacity Asset
IWM RV0PWMLXVHPH
Portfolio Turnover
1.17%
# https://quantpedia.com/strategies/insights-from-the-geopolitical-sentiment-index-made-with-google-trends/
# 
# The investment universe for this strategy consists of two key ETFs: the iShares Russell 2000 ETF (IWM), which represents small-cap stocks, and the SPDR S&P 
# 500 ETF Trust (SPY), which represents large-cap stocks.
# Rationale: These instruments are selected based on the research paper’s focus on the performance differential between small-cap and large-cap stocks in 
# response to changes in geopolitical sentiment.
# Variable Construction: This strategy’s primary tool is the Geopolitical Sentiment Index (GSI), constructed from Google Trends data. The GSI measures public 
# interest in geopolitical issues using keywords such as war, conflict, military, etc. The methodology involves calculating the 12-month percentage change in 
# the GSI to generate trading signals.
# Trading: The buy and sell rules are as follows:
# When the 12-month GSI percentage change is positive (indicating rising geopolitical stress), go long on IWM and short on SPY.
# When the 12-month GSI percentage change is negative (indicating declining geopolitical stress), go short on IWM and long on SPY.
# The strategy involves rebalancing the portfolio at the end of each month to reflect changes in the GSI. The weight of individual securities is in a (equal) 
# 1:1 ratio (50% / 50%).

# region imports
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
from typing import List, Tuple
from pandas.core.frame import DataFrame
# endregion

class InsightsFromTheGeopoliticalSentimentIndexIndexMadeWithGoogleTrends(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2008, 1, 1)
        self.set_cash(100_000)

        self._period: int = 12

        tickers: List[str] = ['SPY', 'IWM']

        self._traded_assets: List[Symbol] = [
            self.add_equity(ticker, Resolution.DAILY).symbol for ticker in tickers
        ]

        self._GSI: Symbol = self.add_data(GeopoliticalSentimentIndex, 'GSI', Resolution.DAILY).symbol

        self._selection_flag: bool = False
        self.settings.daily_precise_end_time = False
        self.settings.minimum_order_margin_portfolio_percentage = 0.
        self.schedule.on(self.date_rules.month_end(self._traded_assets[0]), 
                        self.time_rules.after_market_open(self._traded_assets[0]), 
                        self.selection)

    def on_data(self, slice: Slice) -> None:
        # monthly rebalance
        if not self._selection_flag:
            return
        self._selection_flag = False

        if self.securities[self._GSI].get_last_data() and self.time.date() > GeopoliticalSentimentIndex.get_last_update_date():
            self.liquidate()
            return

        history: DataFrame = self.history(self._GSI, start = self.time.date() - relativedelta(months=self._period), end=self.time.date())
        if len(history) == self._period:
            GSI_diff: float = (history.iloc[-1] / history.iloc[0] - 1).values[0]
            trade_directions: Tuple[int] = (-1, 1) if GSI_diff > 0 else (1, -1)

            # trade execution
            targets: List[PortfolioTarget] = []
            for i, symbol in enumerate(self._traded_assets):
                if slice.contains_key(symbol) and slice[symbol]:
                    targets.append(PortfolioTarget(symbol, trade_directions[i] / len(self._traded_assets)))
            
            self.set_holdings(targets, True)

    def selection(self) -> None:
        self._selection_flag = True

class GeopoliticalSentimentIndex(PythonData):
    _last_update_date: datetime.date = datetime(1,1,1).date()

    @staticmethod
    def get_last_update_date() -> Dict[Symbol, datetime.date]:
       return GeopoliticalSentimentIndex._last_update_date

    def GetSource(self, config: SubscriptionDataConfig, date: datetime, isLiveMode: bool) -> SubscriptionDataSource:
        return SubscriptionDataSource(f"data.quantpedia.com/backtesting_data/index/geopolitical_sentiment_index.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)

    def Reader(self, config: SubscriptionDataConfig, line: str, date: datetime, isLiveMode: bool) -> BaseData:
        data = GeopoliticalSentimentIndex()
        data.Symbol = config.Symbol

        if not line[0].isdigit(): return None
        split = line.split(';')
        
        data.Time = datetime.strptime(split[0], "%Y-%m-%d")
        if split[-1] != '.':
            data.Value = float(split[-1])

        if data.Time.date() > GeopoliticalSentimentIndex._last_update_date:
            GeopoliticalSentimentIndex._last_update_date = data.Time.date()
        
        return data