Module boosted.api.api_type

Expand source code
# Copyright (C) 2020 Gradient Boosted Investments, Inc. - All Rights Reserved

import copy
import datetime as dt
import json
import logging
import re
import string
from collections import defaultdict
from dataclasses import dataclass
from enum import Enum
from typing import Dict, List, Literal, Optional, Union

import numpy as np
import pandas as pd

logger = logging.getLogger("boosted.api.api_type")

BoostedDate = Union[dt.date, str]


class Status(Enum):
    UNKNOWN = 0
    FAIL = 1
    SUCCESS = 2


class DataAddType(Enum):
    CREATION = 1
    HISTORICAL = 2
    LIVE = 3

    def __str__(self):
        if self.name == "CREATION":
            return "CREATION"
        elif self.name == "HISTORICAL":
            return "HISTORICAL"
        elif self.name == "LIVE":
            return "LIVE"


class ChunkStatus(Enum):
    PROCESSING = "PROCESSING"
    ABORTED = "ABORTED"
    SUCCESS = "SUCCESS"
    WARNING = "WARNING"
    ERROR = "ERROR"


class DataSetType(Enum):
    UNKNOWN = 0
    STOCK = 1
    GLOBAL = 2
    STRATEGY = 3

    def __str__(self):
        if self.name == "STOCK":
            return "STOCK"
        elif self.name == "GLOBAL":
            return "GLOBAL"
        elif self.name == "STRATEGY":
            return "STRATEGY"
        else:
            return "UNKNOWN"


class DataSetSubType(Enum):
    UNKNOWN = 0
    DENSE = 1
    SPARSE_HIST = 2
    SPARSE_FWD = 3

    def __str__(self):
        if self.name == "DENSE":
            return "DENSE"
        elif self.name == "SPARSE_HIST":
            return "SPARSE_HIST"
        elif self.name == "SPARSE_FWD":
            return "SPARSE_FWD"
        else:
            return "UNKNOWN"


class DataSetFrequency(Enum):
    UNKNOWN = 0
    DAILY = 1
    WEEKLY = 2
    MONTHLY = 3
    QUARTERLY = 4
    SEMIANNUAL = 5
    ANNUAL = 6

    def __str__(self):
        if self.name == "DAILY":
            return "DAILY"
        elif self.name == "WEEKLY":
            return "WEEKLY"
        elif self.name == "MONTHLY":
            return "MONTHLY"
        elif self.name == "QUARTERLY":
            return "QUARTERLY"
        elif self.name == "SEMIANNUAL":
            return "SEMIANNUAL"
        elif self.name == "ANNUAL":
            return "ANNUAL"
        else:
            return "UNKNOWN"


class ColumnRole(Enum):
    UNKNOWN = 0
    VARIABLE = 1
    GOAL = 2
    METRIC = 3
    IDENTIFIER = 4

    def __str__(self):
        if self.name == "VARIABLE":
            return "VARIABLE"
        elif self.name == "GOAL":
            return "GOAL"
        elif self.name == "METRIC":
            return "METRIC"
        elif self.name == "IDENTIFIER":
            return "IDENTIFIER"
        else:
            return "UNKNOWN"


class ColumnSubRole(Enum):
    UNKNOWN = 0
    GBI_ID = 1
    ISIN = 2
    GVKEY = 3
    IID = 4
    SYMBOL = 5
    COUNTRY = 6
    CURRENCY = 7
    DATE = 8
    COMPANY_NAME = 9
    REPORT_DATE = 10
    REPORT_PERIOD = 11

    def __str__(self):
        if self.name == "GBI_ID":
            return "GBI_ID"
        elif self.name == "ISIN":
            return "ISIN"
        elif self.name == "GVKEY":
            return "GVKEY"
        elif self.name == "IID":
            return "IID"
        elif self.name == "SYMBOL":
            return "SYMBOL"
        elif self.name == "COUNTRY":
            return "COUNTRY"
        elif self.name == "CURRENCY":
            return "CURRENCY"
        elif self.name == "DATE":
            return "DATE"
        elif self.name == "COMPANY_NAME":
            return "COMPANY_NAME"
        elif self.name == "REPORT_DATE":
            return "REPORT_DATE"
        elif self.name == "REPORT_PERIOD":
            return "REPORT_PERIOD"
        else:
            return "UNKNOWN"

    def get_match(column_name):
        def clean_str(name):
            translation = str.maketrans("", "", string.punctuation)
            clean_name = name.strip().translate(translation).lower()
            return re.sub(r"\s+", "", clean_name)

        identifiers = [
            ColumnSubRole.GBI_ID,
            ColumnSubRole.ISIN,
            ColumnSubRole.GVKEY,
            ColumnSubRole.IID,
            ColumnSubRole.SYMBOL,
            ColumnSubRole.COUNTRY,
            ColumnSubRole.CURRENCY,
            ColumnSubRole.COMPANY_NAME,
            ColumnSubRole.REPORT_DATE,
            ColumnSubRole.REPORT_PERIOD,
        ]

        for identifier in identifiers:
            if clean_str(str(identifier)) == clean_str(column_name):
                return identifier
        return None


class ColumnValueType(Enum):
    UNKNOWN = 0
    NUMBER = 1
    STRING = 2

    def __str__(self):
        if self.name == "UNKNOWN":
            return "UNKNOWN"
        elif self.name == "NUMBER":
            return "NUMBER"
        elif self.name == "STRING":
            return "STRING"
        else:
            return "UNKNOWN"


class ColumnConfig:
    def __init__(
        self,
        name=None,
        role=None,
        sub_role=None,
        value_type=ColumnValueType.NUMBER,
        description="",
        investment_horizon=None,
    ):
        self.name = name
        self.role = role
        self.sub_role = sub_role
        self.value_type = value_type
        # More value_types will be supported in the future.
        self.description = description
        self.investment_horizon = investment_horizon

    def __str__(self):
        return 'Name: "{0}", Role: {1}, SubRole: {2}, Value type: {3}, Desc: {4} IH: {5}.'.format(
            self.name,
            self.role,
            self.sub_role,
            self.value_type,
            self.description,
            self.investment_horizon,
        )

    def __repr__(self):
        return self.__str__()


class StrategyConfig:
    def __init__(self, name=None, source_name=None):
        self.name = name
        self.source_name = source_name

    def __str__(self):
        return 'Name: "{0}", Source Name: {1}.'.format(self.name, self.source_name)

    def __repr__(self):
        return self.__str__()


class BoostedDataSetSchemaException(Exception):
    def __init__(self, value, data=None):
        self.value = value
        self.data = data

    def __str__(self):
        return repr(self.value)


class DataSetConfig:
    def __init__(
        self,
        name,
        datasetType=DataSetType.STOCK,
        datasetSubType=DataSetSubType.DENSE,
        datasetFrequency=DataSetFrequency.DAILY,
    ):
        self.name = name
        self.type = datasetType
        self.subtype = datasetSubType
        self.frequency = datasetFrequency
        self.columns = []
        self.strategies = []

    def addColumn(self, columnConfig):
        self.columns.append(columnConfig)

    def addStrategy(self, strategyConfig):
        self.strategies.append(strategyConfig)

    def __getColumnByName(self, col_name):
        for column in self.columns:
            if col_name == column.name:
                return column
        raise BoostedDataSetSchemaException(f"Unable to find column {col_name}")

    def updateColumnToGoal(self, col_name, investment_horizon):
        column = self.__getColumnByName(col_name)
        column.role = ColumnRole.GOAL
        column.investment_horizon = int(investment_horizon)

    def updateColumnToMetric(self, col_name):
        column = self.__getColumnByName(col_name)
        column.role = ColumnRole.METRIC

    def updateColumnToVariable(self, col_name):
        column = self.__getColumnByName(col_name)
        column.role = ColumnRole.VARIABLE

    def __validateSchema(self):
        num_goals = 0
        num_metrics = 0
        dt = self.type
        dst = self.subtype
        dsf = self.frequency
        if len(self.columns) == 0:
            msg = "No feature columns exist."
            raise BoostedDataSetSchemaException(msg)

        if dst in [DataSetSubType.SPARSE_HIST, DataSetSubType.SPARSE_FWD]:
            if dsf not in [DataSetFrequency.QUARTERLY]:
                msg = f"{dsf} frequency is not supported for {dst} sub data"
                raise BoostedDataSetSchemaException(msg)
            if dt not in [DataSetType.STOCK]:
                msg = f"{dst} subtype is not supported for {dt} data"
                raise BoostedDataSetSchemaException(msg)

        for column in self.columns:
            if column.role == ColumnRole.GOAL:
                ih = column.investment_horizon
                gn = column.name
                if dt == DataSetType.GLOBAL:
                    msg = f"{dt} data can not have {ColumnRole.GOAL} type"
                    raise BoostedDataSetSchemaException(msg)
                if not isinstance(ih, int):
                    msg = f"Investment horizon for {gn} must be an integer"
                    raise BoostedDataSetSchemaException(msg)
                if ih < 1 or ih > 252:
                    msg = f"Investment horizon must be between 1 and 252 for {gn}"
                    raise BoostedDataSetSchemaException(msg)
                num_goals += 1
            elif column.role == ColumnRole.METRIC:
                if dt in [DataSetType.GLOBAL, DataSetType.STOCK]:
                    msg = f"{dt} data can not have {ColumnRole.METRIC} type"
                    raise BoostedDataSetSchemaException(msg)
                num_metrics += 1

        if dt == DataSetType.STRATEGY:
            if num_goals == 0:
                msg = "Independent data requires at least one goal."
                raise BoostedDataSetSchemaException(msg)
            if num_metrics == 0:
                msg = "Independent data requires at least one metric."
                raise BoostedDataSetSchemaException(msg)
            if len(self.strategies) <= 1:
                msg = "Independent data requires more than 1 strategy"
                raise BoostedDataSetSchemaException(msg)

    def toDict(self):
        self.__validateSchema()
        config = {}
        config["name"] = self.name
        config["type"] = str(self.type)
        config["subType"] = str(self.subtype)
        config["frequency"] = str(self.frequency)
        featureList = []
        for i, f in enumerate(self.columns):
            fm = {}
            fm["name"] = f.name
            fm["description"] = f.description
            fm["type"] = str(f.role)
            fm["role"] = str(f.role)
            fm["subRole"] = str(f.sub_role) if f.sub_role is not None else None
            fm["valuesType"] = str(f.value_type)
            fm["columnName"] = f.name
            fm["investmentHorizon"] = f.investment_horizon
            featureList.append(fm)
        config["features"] = featureList
        strategyList = []
        for i, s in enumerate(self.strategies):
            sm = {}
            sm["name"] = s.name
            sm["sourceName"] = s.source_name
            strategyList.append(sm)
        config["strategies"] = strategyList
        return config

    def __str__(self):
        a = ""
        a += "Name: {0}\n".format(self.name)
        a += "Columns: \n"
        for c in self.columns:
            a += "  {0}\n".format(c)
        return a

    def __repr__(self):
        return self.__str__()

    def fromDict(schema):
        subtype = DataSetSubType[schema.get("subtype", str(DataSetSubType.DENSE))]
        frequency = DataSetFrequency[schema.get("frequency", str(DataSetFrequency.DAILY))]
        config = DataSetConfig(
            schema["name"],
            datasetType=DataSetType[schema["type"]],
            datasetSubType=subtype,
            datasetFrequency=frequency,
        )
        # two things left to fill in - strategies and columns
        for strategy_data in schema["strategies"]:
            config.addStrategy(
                StrategyConfig(name=strategy_data["name"], source_name=strategy_data["sourceName"])
            )

        for feature_data in schema["features"]:
            config.addColumn(
                ColumnConfig(
                    name=feature_data["columnName"],
                    description=feature_data["description"] or "",
                    investment_horizon=feature_data["investmentHorizon"],
                    value_type=ColumnValueType[feature_data["valuesType"]]
                    if feature_data["valuesType"] is not None
                    else ColumnValueType.NUMBER,
                    role=ColumnRole[feature_data["role"]]
                    if feature_data["role"] is not None
                    else None,
                    sub_role=ColumnSubRole[feature_data["subRole"]]
                    if feature_data["subRole"] is not None
                    else None,
                )
            )

        config.__validateSchema()
        return config


class GbiIdSecurityException(Exception):
    def __init__(self, value, data=None):
        self.value = value
        self.data = data

    def __str__(self):
        return repr(self.value)


class GbiIdSecurity:
    def __init__(self, gbi_id, isin_country_currency_date, ticker, company_name):
        self.gbi_id = gbi_id
        self.isin_info = isin_country_currency_date
        self.ticker = ticker
        self.company_name = company_name

    def __str__(self):
        return '\n(gbi_id: "{0}", isin_info: {1}, ticker: {2}, company_name: {3})'.format(
            self.gbi_id, self.isin_info.__str__(), self.ticker, self.company_name
        )

    def __repr__(self):
        return self.__str__()

    def __eq__(self, __o: object) -> bool:
        return self.gbi_id == __o.gbi_id

    def __hash__(self):
        return hash(self.gbi_id)


class DateIdentCountryCurrencyException(Exception):
    def __init__(self, value, data=None):
        self.value = value
        self.data = data

    def __str__(self):
        return repr(self.value)


# for backward compatibility
class DateIsinCountryCurrencyException(DateIdentCountryCurrencyException):
    pass


class DateIdentCountryCurrency:
    def __init__(
        self,
        date: str,
        identifier: str,
        country: str,
        currency: str,
        id_type: ColumnSubRole = ColumnSubRole.ISIN,
    ):
        self.date = date
        self.identifier = identifier
        self.id_type = id_type
        self.country = country
        self.currency = currency
        self.__validateInput()

    def __str__(self):
        return '(date: "{0}", identifier: "{1}", country: {2}, currency: {3})'.format(
            self.date, self.identifier, self.country, self.currency
        )

    def __repr__(self):
        return self.__str__()

    def __validateInput(self):
        # simply ensure that at least isin and date are filled out.
        # country/currency defaults to ANY if not filled out, but
        # still is not recommended.
        if self.identifier is None or self.date is None:
            raise DateIdentCountryCurrencyException(
                "ISIN or Date not provided while constructing DateIsinCountryCurrency"
            )

        if self.id_type not in (ColumnSubRole.ISIN, ColumnSubRole.SYMBOL):
            raise DateIdentCountryCurrencyException(f"Invalid ID Type: {self.id_type}")

        def check_is_str(value, fieldname):
            if value is not None and not isinstance(value, str):
                raise DateIdentCountryCurrencyException(
                    f"Field {fieldname} in DateIsinCountryCurrency was not a string."
                )

        check_is_str(self.date, "date")
        check_is_str(self.identifier, "identifier")
        check_is_str(self.country, "country")
        check_is_str(self.currency, "currency")


# for backwards compatibility
class DateIsinCountryCurrency(DateIdentCountryCurrency):
    def __init__(self, date, isin, country, currency):
        super().__init__(date, isin, country, currency, ColumnSubRole.ISIN)
        self.isin = isin

    def __str__(self):
        return '(date: "{0}", isin: "{1}", country: {2}, currency: {3})'.format(
            self.date, self.isin, self.country, self.currency
        )


class IdentifierToColumnMapping:
    """
    IdentifierToColumnMapping is a mapping from string to boosted.api.api_type.ColumnSubRole.
    The key of the mapping corresponds to a CSV column name, and the value corresponds
    to the type of identifier that it maps to. Only columns specifying an identifying factor
    for the stock needs to be specified (e.g. ISIN, Date, Symbol, Currency)
    """

    def __init__(self, mapping):
        self.mapping = mapping
        self.__validateInput()

    def __str__(self):
        return repr(self.value)

    def __validateInput(self):
        def validate_key_val(key, val):
            if not isinstance(key, str):
                raise Exception(f"key in IdentifierToColumnMapping {key} was not a string!")
            if not isinstance(val, ColumnSubRole):
                raise Exception(f"val in IdentifierToColumnMapping {val} was not a ColumnSubRole!")

        for key, val in self.mapping:
            validate_key_val(key, val)


class PortfolioSettings:
    """
    This config is a documentation wrapper around a dict representing the portfolio settings json.
    Keys missing in the dict provided will fallback to the listed default when updating the
    the portfolio settings on the server.

    Parameters
    ----------
    activeRisk: bool
        - **Active Risk**:
            Set the portfolio optimization parameters to be relative to the score from the stock
            universe. i.e. if you set Momentum to 0.5 to 1.0 and the stock universe has a market cap
            weighed score of 0.2 then the optimizer will solve for 0.7 to 1.2.
        - Constraints:
            Boolean value
        - Default Value: false

    allowCompanyCollisions: bool
        - **Allow Multiple Securities Per Company**:
            Allows the machine to trade multiple securities (at the same time) for the same company.
            If it is ON and a company has listed PREF shares and COMMON shares, both will show up as
            tradeable options for that company. If it is OFF then system will try to determine the
            "correct" security to trade based on volume, liquidity, and share type.
        - Constraints:
            Boolean value
        - Default Value: true

    allowDR: bool
        - **Allow Depositary Receipts**:
            Companies with tickers ending in .Y and .F will be disallowed from trading within the
            model with this setting off. Turn it on to allow trading in these securities.
        - Constraints:
            Boolean value
        - Default Value: true

    benchmark: array
        - **Benchmark**:
            Set one or more benchmarks and the weighting for each.
        - Constraints:
            Required.
            List of dicts with keys:
            gbi_id: integer representing the GBI ID of the benchmark index.
            weight: floating point value between 0 and 1.
            All gbi_id's in the dictionary must be unique, and the benchmark weights must sum to a
            number between 0 and 1.
        - Default Value: []

    benchmarkAllocation: float
        - **Benchmark Allocation**:
            None
        - Constraints:
            Floating point value representing a percentage between -100 and 100
        - Default Value: 0

    beta_neutral: bool
        - **Beta Neutral**:
            Adjust the weights to get the ex-ante Beta to be the net exposure of the portfolio. i.e.
            70% Long / 30% Short = 0.4 Beta"
        - Constraints:
            Boolean value
        - Default Value: false

    bounds_method: str | null
        - **Optimizer Bounds**:
            Tight: The optimizer will only allow the weightings of securities in your portfolio to
            stay tight (close) to their initial weighting.
            Loose: The optimizer will allow the weightings of securities in your portfolio to move a
            medium amount more from their initial weighting.
            Wide: The optimizer will allow almost any weight between the minimum and maximum weight
            per long or short position.
        - Constraints:
            String enum of {loose, tight, wide}, or null
        - Default Value: null

    compounding: bool
        - **Compound Returns**:
            When toggled on, this will allow the portfolio to compound returns.
        - Constraints:
            Boolean value
        - Default Value: true

    currency: str
        - **Currency**:
            The currency you would like your portfolio to be calculated in. Note that if the
            currency differs from its exchange (i.e. GBP on the S&P), the prior close foreign
            exchange rate will be used for calculations.
        - Constraints:
            String representing a 3 digit ISO currency code.
        - Default Value: "USD"

    equalWeightBenchmark: bool
        - **Equal Weight Benchmark**:
            Instead of using the defined benchmarks, set the benchmark to be an equal weighted (per
            rebalance period) version of the stock universe.
        - Constraints:
            Boolean value
        - Default Value: false

    executionDelay: int
        - **Execution Delay**:
            This option adds the number of days selected to the trade execution. If you select T+1
            and your rebalance period is set to Weekly (Monday), it will trade on Tuesday, and so
            on.
        - Constraints:
            Integer value between 0 and 100, inclusive.
        - Default Value: 0

    factorNeutralizeSignal: bool
        - **Signal Optimizer**:
            Turn on optimization at the signal level that will adjust signals and rankings according
            to the specifications you set. This is done prior to portfolio construction and can help
            reduce bias in the model.
        - Constraints:
            Boolean value
        - Default Value: false

    investmentHorizon: int
        - **Investment Horizon**:
            The investment horizon for the portfolio.
        - Constraints:
            An integer representing a number of days
        - Default Value: 21

    lower_turnover: bool
        - **Lower Turnover**:
            If toggled on the optimizer will try to solve the secondary goal of optimizing the
            primary goals while limiting turnover.
        - Constraints:
            Boolean value
        - Default Value: false

    marketCapBounds: float
        - **Maximum Market Cap Variation**:
            How far from the target market cap weighted index each stock can deviate (positive or
            negative).
        - Constraints:
            Floating point value representing a percentage between 0 and 10
        - Default Value: 1

    marketCapConstraint: bool
        - **Constrain Market Cap**:
            Force weighting to be near the target weights for a market cap weighted index of your
            entire stock universe.
        - Constraints:
            Boolean value
        - Default Value: false

    marketCapFlex: float
        - **Market Cap Flex**:
            How far from the target market cap weighted the stock can deviate relative to the
            existing weight (i.e. at 75% a 1% position cannot exceed 1.75%).
        - Constraints:
            Floating point value representing a percentage between 10 and 200
        - Default Value: 100

    maxLongPosition: float
        - **Max Long Position per Stock**:
            The maximum weighting of a long position within the portfolio.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 10.0

    maxNumStockLong: int
        - **Maximum Number of Longs**:
            The maximum number of stocks to buy. This is on a per “portfolio” basis, so if you have
            sector neutral turned on it will be maximum per sector.
        - Constraints:
            Integer value between 0 and 1000, inclusive. Must be >= minNumStockLong
        - Default Value: 50

    maxNumStockShort: int
        - **Maximum Number of Shorts**:
            The maximum number of stocks to short. This is on a per “portfolio” basis, so if you
            have sector neutral turned on it will be maximum per sector.
        - Constraints:
            Integer value between 0 and 1000, inclusive. Must be >= minNumStockShort
        - Default Value: 50

    maxOwnership: float
        - **Maximum Ownership**:
            The maximum percentage of a company the portfolio is allowed to own. For example, if a
            company had a market cap of $100MM and this was 5% the portfolio could not own more than
            $5MM of that company.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 5

    maxShortPosition: float
        - **Max Short Position per Stock**:
            The maximum weighting of a short position within the portfolio.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 5.0

    minimumSharePrice: float
        - **Minimum Share Price**:
            The minimum share price you want the portfolio to allow.
        - Constraints:
            Floating point value between 0 and 100000
        - Default Value: 2

    minLongPosition: float
        - **Min Long Position per Stock**:
            The minimum weighting of a long position within the portfolio.
        - Constraints:
            Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 0

    minNumStockLong: int
        - **Minimum Number of Longs**:
            The minimum number of stocks to buy. This is on a per “portfolio” basis, so if you have
            sector neutral turned on it will be minimum per sector.
        - Constraints:
            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockLong
        - Default Value: 5

    minNumStockShort: int
        - **Minimum Number of Shorts**:
            The minimum number of stocks to short. This is on a per “portfolio” basis, so if you
            have sector neutral turned on it will be minimum per sector.
        - Constraints:
            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockShort
        - Default Value: 5

    minShortPosition: float
        - **Min Short Position per Stock**:
            The minimum weighting of a short position within the portfolio.
        - Constraints:
            Floating point value representing a percentage between 0.0 and 100.0
        - Default Value: 0

    missingMarketCap: float
        - **Missing Market Cap**:
            The amount in millions (MM) to replace any missing market capitalization information
            with. This will impact the maximum ownership setting.
        - Constraints:
            Floating point value between 0 and 100
        - Default Value: 10

    nameBasedSectorWeighting: bool
        - **Name Based Sector Weighting**:
            Base sector weightings on number of names. For example, if there are 200 names in the
            index and financials has 20 companies, the weight of financials should be 10% (20/200).
        - Constraints:
            Boolean value
        - Default Value: false

    netBenchmark: bool
        - **Adjust Benchmark for Net Exposure**:
            If your portfolio has a net exposure greater or less than 100%, this will adjust the
            benchmark returns to match that net exposure.
        - Constraints:
            Boolean value
        - Default Value: false

    optimizer: str
        - **Optimizer Type**:
            No Optimization: None
            Reduce Risk: Optimizer designed to reduce portfolio volatility. This tends to result in
            the best overall performance.
            Maximize Sharpe: Optimizer designed to maximize Sharpe using out of sample estimates for
            expected return. Estimates for expected return have an outsized impact on the
            performance of this optimizer.
            Maximize Alpha: Optimizer designed to maximize expected return based on out-of-sample
            estimates for expected return. Estimates for expected return have an outsized impact on
            the performance of this optimizer.
            Min VaR: Minimize Value at Risk by looking back at the proposed portfolio over the last
            year and trying to minimize drawdowns.
            Max VaR Sharpe: Maximize Value at Risk Sharpe by looking back at the proposed portfolio
            over the last year and trying to maximize Sharpe by observed return for the portfolio vs
            observed volatility.
            Minimize Skew: Minimize the skew of the portfolio by trying to find a set of portfolio
            weightings that results in returns that are closer to the mean.
        - Constraints:
            Required. String enum: one of
            default: No Optimization
            min_vol: Reduce Risk
            max_sharpe: Maximize Sharpe
            max_alpha: Maximize Alpha
            min_var: Min VaR
            max_var_sharpe: Max VaR Sharpe
            min_skew: Minimize Skew
        - Default Value: "default"

    optimizeTurnover: bool
        - **Turnover Optimization**:
            Turnover optimization will reduce the turnover at the signal level, making that ranked
            list more stable.
        - Constraints:
            Boolean value
        - Default Value: false

    pairsTrading: bool
        - **Pairs Trading**:
            Pairs Trading forces the portfolio to be created by going long / short an equal number
            of stocks. The portfolio will try to find an offsetting position for every long by
            finding securities that are both highly correlated and have a significant difference in
            star rating.
        - Constraints:
            Boolean value
        - Default Value: false

    percentPortfolioLong: float
        - **Percent of Portfolio Long**:
            The exact sum of all long position weightings in the portfolio.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 100.0

    percentPortfolioShort: float
        - **Percent of Portfolio Short**:
            The exact sum of all short position weightings in the portfolio.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 0.0

    percentToLong: float
        - **Percent to Long**:
            The machine will attempt to buy the top X% of stocks, subject to the minimum and maximum
            specified. This is on a per “portfolio” basis, so if you have sector neutral turned on
            it will be top X% per sector. This will be subject to the maximum and minimum number of
            securities you specify below.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 20.0

    percentToShort: float
        - **Percent to Short**:
            The machine will attempt to short the bottom X% of stocks, subject to the minimum and
            maximum specified. This is on a per “portfolio” basis, so if you have sector neutral
            turned on it will be bottom X% per sector. This will be subject to the maximum and
            minimum number of securities you specify below.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 20.0

    portfolioStartingValue: float
        - **Portfolio Starting Value**:
            The value of the portfolio in MM when it begins trading. If its backtest spans from 2005
            - 2020, the starting value in 2005 will be whatever you set here.
        - Constraints:
            Floating point value between 0 and 100000.
        - Default Value: 100.0

    priceCleaning: bool
        - **Price Cleaning**:
            Price Cleaning removes any very large price movements from the securities, specifically
            with a daily change greater than +500% or -83.33%. There are some legitimate securities
            with moves this large that will be impacted by turning this on, but the advantage is it
            prevents these securities from overwhelming the backtest.
        - Constraints:
            Boolean value
        - Default Value: true

    priceType: str
        - **Price Type**:
            None
        - Constraints:
            Required. String enum of {CLOSE, OPEN, VWAP}
        - Default Value: "VWAP"

    rebalancePeriod: str
        - **Rebalance Period**:
            When the machine rebalances the portfolio. You can choose a specific day if you prefer
            to execute trades on a specific day.
        - Constraints:
            Required.
            An enum value in the following list:
            "daily"
            "weekly": Rebalances on every Monday.
            Chooses the next available trade date to rebalance when Monday is a holiday
            "weekly_wednesday": Rebalances on every Wednesday.
            Chooses the next available trade date to rebalance when Wednesday is a holiday
            "weekly_friday": Rebalances on every Friday.
            Chooses the previous available trade date to rebalance when Friday is a holiday
            "monthly"
            "quarterly"
            OR a string in the following format: "custom_x" where x is an integer between 1 and 252
            (days).
        - Default Value: "weekly"

    sectorNeutral: bool
        - **Sector Neutral**:
            Will be constructed as a series of portfolios - each matching the market cap weighting
            of each of the stock universe sectors. This means that min. and max. number of stocks
            "per portfolio" becomes "per sector".
        - Constraints:
            Boolean value
        - Default Value: false

    sectorNeutralEqualWeightBenchmark: bool
        - **Sector Neutral Equal Weight Benchmark**:
            Instead of using the defined benchmarks, set the benchmark to be a "sector neutral"
            equal weighted (per rebalance period) version of the stock universe. This takes the
            sector weighting and divides by the number of stocks in that sector, so each sector will
            have a different individual security weighting.
        - Constraints:
            Boolean value
        - Default Value: false

    sectorNeutralSpread: float
        - **Sector Neutral Spread**:
            The sector neutral spread is how far away from the benchmark sector allocation the
            machine can deviate. This is adjusted for net exposure, i.e. if your net exposure is 0%
            the target sector allocations will be 0%.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 0

    signalIndustryNeutral: bool
        - **Industry Neutral**:
            Make the signal industry neutral, plus or minus the signal sector neutral spread.
        - Constraints:
            Boolean value
        - Default Value: false

    signalSectorNeutral: bool
        - **Sector Neutral**:
            Make the signal sector neutral, plus or minus the signal sector neutral spread.
        - Constraints:
            Boolean value
        - Default Value: false

    signalSectorNeutralSpread: float
        - **Sector Neutral Spread**:
            Make the signal sector neutral, plus or minus the signal sector neutral spread.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 1.0

    smoothPeriods: int
        - **Smooth Periods**:
            The number of periods over which to smooth the signals
        - Constraints:
            Integer value between 1 and 100.
        - Default Value: 1

    smoothWeighting: str
        - **Smooth Weighting Type**:
            Alpha Weight: The strength of the signal matters in the smoothing. For example, if a
            stock had a score of 5 stars in period A, 2 stars in period B, your 2 period smoothing
            would be 7 / 2 = 3.5.
            Equal Weight: Only the direction of the signal matters here. For example, if you were 5
            stars in period A and 2 stars in period B, your 2 period smoothing would be 0 / 2 = 0
            (+1 in period 1 and -1 in period 2)
        - Constraints:
            One of {alpha_weight, equal_weight}
        - Default Value: "alpha_weight"

    stopGain: bool
        - **Enable Stop Gain**:
            Turn on stop gains for the portfolio. This forces sales of securities that have exceeded
            the stop gain level. All trades will occur at the next trading time (i.e. next day OPEN
            for OPEN price models).
        - Constraints:
            Boolean value
        - Default Value: false

    stopGainAmount: float
        - **Stop Gain Percentage to Sell**:
            The percentage of the position the machine will sell if the stop gain is triggered.
        - Constraints:
            Floating point value representing a percentage between 1 and 100.
        - Default Value: 50.0

    stopGainLevel: float
        - **Stop Gain Threshold**:
            Will sell positions after they hit a threshold. For example, 10% / stop gain, when a
            position gains 10% the machine will sell a portion of the position (defined by stop gain
            percentage).
        - Constraints:
            Floating point value representing a percentage between 1 and 100.
        - Default Value: 10.0

    stopLoss: bool
        - **Enable Stop Loss**:
            Turn on stop losses for the portfolio. This forces sales of securities that have
            exceeded the stop loss level. All trades will occur at the next trading time (i.e. next
            day OPEN for OPEN price models).
        - Constraints:
            Boolean value
        - Default Value: false

    stopLossAmount: float
        - **Stop Loss Percentage to Sell**:
            The percentage of the position the machine will sell if the stop loss is triggered.
        - Constraints:
            Floating point value representing a percentage between 1 and 100.
        - Default Value: 50.0

    stopLossLevel: float
        - **Stop Loss Threshold**:
            Will sell positions after they hit a threshold. For example, 10% / stop loss, when a
            position loses 10% the machine will sell a portion of the position (defined by stop loss
            percentage).
        - Constraints:
            Floating point value representing a percentage between 1 and 100.
        - Default Value: 10.0

    stopLossReset: bool
        - **Allow multiple stop loss/gain between rebalance days**:
            Allows the machine to repeatedly trigger stop losses within a rebalance period. For
            example, if you trade Mondays and the stock drops 15% per day during the week and the
            stop loss trigger is 10%, then every day will trigger the stop loss. If toggled off the
            stop loss can only be triggered once per stock per rebalance period.
        - Constraints:
            Boolean value
        - Default Value: false

    trackingConstraint: float
        - **Maximum Tracking Error (vs Benchmark)**:
            Amount of ex-ante tracking error to constrain the optimizer by.
        - Constraints:
            Floating point value representing a percentage between 0 and 30
        - Default Value: 10

    trackingError: bool
        - **Allocate Weight to Benchmark**:
            Allocate a portion (%) of portfolio to the benchmark
        - Constraints:
            Boolean value
        - Default Value: false

    trackingErrorOptimizer: bool
        - **Tracking Error**:
            If toggled on the optimizer will try to solve for an expected tracking error less than
            the amount specified. This is done out-of-sample and the ex post tracking error will
            likely be higher.
        - Constraints:
            Boolean value
        - Default Value: false

    tradingCost: float
        - **Trading Cost**:
            A cost that will be applied to every trade.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 5.0
        - Default Value: 0.01

    turnoverImportance: float
        - **Turnover Importance**:
            High importance means the algorithm will aim to keep turnover low, whereas low
            importance of turnover will let it vary more.
        - Constraints:
            Floating point value between 0.2 and 5 nonlinearly mapped onto the 1-9 interval.
        - Default Value: 1

    useHoldingPeriod: bool
        - **Use Holding Period**:
            Set any covariance or other calculations within the optimizer to use a holding period
            return instead of daily return series
        - Constraints:
            Boolean value
        - Default Value: false

    weightingType: str
        - **Initial Weighting**:
            Alpha Weight: The initial weighting will be done such that higher ranked stocks have a
            higher weight.
            Equal Weight: The initial weighting will be done such that all stocks selected to be in
            the portfolio will have an equal weight.
            Market Cap Weight: The initial weighting will be done such that all stocks are weighted
            according to their previous day’s market capitalization.
        - Constraints:
            One of {alpha_weight, equal_weight, market_cap_weight}
        - Default Value: "alpha_weight"
    """

    # note, this is just the collection of portfolio settings
    # that is exposed to the API:
    # SELECT key, default_value
    # FROM portfolio_settings_validation
    # WHERE exposed_to_client_api;
    # TODO: expand to entire settings? why this way initially?
    __default_settings = {
        "maxOwnership": "5",
        "pairsTrading": "false",
        "minNumStockLong": "5",
        "stopLossAmount": "50.0",
        "stopGainLevel": "10.0",
        "smoothWeighting": '"alpha_weight"',
        "minimumSharePrice": "2",
        "maxShortPosition": "5.0",
        "tradingCost": "0.01",
        "stopGainAmount": "50.0",
        "smoothPeriods": "1",
        "executionDelay": "0",
        "benchmarkAllocation": "0",
        "sectorNeutralSpread": "0",
        "percentToLong": "20.0",
        "maxNumStockLong": "50",
        "trackingConstraint": "10",
        "compounding": "true",
        "priceCleaning": "true",
        "lower_turnover": "false",
        "missingMarketCap": "10",
        "allowCompanyCollisions": "true",
        "allowDR": "true",
        "sectorNeutral": "false",
        "marketCapFlex": "100",
        "marketCapBounds": "1",
        "turnoverImportance": "1",
        "minNumStockShort": "5",
        "maxNumStockShort": "50",
        "beta_neutral": "false",
        "nameBasedSectorWeighting": "false",
        "trackingError": "false",
        "stopLoss": "false",
        "stopGain": "false",
        "stopLossReset": "false",
        "netBenchmark": "false",
        "equalWeightBenchmark": "false",
        "marketCapConstraint": "false",
        "signalIndustryNeutral": "false",
        "signalSectorNeutral": "false",
        "sectorNeutralEqualWeightBenchmark": "false",
        "percentToShort": "20.0",
        "trackingErrorOptimizer": "false",
        "currency": '"USD"',
        "rebalancePeriod": '"weekly"',
        "benchmark": "[]",
        "signalSectorNeutralSpread": "1.0",
        "optimizeTurnover": "false",
        "minShortPosition": "0",
        "minLongPosition": "0",
        "percentPortfolioLong": "100.0",
        "portfolioStartingValue": "100.0",
        "stopLossLevel": "10.0",
        "maxLongPosition": "10.0",
        "percentPortfolioShort": "0.0",
        "bounds_method": None,
        "optimizer": '"default"',
        "activeRisk": "false",
        "useHoldingPeriod": "false",
        "weightingType": '"alpha_weight"',
        "priceType": '"VWAP"',
        "factorNeutralizeSignal": "false",
        "investmentHorizon": "21",
    }

    def __init__(self, settings: Optional[Dict] = None):
        # TODO: i'd rather have kwargs for the different keys and then allow
        # for per-key overrides, but we start with this more conservative approach
        if settings is None:
            self.settings = copy.deepcopy(PortfolioSettings.__default_settings)
        else:
            self.settings = settings

    def toDict(self):
        return self.settings

    def __str__(self):
        return json.dumps(self.settings)

    def __repr__(self):
        return self.__str__()

    def fromDict(settings):
        return PortfolioSettings(settings)


hedge_experiment_type = Literal["HEDGE", "MIMIC"]


@dataclass(frozen=True)  # from _python client's_ perspective, instances will never change!
class HedgeExperiment:
    id: str  # really a uuid, which we represent as a string
    name: str
    user_id: str  # see id comment
    description: str
    experiment_type: hedge_experiment_type  # TODO: enum worth it?
    last_calculated: dt.datetime
    last_modified: dt.datetime
    status: str  # TODO: enum worth it?
    portfolio_calculation_status: str  # TODO: enum worth it?
    selected_models: List[str]  # see id comment
    target_securities: Dict[GbiIdSecurity, float]  # Security is a hashable type because frozen=True
    target_portfolios: List[str]
    selected_stock_universe_ids: List[str]  # see id comment
    baseline_model: Optional[str] = None
    baseline_stock_universe_id: Optional[str] = None
    baseline_scenario: Optional["HedgeExperimentScenario"] = None

    @property
    def config(self) -> Dict:
        """
        Returns a hedge experiment configuration dictionary, ready for JSON-serialization and
        submission.
        """
        weights_by_gbiid = [
            {"gbiId": sec.id, "weight": weight} for sec, weight in self.target_securities.items()
        ]
        return {
            "experimentType": self.experiment_type,
            "selectedModels": self.selected_models,
            "targetSecurities": weights_by_gbiid,
            "selectedStockUniverseIds": self._selected_stock_universe_ids,
        }

    @classmethod
    def from_json_dict(cls, d: Dict) -> "HedgeExperiment":
        # drafts arent fully populated
        if d["status"] == "DRAFT":
            weights_by_security = {}
            selected_models = []
            selected_stock_universe_ids = []
        else:
            weights_by_security = {
                GbiIdSecurity(
                    sec_dict["gbiId"],
                    None,
                    sec_dict["security"]["symbol"],
                    sec_dict["security"]["name"],
                ): sec_dict["weight"]
                for sec_dict in d["targetSecurities"]
            }
            experiment_config = json.loads(d["config"])
            selected_models = experiment_config["selectedModels"]
            selected_stock_universe_ids = experiment_config["selectedStockUniverseIds"]
        baseline_scenario = None
        if d["baselineScenario"] is not None:
            baseline_scenario = HedgeExperimentScenario.from_json_dict(d["baselineScenario"])
        return cls(
            d["hedgeExperimentId"],
            d["experimentName"],
            d["userId"],
            d["description"],
            d["experimentType"],
            d["lastCalculated"],
            d["lastModified"],
            d["status"],
            d["portfolioCalcStatus"],
            selected_models,
            weights_by_security,
            d.get("targetPortfolios"),
            selected_stock_universe_ids or [],
            d.get("baselineModel"),
            d.get("baselineStockUniverseId"),
            baseline_scenario,
        )


@dataclass(frozen=True)
class HedgeExperimentScenario:
    id: str  # really a uuid, which we represent as a string;
    name: str
    description: str
    status: str  # TODO: enum worth it?
    settings: PortfolioSettings
    summary: pd.DataFrame
    # we currently a portfolios field. they get wrapped up into the summary table

    @classmethod
    def from_json_dict(cls, d: Dict) -> "HedgeExperimentScenario":
        return cls(
            d["hedgeExperimentScenarioId"],
            d["scenarioName"],
            d["description"],
            d.get("status"),
            PortfolioSettings(json.loads(d["portfolioSettingsJson"])),
            HedgeExperimentScenario._build_portfolio_summary(d.get("hedgeExperimentPortfolios")),
        )

    @staticmethod
    def _build_portfolio_summary(portfolios: Dict) -> pd.DataFrame:

        # this happens for added scenarios that havent run yet
        if portfolios is None:
            return pd.DataFrame()

        df_dict = defaultdict(list)
        for pf in portfolios:

            p = pf["portfolio"]

            df_dict["portfolio_id"].append(p["id"])
            df_dict["scenario_name"].append(p["name"])
            df_dict["model_id"].append(p["modelId"])
            df_dict["status"].append(p["status"])

            if p["status"] != "READY":  # data isn't yet available
                df_dict["1M"].append(np.nan)
                df_dict["3M"].append(np.nan)
                df_dict["1Y"].append(np.nan)
                df_dict["sharpe"].append(np.nan)
                df_dict["vol"].append(np.nan)
            else:
                # NOTE:
                # these are the magic indices/keys of these values; inspect passed arg to see
                # if upstream changes the order of (sub-)elements in the response...uh oh
                # TODO: specific key search? wrap with try/except and fail loudly?
                df_dict["1M"].append(p["performanceGrid"][0][4])
                df_dict["3M"].append(p["performanceGrid"][0][5])
                df_dict["1Y"].append(p["performanceGrid"][0][7])
                df_dict["sharpe"].append(p["tearSheet"][0]["members"][1]["value"])
                pf_stddev = p["tearSheet"][1]["members"][1]["value"]
                bnchmk_stddev = p["tearSheet"][1]["members"][2]["value"]
                df_dict["vol"].append((pf_stddev - bnchmk_stddev) * 100)
                # TODO: this is how it is done on the front-end,
                # should be pulled out of both here and there

        df = pd.DataFrame(df_dict)
        return df


@dataclass(frozen=True)  # from _python client's_ perspective, instances will never change!
class HedgeExperimentDetails:
    # this will probably lead to violations of the law of demeter, but it's easy to do right now,
    # particularly for an unproven API
    experiment: HedgeExperiment
    scenarios: Dict[str, HedgeExperimentScenario]

    @property
    def summary(self) -> pd.DataFrame:
        """
        Returns a dataframe corresponding to the Hedge Experiment "flat view" - all portfolio
        options are displayed
        """
        if len(self.scenarios) == 0 or all(s.status != "READY" for s in self.scenarios.values()):
            return pd.DataFrame()
        return pd.concat(scenario.summary for scenario in self.scenarios.values()).sort_values(
            ["scenario_name"]
        )  # sort for stable presentation

    @classmethod
    def from_json_dict(cls, d: Dict) -> "HedgeExperimentDetails":
        he = HedgeExperiment.from_json_dict(d)
        scenarios = {
            scenario_dict["hedgeExperimentScenarioId"]: HedgeExperimentScenario.from_json_dict(
                scenario_dict
            )
            for scenario_dict in d["hedgeExperimentScenarios"]
        }
        return cls(he, scenarios)

Classes

class BoostedDataSetSchemaException (value, data=None)

Common base class for all non-exit exceptions.

Expand source code
class BoostedDataSetSchemaException(Exception):
    def __init__(self, value, data=None):
        self.value = value
        self.data = data

    def __str__(self):
        return repr(self.value)

Ancestors

  • builtins.Exception
  • builtins.BaseException
class ChunkStatus (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class ChunkStatus(Enum):
    PROCESSING = "PROCESSING"
    ABORTED = "ABORTED"
    SUCCESS = "SUCCESS"
    WARNING = "WARNING"
    ERROR = "ERROR"

Ancestors

  • enum.Enum

Class variables

var ABORTED
var ERROR
var PROCESSING
var SUCCESS
var WARNING
class ColumnConfig (name=None, role=None, sub_role=None, value_type=NUMBER, description='', investment_horizon=None)
Expand source code
class ColumnConfig:
    def __init__(
        self,
        name=None,
        role=None,
        sub_role=None,
        value_type=ColumnValueType.NUMBER,
        description="",
        investment_horizon=None,
    ):
        self.name = name
        self.role = role
        self.sub_role = sub_role
        self.value_type = value_type
        # More value_types will be supported in the future.
        self.description = description
        self.investment_horizon = investment_horizon

    def __str__(self):
        return 'Name: "{0}", Role: {1}, SubRole: {2}, Value type: {3}, Desc: {4} IH: {5}.'.format(
            self.name,
            self.role,
            self.sub_role,
            self.value_type,
            self.description,
            self.investment_horizon,
        )

    def __repr__(self):
        return self.__str__()
class ColumnRole (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class ColumnRole(Enum):
    UNKNOWN = 0
    VARIABLE = 1
    GOAL = 2
    METRIC = 3
    IDENTIFIER = 4

    def __str__(self):
        if self.name == "VARIABLE":
            return "VARIABLE"
        elif self.name == "GOAL":
            return "GOAL"
        elif self.name == "METRIC":
            return "METRIC"
        elif self.name == "IDENTIFIER":
            return "IDENTIFIER"
        else:
            return "UNKNOWN"

Ancestors

  • enum.Enum

Class variables

var GOAL
var IDENTIFIER
var METRIC
var UNKNOWN
var VARIABLE
class ColumnSubRole (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class ColumnSubRole(Enum):
    UNKNOWN = 0
    GBI_ID = 1
    ISIN = 2
    GVKEY = 3
    IID = 4
    SYMBOL = 5
    COUNTRY = 6
    CURRENCY = 7
    DATE = 8
    COMPANY_NAME = 9
    REPORT_DATE = 10
    REPORT_PERIOD = 11

    def __str__(self):
        if self.name == "GBI_ID":
            return "GBI_ID"
        elif self.name == "ISIN":
            return "ISIN"
        elif self.name == "GVKEY":
            return "GVKEY"
        elif self.name == "IID":
            return "IID"
        elif self.name == "SYMBOL":
            return "SYMBOL"
        elif self.name == "COUNTRY":
            return "COUNTRY"
        elif self.name == "CURRENCY":
            return "CURRENCY"
        elif self.name == "DATE":
            return "DATE"
        elif self.name == "COMPANY_NAME":
            return "COMPANY_NAME"
        elif self.name == "REPORT_DATE":
            return "REPORT_DATE"
        elif self.name == "REPORT_PERIOD":
            return "REPORT_PERIOD"
        else:
            return "UNKNOWN"

    def get_match(column_name):
        def clean_str(name):
            translation = str.maketrans("", "", string.punctuation)
            clean_name = name.strip().translate(translation).lower()
            return re.sub(r"\s+", "", clean_name)

        identifiers = [
            ColumnSubRole.GBI_ID,
            ColumnSubRole.ISIN,
            ColumnSubRole.GVKEY,
            ColumnSubRole.IID,
            ColumnSubRole.SYMBOL,
            ColumnSubRole.COUNTRY,
            ColumnSubRole.CURRENCY,
            ColumnSubRole.COMPANY_NAME,
            ColumnSubRole.REPORT_DATE,
            ColumnSubRole.REPORT_PERIOD,
        ]

        for identifier in identifiers:
            if clean_str(str(identifier)) == clean_str(column_name):
                return identifier
        return None

Ancestors

  • enum.Enum

Class variables

var COMPANY_NAME
var COUNTRY
var CURRENCY
var DATE
var GBI_ID
var GVKEY
var IID
var ISIN
var REPORT_DATE
var REPORT_PERIOD
var SYMBOL
var UNKNOWN

Methods

def get_match(column_name)
Expand source code
def get_match(column_name):
    def clean_str(name):
        translation = str.maketrans("", "", string.punctuation)
        clean_name = name.strip().translate(translation).lower()
        return re.sub(r"\s+", "", clean_name)

    identifiers = [
        ColumnSubRole.GBI_ID,
        ColumnSubRole.ISIN,
        ColumnSubRole.GVKEY,
        ColumnSubRole.IID,
        ColumnSubRole.SYMBOL,
        ColumnSubRole.COUNTRY,
        ColumnSubRole.CURRENCY,
        ColumnSubRole.COMPANY_NAME,
        ColumnSubRole.REPORT_DATE,
        ColumnSubRole.REPORT_PERIOD,
    ]

    for identifier in identifiers:
        if clean_str(str(identifier)) == clean_str(column_name):
            return identifier
    return None
class ColumnValueType (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class ColumnValueType(Enum):
    UNKNOWN = 0
    NUMBER = 1
    STRING = 2

    def __str__(self):
        if self.name == "UNKNOWN":
            return "UNKNOWN"
        elif self.name == "NUMBER":
            return "NUMBER"
        elif self.name == "STRING":
            return "STRING"
        else:
            return "UNKNOWN"

Ancestors

  • enum.Enum

Class variables

var NUMBER
var STRING
var UNKNOWN
class DataAddType (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class DataAddType(Enum):
    CREATION = 1
    HISTORICAL = 2
    LIVE = 3

    def __str__(self):
        if self.name == "CREATION":
            return "CREATION"
        elif self.name == "HISTORICAL":
            return "HISTORICAL"
        elif self.name == "LIVE":
            return "LIVE"

Ancestors

  • enum.Enum

Class variables

var CREATION
var HISTORICAL
var LIVE
class DataSetConfig (name, datasetType=STOCK, datasetSubType=DENSE, datasetFrequency=DAILY)
Expand source code
class DataSetConfig:
    def __init__(
        self,
        name,
        datasetType=DataSetType.STOCK,
        datasetSubType=DataSetSubType.DENSE,
        datasetFrequency=DataSetFrequency.DAILY,
    ):
        self.name = name
        self.type = datasetType
        self.subtype = datasetSubType
        self.frequency = datasetFrequency
        self.columns = []
        self.strategies = []

    def addColumn(self, columnConfig):
        self.columns.append(columnConfig)

    def addStrategy(self, strategyConfig):
        self.strategies.append(strategyConfig)

    def __getColumnByName(self, col_name):
        for column in self.columns:
            if col_name == column.name:
                return column
        raise BoostedDataSetSchemaException(f"Unable to find column {col_name}")

    def updateColumnToGoal(self, col_name, investment_horizon):
        column = self.__getColumnByName(col_name)
        column.role = ColumnRole.GOAL
        column.investment_horizon = int(investment_horizon)

    def updateColumnToMetric(self, col_name):
        column = self.__getColumnByName(col_name)
        column.role = ColumnRole.METRIC

    def updateColumnToVariable(self, col_name):
        column = self.__getColumnByName(col_name)
        column.role = ColumnRole.VARIABLE

    def __validateSchema(self):
        num_goals = 0
        num_metrics = 0
        dt = self.type
        dst = self.subtype
        dsf = self.frequency
        if len(self.columns) == 0:
            msg = "No feature columns exist."
            raise BoostedDataSetSchemaException(msg)

        if dst in [DataSetSubType.SPARSE_HIST, DataSetSubType.SPARSE_FWD]:
            if dsf not in [DataSetFrequency.QUARTERLY]:
                msg = f"{dsf} frequency is not supported for {dst} sub data"
                raise BoostedDataSetSchemaException(msg)
            if dt not in [DataSetType.STOCK]:
                msg = f"{dst} subtype is not supported for {dt} data"
                raise BoostedDataSetSchemaException(msg)

        for column in self.columns:
            if column.role == ColumnRole.GOAL:
                ih = column.investment_horizon
                gn = column.name
                if dt == DataSetType.GLOBAL:
                    msg = f"{dt} data can not have {ColumnRole.GOAL} type"
                    raise BoostedDataSetSchemaException(msg)
                if not isinstance(ih, int):
                    msg = f"Investment horizon for {gn} must be an integer"
                    raise BoostedDataSetSchemaException(msg)
                if ih < 1 or ih > 252:
                    msg = f"Investment horizon must be between 1 and 252 for {gn}"
                    raise BoostedDataSetSchemaException(msg)
                num_goals += 1
            elif column.role == ColumnRole.METRIC:
                if dt in [DataSetType.GLOBAL, DataSetType.STOCK]:
                    msg = f"{dt} data can not have {ColumnRole.METRIC} type"
                    raise BoostedDataSetSchemaException(msg)
                num_metrics += 1

        if dt == DataSetType.STRATEGY:
            if num_goals == 0:
                msg = "Independent data requires at least one goal."
                raise BoostedDataSetSchemaException(msg)
            if num_metrics == 0:
                msg = "Independent data requires at least one metric."
                raise BoostedDataSetSchemaException(msg)
            if len(self.strategies) <= 1:
                msg = "Independent data requires more than 1 strategy"
                raise BoostedDataSetSchemaException(msg)

    def toDict(self):
        self.__validateSchema()
        config = {}
        config["name"] = self.name
        config["type"] = str(self.type)
        config["subType"] = str(self.subtype)
        config["frequency"] = str(self.frequency)
        featureList = []
        for i, f in enumerate(self.columns):
            fm = {}
            fm["name"] = f.name
            fm["description"] = f.description
            fm["type"] = str(f.role)
            fm["role"] = str(f.role)
            fm["subRole"] = str(f.sub_role) if f.sub_role is not None else None
            fm["valuesType"] = str(f.value_type)
            fm["columnName"] = f.name
            fm["investmentHorizon"] = f.investment_horizon
            featureList.append(fm)
        config["features"] = featureList
        strategyList = []
        for i, s in enumerate(self.strategies):
            sm = {}
            sm["name"] = s.name
            sm["sourceName"] = s.source_name
            strategyList.append(sm)
        config["strategies"] = strategyList
        return config

    def __str__(self):
        a = ""
        a += "Name: {0}\n".format(self.name)
        a += "Columns: \n"
        for c in self.columns:
            a += "  {0}\n".format(c)
        return a

    def __repr__(self):
        return self.__str__()

    def fromDict(schema):
        subtype = DataSetSubType[schema.get("subtype", str(DataSetSubType.DENSE))]
        frequency = DataSetFrequency[schema.get("frequency", str(DataSetFrequency.DAILY))]
        config = DataSetConfig(
            schema["name"],
            datasetType=DataSetType[schema["type"]],
            datasetSubType=subtype,
            datasetFrequency=frequency,
        )
        # two things left to fill in - strategies and columns
        for strategy_data in schema["strategies"]:
            config.addStrategy(
                StrategyConfig(name=strategy_data["name"], source_name=strategy_data["sourceName"])
            )

        for feature_data in schema["features"]:
            config.addColumn(
                ColumnConfig(
                    name=feature_data["columnName"],
                    description=feature_data["description"] or "",
                    investment_horizon=feature_data["investmentHorizon"],
                    value_type=ColumnValueType[feature_data["valuesType"]]
                    if feature_data["valuesType"] is not None
                    else ColumnValueType.NUMBER,
                    role=ColumnRole[feature_data["role"]]
                    if feature_data["role"] is not None
                    else None,
                    sub_role=ColumnSubRole[feature_data["subRole"]]
                    if feature_data["subRole"] is not None
                    else None,
                )
            )

        config.__validateSchema()
        return config

Methods

def addColumn(self, columnConfig)
Expand source code
def addColumn(self, columnConfig):
    self.columns.append(columnConfig)
def addStrategy(self, strategyConfig)
Expand source code
def addStrategy(self, strategyConfig):
    self.strategies.append(strategyConfig)
def fromDict(schema)
Expand source code
def fromDict(schema):
    subtype = DataSetSubType[schema.get("subtype", str(DataSetSubType.DENSE))]
    frequency = DataSetFrequency[schema.get("frequency", str(DataSetFrequency.DAILY))]
    config = DataSetConfig(
        schema["name"],
        datasetType=DataSetType[schema["type"]],
        datasetSubType=subtype,
        datasetFrequency=frequency,
    )
    # two things left to fill in - strategies and columns
    for strategy_data in schema["strategies"]:
        config.addStrategy(
            StrategyConfig(name=strategy_data["name"], source_name=strategy_data["sourceName"])
        )

    for feature_data in schema["features"]:
        config.addColumn(
            ColumnConfig(
                name=feature_data["columnName"],
                description=feature_data["description"] or "",
                investment_horizon=feature_data["investmentHorizon"],
                value_type=ColumnValueType[feature_data["valuesType"]]
                if feature_data["valuesType"] is not None
                else ColumnValueType.NUMBER,
                role=ColumnRole[feature_data["role"]]
                if feature_data["role"] is not None
                else None,
                sub_role=ColumnSubRole[feature_data["subRole"]]
                if feature_data["subRole"] is not None
                else None,
            )
        )

    config.__validateSchema()
    return config
def toDict(self)
Expand source code
def toDict(self):
    self.__validateSchema()
    config = {}
    config["name"] = self.name
    config["type"] = str(self.type)
    config["subType"] = str(self.subtype)
    config["frequency"] = str(self.frequency)
    featureList = []
    for i, f in enumerate(self.columns):
        fm = {}
        fm["name"] = f.name
        fm["description"] = f.description
        fm["type"] = str(f.role)
        fm["role"] = str(f.role)
        fm["subRole"] = str(f.sub_role) if f.sub_role is not None else None
        fm["valuesType"] = str(f.value_type)
        fm["columnName"] = f.name
        fm["investmentHorizon"] = f.investment_horizon
        featureList.append(fm)
    config["features"] = featureList
    strategyList = []
    for i, s in enumerate(self.strategies):
        sm = {}
        sm["name"] = s.name
        sm["sourceName"] = s.source_name
        strategyList.append(sm)
    config["strategies"] = strategyList
    return config
def updateColumnToGoal(self, col_name, investment_horizon)
Expand source code
def updateColumnToGoal(self, col_name, investment_horizon):
    column = self.__getColumnByName(col_name)
    column.role = ColumnRole.GOAL
    column.investment_horizon = int(investment_horizon)
def updateColumnToMetric(self, col_name)
Expand source code
def updateColumnToMetric(self, col_name):
    column = self.__getColumnByName(col_name)
    column.role = ColumnRole.METRIC
def updateColumnToVariable(self, col_name)
Expand source code
def updateColumnToVariable(self, col_name):
    column = self.__getColumnByName(col_name)
    column.role = ColumnRole.VARIABLE
class DataSetFrequency (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class DataSetFrequency(Enum):
    UNKNOWN = 0
    DAILY = 1
    WEEKLY = 2
    MONTHLY = 3
    QUARTERLY = 4
    SEMIANNUAL = 5
    ANNUAL = 6

    def __str__(self):
        if self.name == "DAILY":
            return "DAILY"
        elif self.name == "WEEKLY":
            return "WEEKLY"
        elif self.name == "MONTHLY":
            return "MONTHLY"
        elif self.name == "QUARTERLY":
            return "QUARTERLY"
        elif self.name == "SEMIANNUAL":
            return "SEMIANNUAL"
        elif self.name == "ANNUAL":
            return "ANNUAL"
        else:
            return "UNKNOWN"

Ancestors

  • enum.Enum

Class variables

var ANNUAL
var DAILY
var MONTHLY
var QUARTERLY
var SEMIANNUAL
var UNKNOWN
var WEEKLY
class DataSetSubType (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class DataSetSubType(Enum):
    UNKNOWN = 0
    DENSE = 1
    SPARSE_HIST = 2
    SPARSE_FWD = 3

    def __str__(self):
        if self.name == "DENSE":
            return "DENSE"
        elif self.name == "SPARSE_HIST":
            return "SPARSE_HIST"
        elif self.name == "SPARSE_FWD":
            return "SPARSE_FWD"
        else:
            return "UNKNOWN"

Ancestors

  • enum.Enum

Class variables

var DENSE
var SPARSE_FWD
var SPARSE_HIST
var UNKNOWN
class DataSetType (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class DataSetType(Enum):
    UNKNOWN = 0
    STOCK = 1
    GLOBAL = 2
    STRATEGY = 3

    def __str__(self):
        if self.name == "STOCK":
            return "STOCK"
        elif self.name == "GLOBAL":
            return "GLOBAL"
        elif self.name == "STRATEGY":
            return "STRATEGY"
        else:
            return "UNKNOWN"

Ancestors

  • enum.Enum

Class variables

var GLOBAL
var STOCK
var STRATEGY
var UNKNOWN
class DateIdentCountryCurrency (date: str, identifier: str, country: str, currency: str, id_type: ColumnSubRole = ISIN)
Expand source code
class DateIdentCountryCurrency:
    def __init__(
        self,
        date: str,
        identifier: str,
        country: str,
        currency: str,
        id_type: ColumnSubRole = ColumnSubRole.ISIN,
    ):
        self.date = date
        self.identifier = identifier
        self.id_type = id_type
        self.country = country
        self.currency = currency
        self.__validateInput()

    def __str__(self):
        return '(date: "{0}", identifier: "{1}", country: {2}, currency: {3})'.format(
            self.date, self.identifier, self.country, self.currency
        )

    def __repr__(self):
        return self.__str__()

    def __validateInput(self):
        # simply ensure that at least isin and date are filled out.
        # country/currency defaults to ANY if not filled out, but
        # still is not recommended.
        if self.identifier is None or self.date is None:
            raise DateIdentCountryCurrencyException(
                "ISIN or Date not provided while constructing DateIsinCountryCurrency"
            )

        if self.id_type not in (ColumnSubRole.ISIN, ColumnSubRole.SYMBOL):
            raise DateIdentCountryCurrencyException(f"Invalid ID Type: {self.id_type}")

        def check_is_str(value, fieldname):
            if value is not None and not isinstance(value, str):
                raise DateIdentCountryCurrencyException(
                    f"Field {fieldname} in DateIsinCountryCurrency was not a string."
                )

        check_is_str(self.date, "date")
        check_is_str(self.identifier, "identifier")
        check_is_str(self.country, "country")
        check_is_str(self.currency, "currency")

Subclasses

class DateIdentCountryCurrencyException (value, data=None)

Common base class for all non-exit exceptions.

Expand source code
class DateIdentCountryCurrencyException(Exception):
    def __init__(self, value, data=None):
        self.value = value
        self.data = data

    def __str__(self):
        return repr(self.value)

Ancestors

  • builtins.Exception
  • builtins.BaseException

Subclasses

class DateIsinCountryCurrency (date, isin, country, currency)
Expand source code
class DateIsinCountryCurrency(DateIdentCountryCurrency):
    def __init__(self, date, isin, country, currency):
        super().__init__(date, isin, country, currency, ColumnSubRole.ISIN)
        self.isin = isin

    def __str__(self):
        return '(date: "{0}", isin: "{1}", country: {2}, currency: {3})'.format(
            self.date, self.isin, self.country, self.currency
        )

Ancestors

class DateIsinCountryCurrencyException (value, data=None)

Common base class for all non-exit exceptions.

Expand source code
class DateIsinCountryCurrencyException(DateIdentCountryCurrencyException):
    pass

Ancestors

class GbiIdSecurity (gbi_id, isin_country_currency_date, ticker, company_name)
Expand source code
class GbiIdSecurity:
    def __init__(self, gbi_id, isin_country_currency_date, ticker, company_name):
        self.gbi_id = gbi_id
        self.isin_info = isin_country_currency_date
        self.ticker = ticker
        self.company_name = company_name

    def __str__(self):
        return '\n(gbi_id: "{0}", isin_info: {1}, ticker: {2}, company_name: {3})'.format(
            self.gbi_id, self.isin_info.__str__(), self.ticker, self.company_name
        )

    def __repr__(self):
        return self.__str__()

    def __eq__(self, __o: object) -> bool:
        return self.gbi_id == __o.gbi_id

    def __hash__(self):
        return hash(self.gbi_id)
class GbiIdSecurityException (value, data=None)

Common base class for all non-exit exceptions.

Expand source code
class GbiIdSecurityException(Exception):
    def __init__(self, value, data=None):
        self.value = value
        self.data = data

    def __str__(self):
        return repr(self.value)

Ancestors

  • builtins.Exception
  • builtins.BaseException
class HedgeExperiment (id: str, name: str, user_id: str, description: str, experiment_type: Literal['HEDGE', 'MIMIC'], last_calculated: datetime.datetime, last_modified: datetime.datetime, status: str, portfolio_calculation_status: str, selected_models: List[str], target_securities: Dict[GbiIdSecurity, float], target_portfolios: List[str], selected_stock_universe_ids: List[str], baseline_model: Optional[str] = None, baseline_stock_universe_id: Optional[str] = None, baseline_scenario: Optional[ForwardRef('HedgeExperimentScenario')] = None)

HedgeExperiment(id: str, name: str, user_id: str, description: str, experiment_type: Literal['HEDGE', 'MIMIC'], last_calculated: datetime.datetime, last_modified: datetime.datetime, status: str, portfolio_calculation_status: str, selected_models: List[str], target_securities: Dict[boosted.api.api_type.GbiIdSecurity, float], target_portfolios: List[str], selected_stock_universe_ids: List[str], baseline_model: Union[str, NoneType] = None, baseline_stock_universe_id: Union[str, NoneType] = None, baseline_scenario: Union[ForwardRef('HedgeExperimentScenario'), NoneType] = None)

Expand source code
class HedgeExperiment:
    id: str  # really a uuid, which we represent as a string
    name: str
    user_id: str  # see id comment
    description: str
    experiment_type: hedge_experiment_type  # TODO: enum worth it?
    last_calculated: dt.datetime
    last_modified: dt.datetime
    status: str  # TODO: enum worth it?
    portfolio_calculation_status: str  # TODO: enum worth it?
    selected_models: List[str]  # see id comment
    target_securities: Dict[GbiIdSecurity, float]  # Security is a hashable type because frozen=True
    target_portfolios: List[str]
    selected_stock_universe_ids: List[str]  # see id comment
    baseline_model: Optional[str] = None
    baseline_stock_universe_id: Optional[str] = None
    baseline_scenario: Optional["HedgeExperimentScenario"] = None

    @property
    def config(self) -> Dict:
        """
        Returns a hedge experiment configuration dictionary, ready for JSON-serialization and
        submission.
        """
        weights_by_gbiid = [
            {"gbiId": sec.id, "weight": weight} for sec, weight in self.target_securities.items()
        ]
        return {
            "experimentType": self.experiment_type,
            "selectedModels": self.selected_models,
            "targetSecurities": weights_by_gbiid,
            "selectedStockUniverseIds": self._selected_stock_universe_ids,
        }

    @classmethod
    def from_json_dict(cls, d: Dict) -> "HedgeExperiment":
        # drafts arent fully populated
        if d["status"] == "DRAFT":
            weights_by_security = {}
            selected_models = []
            selected_stock_universe_ids = []
        else:
            weights_by_security = {
                GbiIdSecurity(
                    sec_dict["gbiId"],
                    None,
                    sec_dict["security"]["symbol"],
                    sec_dict["security"]["name"],
                ): sec_dict["weight"]
                for sec_dict in d["targetSecurities"]
            }
            experiment_config = json.loads(d["config"])
            selected_models = experiment_config["selectedModels"]
            selected_stock_universe_ids = experiment_config["selectedStockUniverseIds"]
        baseline_scenario = None
        if d["baselineScenario"] is not None:
            baseline_scenario = HedgeExperimentScenario.from_json_dict(d["baselineScenario"])
        return cls(
            d["hedgeExperimentId"],
            d["experimentName"],
            d["userId"],
            d["description"],
            d["experimentType"],
            d["lastCalculated"],
            d["lastModified"],
            d["status"],
            d["portfolioCalcStatus"],
            selected_models,
            weights_by_security,
            d.get("targetPortfolios"),
            selected_stock_universe_ids or [],
            d.get("baselineModel"),
            d.get("baselineStockUniverseId"),
            baseline_scenario,
        )

Class variables

var baseline_model : Optional[str]
var baseline_scenario : Optional[HedgeExperimentScenario]
var baseline_stock_universe_id : Optional[str]
var description : str
var experiment_type : Literal['HEDGE', 'MIMIC']
var id : str
var last_calculated : datetime.datetime
var last_modified : datetime.datetime
var name : str
var portfolio_calculation_status : str
var selected_models : List[str]
var selected_stock_universe_ids : List[str]
var status : str
var target_portfolios : List[str]
var target_securities : Dict[GbiIdSecurity, float]
var user_id : str

Static methods

def from_json_dict(d: Dict[~KT, ~VT]) ‑> HedgeExperiment
Expand source code
@classmethod
def from_json_dict(cls, d: Dict) -> "HedgeExperiment":
    # drafts arent fully populated
    if d["status"] == "DRAFT":
        weights_by_security = {}
        selected_models = []
        selected_stock_universe_ids = []
    else:
        weights_by_security = {
            GbiIdSecurity(
                sec_dict["gbiId"],
                None,
                sec_dict["security"]["symbol"],
                sec_dict["security"]["name"],
            ): sec_dict["weight"]
            for sec_dict in d["targetSecurities"]
        }
        experiment_config = json.loads(d["config"])
        selected_models = experiment_config["selectedModels"]
        selected_stock_universe_ids = experiment_config["selectedStockUniverseIds"]
    baseline_scenario = None
    if d["baselineScenario"] is not None:
        baseline_scenario = HedgeExperimentScenario.from_json_dict(d["baselineScenario"])
    return cls(
        d["hedgeExperimentId"],
        d["experimentName"],
        d["userId"],
        d["description"],
        d["experimentType"],
        d["lastCalculated"],
        d["lastModified"],
        d["status"],
        d["portfolioCalcStatus"],
        selected_models,
        weights_by_security,
        d.get("targetPortfolios"),
        selected_stock_universe_ids or [],
        d.get("baselineModel"),
        d.get("baselineStockUniverseId"),
        baseline_scenario,
    )

Instance variables

var config : Dict[~KT, ~VT]

Returns a hedge experiment configuration dictionary, ready for JSON-serialization and submission.

Expand source code
@property
def config(self) -> Dict:
    """
    Returns a hedge experiment configuration dictionary, ready for JSON-serialization and
    submission.
    """
    weights_by_gbiid = [
        {"gbiId": sec.id, "weight": weight} for sec, weight in self.target_securities.items()
    ]
    return {
        "experimentType": self.experiment_type,
        "selectedModels": self.selected_models,
        "targetSecurities": weights_by_gbiid,
        "selectedStockUniverseIds": self._selected_stock_universe_ids,
    }
class HedgeExperimentDetails (experiment: HedgeExperiment, scenarios: Dict[str, HedgeExperimentScenario])

HedgeExperimentDetails(experiment: boosted.api.api_type.HedgeExperiment, scenarios: Dict[str, boosted.api.api_type.HedgeExperimentScenario])

Expand source code
class HedgeExperimentDetails:
    # this will probably lead to violations of the law of demeter, but it's easy to do right now,
    # particularly for an unproven API
    experiment: HedgeExperiment
    scenarios: Dict[str, HedgeExperimentScenario]

    @property
    def summary(self) -> pd.DataFrame:
        """
        Returns a dataframe corresponding to the Hedge Experiment "flat view" - all portfolio
        options are displayed
        """
        if len(self.scenarios) == 0 or all(s.status != "READY" for s in self.scenarios.values()):
            return pd.DataFrame()
        return pd.concat(scenario.summary for scenario in self.scenarios.values()).sort_values(
            ["scenario_name"]
        )  # sort for stable presentation

    @classmethod
    def from_json_dict(cls, d: Dict) -> "HedgeExperimentDetails":
        he = HedgeExperiment.from_json_dict(d)
        scenarios = {
            scenario_dict["hedgeExperimentScenarioId"]: HedgeExperimentScenario.from_json_dict(
                scenario_dict
            )
            for scenario_dict in d["hedgeExperimentScenarios"]
        }
        return cls(he, scenarios)

Class variables

var experimentHedgeExperiment
var scenarios : Dict[str, HedgeExperimentScenario]

Static methods

def from_json_dict(d: Dict[~KT, ~VT]) ‑> HedgeExperimentDetails
Expand source code
@classmethod
def from_json_dict(cls, d: Dict) -> "HedgeExperimentDetails":
    he = HedgeExperiment.from_json_dict(d)
    scenarios = {
        scenario_dict["hedgeExperimentScenarioId"]: HedgeExperimentScenario.from_json_dict(
            scenario_dict
        )
        for scenario_dict in d["hedgeExperimentScenarios"]
    }
    return cls(he, scenarios)

Instance variables

var summary : pandas.core.frame.DataFrame

Returns a dataframe corresponding to the Hedge Experiment "flat view" - all portfolio options are displayed

Expand source code
@property
def summary(self) -> pd.DataFrame:
    """
    Returns a dataframe corresponding to the Hedge Experiment "flat view" - all portfolio
    options are displayed
    """
    if len(self.scenarios) == 0 or all(s.status != "READY" for s in self.scenarios.values()):
        return pd.DataFrame()
    return pd.concat(scenario.summary for scenario in self.scenarios.values()).sort_values(
        ["scenario_name"]
    )  # sort for stable presentation
class HedgeExperimentScenario (id: str, name: str, description: str, status: str, settings: PortfolioSettings, summary: pandas.core.frame.DataFrame)

HedgeExperimentScenario(id: str, name: str, description: str, status: str, settings: boosted.api.api_type.PortfolioSettings, summary: pandas.core.frame.DataFrame)

Expand source code
class HedgeExperimentScenario:
    id: str  # really a uuid, which we represent as a string;
    name: str
    description: str
    status: str  # TODO: enum worth it?
    settings: PortfolioSettings
    summary: pd.DataFrame
    # we currently a portfolios field. they get wrapped up into the summary table

    @classmethod
    def from_json_dict(cls, d: Dict) -> "HedgeExperimentScenario":
        return cls(
            d["hedgeExperimentScenarioId"],
            d["scenarioName"],
            d["description"],
            d.get("status"),
            PortfolioSettings(json.loads(d["portfolioSettingsJson"])),
            HedgeExperimentScenario._build_portfolio_summary(d.get("hedgeExperimentPortfolios")),
        )

    @staticmethod
    def _build_portfolio_summary(portfolios: Dict) -> pd.DataFrame:

        # this happens for added scenarios that havent run yet
        if portfolios is None:
            return pd.DataFrame()

        df_dict = defaultdict(list)
        for pf in portfolios:

            p = pf["portfolio"]

            df_dict["portfolio_id"].append(p["id"])
            df_dict["scenario_name"].append(p["name"])
            df_dict["model_id"].append(p["modelId"])
            df_dict["status"].append(p["status"])

            if p["status"] != "READY":  # data isn't yet available
                df_dict["1M"].append(np.nan)
                df_dict["3M"].append(np.nan)
                df_dict["1Y"].append(np.nan)
                df_dict["sharpe"].append(np.nan)
                df_dict["vol"].append(np.nan)
            else:
                # NOTE:
                # these are the magic indices/keys of these values; inspect passed arg to see
                # if upstream changes the order of (sub-)elements in the response...uh oh
                # TODO: specific key search? wrap with try/except and fail loudly?
                df_dict["1M"].append(p["performanceGrid"][0][4])
                df_dict["3M"].append(p["performanceGrid"][0][5])
                df_dict["1Y"].append(p["performanceGrid"][0][7])
                df_dict["sharpe"].append(p["tearSheet"][0]["members"][1]["value"])
                pf_stddev = p["tearSheet"][1]["members"][1]["value"]
                bnchmk_stddev = p["tearSheet"][1]["members"][2]["value"]
                df_dict["vol"].append((pf_stddev - bnchmk_stddev) * 100)
                # TODO: this is how it is done on the front-end,
                # should be pulled out of both here and there

        df = pd.DataFrame(df_dict)
        return df

Class variables

var description : str
var id : str
var name : str
var settingsPortfolioSettings
var status : str
var summary : pandas.core.frame.DataFrame

Static methods

def from_json_dict(d: Dict[~KT, ~VT]) ‑> HedgeExperimentScenario
Expand source code
@classmethod
def from_json_dict(cls, d: Dict) -> "HedgeExperimentScenario":
    return cls(
        d["hedgeExperimentScenarioId"],
        d["scenarioName"],
        d["description"],
        d.get("status"),
        PortfolioSettings(json.loads(d["portfolioSettingsJson"])),
        HedgeExperimentScenario._build_portfolio_summary(d.get("hedgeExperimentPortfolios")),
    )
class IdentifierToColumnMapping (mapping)

IdentifierToColumnMapping is a mapping from string to boosted.api.api_type.ColumnSubRole. The key of the mapping corresponds to a CSV column name, and the value corresponds to the type of identifier that it maps to. Only columns specifying an identifying factor for the stock needs to be specified (e.g. ISIN, Date, Symbol, Currency)

Expand source code
class IdentifierToColumnMapping:
    """
    IdentifierToColumnMapping is a mapping from string to boosted.api.api_type.ColumnSubRole.
    The key of the mapping corresponds to a CSV column name, and the value corresponds
    to the type of identifier that it maps to. Only columns specifying an identifying factor
    for the stock needs to be specified (e.g. ISIN, Date, Symbol, Currency)
    """

    def __init__(self, mapping):
        self.mapping = mapping
        self.__validateInput()

    def __str__(self):
        return repr(self.value)

    def __validateInput(self):
        def validate_key_val(key, val):
            if not isinstance(key, str):
                raise Exception(f"key in IdentifierToColumnMapping {key} was not a string!")
            if not isinstance(val, ColumnSubRole):
                raise Exception(f"val in IdentifierToColumnMapping {val} was not a ColumnSubRole!")

        for key, val in self.mapping:
            validate_key_val(key, val)
class PortfolioSettings (settings: Optional[Dict[~KT, ~VT]] = None)

This config is a documentation wrapper around a dict representing the portfolio settings json. Keys missing in the dict provided will fallback to the listed default when updating the the portfolio settings on the server.

Parameters

activeRisk : bool
  • Active Risk: Set the portfolio optimization parameters to be relative to the score from the stock universe. i.e. if you set Momentum to 0.5 to 1.0 and the stock universe has a market cap weighed score of 0.2 then the optimizer will solve for 0.7 to 1.2.
  • Constraints: Boolean value
  • Default Value: false
allowCompanyCollisions : bool
  • Allow Multiple Securities Per Company: Allows the machine to trade multiple securities (at the same time) for the same company. If it is ON and a company has listed PREF shares and COMMON shares, both will show up as tradeable options for that company. If it is OFF then system will try to determine the "correct" security to trade based on volume, liquidity, and share type.
  • Constraints: Boolean value
  • Default Value: true
allowDR : bool
  • Allow Depositary Receipts: Companies with tickers ending in .Y and .F will be disallowed from trading within the model with this setting off. Turn it on to allow trading in these securities.
  • Constraints: Boolean value
  • Default Value: true
benchmark : array
  • Benchmark: Set one or more benchmarks and the weighting for each.
  • Constraints: Required. List of dicts with keys: gbi_id: integer representing the GBI ID of the benchmark index. weight: floating point value between 0 and 1. All gbi_id's in the dictionary must be unique, and the benchmark weights must sum to a number between 0 and 1.
  • Default Value: []
benchmarkAllocation : float
  • Benchmark Allocation: None
  • Constraints: Floating point value representing a percentage between -100 and 100
  • Default Value: 0
beta_neutral : bool
  • Beta Neutral: Adjust the weights to get the ex-ante Beta to be the net exposure of the portfolio. i.e. 70% Long / 30% Short = 0.4 Beta"
  • Constraints: Boolean value
  • Default Value: false
bounds_method : str | null
  • Optimizer Bounds: Tight: The optimizer will only allow the weightings of securities in your portfolio to stay tight (close) to their initial weighting. Loose: The optimizer will allow the weightings of securities in your portfolio to move a medium amount more from their initial weighting. Wide: The optimizer will allow almost any weight between the minimum and maximum weight per long or short position.
  • Constraints: String enum of {loose, tight, wide}, or null
  • Default Value: null
compounding : bool
  • Compound Returns: When toggled on, this will allow the portfolio to compound returns.
  • Constraints: Boolean value
  • Default Value: true
currency : str
  • Currency: The currency you would like your portfolio to be calculated in. Note that if the currency differs from its exchange (i.e. GBP on the S&P), the prior close foreign exchange rate will be used for calculations.
  • Constraints: String representing a 3 digit ISO currency code.
  • Default Value: "USD"
equalWeightBenchmark : bool
  • Equal Weight Benchmark: Instead of using the defined benchmarks, set the benchmark to be an equal weighted (per rebalance period) version of the stock universe.
  • Constraints: Boolean value
  • Default Value: false
executionDelay : int
  • Execution Delay: This option adds the number of days selected to the trade execution. If you select T+1 and your rebalance period is set to Weekly (Monday), it will trade on Tuesday, and so on.
  • Constraints: Integer value between 0 and 100, inclusive.
  • Default Value: 0
factorNeutralizeSignal : bool
  • Signal Optimizer: Turn on optimization at the signal level that will adjust signals and rankings according to the specifications you set. This is done prior to portfolio construction and can help reduce bias in the model.
  • Constraints: Boolean value
  • Default Value: false
investmentHorizon : int
  • Investment Horizon: The investment horizon for the portfolio.
  • Constraints: An integer representing a number of days
  • Default Value: 21
lower_turnover : bool
  • Lower Turnover: If toggled on the optimizer will try to solve the secondary goal of optimizing the primary goals while limiting turnover.
  • Constraints: Boolean value
  • Default Value: false
marketCapBounds : float
  • Maximum Market Cap Variation: How far from the target market cap weighted index each stock can deviate (positive or negative).
  • Constraints: Floating point value representing a percentage between 0 and 10
  • Default Value: 1
marketCapConstraint : bool
  • Constrain Market Cap: Force weighting to be near the target weights for a market cap weighted index of your entire stock universe.
  • Constraints: Boolean value
  • Default Value: false
marketCapFlex : float
  • Market Cap Flex: How far from the target market cap weighted the stock can deviate relative to the existing weight (i.e. at 75% a 1% position cannot exceed 1.75%).
  • Constraints: Floating point value representing a percentage between 10 and 200
  • Default Value: 100
maxLongPosition : float
  • Max Long Position per Stock: The maximum weighting of a long position within the portfolio.
  • Constraints: Required. Floating point value representing a percentage between 0.0 and 300.0
  • Default Value: 10.0
maxNumStockLong : int
  • Maximum Number of Longs: The maximum number of stocks to buy. This is on a per “portfolio” basis, so if you have sector neutral turned on it will be maximum per sector.
  • Constraints: Integer value between 0 and 1000, inclusive. Must be >= minNumStockLong
  • Default Value: 50
maxNumStockShort : int
  • Maximum Number of Shorts: The maximum number of stocks to short. This is on a per “portfolio” basis, so if you have sector neutral turned on it will be maximum per sector.
  • Constraints: Integer value between 0 and 1000, inclusive. Must be >= minNumStockShort
  • Default Value: 50
maxOwnership : float
  • Maximum Ownership: The maximum percentage of a company the portfolio is allowed to own. For example, if a company had a market cap of $100MM and this was 5% the portfolio could not own more than $5MM of that company.
  • Constraints: Floating point value representing a percentage between 0 and 100
  • Default Value: 5
maxShortPosition : float
  • Max Short Position per Stock: The maximum weighting of a short position within the portfolio.
  • Constraints: Required. Floating point value representing a percentage between 0.0 and 300.0
  • Default Value: 5.0
minimumSharePrice : float
  • Minimum Share Price: The minimum share price you want the portfolio to allow.
  • Constraints: Floating point value between 0 and 100000
  • Default Value: 2
minLongPosition : float
  • Min Long Position per Stock: The minimum weighting of a long position within the portfolio.
  • Constraints: Floating point value representing a percentage between 0.0 and 300.0
  • Default Value: 0
minNumStockLong : int
  • Minimum Number of Longs: The minimum number of stocks to buy. This is on a per “portfolio” basis, so if you have sector neutral turned on it will be minimum per sector.
  • Constraints: Integer value between 0 and 1000, inclusive. Must be <= maxNumStockLong
  • Default Value: 5
minNumStockShort : int
  • Minimum Number of Shorts: The minimum number of stocks to short. This is on a per “portfolio” basis, so if you have sector neutral turned on it will be minimum per sector.
  • Constraints: Integer value between 0 and 1000, inclusive. Must be <= maxNumStockShort
  • Default Value: 5
minShortPosition : float
  • Min Short Position per Stock: The minimum weighting of a short position within the portfolio.
  • Constraints: Floating point value representing a percentage between 0.0 and 100.0
  • Default Value: 0
missingMarketCap : float
  • Missing Market Cap: The amount in millions (MM) to replace any missing market capitalization information with. This will impact the maximum ownership setting.
  • Constraints: Floating point value between 0 and 100
  • Default Value: 10
nameBasedSectorWeighting : bool
  • Name Based Sector Weighting: Base sector weightings on number of names. For example, if there are 200 names in the index and financials has 20 companies, the weight of financials should be 10% (20/200).
  • Constraints: Boolean value
  • Default Value: false
netBenchmark : bool
  • Adjust Benchmark for Net Exposure: If your portfolio has a net exposure greater or less than 100%, this will adjust the benchmark returns to match that net exposure.
  • Constraints: Boolean value
  • Default Value: false
optimizer : str
  • Optimizer Type: No Optimization: None Reduce Risk: Optimizer designed to reduce portfolio volatility. This tends to result in the best overall performance. Maximize Sharpe: Optimizer designed to maximize Sharpe using out of sample estimates for expected return. Estimates for expected return have an outsized impact on the performance of this optimizer. Maximize Alpha: Optimizer designed to maximize expected return based on out-of-sample estimates for expected return. Estimates for expected return have an outsized impact on the performance of this optimizer. Min VaR: Minimize Value at Risk by looking back at the proposed portfolio over the last year and trying to minimize drawdowns. Max VaR Sharpe: Maximize Value at Risk Sharpe by looking back at the proposed portfolio over the last year and trying to maximize Sharpe by observed return for the portfolio vs observed volatility. Minimize Skew: Minimize the skew of the portfolio by trying to find a set of portfolio weightings that results in returns that are closer to the mean.
  • Constraints: Required. String enum: one of default: No Optimization min_vol: Reduce Risk max_sharpe: Maximize Sharpe max_alpha: Maximize Alpha min_var: Min VaR max_var_sharpe: Max VaR Sharpe min_skew: Minimize Skew
  • Default Value: "default"
optimizeTurnover : bool
  • Turnover Optimization: Turnover optimization will reduce the turnover at the signal level, making that ranked list more stable.
  • Constraints: Boolean value
  • Default Value: false
pairsTrading : bool
  • Pairs Trading: Pairs Trading forces the portfolio to be created by going long / short an equal number of stocks. The portfolio will try to find an offsetting position for every long by finding securities that are both highly correlated and have a significant difference in star rating.
  • Constraints: Boolean value
  • Default Value: false
percentPortfolioLong : float
  • Percent of Portfolio Long: The exact sum of all long position weightings in the portfolio.
  • Constraints: Required. Floating point value representing a percentage between 0.0 and 300.0
  • Default Value: 100.0
percentPortfolioShort : float
  • Percent of Portfolio Short: The exact sum of all short position weightings in the portfolio.
  • Constraints: Required. Floating point value representing a percentage between 0.0 and 300.0
  • Default Value: 0.0
percentToLong : float
  • Percent to Long: The machine will attempt to buy the top X% of stocks, subject to the minimum and maximum specified. This is on a per “portfolio” basis, so if you have sector neutral turned on it will be top X% per sector. This will be subject to the maximum and minimum number of securities you specify below.
  • Constraints: Floating point value representing a percentage between 0 and 100
  • Default Value: 20.0
percentToShort : float
  • Percent to Short: The machine will attempt to short the bottom X% of stocks, subject to the minimum and maximum specified. This is on a per “portfolio” basis, so if you have sector neutral turned on it will be bottom X% per sector. This will be subject to the maximum and minimum number of securities you specify below.
  • Constraints: Floating point value representing a percentage between 0 and 100
  • Default Value: 20.0
portfolioStartingValue : float
  • Portfolio Starting Value: The value of the portfolio in MM when it begins trading. If its backtest spans from 2005
    • 2020, the starting value in 2005 will be whatever you set here.
  • Constraints: Floating point value between 0 and 100000.
  • Default Value: 100.0
priceCleaning : bool
  • Price Cleaning: Price Cleaning removes any very large price movements from the securities, specifically with a daily change greater than +500% or -83.33%. There are some legitimate securities with moves this large that will be impacted by turning this on, but the advantage is it prevents these securities from overwhelming the backtest.
  • Constraints: Boolean value
  • Default Value: true
priceType : str
  • Price Type: None
  • Constraints: Required. String enum of {CLOSE, OPEN, VWAP}
  • Default Value: "VWAP"
rebalancePeriod : str
  • Rebalance Period: When the machine rebalances the portfolio. You can choose a specific day if you prefer to execute trades on a specific day.
  • Constraints: Required. An enum value in the following list: "daily" "weekly": Rebalances on every Monday. Chooses the next available trade date to rebalance when Monday is a holiday "weekly_wednesday": Rebalances on every Wednesday. Chooses the next available trade date to rebalance when Wednesday is a holiday "weekly_friday": Rebalances on every Friday. Chooses the previous available trade date to rebalance when Friday is a holiday "monthly" "quarterly" OR a string in the following format: "custom_x" where x is an integer between 1 and 252 (days).
  • Default Value: "weekly"
sectorNeutral : bool
  • Sector Neutral: Will be constructed as a series of portfolios - each matching the market cap weighting of each of the stock universe sectors. This means that min. and max. number of stocks "per portfolio" becomes "per sector".
  • Constraints: Boolean value
  • Default Value: false
sectorNeutralEqualWeightBenchmark : bool
  • Sector Neutral Equal Weight Benchmark: Instead of using the defined benchmarks, set the benchmark to be a "sector neutral" equal weighted (per rebalance period) version of the stock universe. This takes the sector weighting and divides by the number of stocks in that sector, so each sector will have a different individual security weighting.
  • Constraints: Boolean value
  • Default Value: false
sectorNeutralSpread : float
  • Sector Neutral Spread: The sector neutral spread is how far away from the benchmark sector allocation the machine can deviate. This is adjusted for net exposure, i.e. if your net exposure is 0% the target sector allocations will be 0%.
  • Constraints: Floating point value representing a percentage between 0 and 100
  • Default Value: 0
signalIndustryNeutral : bool
  • Industry Neutral: Make the signal industry neutral, plus or minus the signal sector neutral spread.
  • Constraints: Boolean value
  • Default Value: false
signalSectorNeutral : bool
  • Sector Neutral: Make the signal sector neutral, plus or minus the signal sector neutral spread.
  • Constraints: Boolean value
  • Default Value: false
signalSectorNeutralSpread : float
  • Sector Neutral Spread: Make the signal sector neutral, plus or minus the signal sector neutral spread.
  • Constraints: Floating point value representing a percentage between 0 and 100
  • Default Value: 1.0
smoothPeriods : int
  • Smooth Periods: The number of periods over which to smooth the signals
  • Constraints: Integer value between 1 and 100.
  • Default Value: 1
smoothWeighting : str
  • Smooth Weighting Type: Alpha Weight: The strength of the signal matters in the smoothing. For example, if a stock had a score of 5 stars in period A, 2 stars in period B, your 2 period smoothing would be 7 / 2 = 3.5. Equal Weight: Only the direction of the signal matters here. For example, if you were 5 stars in period A and 2 stars in period B, your 2 period smoothing would be 0 / 2 = 0 (+1 in period 1 and -1 in period 2)
  • Constraints: One of {alpha_weight, equal_weight}
  • Default Value: "alpha_weight"
stopGain : bool
  • Enable Stop Gain: Turn on stop gains for the portfolio. This forces sales of securities that have exceeded the stop gain level. All trades will occur at the next trading time (i.e. next day OPEN for OPEN price models).
  • Constraints: Boolean value
  • Default Value: false
stopGainAmount : float
  • Stop Gain Percentage to Sell: The percentage of the position the machine will sell if the stop gain is triggered.
  • Constraints: Floating point value representing a percentage between 1 and 100.
  • Default Value: 50.0
stopGainLevel : float
  • Stop Gain Threshold: Will sell positions after they hit a threshold. For example, 10% / stop gain, when a position gains 10% the machine will sell a portion of the position (defined by stop gain percentage).
  • Constraints: Floating point value representing a percentage between 1 and 100.
  • Default Value: 10.0
stopLoss : bool
  • Enable Stop Loss: Turn on stop losses for the portfolio. This forces sales of securities that have exceeded the stop loss level. All trades will occur at the next trading time (i.e. next day OPEN for OPEN price models).
  • Constraints: Boolean value
  • Default Value: false
stopLossAmount : float
  • Stop Loss Percentage to Sell: The percentage of the position the machine will sell if the stop loss is triggered.
  • Constraints: Floating point value representing a percentage between 1 and 100.
  • Default Value: 50.0
stopLossLevel : float
  • Stop Loss Threshold: Will sell positions after they hit a threshold. For example, 10% / stop loss, when a position loses 10% the machine will sell a portion of the position (defined by stop loss percentage).
  • Constraints: Floating point value representing a percentage between 1 and 100.
  • Default Value: 10.0
stopLossReset : bool
  • Allow multiple stop loss/gain between rebalance days: Allows the machine to repeatedly trigger stop losses within a rebalance period. For example, if you trade Mondays and the stock drops 15% per day during the week and the stop loss trigger is 10%, then every day will trigger the stop loss. If toggled off the stop loss can only be triggered once per stock per rebalance period.
  • Constraints: Boolean value
  • Default Value: false
trackingConstraint : float
  • Maximum Tracking Error (vs Benchmark): Amount of ex-ante tracking error to constrain the optimizer by.
  • Constraints: Floating point value representing a percentage between 0 and 30
  • Default Value: 10
trackingError : bool
  • Allocate Weight to Benchmark: Allocate a portion (%) of portfolio to the benchmark
  • Constraints: Boolean value
  • Default Value: false
trackingErrorOptimizer : bool
  • Tracking Error: If toggled on the optimizer will try to solve for an expected tracking error less than the amount specified. This is done out-of-sample and the ex post tracking error will likely be higher.
  • Constraints: Boolean value
  • Default Value: false
tradingCost : float
  • Trading Cost: A cost that will be applied to every trade.
  • Constraints: Required. Floating point value representing a percentage between 0.0 and 5.0
  • Default Value: 0.01
turnoverImportance : float
  • Turnover Importance: High importance means the algorithm will aim to keep turnover low, whereas low importance of turnover will let it vary more.
  • Constraints: Floating point value between 0.2 and 5 nonlinearly mapped onto the 1-9 interval.
  • Default Value: 1
useHoldingPeriod : bool
  • Use Holding Period: Set any covariance or other calculations within the optimizer to use a holding period return instead of daily return series
  • Constraints: Boolean value
  • Default Value: false
weightingType : str
  • Initial Weighting: Alpha Weight: The initial weighting will be done such that higher ranked stocks have a higher weight. Equal Weight: The initial weighting will be done such that all stocks selected to be in the portfolio will have an equal weight. Market Cap Weight: The initial weighting will be done such that all stocks are weighted according to their previous day’s market capitalization.
  • Constraints: One of {alpha_weight, equal_weight, market_cap_weight}
  • Default Value: "alpha_weight"
Expand source code
class PortfolioSettings:
    """
    This config is a documentation wrapper around a dict representing the portfolio settings json.
    Keys missing in the dict provided will fallback to the listed default when updating the
    the portfolio settings on the server.

    Parameters
    ----------
    activeRisk: bool
        - **Active Risk**:
            Set the portfolio optimization parameters to be relative to the score from the stock
            universe. i.e. if you set Momentum to 0.5 to 1.0 and the stock universe has a market cap
            weighed score of 0.2 then the optimizer will solve for 0.7 to 1.2.
        - Constraints:
            Boolean value
        - Default Value: false

    allowCompanyCollisions: bool
        - **Allow Multiple Securities Per Company**:
            Allows the machine to trade multiple securities (at the same time) for the same company.
            If it is ON and a company has listed PREF shares and COMMON shares, both will show up as
            tradeable options for that company. If it is OFF then system will try to determine the
            "correct" security to trade based on volume, liquidity, and share type.
        - Constraints:
            Boolean value
        - Default Value: true

    allowDR: bool
        - **Allow Depositary Receipts**:
            Companies with tickers ending in .Y and .F will be disallowed from trading within the
            model with this setting off. Turn it on to allow trading in these securities.
        - Constraints:
            Boolean value
        - Default Value: true

    benchmark: array
        - **Benchmark**:
            Set one or more benchmarks and the weighting for each.
        - Constraints:
            Required.
            List of dicts with keys:
            gbi_id: integer representing the GBI ID of the benchmark index.
            weight: floating point value between 0 and 1.
            All gbi_id's in the dictionary must be unique, and the benchmark weights must sum to a
            number between 0 and 1.
        - Default Value: []

    benchmarkAllocation: float
        - **Benchmark Allocation**:
            None
        - Constraints:
            Floating point value representing a percentage between -100 and 100
        - Default Value: 0

    beta_neutral: bool
        - **Beta Neutral**:
            Adjust the weights to get the ex-ante Beta to be the net exposure of the portfolio. i.e.
            70% Long / 30% Short = 0.4 Beta"
        - Constraints:
            Boolean value
        - Default Value: false

    bounds_method: str | null
        - **Optimizer Bounds**:
            Tight: The optimizer will only allow the weightings of securities in your portfolio to
            stay tight (close) to their initial weighting.
            Loose: The optimizer will allow the weightings of securities in your portfolio to move a
            medium amount more from their initial weighting.
            Wide: The optimizer will allow almost any weight between the minimum and maximum weight
            per long or short position.
        - Constraints:
            String enum of {loose, tight, wide}, or null
        - Default Value: null

    compounding: bool
        - **Compound Returns**:
            When toggled on, this will allow the portfolio to compound returns.
        - Constraints:
            Boolean value
        - Default Value: true

    currency: str
        - **Currency**:
            The currency you would like your portfolio to be calculated in. Note that if the
            currency differs from its exchange (i.e. GBP on the S&P), the prior close foreign
            exchange rate will be used for calculations.
        - Constraints:
            String representing a 3 digit ISO currency code.
        - Default Value: "USD"

    equalWeightBenchmark: bool
        - **Equal Weight Benchmark**:
            Instead of using the defined benchmarks, set the benchmark to be an equal weighted (per
            rebalance period) version of the stock universe.
        - Constraints:
            Boolean value
        - Default Value: false

    executionDelay: int
        - **Execution Delay**:
            This option adds the number of days selected to the trade execution. If you select T+1
            and your rebalance period is set to Weekly (Monday), it will trade on Tuesday, and so
            on.
        - Constraints:
            Integer value between 0 and 100, inclusive.
        - Default Value: 0

    factorNeutralizeSignal: bool
        - **Signal Optimizer**:
            Turn on optimization at the signal level that will adjust signals and rankings according
            to the specifications you set. This is done prior to portfolio construction and can help
            reduce bias in the model.
        - Constraints:
            Boolean value
        - Default Value: false

    investmentHorizon: int
        - **Investment Horizon**:
            The investment horizon for the portfolio.
        - Constraints:
            An integer representing a number of days
        - Default Value: 21

    lower_turnover: bool
        - **Lower Turnover**:
            If toggled on the optimizer will try to solve the secondary goal of optimizing the
            primary goals while limiting turnover.
        - Constraints:
            Boolean value
        - Default Value: false

    marketCapBounds: float
        - **Maximum Market Cap Variation**:
            How far from the target market cap weighted index each stock can deviate (positive or
            negative).
        - Constraints:
            Floating point value representing a percentage between 0 and 10
        - Default Value: 1

    marketCapConstraint: bool
        - **Constrain Market Cap**:
            Force weighting to be near the target weights for a market cap weighted index of your
            entire stock universe.
        - Constraints:
            Boolean value
        - Default Value: false

    marketCapFlex: float
        - **Market Cap Flex**:
            How far from the target market cap weighted the stock can deviate relative to the
            existing weight (i.e. at 75% a 1% position cannot exceed 1.75%).
        - Constraints:
            Floating point value representing a percentage between 10 and 200
        - Default Value: 100

    maxLongPosition: float
        - **Max Long Position per Stock**:
            The maximum weighting of a long position within the portfolio.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 10.0

    maxNumStockLong: int
        - **Maximum Number of Longs**:
            The maximum number of stocks to buy. This is on a per “portfolio” basis, so if you have
            sector neutral turned on it will be maximum per sector.
        - Constraints:
            Integer value between 0 and 1000, inclusive. Must be >= minNumStockLong
        - Default Value: 50

    maxNumStockShort: int
        - **Maximum Number of Shorts**:
            The maximum number of stocks to short. This is on a per “portfolio” basis, so if you
            have sector neutral turned on it will be maximum per sector.
        - Constraints:
            Integer value between 0 and 1000, inclusive. Must be >= minNumStockShort
        - Default Value: 50

    maxOwnership: float
        - **Maximum Ownership**:
            The maximum percentage of a company the portfolio is allowed to own. For example, if a
            company had a market cap of $100MM and this was 5% the portfolio could not own more than
            $5MM of that company.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 5

    maxShortPosition: float
        - **Max Short Position per Stock**:
            The maximum weighting of a short position within the portfolio.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 5.0

    minimumSharePrice: float
        - **Minimum Share Price**:
            The minimum share price you want the portfolio to allow.
        - Constraints:
            Floating point value between 0 and 100000
        - Default Value: 2

    minLongPosition: float
        - **Min Long Position per Stock**:
            The minimum weighting of a long position within the portfolio.
        - Constraints:
            Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 0

    minNumStockLong: int
        - **Minimum Number of Longs**:
            The minimum number of stocks to buy. This is on a per “portfolio” basis, so if you have
            sector neutral turned on it will be minimum per sector.
        - Constraints:
            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockLong
        - Default Value: 5

    minNumStockShort: int
        - **Minimum Number of Shorts**:
            The minimum number of stocks to short. This is on a per “portfolio” basis, so if you
            have sector neutral turned on it will be minimum per sector.
        - Constraints:
            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockShort
        - Default Value: 5

    minShortPosition: float
        - **Min Short Position per Stock**:
            The minimum weighting of a short position within the portfolio.
        - Constraints:
            Floating point value representing a percentage between 0.0 and 100.0
        - Default Value: 0

    missingMarketCap: float
        - **Missing Market Cap**:
            The amount in millions (MM) to replace any missing market capitalization information
            with. This will impact the maximum ownership setting.
        - Constraints:
            Floating point value between 0 and 100
        - Default Value: 10

    nameBasedSectorWeighting: bool
        - **Name Based Sector Weighting**:
            Base sector weightings on number of names. For example, if there are 200 names in the
            index and financials has 20 companies, the weight of financials should be 10% (20/200).
        - Constraints:
            Boolean value
        - Default Value: false

    netBenchmark: bool
        - **Adjust Benchmark for Net Exposure**:
            If your portfolio has a net exposure greater or less than 100%, this will adjust the
            benchmark returns to match that net exposure.
        - Constraints:
            Boolean value
        - Default Value: false

    optimizer: str
        - **Optimizer Type**:
            No Optimization: None
            Reduce Risk: Optimizer designed to reduce portfolio volatility. This tends to result in
            the best overall performance.
            Maximize Sharpe: Optimizer designed to maximize Sharpe using out of sample estimates for
            expected return. Estimates for expected return have an outsized impact on the
            performance of this optimizer.
            Maximize Alpha: Optimizer designed to maximize expected return based on out-of-sample
            estimates for expected return. Estimates for expected return have an outsized impact on
            the performance of this optimizer.
            Min VaR: Minimize Value at Risk by looking back at the proposed portfolio over the last
            year and trying to minimize drawdowns.
            Max VaR Sharpe: Maximize Value at Risk Sharpe by looking back at the proposed portfolio
            over the last year and trying to maximize Sharpe by observed return for the portfolio vs
            observed volatility.
            Minimize Skew: Minimize the skew of the portfolio by trying to find a set of portfolio
            weightings that results in returns that are closer to the mean.
        - Constraints:
            Required. String enum: one of
            default: No Optimization
            min_vol: Reduce Risk
            max_sharpe: Maximize Sharpe
            max_alpha: Maximize Alpha
            min_var: Min VaR
            max_var_sharpe: Max VaR Sharpe
            min_skew: Minimize Skew
        - Default Value: "default"

    optimizeTurnover: bool
        - **Turnover Optimization**:
            Turnover optimization will reduce the turnover at the signal level, making that ranked
            list more stable.
        - Constraints:
            Boolean value
        - Default Value: false

    pairsTrading: bool
        - **Pairs Trading**:
            Pairs Trading forces the portfolio to be created by going long / short an equal number
            of stocks. The portfolio will try to find an offsetting position for every long by
            finding securities that are both highly correlated and have a significant difference in
            star rating.
        - Constraints:
            Boolean value
        - Default Value: false

    percentPortfolioLong: float
        - **Percent of Portfolio Long**:
            The exact sum of all long position weightings in the portfolio.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 100.0

    percentPortfolioShort: float
        - **Percent of Portfolio Short**:
            The exact sum of all short position weightings in the portfolio.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 300.0
        - Default Value: 0.0

    percentToLong: float
        - **Percent to Long**:
            The machine will attempt to buy the top X% of stocks, subject to the minimum and maximum
            specified. This is on a per “portfolio” basis, so if you have sector neutral turned on
            it will be top X% per sector. This will be subject to the maximum and minimum number of
            securities you specify below.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 20.0

    percentToShort: float
        - **Percent to Short**:
            The machine will attempt to short the bottom X% of stocks, subject to the minimum and
            maximum specified. This is on a per “portfolio” basis, so if you have sector neutral
            turned on it will be bottom X% per sector. This will be subject to the maximum and
            minimum number of securities you specify below.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 20.0

    portfolioStartingValue: float
        - **Portfolio Starting Value**:
            The value of the portfolio in MM when it begins trading. If its backtest spans from 2005
            - 2020, the starting value in 2005 will be whatever you set here.
        - Constraints:
            Floating point value between 0 and 100000.
        - Default Value: 100.0

    priceCleaning: bool
        - **Price Cleaning**:
            Price Cleaning removes any very large price movements from the securities, specifically
            with a daily change greater than +500% or -83.33%. There are some legitimate securities
            with moves this large that will be impacted by turning this on, but the advantage is it
            prevents these securities from overwhelming the backtest.
        - Constraints:
            Boolean value
        - Default Value: true

    priceType: str
        - **Price Type**:
            None
        - Constraints:
            Required. String enum of {CLOSE, OPEN, VWAP}
        - Default Value: "VWAP"

    rebalancePeriod: str
        - **Rebalance Period**:
            When the machine rebalances the portfolio. You can choose a specific day if you prefer
            to execute trades on a specific day.
        - Constraints:
            Required.
            An enum value in the following list:
            "daily"
            "weekly": Rebalances on every Monday.
            Chooses the next available trade date to rebalance when Monday is a holiday
            "weekly_wednesday": Rebalances on every Wednesday.
            Chooses the next available trade date to rebalance when Wednesday is a holiday
            "weekly_friday": Rebalances on every Friday.
            Chooses the previous available trade date to rebalance when Friday is a holiday
            "monthly"
            "quarterly"
            OR a string in the following format: "custom_x" where x is an integer between 1 and 252
            (days).
        - Default Value: "weekly"

    sectorNeutral: bool
        - **Sector Neutral**:
            Will be constructed as a series of portfolios - each matching the market cap weighting
            of each of the stock universe sectors. This means that min. and max. number of stocks
            "per portfolio" becomes "per sector".
        - Constraints:
            Boolean value
        - Default Value: false

    sectorNeutralEqualWeightBenchmark: bool
        - **Sector Neutral Equal Weight Benchmark**:
            Instead of using the defined benchmarks, set the benchmark to be a "sector neutral"
            equal weighted (per rebalance period) version of the stock universe. This takes the
            sector weighting and divides by the number of stocks in that sector, so each sector will
            have a different individual security weighting.
        - Constraints:
            Boolean value
        - Default Value: false

    sectorNeutralSpread: float
        - **Sector Neutral Spread**:
            The sector neutral spread is how far away from the benchmark sector allocation the
            machine can deviate. This is adjusted for net exposure, i.e. if your net exposure is 0%
            the target sector allocations will be 0%.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 0

    signalIndustryNeutral: bool
        - **Industry Neutral**:
            Make the signal industry neutral, plus or minus the signal sector neutral spread.
        - Constraints:
            Boolean value
        - Default Value: false

    signalSectorNeutral: bool
        - **Sector Neutral**:
            Make the signal sector neutral, plus or minus the signal sector neutral spread.
        - Constraints:
            Boolean value
        - Default Value: false

    signalSectorNeutralSpread: float
        - **Sector Neutral Spread**:
            Make the signal sector neutral, plus or minus the signal sector neutral spread.
        - Constraints:
            Floating point value representing a percentage between 0 and 100
        - Default Value: 1.0

    smoothPeriods: int
        - **Smooth Periods**:
            The number of periods over which to smooth the signals
        - Constraints:
            Integer value between 1 and 100.
        - Default Value: 1

    smoothWeighting: str
        - **Smooth Weighting Type**:
            Alpha Weight: The strength of the signal matters in the smoothing. For example, if a
            stock had a score of 5 stars in period A, 2 stars in period B, your 2 period smoothing
            would be 7 / 2 = 3.5.
            Equal Weight: Only the direction of the signal matters here. For example, if you were 5
            stars in period A and 2 stars in period B, your 2 period smoothing would be 0 / 2 = 0
            (+1 in period 1 and -1 in period 2)
        - Constraints:
            One of {alpha_weight, equal_weight}
        - Default Value: "alpha_weight"

    stopGain: bool
        - **Enable Stop Gain**:
            Turn on stop gains for the portfolio. This forces sales of securities that have exceeded
            the stop gain level. All trades will occur at the next trading time (i.e. next day OPEN
            for OPEN price models).
        - Constraints:
            Boolean value
        - Default Value: false

    stopGainAmount: float
        - **Stop Gain Percentage to Sell**:
            The percentage of the position the machine will sell if the stop gain is triggered.
        - Constraints:
            Floating point value representing a percentage between 1 and 100.
        - Default Value: 50.0

    stopGainLevel: float
        - **Stop Gain Threshold**:
            Will sell positions after they hit a threshold. For example, 10% / stop gain, when a
            position gains 10% the machine will sell a portion of the position (defined by stop gain
            percentage).
        - Constraints:
            Floating point value representing a percentage between 1 and 100.
        - Default Value: 10.0

    stopLoss: bool
        - **Enable Stop Loss**:
            Turn on stop losses for the portfolio. This forces sales of securities that have
            exceeded the stop loss level. All trades will occur at the next trading time (i.e. next
            day OPEN for OPEN price models).
        - Constraints:
            Boolean value
        - Default Value: false

    stopLossAmount: float
        - **Stop Loss Percentage to Sell**:
            The percentage of the position the machine will sell if the stop loss is triggered.
        - Constraints:
            Floating point value representing a percentage between 1 and 100.
        - Default Value: 50.0

    stopLossLevel: float
        - **Stop Loss Threshold**:
            Will sell positions after they hit a threshold. For example, 10% / stop loss, when a
            position loses 10% the machine will sell a portion of the position (defined by stop loss
            percentage).
        - Constraints:
            Floating point value representing a percentage between 1 and 100.
        - Default Value: 10.0

    stopLossReset: bool
        - **Allow multiple stop loss/gain between rebalance days**:
            Allows the machine to repeatedly trigger stop losses within a rebalance period. For
            example, if you trade Mondays and the stock drops 15% per day during the week and the
            stop loss trigger is 10%, then every day will trigger the stop loss. If toggled off the
            stop loss can only be triggered once per stock per rebalance period.
        - Constraints:
            Boolean value
        - Default Value: false

    trackingConstraint: float
        - **Maximum Tracking Error (vs Benchmark)**:
            Amount of ex-ante tracking error to constrain the optimizer by.
        - Constraints:
            Floating point value representing a percentage between 0 and 30
        - Default Value: 10

    trackingError: bool
        - **Allocate Weight to Benchmark**:
            Allocate a portion (%) of portfolio to the benchmark
        - Constraints:
            Boolean value
        - Default Value: false

    trackingErrorOptimizer: bool
        - **Tracking Error**:
            If toggled on the optimizer will try to solve for an expected tracking error less than
            the amount specified. This is done out-of-sample and the ex post tracking error will
            likely be higher.
        - Constraints:
            Boolean value
        - Default Value: false

    tradingCost: float
        - **Trading Cost**:
            A cost that will be applied to every trade.
        - Constraints:
            Required. Floating point value representing a percentage between 0.0 and 5.0
        - Default Value: 0.01

    turnoverImportance: float
        - **Turnover Importance**:
            High importance means the algorithm will aim to keep turnover low, whereas low
            importance of turnover will let it vary more.
        - Constraints:
            Floating point value between 0.2 and 5 nonlinearly mapped onto the 1-9 interval.
        - Default Value: 1

    useHoldingPeriod: bool
        - **Use Holding Period**:
            Set any covariance or other calculations within the optimizer to use a holding period
            return instead of daily return series
        - Constraints:
            Boolean value
        - Default Value: false

    weightingType: str
        - **Initial Weighting**:
            Alpha Weight: The initial weighting will be done such that higher ranked stocks have a
            higher weight.
            Equal Weight: The initial weighting will be done such that all stocks selected to be in
            the portfolio will have an equal weight.
            Market Cap Weight: The initial weighting will be done such that all stocks are weighted
            according to their previous day’s market capitalization.
        - Constraints:
            One of {alpha_weight, equal_weight, market_cap_weight}
        - Default Value: "alpha_weight"
    """

    # note, this is just the collection of portfolio settings
    # that is exposed to the API:
    # SELECT key, default_value
    # FROM portfolio_settings_validation
    # WHERE exposed_to_client_api;
    # TODO: expand to entire settings? why this way initially?
    __default_settings = {
        "maxOwnership": "5",
        "pairsTrading": "false",
        "minNumStockLong": "5",
        "stopLossAmount": "50.0",
        "stopGainLevel": "10.0",
        "smoothWeighting": '"alpha_weight"',
        "minimumSharePrice": "2",
        "maxShortPosition": "5.0",
        "tradingCost": "0.01",
        "stopGainAmount": "50.0",
        "smoothPeriods": "1",
        "executionDelay": "0",
        "benchmarkAllocation": "0",
        "sectorNeutralSpread": "0",
        "percentToLong": "20.0",
        "maxNumStockLong": "50",
        "trackingConstraint": "10",
        "compounding": "true",
        "priceCleaning": "true",
        "lower_turnover": "false",
        "missingMarketCap": "10",
        "allowCompanyCollisions": "true",
        "allowDR": "true",
        "sectorNeutral": "false",
        "marketCapFlex": "100",
        "marketCapBounds": "1",
        "turnoverImportance": "1",
        "minNumStockShort": "5",
        "maxNumStockShort": "50",
        "beta_neutral": "false",
        "nameBasedSectorWeighting": "false",
        "trackingError": "false",
        "stopLoss": "false",
        "stopGain": "false",
        "stopLossReset": "false",
        "netBenchmark": "false",
        "equalWeightBenchmark": "false",
        "marketCapConstraint": "false",
        "signalIndustryNeutral": "false",
        "signalSectorNeutral": "false",
        "sectorNeutralEqualWeightBenchmark": "false",
        "percentToShort": "20.0",
        "trackingErrorOptimizer": "false",
        "currency": '"USD"',
        "rebalancePeriod": '"weekly"',
        "benchmark": "[]",
        "signalSectorNeutralSpread": "1.0",
        "optimizeTurnover": "false",
        "minShortPosition": "0",
        "minLongPosition": "0",
        "percentPortfolioLong": "100.0",
        "portfolioStartingValue": "100.0",
        "stopLossLevel": "10.0",
        "maxLongPosition": "10.0",
        "percentPortfolioShort": "0.0",
        "bounds_method": None,
        "optimizer": '"default"',
        "activeRisk": "false",
        "useHoldingPeriod": "false",
        "weightingType": '"alpha_weight"',
        "priceType": '"VWAP"',
        "factorNeutralizeSignal": "false",
        "investmentHorizon": "21",
    }

    def __init__(self, settings: Optional[Dict] = None):
        # TODO: i'd rather have kwargs for the different keys and then allow
        # for per-key overrides, but we start with this more conservative approach
        if settings is None:
            self.settings = copy.deepcopy(PortfolioSettings.__default_settings)
        else:
            self.settings = settings

    def toDict(self):
        return self.settings

    def __str__(self):
        return json.dumps(self.settings)

    def __repr__(self):
        return self.__str__()

    def fromDict(settings):
        return PortfolioSettings(settings)

Methods

def fromDict(settings)
Expand source code
def fromDict(settings):
    return PortfolioSettings(settings)
def toDict(self)
Expand source code
def toDict(self):
    return self.settings
class Status (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class Status(Enum):
    UNKNOWN = 0
    FAIL = 1
    SUCCESS = 2

Ancestors

  • enum.Enum

Class variables

var FAIL
var SUCCESS
var UNKNOWN
class StrategyConfig (name=None, source_name=None)
Expand source code
class StrategyConfig:
    def __init__(self, name=None, source_name=None):
        self.name = name
        self.source_name = source_name

    def __str__(self):
        return 'Name: "{0}", Source Name: {1}.'.format(self.name, self.source_name)

    def __repr__(self):
        return self.__str__()