Overall Statistics
Total Orders
52369
Average Win
0.04%
Average Loss
-0.04%
Compounding Annual Return
-1.257%
Drawdown
50.100%
Expectancy
-0.005
Start Equity
100000
End Equity
92122.86
Net Profit
-7.877%
Sharpe Ratio
-0.074
Sortino Ratio
-0.079
Probabilistic Sharpe Ratio
0.184%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
1.01
Alpha
-0.099
Beta
1.042
Annual Standard Deviation
0.193
Annual Variance
0.037
Information Ratio
-1.083
Tracking Error
0.088
Treynor Ratio
-0.014
Total Fees
$52745.07
Estimated Strategy Capacity
$0
Lowest Capacity Asset
ENBL VPMVIGHDN339
Portfolio Turnover
23.32%
# region imports
from AlgorithmImports import *
# endregion


class NorthFieldAlpha(PythonData):

    def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live: bool):
        return SubscriptionDataSource(
            f"Alphas/Alphas {date.strftime('%Y%m%d')}.txt",
            SubscriptionTransportMedium.OBJECT_STORE,
            FileFormat.CSV
        )

    def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live: bool):
        if not line[0].isdigit():
            return None
        data = line.split('|')
        asset = NorthFieldAlpha()
        asset.symbol = SecurityDefinitionSymbolResolver.get_instance().cusip(data[0], date)
        if not asset.symbol:
            return
        asset.name = data[1]
        try:
            asset.alpha = float(data[2]) # These fields can be empty in the data file.
            asset.market_cap = float(data[3])
        except:
            return None
        asset.end_time = date #+ timedelta(1)
        return asset
# region imports
from AlgorithmImports import *
# endregion

class NorthFieldFactorCorrelation(PythonData):

    _rows_seen_by_date = {}

    def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live: bool):
        return SubscriptionDataSource(
            f"US_2_19_9g/FF_RSQ_RSQRM_US_v2_19_9g_USD_{date.strftime('%Y%m%d')}_Correl.txt",
            SubscriptionTransportMedium.OBJECT_STORE,
            FileFormat.CSV
        )

    def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live: bool):
        data = line.split('|')
        try:
            float(data[0])
        except:
            self._columns = data
            return None

        if date not in self._rows_seen_by_date:
            self._rows_seen_by_date[date] = 0
        else:
            self._rows_seen_by_date[date] += 1

        fc = NorthFieldFactorCorrelation()
        factor_name = self._columns[self._rows_seen_by_date[date]].replace(' ', '')
        fc.symbol = Symbol.create(factor_name, SecurityType.BASE, Market.USA)
        fc.end_time = date
        
        # Parse columns.
        fc.series = pd.Series(data, index=self._columns).astype(float)

        return fc
# region imports
from AlgorithmImports import *
# endregion

class NorthFieldFactorDefinition(PythonData):

    def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live: bool):
        return SubscriptionDataSource(
            f"US_2_19_9g/FF_RSQ_RSQRM_US_v2_19_9g_USD_{date.strftime('%Y%m%d')}_FactorDef.txt",
            SubscriptionTransportMedium.OBJECT_STORE,
            FileFormat.CSV
        )

    def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live: bool):
        if not line[0].isdigit():
            return None
        data = line.split('|')
        fd = NorthFieldFactorDefinition()
        
        # Parse columns.
        fd.name = data[1]
        fd.code = data[2]
        fd.variance = float(data[3])

        fd.symbol = Symbol.create(fd.code, SecurityType.BASE, Market.USA, fd.name)
        fd.end_time = date

        return fd
# region imports
from AlgorithmImports import *

import csv
from io import StringIO
# endregion

class NorthFieldFactorExposure(PythonData):

    _drop_unsupported_assets = False  # Change this if you want.

    def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live: bool):
        return SubscriptionDataSource(
            f"US_2_19_9g/RSQRM_US_v2_19_9g{date.strftime('%Y%m%d')}_USDcusip.csv",
            SubscriptionTransportMedium.OBJECT_STORE,
            FileFormat.CSV
        )

    def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live: bool):       
        data = next(csv.reader(StringIO(line)))
        fe = NorthFieldFactorExposure()
        
        # Parse columns.
        fe.name = data[1]
        fe.value = data[3]
        fe.monthly_residual_sd_pct = float(data[4])
        fe.betas = [float(data[i]) for i in range(6, 47)]
        fe.currency_quotation = data[48]
        fe.total_forecast_risk_ann_pct = data[49]

        # QC doesn't have ADRs, OTC stocks, European stocks, Forex without quote currencies, industry proxies...
        if "*" in data[0] or "_" in data[0]:
            fe.symbol = Symbol.create(data[0], SecurityType.BASE, Market.USA)
        else:
            fe.symbol = SecurityDefinitionSymbolResolver.get_instance().cusip(data[0], date)

        if not fe.symbol:
            #NorthFieldFactorExposure.algorithm.log(f"Error creating Symbol for {data[0]} at {date}. Name: {fe.name}")
            # Example CUSIPs that are dropped:
            # 2024-06-05 00:00:00 Error creating Symbol for G9T17Y80 at 2024-06-05 00:00:00. Name: "VANGD.US TRSY.0-1Y (OTC) BD UCITS ETF USD ACC"
            # 2024-06-05 00:00:00 Error creating Symbol for 06254520 at 2024-06-05 00:00:00. Name: "BANK OF HAWAII DRC EACH"
            # 2024-06-05 00:00:00 Error creating Symbol for 57142B10 at 2024-06-05 00:00:00. Name: "MARQETA A"
            # 2024-06-05 00:00:00 Error creating Symbol for G6543112 at 2024-06-05 00:00:00. Name: "NOBLE CORPORATION"
            # 2024-06-05 00:00:00 Error creating Symbol for 87283Q50 at 2024-06-05 00:00:00. Name: "T ROWE PRICE US EQUITY RESEARCH ETF"
            # 2024-06-05 00:00:00 Error creating Symbol for 50076757 at 2024-06-05 00:00:00. Name: "KRANESHARES HANG SENG TECH INDEX ETF"
            # 2024-06-05 00:00:00 Error creating Symbol for 29482Y20 at 2024-06-05 00:00:00. Name: "ERICKSON"
            # 2024-06-05 00:00:00 Error creating Symbol for 44615078 at 2024-06-05 00:00:00. Name: "HUNTINGTON BANCSHARES DEP"
            # 2024-06-05 00:00:00 Error creating Symbol for 41150T20 at 2024-06-05 00:00:00. Name: "HARBOR CUSTOM DEV.8 0 CUM CONV PREF. SR.A"
            if self._drop_unsupported_assets:
                return None
            else:
                fe.symbol = Symbol.create(data[0], SecurityType.BASE, Market.USA)

        fe.end_time = date
        
        return fe
# region imports
from AlgorithmImports import *
from alpha import NorthFieldAlpha
# endregion

# Documentation:
# - README: https://www.dropbox.com/scl/fi/5lubbi4p7b9art7ig73v6/read-me.docx?rlkey=bwvgomxo4b1kh3dtkzf8mvdtm&st=bz0ja53n&dl=0
# - Flat File description: https://www.dropbox.com/scl/fi/s5mgdu69lw7ef5k6wfs00/Flat-File-description-FF_-prefix.pdf?rlkey=uywqpaw8wvpw26no3isomqn5y&st=h0waii0g&dl=0

# Demo 4: Top 10 based on Alpha
class NorthFieldDemoAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2017, 12, 20)
        self.set_end_date(2024, 8, 15)
        self.universe_settings.resolution = Resolution.MINUTE

        # Add alpha universe.
        self._universe = self.add_universe(NorthFieldAlpha, 'NorthFieldAlpha', Resolution.DAILY, self._select_assets)

        spy = Symbol.create("SPY", SecurityType.EQUITY, Market.USA)
        self.schedule.on(self.date_rules.every_day(spy), self.time_rules.after_market_open(spy, 30), self.rebalance)

    def _select_assets(self, data):
        data = sorted(data, key=lambda x: x.alpha, reverse=True)[:10]
        return [x.symbol for x in data]

    def on_securities_changed(self, changes):
        for security in changes.removed_securities:
            self.liquidate(security.symbol)

    def rebalance(self):
        weight = 1 / len(self.active_securities.keys) if self.active_securities.keys else 0
        self.set_holdings([PortfolioTarget(symbol, weight) for symbol in self.active_securities.keys])
# region imports
from AlgorithmImports import *
# endregion


class NorthFieldUniverse(PythonData):

    def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live: bool):
        return SubscriptionDataSource(
            f"GeneralUniverse/Universe {date.strftime('%Y%m%d')}.txt",
            SubscriptionTransportMedium.OBJECT_STORE,
            FileFormat.CSV
        )

    def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live: bool):
        if not line[0].isdigit():
            return None
        data = line.split('|')
        asset = NorthFieldUniverse()
        symbol = SecurityDefinitionSymbolResolver.get_instance().cusip(data[0], date)
        if not symbol:
            return
        asset.symbol = symbol
        asset.market_cap = float(data[1])
        asset.name = data[2]
        asset.value = asset.market_cap
        asset.end_time = date
        return asset


class NorthFieldInvestableUniverse(NorthFieldUniverse):

    def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live: bool):
        return SubscriptionDataSource(
            f"InvestableUniverse/InvestableUniverse {date.strftime('%Y%m%d')}.txt",
            SubscriptionTransportMedium.OBJECT_STORE,
            FileFormat.CSV
        )