Overall Statistics
Total Orders
870
Average Win
0.20%
Average Loss
-0.25%
Compounding Annual Return
-4.439%
Drawdown
29.600%
Expectancy
-0.219
Start Equity
100000
End Equity
75567.66
Net Profit
-24.432%
Sharpe Ratio
-0.806
Sortino Ratio
-1.03
Probabilistic Sharpe Ratio
0.000%
Loss Rate
57%
Win Rate
43%
Profit-Loss Ratio
0.82
Alpha
-0.022
Beta
-0.179
Annual Standard Deviation
0.049
Annual Variance
0.002
Information Ratio
-1.077
Tracking Error
0.129
Treynor Ratio
0.22
Total Fees
$1006.83
Estimated Strategy Capacity
$17000.00
Lowest Capacity Asset
ADRU SJNVOV70T9B9
Portfolio Turnover
0.90%
#region imports
from AlgorithmImports import *

from collections import deque
import calendar
#endregion


class BetaAlphaModel(AlphaModel):

    def __init__(self):
        self.name = 'BetaAlphaModel'
        self._assets = {}
        self._month = datetime.min
        
    def update(self, algorithm, data):
        if data.contains_key("SPY"):
            self._market_price.append(float(algorithm.securities["SPY"].price))
        for key, value in self._assets.items():
            if data.contains_key(key):
                value.price.append(float(algorithm.securities[key].price))

        insights = []

        if self._month != algorithm.time.month:    
            self._month = algorithm.time.month 
            beta_values = {} 
            market_return = np.diff(np.array(self._market_price))/np.array(self._market_price)[:-1]
            long_ = None
            for key, value in self._assets.items():
                if key != "SPY" and len(value.price) == value.price.maxlen:
                    asset_return = np.diff(np.array(value.price))/np.array(value.price)[:-1]
                    beta_values[key] = self._beta(asset_return, market_return)
            sorted_by_beta = sorted(beta_values, key = lambda x: beta_values[x])
            long_ = sorted_by_beta[:int(0.25*len(sorted_by_beta))]
            short = sorted_by_beta[-int(0.25*len(sorted_by_beta)):]
            
            # day: the weekday of first day of the month 
            # num_days: number of days in month  
            day, num_days = calendar.monthrange(algorithm.time.year, algorithm.time.month)
            insight_period = num_days - algorithm.time.day - 1
            if long_ and short:
                invested = [x.key for x in algorithm.portfolio if x.value.invested]
                for i in invested:
                    if algorithm.portfolio[i].is_long and i not in long_:  
                        insights.append(Insight.price(i, timedelta(days=1), InsightDirection.FLAT))
                    if algorithm.portfolio[i].is_short and i not in short:  
                        insights.append(Insight.price(i, timedelta(days=1), InsightDirection.FLAT))
                        
                for i in long_:
                    insights.append(Insight.price(i, timedelta(days=insight_period), InsightDirection.UP))
                for i in short:
                    insights.append(Insight.price(i, timedelta(days=insight_period), InsightDirection.DOWN))
           
        return insights

    def on_securities_changed(self, algorithm, changes):
        for added in changes.added_securities:
            if added.symbol.value == "SPY":
                self._market_price = deque(maxlen=253)
                hist_spy = algorithm.history(["SPY"], 500, Resolution.DAILY)
                for i in hist_spy.loc["SPY"].itertuples():
                    self._market_price.append(i.close)     

            if added not in self._assets and added.symbol.value != "SPY":
                hist = algorithm.history([added.symbol.value], 500, Resolution.DAILY)
                if not hist.empty:
                    self._assets[added.symbol] = SymbolData(added)
                    for i in hist.loc[added.symbol.value].itertuples():
                        self._assets[added.symbol].price.append(i.close)                

        for removed in changes.removed_securities:
            if removed.symbol in self._assets:
                self._assets.pop(removed.symbol)

    def _beta(self, asset_return, market_return):
        asset_return = np.array(asset_return, dtype=np.float32)
        market_return = np.array(market_return, dtype=np.float32)
        return np.cov(asset_return, market_return)[0][1]/np.var(market_return)
    

class SymbolData:
    def __init__(self, symbol):
        self.symbol = symbol
        self.price = deque(maxlen=253)
        
#region imports
from AlgorithmImports import *

from alpha import BetaAlphaModel
from portfolio import MonthlyPortfolioConstructionModel
#endregion


class BetaAlgorithm(QCAlgorithmFramework):

    def initialize(self):

        self.set_start_date(2012, 1, 1)   #Set Start Date
        self.set_end_date(2018, 3, 1)    #Set End Date
        self.set_cash(100000)            #Set Strategy Cash

        self.universe_settings.resolution = Resolution.DAILY
        tickers =  [
                    "EWJ",  # iShares MSCI Japan Index ETF
                    "EZU",  # iShares MSCI Eurozone ETF
                    "EFNL", # iShares MSCI Finland Capped Investable Market Index ETF
                    "EWW",  # iShares MSCI Mexico Inv. Mt. Idx
                    "ERUS", # iShares MSCI Russia ETF
                    "IVV",  # iShares S&P 500 Index
                    "ICOL", # Consumer Discretionary Select Sector SPDR Fund
                    "AAXJ", # iShares MSCI All Country Asia ex Japan Index ETF
                    "AUD",  # Australia Bond Index Fund
                    "EWQ",  # iShares MSCI France Index ETF
                    "BUND", # Pimco Germany Bond Index Fund
                    "EWH",  # iShares MSCI Hong Kong Index ETF
                    "EPI",  # WisdomTree India Earnings ETF
                    "EIDO"  # iShares MSCI Indonesia Investable Market Index ETF
                    "EWI",  # iShares MSCI Italy Index ETF
                    "GAF",  # SPDR S&P Emerging Middle East & Africa ETF
                    "ENZL", # iShares MSCI New Zealand Investable Market Index Fund
                    "NORW"  # Global X FTSE Norway 30 ETF
                    "EWY",  # iShares MSCI South Korea Index ETF
                    "EWP",  # iShares MSCI Spain Index ETF
                    "EWD",  # iShares MSCI Sweden Index ETF
                    "EWL",  # iShares MSCI Switzerland Index ETF
                    "GXC",  # SPDR S&P China ETF
                    "EWC",  # iShares MSCI Canada Index ETF
                    "EWZ",  # iShares MSCI Brazil Index ETF
                    "ARGT", # Global X FTSE Argentina 20 ETF
                    "AND",  # Global X FTSE Andean 40 ETF
                    "AIA",  # iShares S&P Asia 50 Index ETF
                    "EWO",  # iShares MSCI Austria Investable Mkt Index ETF
                    "EWK",  # iShares MSCI Belgium Investable Market Index ETF
                    "BRAQ", # Global X Brazil Consumer ETF
                    "ECH",  # iShares MSCI Chile Investable Market Index ETF
                    "CHIB", # Global X China Technology ETF
                    "EGPT", # Market Vectors Egypt Index ETF
                    "ADRU"] # BLDRS Europe 100 ADR Index ETF
        symbols = [Symbol.create(ticker, SecurityType.EQUITY, Market.USA) for ticker in tickers]
        self.set_universe_selection(ManualUniverseSelectionModel(symbols))
        self.set_alpha(BetaAlphaModel())
        self.set_portfolio_construction(MonthlyPortfolioConstructionModel())
        self.set_execution(ImmediateExecutionModel())
        self.set_risk_management(NullRiskManagementModel())
        self.add_equity("SPY", Resolution.DAILY)
        
#region imports
from AlgorithmImports import *
#endregion


class MonthlyPortfolioConstructionModel(PortfolioConstructionModel):

    def __init__(self):
        self._month = datetime.min

    def create_targets(self, algorithm, insights):
        if self._month == algorithm.time.month:
            return []
        
        targets = []
        self._month = algorithm.time.month
        if len(insights) != 0:
            percent = 1 / len(insights)
            for insight in insights:
                target = PortfolioTarget.percent(algorithm, insight.symbol, insight.direction * percent)
                if not target is None:
                    targets.append(target)
        return targets