boosted.api.api_type

   1# Copyright (C) 2020 Gradient Boosted Investments, Inc. - All Rights Reserved
   2
   3import copy
   4import datetime as dt
   5import json
   6import logging
   7import re
   8import string
   9from collections import defaultdict
  10from dataclasses import dataclass
  11from enum import Enum
  12from typing import Dict, List, Literal, Optional, Union
  13
  14import numpy as np
  15import pandas as pd
  16
  17logger = logging.getLogger("boosted.api.api_type")
  18
  19BoostedDate = Union[dt.date, str]
  20
  21
  22class Status(Enum):
  23    UNKNOWN = 0
  24    FAIL = 1
  25    SUCCESS = 2
  26
  27
  28class DataAddType(Enum):
  29    CREATION = 1
  30    HISTORICAL = 2
  31    LIVE = 3
  32
  33    def __str__(self):
  34        if self.name == "CREATION":
  35            return "CREATION"
  36        elif self.name == "HISTORICAL":
  37            return "HISTORICAL"
  38        elif self.name == "LIVE":
  39            return "LIVE"
  40
  41
  42class ChunkStatus(Enum):
  43    PROCESSING = "PROCESSING"
  44    ABORTED = "ABORTED"
  45    SUCCESS = "SUCCESS"
  46    WARNING = "WARNING"
  47    ERROR = "ERROR"
  48
  49
  50class DataSetType(Enum):
  51    UNKNOWN = 0
  52    STOCK = 1
  53    GLOBAL = 2
  54    STRATEGY = 3
  55    SECURITIES_DAILY = 4
  56
  57    def __str__(self):
  58        if self.name == "STOCK":
  59            return "STOCK"
  60        elif self.name == "GLOBAL":
  61            return "GLOBAL"
  62        elif self.name == "STRATEGY":
  63            return "STRATEGY"
  64        elif self.name == "SECURITIES_DAILY":
  65            return "SECURITIES_DAILY"
  66        else:
  67            return "UNKNOWN"
  68
  69
  70class DataSetSubType(Enum):
  71    UNKNOWN = 0
  72    DENSE = 1
  73    SPARSE_HIST = 2
  74    SPARSE_FWD = 3
  75
  76    def __str__(self):
  77        if self.name == "DENSE":
  78            return "DENSE"
  79        elif self.name == "SPARSE_HIST":
  80            return "SPARSE_HIST"
  81        elif self.name == "SPARSE_FWD":
  82            return "SPARSE_FWD"
  83        else:
  84            return "UNKNOWN"
  85
  86
  87class DataSetFrequency(Enum):
  88    UNKNOWN = 0
  89    DAILY = 1
  90    WEEKLY = 2
  91    MONTHLY = 3
  92    QUARTERLY = 4
  93    SEMIANNUAL = 5
  94    ANNUAL = 6
  95
  96    def __str__(self):
  97        if self.name == "DAILY":
  98            return "DAILY"
  99        elif self.name == "WEEKLY":
 100            return "WEEKLY"
 101        elif self.name == "MONTHLY":
 102            return "MONTHLY"
 103        elif self.name == "QUARTERLY":
 104            return "QUARTERLY"
 105        elif self.name == "SEMIANNUAL":
 106            return "SEMIANNUAL"
 107        elif self.name == "ANNUAL":
 108            return "ANNUAL"
 109        else:
 110            return "UNKNOWN"
 111
 112
 113class ThemeUniverse(Enum):
 114    XIC = "XIC Membership (TSX Composite)"  # bd4163fd-ad77-4412-a250-a2c9e0c13802
 115    SPY = "SPY Membership (S&P 500)"  # 4e5f2fd3-394e-4db9-aad3-5c20abf9bf3c
 116    QQQ = "QQQ Membership (Nasdaq)"  # d532609b-620a-425b-b8f1-e94f51e0394d
 117    IWM = "IWM Membership (Russell 2000)"  # 5ad0a5b3-d4e2-439e-af57-6ea5475c19e8
 118    IWB = "IWB Membership (Russell 1000)"  # ed851cea-f19a-45b2-8948-f6cc3503d087
 119    IWV = "IWV Membership (iShares Russell 3000 ETF)"  # c122c187-bb0d-4207-87a3-e06f0b877bc9
 120    EXSA = "EXSA Membership (Stoxx 600)"  # e160fe15-038b-44ea-be12-1cc1054a26d8
 121    ASHR = "ASHR Membership (CSI 300)"  # ee1f70fd-1010-4a90-b2fa-be633ad3d163
 122
 123    @classmethod
 124    def get_ticker_from_name(cls, name: str) -> Optional[str]:
 125        for key, value in cls.__members__.items():
 126            if value.value == name:
 127                return key
 128
 129        return None
 130
 131
 132class Language(str, Enum):
 133    ENGLISH = "en"
 134    CHINESE = "zh-CN"
 135
 136
 137class NewsHorizon(Enum):
 138    ONE_DAY = "1D"
 139    ONE_WEEK = "1W"
 140    ONE_MONTH = "1M"
 141
 142
 143class ColumnRole(Enum):
 144    UNKNOWN = 0
 145    VARIABLE = 1
 146    GOAL = 2
 147    METRIC = 3
 148    IDENTIFIER = 4
 149
 150    def __str__(self):
 151        if self.name == "VARIABLE":
 152            return "VARIABLE"
 153        elif self.name == "GOAL":
 154            return "GOAL"
 155        elif self.name == "METRIC":
 156            return "METRIC"
 157        elif self.name == "IDENTIFIER":
 158            return "IDENTIFIER"
 159        else:
 160            return "UNKNOWN"
 161
 162
 163class ColumnSubRole(Enum):
 164    UNKNOWN = 0
 165    GBI_ID = 1
 166    ISIN = 2
 167    GVKEY = 3
 168    IID = 4
 169    SYMBOL = 5
 170    COUNTRY = 6
 171    CURRENCY = 7
 172    DATE = 8
 173    COMPANY_NAME = 9
 174    REPORT_DATE = 10
 175    REPORT_PERIOD = 11
 176    CUSTOM_SECURITY = 12
 177
 178    def __str__(self):
 179        if self.name == "GBI_ID":
 180            return "GBI_ID"
 181        elif self.name == "ISIN":
 182            return "ISIN"
 183        elif self.name == "GVKEY":
 184            return "GVKEY"
 185        elif self.name == "IID":
 186            return "IID"
 187        elif self.name == "SYMBOL":
 188            return "SYMBOL"
 189        elif self.name == "COUNTRY":
 190            return "COUNTRY"
 191        elif self.name == "CURRENCY":
 192            return "CURRENCY"
 193        elif self.name == "DATE":
 194            return "DATE"
 195        elif self.name == "COMPANY_NAME":
 196            return "COMPANY_NAME"
 197        elif self.name == "REPORT_DATE":
 198            return "REPORT_DATE"
 199        elif self.name == "REPORT_PERIOD":
 200            return "REPORT_PERIOD"
 201        elif self.name == "CUSTOM_SECURITY":
 202            return "CUSTOM_SECURITY"
 203        else:
 204            return "UNKNOWN"
 205
 206    def get_match(column_name):
 207        def clean_str(name):
 208            translation = str.maketrans("", "", string.punctuation)
 209            clean_name = name.strip().translate(translation).lower()
 210            return re.sub(r"\s+", "", clean_name)
 211
 212        identifiers = [
 213            ColumnSubRole.GBI_ID,
 214            ColumnSubRole.ISIN,
 215            ColumnSubRole.GVKEY,
 216            ColumnSubRole.IID,
 217            ColumnSubRole.SYMBOL,
 218            ColumnSubRole.COUNTRY,
 219            ColumnSubRole.CURRENCY,
 220            ColumnSubRole.COMPANY_NAME,
 221            ColumnSubRole.REPORT_DATE,
 222            ColumnSubRole.REPORT_PERIOD,
 223            ColumnSubRole.CUSTOM_SECURITY,
 224        ]
 225
 226        for identifier in identifiers:
 227            if clean_str(str(identifier)) == clean_str(column_name):
 228                return identifier
 229        return None
 230
 231
 232class ColumnValueType(Enum):
 233    UNKNOWN = 0
 234    NUMBER = 1
 235    STRING = 2
 236
 237    def __str__(self):
 238        if self.name == "UNKNOWN":
 239            return "UNKNOWN"
 240        elif self.name == "NUMBER":
 241            return "NUMBER"
 242        elif self.name == "STRING":
 243            return "STRING"
 244        else:
 245            return "UNKNOWN"
 246
 247
 248class CustomNamespaceVariableRole(Enum):
 249    UNKNOWN = "UNKNOWN"
 250    DAILY_OPEN = "DAILY_OPEN"
 251    DAILY_CLOSE = "DAILY_CLOSE"  # mandatory
 252    DAILY_VWAP = "DAILY_VWAP"
 253    DAILY_VOLUME = "DAILY_VOLUME"
 254    DAILY_HIGH = "DAILY_HIGH"
 255    DAILY_LOW = "DAILY_LOW"
 256    DAILY_BID = "DAILY_BID"
 257    DAILY_ASK = "DAILY_ASK"
 258    DAILY_MCAP = "DAILY_MCAP"
 259
 260    def get_match(column_name):
 261        def clean_str(name):
 262            translation = str.maketrans("", "", string.punctuation)
 263            clean_name = name.strip().translate(translation).lower()
 264            return re.sub(r"\s+", "", clean_name)
 265
 266        identifiers = [
 267            CustomNamespaceVariableRole.DAILY_OPEN,
 268            CustomNamespaceVariableRole.DAILY_CLOSE,
 269            CustomNamespaceVariableRole.DAILY_VWAP,
 270            CustomNamespaceVariableRole.DAILY_VOLUME,
 271            CustomNamespaceVariableRole.DAILY_HIGH,
 272            CustomNamespaceVariableRole.DAILY_LOW,
 273            CustomNamespaceVariableRole.DAILY_BID,
 274            CustomNamespaceVariableRole.DAILY_ASK,
 275            CustomNamespaceVariableRole.DAILY_MCAP,
 276        ]
 277
 278        for identifier in identifiers:
 279            if clean_str(str(identifier)) == clean_str(column_name):
 280                return identifier
 281        return None
 282
 283    def __str__(self):
 284        return self.value
 285
 286
 287class ColumnConfig:
 288    def __init__(
 289        self,
 290        name=None,
 291        role=None,
 292        sub_role=None,
 293        value_type=ColumnValueType.NUMBER,
 294        description="",
 295        investment_horizon=None,
 296        custom_namespace_variable_role=None,
 297    ):
 298        self.name = name
 299        self.role = role
 300        self.sub_role = sub_role
 301        self.value_type = value_type
 302        # More value_types will be supported in the future.
 303        self.description = description
 304        self.investment_horizon = investment_horizon
 305        self.custom_namespace_variable_role = custom_namespace_variable_role
 306
 307    def __str__(self):
 308        return (
 309            'Name: "{0}", Role: {1}, SubRole: {2}, Value type: {3}, '
 310            + "Desc: {4} IH: {5} CNSRole: {6}."
 311        ).format(
 312            self.name,
 313            self.role,
 314            self.sub_role,
 315            self.value_type,
 316            self.description,
 317            self.investment_horizon,
 318            self.custom_namespace_variable_role,
 319        )
 320
 321    def __repr__(self):
 322        return self.__str__()
 323
 324
 325class StrategyConfig:
 326    def __init__(self, name=None, source_name=None):
 327        self.name = name
 328        self.source_name = source_name
 329
 330    def __str__(self):
 331        return 'Name: "{0}", Source Name: {1}.'.format(self.name, self.source_name)
 332
 333    def __repr__(self):
 334        return self.__str__()
 335
 336
 337class BoostedAPIException(Exception):
 338    def __init__(self, value, data=None):
 339        self.value = value
 340        self.data = data
 341
 342    def __str__(self):
 343        return repr(self.value)
 344
 345
 346class BoostedDataSetSchemaException(Exception):
 347    def __init__(self, value, data=None):
 348        self.value = value
 349        self.data = data
 350
 351    def __str__(self):
 352        return repr(self.value)
 353
 354
 355class DataSetConfig:
 356    def __init__(
 357        self,
 358        name,
 359        datasetType=DataSetType.STOCK,
 360        datasetSubType=DataSetSubType.DENSE,
 361        datasetFrequency=DataSetFrequency.DAILY,
 362    ):
 363        self.name = name
 364        self.type = datasetType
 365        self.subtype = datasetSubType
 366        self.frequency = datasetFrequency
 367        self.columns = []
 368        self.strategies = []
 369
 370    def addColumn(self, columnConfig):
 371        self.columns.append(columnConfig)
 372
 373    def addStrategy(self, strategyConfig):
 374        self.strategies.append(strategyConfig)
 375
 376    def __getColumnByName(self, col_name):
 377        for column in self.columns:
 378            if col_name == column.name:
 379                return column
 380        raise BoostedDataSetSchemaException(f"Unable to find column {col_name}")
 381
 382    def updateColumnToGoal(self, col_name, investment_horizon):
 383        column = self.__getColumnByName(col_name)
 384        column.role = ColumnRole.GOAL
 385        column.investment_horizon = int(investment_horizon)
 386
 387    def updateColumnToMetric(self, col_name):
 388        column = self.__getColumnByName(col_name)
 389        column.role = ColumnRole.METRIC
 390
 391    def updateColumnToVariable(self, col_name):
 392        column = self.__getColumnByName(col_name)
 393        column.role = ColumnRole.VARIABLE
 394
 395    def __validateSchema(self):
 396        num_goals = 0
 397        num_metrics = 0
 398        dt = self.type
 399        dst = self.subtype
 400        dsf = self.frequency
 401        if len(self.columns) == 0:
 402            msg = "No feature columns exist."
 403            raise BoostedDataSetSchemaException(msg)
 404
 405        if dst in [DataSetSubType.SPARSE_HIST, DataSetSubType.SPARSE_FWD]:
 406            if dsf not in [DataSetFrequency.QUARTERLY]:
 407                msg = f"{dsf} frequency is not supported for {dst} sub data"
 408                raise BoostedDataSetSchemaException(msg)
 409            if dt not in [DataSetType.STOCK]:
 410                msg = f"{dst} subtype is not supported for {dt} data"
 411                raise BoostedDataSetSchemaException(msg)
 412
 413        for column in self.columns:
 414            if column.role == ColumnRole.GOAL:
 415                ih = column.investment_horizon
 416                gn = column.name
 417                if dt == DataSetType.GLOBAL:
 418                    msg = f"{dt} data can not have {ColumnRole.GOAL} type"
 419                    raise BoostedDataSetSchemaException(msg)
 420                if not isinstance(ih, int):
 421                    msg = f"Investment horizon for {gn} must be an integer"
 422                    raise BoostedDataSetSchemaException(msg)
 423                if ih < 1 or ih > 252:
 424                    msg = f"Investment horizon must be between 1 and 252 for {gn}"
 425                    raise BoostedDataSetSchemaException(msg)
 426                num_goals += 1
 427            elif column.role == ColumnRole.METRIC:
 428                if dt in [DataSetType.GLOBAL, DataSetType.STOCK]:
 429                    msg = f"{dt} data can not have {ColumnRole.METRIC} type"
 430                    raise BoostedDataSetSchemaException(msg)
 431                num_metrics += 1
 432
 433        if dt == DataSetType.STRATEGY:
 434            if num_goals == 0:
 435                msg = "Independent data requires at least one goal."
 436                raise BoostedDataSetSchemaException(msg)
 437            if num_metrics == 0:
 438                msg = "Independent data requires at least one metric."
 439                raise BoostedDataSetSchemaException(msg)
 440            if len(self.strategies) <= 1:
 441                msg = "Independent data requires more than 1 strategy"
 442                raise BoostedDataSetSchemaException(msg)
 443
 444    def toDict(self):
 445        self.__validateSchema()
 446        config = {}
 447        config["name"] = self.name
 448        config["type"] = str(self.type)
 449        config["subType"] = str(self.subtype)
 450        config["frequency"] = str(self.frequency)
 451        featureList = []
 452        for i, f in enumerate(self.columns):
 453            fm = {}
 454            fm["name"] = f.name
 455            fm["description"] = f.description
 456            fm["type"] = str(f.role)
 457            fm["role"] = str(f.role)
 458            fm["subRole"] = str(f.sub_role) if f.sub_role is not None else None
 459            fm["valuesType"] = str(f.value_type)
 460            fm["columnName"] = f.name
 461            fm["investmentHorizon"] = f.investment_horizon
 462            fm["customNamespaceDatasetVariableRole"] = (
 463                str(f.custom_namespace_variable_role)
 464                if f.custom_namespace_variable_role is not None
 465                else None
 466            )
 467            featureList.append(fm)
 468        config["features"] = featureList
 469        strategyList = []
 470        for i, s in enumerate(self.strategies):
 471            sm = {}
 472            sm["name"] = s.name
 473            sm["sourceName"] = s.source_name
 474            strategyList.append(sm)
 475        config["strategies"] = strategyList
 476        return config
 477
 478    def __str__(self):
 479        a = ""
 480        a += "Name: {0}\n".format(self.name)
 481        a += "Columns: \n"
 482        for c in self.columns:
 483            a += "  {0}\n".format(c)
 484        return a
 485
 486    def __repr__(self):
 487        return self.__str__()
 488
 489    def fromDict(schema):
 490        subtype = DataSetSubType[schema.get("subtype", str(DataSetSubType.DENSE))]
 491        frequency = DataSetFrequency[schema.get("frequency", str(DataSetFrequency.DAILY))]
 492        config = DataSetConfig(
 493            schema["name"],
 494            datasetType=DataSetType[schema["type"]],
 495            datasetSubType=subtype,
 496            datasetFrequency=frequency,
 497        )
 498        # two things left to fill in - strategies and columns
 499        for strategy_data in schema["strategies"]:
 500            config.addStrategy(
 501                StrategyConfig(name=strategy_data["name"], source_name=strategy_data["sourceName"])
 502            )
 503
 504        for feature_data in schema["features"]:
 505            config.addColumn(
 506                ColumnConfig(
 507                    name=feature_data["columnName"],
 508                    description=feature_data["description"] or "",
 509                    investment_horizon=feature_data["investmentHorizon"],
 510                    value_type=(
 511                        ColumnValueType[feature_data["valuesType"]]
 512                        if feature_data["valuesType"] is not None
 513                        else ColumnValueType.NUMBER
 514                    ),
 515                    role=(
 516                        ColumnRole[feature_data["role"]]
 517                        if feature_data["role"] is not None
 518                        else None
 519                    ),
 520                    sub_role=(
 521                        ColumnSubRole[feature_data["subRole"]]
 522                        if feature_data["subRole"] is not None
 523                        else None
 524                    ),
 525                    custom_namespace_variable_role=(
 526                        CustomNamespaceVariableRole[
 527                            feature_data["customNamespaceDatasetVariableRole"]
 528                        ]
 529                        if feature_data["customNamespaceDatasetVariableRole"] is not None
 530                        else None
 531                    ),
 532                )
 533            )
 534
 535        config.__validateSchema()
 536        return config
 537
 538
 539class GbiIdSecurityException(Exception):
 540    def __init__(self, value, data=None):
 541        self.value = value
 542        self.data = data
 543
 544    def __str__(self):
 545        return repr(self.value)
 546
 547
 548class GbiIdTickerISIN:
 549    def __init__(self, gbi_id: int, ticker: str, isin: str):
 550        self.gbi_id = int(gbi_id)
 551        self.ticker = ticker
 552        self.isin = isin
 553
 554    def __str__(self):
 555        return "(gbi_id: {0}, ticker: {1}, isin: {2})".format(self.gbi_id, self.ticker, self.isin)
 556
 557    def __repr__(self):
 558        return self.__str__()
 559
 560
 561class GbiIdSecurity:
 562    def __init__(self, gbi_id, isin_country_currency_date, ticker, company_name):
 563        self.gbi_id = gbi_id
 564        self.isin_info = isin_country_currency_date
 565        self.ticker = ticker
 566        self.company_name = company_name
 567
 568    def __str__(self):
 569        return '\n(gbi_id: "{0}", isin_info: {1}, ticker: {2}, company_name: {3})'.format(
 570            self.gbi_id, self.isin_info.__str__(), self.ticker, self.company_name
 571        )
 572
 573    def __repr__(self):
 574        return self.__str__()
 575
 576    def __eq__(self, __o: object) -> bool:
 577        return self.gbi_id == __o.gbi_id
 578
 579    def __hash__(self):
 580        return hash(self.gbi_id)
 581
 582
 583class DateIdentCountryCurrencyException(Exception):
 584    def __init__(self, value, data=None):
 585        self.value = value
 586        self.data = data
 587
 588    def __str__(self):
 589        return repr(self.value)
 590
 591
 592# for backward compatibility
 593class DateIsinCountryCurrencyException(DateIdentCountryCurrencyException):
 594    pass
 595
 596
 597class DateIdentCountryCurrency:
 598    def __init__(
 599        self,
 600        date: str,
 601        identifier: str,
 602        country: Optional[str] = None,
 603        currency: Optional[str] = None,
 604        id_type: ColumnSubRole = ColumnSubRole.ISIN,
 605    ):
 606        self.date = date
 607        self.identifier = identifier
 608        self.id_type = id_type
 609        self.country = country
 610        self.currency = currency
 611        self.__validateInput()
 612
 613    def __str__(self):
 614        return '(date: "{0}", identifier: "{1}", country: {2}, currency: {3})'.format(
 615            self.date, self.identifier, self.country, self.currency
 616        )
 617
 618    def __repr__(self):
 619        return self.__str__()
 620
 621    def __validateInput(self):
 622        # simply ensure that at least isin and date are filled out.
 623        # country/currency defaults to ANY if not filled out, but
 624        # still is not recommended.
 625        if self.identifier is None or self.date is None:
 626            raise DateIdentCountryCurrencyException(
 627                "ISIN or Date not provided while constructing DateIsinCountryCurrency"
 628            )
 629
 630        if self.id_type not in (ColumnSubRole.ISIN, ColumnSubRole.SYMBOL):
 631            raise DateIdentCountryCurrencyException(f"Invalid ID Type: {self.id_type}")
 632
 633        def check_is_str(value, fieldname):
 634            if value is not None and not isinstance(value, str):
 635                raise DateIdentCountryCurrencyException(
 636                    f"Field {fieldname} in DateIsinCountryCurrency was not a string."
 637                )
 638
 639        check_is_str(self.date, "date")
 640        check_is_str(self.identifier, "identifier")
 641        check_is_str(self.country, "country")
 642        check_is_str(self.currency, "currency")
 643
 644
 645# for backwards compatibility
 646class DateIsinCountryCurrency(DateIdentCountryCurrency):
 647    def __init__(self, date, isin, country, currency):
 648        super().__init__(date, isin, country, currency, ColumnSubRole.ISIN)
 649        self.isin = isin
 650
 651    def __str__(self):
 652        return '(date: "{0}", isin: "{1}", country: {2}, currency: {3})'.format(
 653            self.date, self.isin, self.country, self.currency
 654        )
 655
 656
 657class IdentifierToColumnMapping:
 658    """
 659    IdentifierToColumnMapping is a mapping from string to boosted.api.api_type.ColumnSubRole.
 660    The key of the mapping corresponds to a CSV column name, and the value corresponds
 661    to the type of identifier that it maps to. Only columns specifying an identifying factor
 662    for the stock needs to be specified (e.g. ISIN, Date, Symbol, Currency)
 663    """
 664
 665    def __init__(self, mapping):
 666        self.mapping = mapping
 667        self.__validateInput()
 668
 669    def __str__(self):
 670        return repr(self.value)
 671
 672    def __validateInput(self):
 673        def validate_key_val(key, val):
 674            if not isinstance(key, str):
 675                raise Exception(f"key in IdentifierToColumnMapping {key} was not a string!")
 676            if not isinstance(val, ColumnSubRole):
 677                raise Exception(f"val in IdentifierToColumnMapping {val} was not a ColumnSubRole!")
 678
 679        for key, val in self.mapping:
 680            validate_key_val(key, val)
 681
 682
 683class PortfolioSettings:
 684    """
 685    This config is a documentation wrapper around a dict representing the portfolio settings json.
 686    Keys missing in the dict provided will fallback to the listed default when updating the
 687    the portfolio settings on the server.
 688
 689    Parameters
 690    ----------
 691    activeRisk: bool
 692        - **Active Risk**:
 693            Set the portfolio optimization parameters to be relative to the score from the stock
 694            universe. i.e. if you set Momentum to 0.5 to 1.0 and the stock universe has a market cap
 695            weighed score of 0.2 then the optimizer will solve for 0.7 to 1.2.
 696        - Constraints:
 697            Boolean value
 698        - Default Value: false
 699
 700    allowCompanyCollisions: bool
 701        - **Allow Multiple Securities Per Company**:
 702            Allows the machine to trade multiple securities (at the same time) for the same company.
 703            If it is ON and a company has listed PREF shares and COMMON shares, both will show up as
 704            tradeable options for that company. If it is OFF then system will try to determine the
 705            "correct" security to trade based on volume, liquidity, and share type.
 706        - Constraints:
 707            Boolean value
 708        - Default Value: true
 709
 710    allowDR: bool
 711        - **Allow Depositary Receipts**:
 712            Companies with tickers ending in .Y and .F will be disallowed from trading within the
 713            model with this setting off. Turn it on to allow trading in these securities.
 714        - Constraints:
 715            Boolean value
 716        - Default Value: true
 717
 718    benchmark: array
 719        - **Benchmark**:
 720            Set one or more benchmarks and the weighting for each.
 721        - Constraints:
 722            Required.
 723            List of dicts with keys:
 724            gbi_id: integer representing the GBI ID of the benchmark index.
 725            weight: floating point value between 0 and 1.
 726            All gbi_id's in the dictionary must be unique, and the benchmark weights must sum to a
 727            number between 0 and 1.
 728        - Default Value: []
 729
 730    benchmarkAllocation: float
 731        - **Benchmark Allocation**:
 732            None
 733        - Constraints:
 734            Floating point value representing a percentage between -100 and 100
 735        - Default Value: 0
 736
 737    beta_neutral: bool
 738        - **Beta Neutral**:
 739            Adjust the weights to get the ex-ante Beta to be the net exposure of the portfolio. i.e.
 740            70% Long / 30% Short = 0.4 Beta"
 741        - Constraints:
 742            Boolean value
 743        - Default Value: false
 744
 745    bounds_method: str | null
 746        - **Optimizer Bounds**:
 747            Tight: The optimizer will only allow the weightings of securities in your portfolio to
 748            stay tight (close) to their initial weighting.
 749            Loose: The optimizer will allow the weightings of securities in your portfolio to move a
 750            medium amount more from their initial weighting.
 751            Wide: The optimizer will allow almost any weight between the minimum and maximum weight
 752            per long or short position.
 753        - Constraints:
 754            String enum of {loose, tight, wide}, or null
 755        - Default Value: null
 756
 757    compounding: bool
 758        - **Compound Returns**:
 759            When toggled on, this will allow the portfolio to compound returns.
 760        - Constraints:
 761            Boolean value
 762        - Default Value: true
 763
 764    currency: str
 765        - **Currency**:
 766            The currency you would like your portfolio to be calculated in. Note that if the
 767            currency differs from its exchange (i.e. GBP on the S&P), the prior close foreign
 768            exchange rate will be used for calculations.
 769        - Constraints:
 770            String representing a 3 digit ISO currency code.
 771        - Default Value: "USD"
 772
 773    equalWeightBenchmark: bool
 774        - **Equal Weight Benchmark**:
 775            Instead of using the defined benchmarks, set the benchmark to be an equal weighted (per
 776            rebalance period) version of the stock universe.
 777        - Constraints:
 778            Boolean value
 779        - Default Value: false
 780
 781    executionDelay: int
 782        - **Execution Delay**:
 783            This option adds the number of days selected to the trade execution. If you select T+1
 784            and your rebalance period is set to Weekly (Monday), it will trade on Tuesday, and so
 785            on.
 786        - Constraints:
 787            Integer value between 0 and 100, inclusive.
 788        - Default Value: 0
 789
 790    factorNeutralizeSignal: bool
 791        - **Signal Optimizer**:
 792            Turn on optimization at the signal level that will adjust signals and rankings according
 793            to the specifications you set. This is done prior to portfolio construction and can help
 794            reduce bias in the model.
 795        - Constraints:
 796            Boolean value
 797        - Default Value: false
 798
 799    investmentHorizon: int
 800        - **Investment Horizon**:
 801            The investment horizon for the portfolio.
 802        - Constraints:
 803            An integer representing a number of days
 804        - Default Value: 21
 805
 806    lower_turnover: bool
 807        - **Lower Turnover**:
 808            If toggled on the optimizer will try to solve the secondary goal of optimizing the
 809            primary goals while limiting turnover.
 810        - Constraints:
 811            Boolean value
 812        - Default Value: false
 813
 814    marketCapBounds: float
 815        - **Maximum Market Cap Variation**:
 816            How far from the target market cap weighted index each stock can deviate (positive or
 817            negative).
 818        - Constraints:
 819            Floating point value representing a percentage between 0 and 10
 820        - Default Value: 1
 821
 822    marketCapConstraint: bool
 823        - **Constrain Market Cap**:
 824            Force weighting to be near the target weights for a market cap weighted index of your
 825            entire stock universe.
 826        - Constraints:
 827            Boolean value
 828        - Default Value: false
 829
 830    marketCapFlex: float
 831        - **Market Cap Flex**:
 832            How far from the target market cap weighted the stock can deviate relative to the
 833            existing weight (i.e. at 75% a 1% position cannot exceed 1.75%).
 834        - Constraints:
 835            Floating point value representing a percentage between 10 and 200
 836        - Default Value: 100
 837
 838    maxLongPosition: float
 839        - **Max Long Position per Stock**:
 840            The maximum weighting of a long position within the portfolio.
 841        - Constraints:
 842            Required. Floating point value representing a percentage between 0.0 and 300.0
 843        - Default Value: 10.0
 844
 845    maxNumStockLong: int
 846        - **Maximum Number of Longs**:
 847            The maximum number of stocks to buy. This is on a per “portfolio” basis, so if you have
 848            sector neutral turned on it will be maximum per sector.
 849        - Constraints:
 850            Integer value between 0 and 1000, inclusive. Must be >= minNumStockLong
 851        - Default Value: 50
 852
 853    maxNumStockShort: int
 854        - **Maximum Number of Shorts**:
 855            The maximum number of stocks to short. This is on a per “portfolio” basis, so if you
 856            have sector neutral turned on it will be maximum per sector.
 857        - Constraints:
 858            Integer value between 0 and 1000, inclusive. Must be >= minNumStockShort
 859        - Default Value: 50
 860
 861    maxOwnership: float
 862        - **Maximum Ownership**:
 863            The maximum percentage of a company the portfolio is allowed to own. For example, if a
 864            company had a market cap of $100MM and this was 5% the portfolio could not own more than
 865            $5MM of that company.
 866        - Constraints:
 867            Floating point value representing a percentage between 0 and 100
 868        - Default Value: 5
 869
 870    maxShortPosition: float
 871        - **Max Short Position per Stock**:
 872            The maximum weighting of a short position within the portfolio.
 873        - Constraints:
 874            Required. Floating point value representing a percentage between 0.0 and 300.0
 875        - Default Value: 5.0
 876
 877    minimumSharePrice: float
 878        - **Minimum Share Price**:
 879            The minimum share price you want the portfolio to allow.
 880        - Constraints:
 881            Floating point value between 0 and 100000
 882        - Default Value: 2
 883
 884    minLongPosition: float
 885        - **Min Long Position per Stock**:
 886            The minimum weighting of a long position within the portfolio.
 887        - Constraints:
 888            Floating point value representing a percentage between 0.0 and 300.0
 889        - Default Value: 0
 890
 891    minNumStockLong: int
 892        - **Minimum Number of Longs**:
 893            The minimum number of stocks to buy. This is on a per “portfolio” basis, so if you have
 894            sector neutral turned on it will be minimum per sector.
 895        - Constraints:
 896            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockLong
 897        - Default Value: 5
 898
 899    minNumStockShort: int
 900        - **Minimum Number of Shorts**:
 901            The minimum number of stocks to short. This is on a per “portfolio” basis, so if you
 902            have sector neutral turned on it will be minimum per sector.
 903        - Constraints:
 904            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockShort
 905        - Default Value: 5
 906
 907    minShortPosition: float
 908        - **Min Short Position per Stock**:
 909            The minimum weighting of a short position within the portfolio.
 910        - Constraints:
 911            Floating point value representing a percentage between 0.0 and 100.0
 912        - Default Value: 0
 913
 914    missingMarketCap: float
 915        - **Missing Market Cap**:
 916            The amount in millions (MM) to replace any missing market capitalization information
 917            with. This will impact the maximum ownership setting.
 918        - Constraints:
 919            Floating point value between 0 and 100
 920        - Default Value: 10
 921
 922    nameBasedSectorWeighting: bool
 923        - **Name Based Sector Weighting**:
 924            Base sector weightings on number of names. For example, if there are 200 names in the
 925            index and financials has 20 companies, the weight of financials should be 10% (20/200).
 926        - Constraints:
 927            Boolean value
 928        - Default Value: false
 929
 930    netBenchmark: bool
 931        - **Adjust Benchmark for Net Exposure**:
 932            If your portfolio has a net exposure greater or less than 100%, this will adjust the
 933            benchmark returns to match that net exposure.
 934        - Constraints:
 935            Boolean value
 936        - Default Value: false
 937
 938    optimizer: str
 939        - **Optimizer Type**:
 940            No Optimization: None
 941            Reduce Risk: Optimizer designed to reduce portfolio volatility. This tends to result in
 942            the best overall performance.
 943            Maximize Sharpe: Optimizer designed to maximize Sharpe using out of sample estimates for
 944            expected return. Estimates for expected return have an outsized impact on the
 945            performance of this optimizer.
 946            Maximize Alpha: Optimizer designed to maximize expected return based on out-of-sample
 947            estimates for expected return. Estimates for expected return have an outsized impact on
 948            the performance of this optimizer.
 949            Min VaR: Minimize Value at Risk by looking back at the proposed portfolio over the last
 950            year and trying to minimize drawdowns.
 951            Max VaR Sharpe: Maximize Value at Risk Sharpe by looking back at the proposed portfolio
 952            over the last year and trying to maximize Sharpe by observed return for the portfolio vs
 953            observed volatility.
 954            Minimize Skew: Minimize the skew of the portfolio by trying to find a set of portfolio
 955            weightings that results in returns that are closer to the mean.
 956        - Constraints:
 957            Required. String enum: one of
 958            default: No Optimization
 959            min_vol: Reduce Risk
 960            max_sharpe: Maximize Sharpe
 961            max_alpha: Maximize Alpha
 962            min_var: Min VaR
 963            max_var_sharpe: Max VaR Sharpe
 964            min_skew: Minimize Skew
 965        - Default Value: "default"
 966
 967    optimizeTurnover: bool
 968        - **Turnover Optimization**:
 969            Turnover optimization will reduce the turnover at the signal level, making that ranked
 970            list more stable.
 971        - Constraints:
 972            Boolean value
 973        - Default Value: false
 974
 975    pairsTrading: bool
 976        - **Pairs Trading**:
 977            Pairs Trading forces the portfolio to be created by going long / short an equal number
 978            of stocks. The portfolio will try to find an offsetting position for every long by
 979            finding securities that are both highly correlated and have a significant difference in
 980            star rating.
 981        - Constraints:
 982            Boolean value
 983        - Default Value: false
 984
 985    percentPortfolioLong: float
 986        - **Percent of Portfolio Long**:
 987            The exact sum of all long position weightings in the portfolio.
 988        - Constraints:
 989            Required. Floating point value representing a percentage between 0.0 and 300.0
 990        - Default Value: 100.0
 991
 992    percentPortfolioShort: float
 993        - **Percent of Portfolio Short**:
 994            The exact sum of all short position weightings in the portfolio.
 995        - Constraints:
 996            Required. Floating point value representing a percentage between 0.0 and 300.0
 997        - Default Value: 0.0
 998
 999    percentToLong: float
1000        - **Percent to Long**:
1001            The machine will attempt to buy the top X% of stocks, subject to the minimum and maximum
1002            specified. This is on a per “portfolio” basis, so if you have sector neutral turned on
1003            it will be top X% per sector. This will be subject to the maximum and minimum number of
1004            securities you specify below.
1005        - Constraints:
1006            Floating point value representing a percentage between 0 and 100
1007        - Default Value: 20.0
1008
1009    percentToShort: float
1010        - **Percent to Short**:
1011            The machine will attempt to short the bottom X% of stocks, subject to the minimum and
1012            maximum specified. This is on a per “portfolio” basis, so if you have sector neutral
1013            turned on it will be bottom X% per sector. This will be subject to the maximum and
1014            minimum number of securities you specify below.
1015        - Constraints:
1016            Floating point value representing a percentage between 0 and 100
1017        - Default Value: 20.0
1018
1019    portfolioStartingValue: float
1020        - **Portfolio Starting Value**:
1021            The value of the portfolio in MM when it begins trading. If its backtest spans from 2005
1022            - 2020, the starting value in 2005 will be whatever you set here.
1023        - Constraints:
1024            Floating point value between 0 and 100000.
1025        - Default Value: 100.0
1026
1027    priceCleaning: bool
1028        - **Price Cleaning**:
1029            Price Cleaning removes any very large price movements from the securities, specifically
1030            with a daily change greater than +500% or -83.33%. There are some legitimate securities
1031            with moves this large that will be impacted by turning this on, but the advantage is it
1032            prevents these securities from overwhelming the backtest.
1033        - Constraints:
1034            Boolean value
1035        - Default Value: true
1036
1037    priceType: str
1038        - **Price Type**:
1039            None
1040        - Constraints:
1041            Required. String enum of {CLOSE, OPEN, VWAP}
1042        - Default Value: "VWAP"
1043
1044    rebalancePeriod: str
1045        - **Rebalance Period**:
1046            When the machine rebalances the portfolio. You can choose a specific day if you prefer
1047            to execute trades on a specific day.
1048        - Constraints:
1049            Required.
1050            An enum value in the following list:
1051            "daily"
1052            "weekly": Rebalances on every Monday.
1053            Chooses the next available trade date to rebalance when Monday is a holiday
1054            "weekly_wednesday": Rebalances on every Wednesday.
1055            Chooses the next available trade date to rebalance when Wednesday is a holiday
1056            "weekly_friday": Rebalances on every Friday.
1057            Chooses the previous available trade date to rebalance when Friday is a holiday
1058            "monthly"
1059            "quarterly"
1060            OR a string in the following format: "custom_x" where x is an integer between 1 and 252
1061            (days).
1062        - Default Value: "weekly"
1063
1064    sectorNeutral: bool
1065        - **Sector Neutral**:
1066            Will be constructed as a series of portfolios - each matching the market cap weighting
1067            of each of the stock universe sectors. This means that min. and max. number of stocks
1068            "per portfolio" becomes "per sector".
1069        - Constraints:
1070            Boolean value
1071        - Default Value: false
1072
1073    sectorNeutralEqualWeightBenchmark: bool
1074        - **Sector Neutral Equal Weight Benchmark**:
1075            Instead of using the defined benchmarks, set the benchmark to be a "sector neutral"
1076            equal weighted (per rebalance period) version of the stock universe. This takes the
1077            sector weighting and divides by the number of stocks in that sector, so each sector will
1078            have a different individual security weighting.
1079        - Constraints:
1080            Boolean value
1081        - Default Value: false
1082
1083    sectorNeutralSpread: float
1084        - **Sector Neutral Spread**:
1085            The sector neutral spread is how far away from the benchmark sector allocation the
1086            machine can deviate. This is adjusted for net exposure, i.e. if your net exposure is 0%
1087            the target sector allocations will be 0%.
1088        - Constraints:
1089            Floating point value representing a percentage between 0 and 100
1090        - Default Value: 0
1091
1092    signalIndustryNeutral: bool
1093        - **Industry Neutral**:
1094            Make the signal industry neutral, plus or minus the signal sector neutral spread.
1095        - Constraints:
1096            Boolean value
1097        - Default Value: false
1098
1099    signalSectorNeutral: bool
1100        - **Sector Neutral**:
1101            Make the signal sector neutral, plus or minus the signal sector neutral spread.
1102        - Constraints:
1103            Boolean value
1104        - Default Value: false
1105
1106    signalSectorNeutralSpread: float
1107        - **Sector Neutral Spread**:
1108            Make the signal sector neutral, plus or minus the signal sector neutral spread.
1109        - Constraints:
1110            Floating point value representing a percentage between 0 and 100
1111        - Default Value: 1.0
1112
1113    smoothPeriods: int
1114        - **Smooth Periods**:
1115            The number of periods over which to smooth the signals
1116        - Constraints:
1117            Integer value between 1 and 100.
1118        - Default Value: 1
1119
1120    smoothWeighting: str
1121        - **Smooth Weighting Type**:
1122            Alpha Weight: The strength of the signal matters in the smoothing. For example, if a
1123            stock had a score of 5 stars in period A, 2 stars in period B, your 2 period smoothing
1124            would be 7 / 2 = 3.5.
1125            Equal Weight: Only the direction of the signal matters here. For example, if you were 5
1126            stars in period A and 2 stars in period B, your 2 period smoothing would be 0 / 2 = 0
1127            (+1 in period 1 and -1 in period 2)
1128        - Constraints:
1129            One of {alpha_weight, equal_weight}
1130        - Default Value: "alpha_weight"
1131
1132    stopGain: bool
1133        - **Enable Stop Gain**:
1134            Turn on stop gains for the portfolio. This forces sales of securities that have exceeded
1135            the stop gain level. All trades will occur at the next trading time (i.e. next day OPEN
1136            for OPEN price models).
1137        - Constraints:
1138            Boolean value
1139        - Default Value: false
1140
1141    stopGainAmount: float
1142        - **Stop Gain Percentage to Sell**:
1143            The percentage of the position the machine will sell if the stop gain is triggered.
1144        - Constraints:
1145            Floating point value representing a percentage between 1 and 100.
1146        - Default Value: 50.0
1147
1148    stopGainLevel: float
1149        - **Stop Gain Threshold**:
1150            Will sell positions after they hit a threshold. For example, 10% / stop gain, when a
1151            position gains 10% the machine will sell a portion of the position (defined by stop gain
1152            percentage).
1153        - Constraints:
1154            Floating point value representing a percentage between 1 and 100.
1155        - Default Value: 10.0
1156
1157    stopLoss: bool
1158        - **Enable Stop Loss**:
1159            Turn on stop losses for the portfolio. This forces sales of securities that have
1160            exceeded the stop loss level. All trades will occur at the next trading time (i.e. next
1161            day OPEN for OPEN price models).
1162        - Constraints:
1163            Boolean value
1164        - Default Value: false
1165
1166    stopLossAmount: float
1167        - **Stop Loss Percentage to Sell**:
1168            The percentage of the position the machine will sell if the stop loss is triggered.
1169        - Constraints:
1170            Floating point value representing a percentage between 1 and 100.
1171        - Default Value: 50.0
1172
1173    stopLossLevel: float
1174        - **Stop Loss Threshold**:
1175            Will sell positions after they hit a threshold. For example, 10% / stop loss, when a
1176            position loses 10% the machine will sell a portion of the position (defined by stop loss
1177            percentage).
1178        - Constraints:
1179            Floating point value representing a percentage between 1 and 100.
1180        - Default Value: 10.0
1181
1182    stopLossReset: bool
1183        - **Allow multiple stop loss/gain between rebalance days**:
1184            Allows the machine to repeatedly trigger stop losses within a rebalance period. For
1185            example, if you trade Mondays and the stock drops 15% per day during the week and the
1186            stop loss trigger is 10%, then every day will trigger the stop loss. If toggled off the
1187            stop loss can only be triggered once per stock per rebalance period.
1188        - Constraints:
1189            Boolean value
1190        - Default Value: false
1191
1192    trackingConstraint: float
1193        - **Maximum Tracking Error (vs Benchmark)**:
1194            Amount of ex-ante tracking error to constrain the optimizer by.
1195        - Constraints:
1196            Floating point value representing a percentage between 0 and 30
1197        - Default Value: 10
1198
1199    trackingError: bool
1200        - **Allocate Weight to Benchmark**:
1201            Allocate a portion (%) of portfolio to the benchmark
1202        - Constraints:
1203            Boolean value
1204        - Default Value: false
1205
1206    trackingErrorOptimizer: bool
1207        - **Tracking Error**:
1208            If toggled on the optimizer will try to solve for an expected tracking error less than
1209            the amount specified. This is done out-of-sample and the ex post tracking error will
1210            likely be higher.
1211        - Constraints:
1212            Boolean value
1213        - Default Value: false
1214
1215    tradingCost: float
1216        - **Trading Cost**:
1217            A cost that will be applied to every trade.
1218        - Constraints:
1219            Required. Floating point value representing a percentage between 0.0 and 5.0
1220        - Default Value: 0.01
1221
1222    turnoverImportance: float
1223        - **Turnover Importance**:
1224            High importance means the algorithm will aim to keep turnover low, whereas low
1225            importance of turnover will let it vary more.
1226        - Constraints:
1227            Floating point value between 0.2 and 5 nonlinearly mapped onto the 1-9 interval.
1228        - Default Value: 1
1229
1230    useHoldingPeriod: bool
1231        - **Use Holding Period**:
1232            Set any covariance or other calculations within the optimizer to use a holding period
1233            return instead of daily return series
1234        - Constraints:
1235            Boolean value
1236        - Default Value: false
1237
1238    weightingType: str
1239        - **Initial Weighting**:
1240            Alpha Weight: The initial weighting will be done such that higher ranked stocks have a
1241            higher weight.
1242            Equal Weight: The initial weighting will be done such that all stocks selected to be in
1243            the portfolio will have an equal weight.
1244            Market Cap Weight: The initial weighting will be done such that all stocks are weighted
1245            according to their previous day’s market capitalization.
1246        - Constraints:
1247            One of {alpha_weight, equal_weight, market_cap_weight}
1248        - Default Value: "alpha_weight"
1249    """
1250
1251    # note, this is just the collection of portfolio settings
1252    # that is exposed to the API:
1253    # SELECT key, default_value
1254    # FROM portfolio_settings_validation
1255    # WHERE exposed_to_client_api;
1256    # TODO: expand to entire settings? why this way initially?
1257    __default_settings = json.loads(
1258        """{
1259      "adv": {
1260        "max": 25,
1261        "min": 0,
1262        "active": false,
1263        "method": "average",
1264        "num_days": 21,
1265        "daily_max": 100
1266      },
1267      "region": [
1268        "USA",
1269        "CAN"
1270      ],
1271      "allowDR": true,
1272      "lotSize": 1,
1273      "currency": "USD",
1274      "stopGain": false,
1275      "stopLoss": false,
1276      "twapDays": 1,
1277      "advFilter": {
1278        "active": false,
1279        "method": "average",
1280        "num_days": 21
1281      },
1282      "benchmark": [],
1283      "blacklist": "none",
1284      "closeEval": true,
1285      "hypermode": 0,
1286      "optimizer": "default",
1287      "priceType": "VWAP",
1288      "activeRisk": false,
1289      "enableTWAP": false,
1290      "mcapFilter": {
1291        "max": 10000,
1292        "min": 100,
1293        "active": false,
1294        "num_days": 21
1295      },
1296      "shortModel": "",
1297      "compounding": true,
1298      "fullOverlay": false,
1299      "model_style": "default",
1300      "recommender": {
1301        "active": false,
1302        "targetPortfolio": "",
1303        "investmentHorizon": "1M",
1304        "allocationMethodology": "recommenderPV"
1305      },
1306      "totalReturn": true,
1307      "tradingCost": 0.01,
1308      "beta_neutral": false,
1309      "combineModel": [],
1310      "denseSignals": {
1311        "addFwdVol": false,
1312        "addRicCode": false,
1313        "addFwdReturns": false,
1314        "addShareCounts": false
1315      },
1316      "macroTrading": {
1317        "active": false,
1318        "daysAbove": 5,
1319        "movingAverageDays": 50,
1320        "portfolioReduction": 30
1321      },
1322      "masterRegion": "NA",
1323      "maxOwnership": 5,
1324      "netBenchmark": false,
1325      "paParameters": {
1326        "data_type": "rank_all"
1327      },
1328      "pairsTrading": false,
1329      "targetEquity": {
1330        "active": false,
1331        "targetDates": []
1332      },
1333      "volTargeting": {
1334        "lb": 5,
1335        "ub": 20,
1336        "active": false
1337      },
1338      "basketTrading": {
1339        "active": false,
1340        "lookback": 252,
1341        "esgWeight": 1,
1342        "optimizer": "min_var",
1343        "betaWeight": 1,
1344        "dataWeight": 1,
1345        "correlation": false,
1346        "equalWeight": true,
1347        "hedgeMethod": "simple",
1348        "hedgeStocks": [],
1349        "longPrimary": true,
1350        "priceWeight": 1,
1351        "factorWeight": 1,
1352        "primaryStocks": [],
1353        "hedgePortfolio": false,
1354        "numHedgeStocks": 20,
1355        "factorTargeting": false,
1356        "maxPositionSize": 0,
1357        "minPositionSize": 0,
1358        "optimizerWeight": 75,
1359        "secondaryStocks": [],
1360        "secondaryWeight": 0,
1361        "worstPercentage": 50,
1362        "numPrimaryStocks": 10,
1363        "restrictedStocks": [],
1364        "signalImportance": 1,
1365        "factorsAndTargets": [],
1366        "numSecondaryStocks": 10,
1367        "positionSizeBounds": "",
1368        "turnoverImportance": 1,
1369        "secondaryBasketType": "hedge",
1370        "hedgePortfolioPicker": [],
1371        "portfolioPointInTime": false
1372      },
1373      "bounds_method": null,
1374      "combineMethod": "overlay",
1375      "explain_model": {
1376        "active": false,
1377        "method": "winsorize",
1378        "threshold": 3
1379      },
1380      "factorFilters": [],
1381      "indexMatching": {
1382        "active": false,
1383        "numStocksToIndex": 1,
1384        "highestRankAllowed": 3
1385      },
1386      "marketCapFlex": 100,
1387      "percentToLong": 20,
1388      "priceCleaning": true,
1389      "rebalanceDays": [
1390        true,
1391        true,
1392        true,
1393        true,
1394        true
1395      ],
1396      "regionNeutral": false,
1397      "sectorNeutral": false,
1398      "sectorWeights": {},
1399      "smoothPeriods": 1,
1400      "stopGainLevel": 10,
1401      "stopLossLevel": 10,
1402      "stopLossReset": false,
1403      "trackingError": false,
1404      "triggerEvents": {
1405        "active": false
1406      },
1407      "weightingType": "equal_weight",
1408      "executionDelay": 0,
1409      "insertUniverse": null,
1410      "lower_turnover": false,
1411      "mlModelFactors": {
1412        "max": 10,
1413        "active": false
1414      },
1415      "percentToShort": 5,
1416      "stopGainAmount": 50,
1417      "stopLossAmount": 50,
1418      "backtestEndDate": "2050-01-01",
1419      "benchmarkAdjust": 2,
1420      "coreModelWeight": 33.34,
1421      "longBufferLimit": 50,
1422      "marketCapBounds": 1,
1423      "maxLongPosition": 10,
1424      "maxNumStockLong": 100,
1425      "minLongPosition": 0,
1426      "minNumStockLong": 20,
1427      "rebalancePeriod": "weekly_monday",
1428      "smoothWeighting": "alpha_weight",
1429      "taxOptimization": {
1430        "active": false,
1431        "num_days": 30,
1432        "taxThreshold": 20,
1433        "useSectorETFs": false,
1434        "readjustToCore": false
1435      },
1436      "tradingSettings": {
1437        "borrowRate": 0.5,
1438        "useBorrowRateData": true,
1439        "forceRebalanceDates": []
1440      },
1441      "factorTradeRules": [],
1442      "heuristicTrading": false,
1443      "holidayTolerance": 0,
1444      "includeDividends": true,
1445      "isDirectIndexing": false,
1446      "macroTradingLong": {
1447        "active": false,
1448        "daysAbove": 5,
1449        "movingAverageDays": 50,
1450        "portfolioReduction": 30
1451      },
1452      "maxNumStockShort": 200,
1453      "maxSectorWeights": {},
1454      "maxShortPosition": 1,
1455      "maximumOwnership": 100,
1456      "minNumStockShort": 20,
1457      "minShortPosition": 0,
1458      "missingMarketCap": 10,
1459      "optimizeTurnover": false,
1460      "percentileFilter": false,
1461      "shortBufferLimit": 50,
1462      "stopLossGainLong": {
1463        "stopGain": false,
1464        "stopLoss": false,
1465        "stopGainLevel": 10,
1466        "stopLossLevel": 10,
1467        "stopGainAmount": 50,
1468        "stopLossAmount": 50
1469      },
1470      "useHoldingPeriod": false,
1471      "backtestStartDate": "2008-01-01",
1472      "benchmarkOperator": "addition",
1473      "benchmarkSettings": {
1474        "convertSignalsToMarketCap": false
1475      },
1476      "combinePortfolios": [],
1477      "factorConstraints": [],
1478      "investmentHorizon": 21,
1479      "longTermTimeFrame": 63,
1480      "macroTradingShort": {
1481        "active": false,
1482        "daysAbove": 5,
1483        "movingAverageDays": 50,
1484        "portfolioReduction": 30
1485      },
1486      "minimumSharePrice": 2,
1487      "proportionateRisk": false,
1488      "recalcRequestTime": "2024-04-02T19:14:59.145Z",
1489      "selectedBenchmark": 10076,
1490      "stopLossGainShort": {
1491        "stopGain": false,
1492        "stopLoss": false,
1493        "stopGainLevel": 10,
1494        "stopLossLevel": 10,
1495        "stopGainAmount": 50,
1496        "stopLossAmount": 50
1497      },
1498      "turnoverBuffering": false,
1499      "benchmarkPortfolio": false,
1500      "factorSegmentation": {
1501        "active": false,
1502        "factorSlices": 10,
1503        "targetFactor": "size"
1504      },
1505      "ignore_constraints": false,
1506      "longTermImportance": 0,
1507      "reduceMinorTrading": {
1508        "min": 1,
1509        "active": false
1510      },
1511      "shortTermTimeFrame": 5,
1512      "signalsByMarketCap": {
1513        "active": false
1514      },
1515      "trackingConstraint": 10,
1516      "turnoverImportance": 5,
1517      "benchmarkAllocation": 0,
1518      "defineRegionWeights": {
1519        "active": false,
1520        "default": null,
1521        "weights": {}
1522      },
1523      "defineSectorWeights": false,
1524      "longBufferNumStocks": 5,
1525      "marketCapConstraint": false,
1526      "mcapWeightBenchmark": false,
1527      "mediumTermTimeFrame": 21,
1528      "regionNeutralSpread": 2,
1529      "sectorNeutralSpread": 3,
1530      "shortTermImportance": 0,
1531      "signalOptimizerType": "max_alpha",
1532      "signalSectorNeutral": false,
1533      "customRebalanceDates": [],
1534      "equalWeightBenchmark": true,
1535      "insertUniverseToggle": false,
1536      "maxStockForPortfolio": 200,
1537      "mediumTermImportance": 50,
1538      "minStockForPortfolio": 5,
1539      "percentPortfolioLong": 100,
1540      "reportingStockFilter": {
1541        "days": 10,
1542        "active": false
1543      },
1544      "secondaryConstraints": [],
1545      "shortBufferNumStocks": 5,
1546      "originalSignalWeights": false,
1547      "percentPortfolioShort": 0,
1548      "signalIndustryNeutral": false,
1549      "allowCompanyCollisions": true,
1550      "defineMaxSectorWeights": false,
1551      "dividendYieldOptimizer": false,
1552      "factorNeutralizeSignal": false,
1553      "portfolioStartingValue": 100,
1554      "trackingErrorOptimizer": false,
1555      "signalFactorConstraints": [
1556        {
1557          "lb": -0.25,
1558          "ub": 0.25,
1559          "factor": "machine_1"
1560        },
1561        {
1562          "lb": -0.25,
1563          "ub": 0.25,
1564          "factor": "machine_2"
1565        },
1566        {
1567          "lb": -0.25,
1568          "ub": 0.25,
1569          "factor": "machine_3"
1570        },
1571        {
1572          "lb": -0.25,
1573          "ub": 0.25,
1574          "factor": "machine_4"
1575        },
1576        {
1577          "lb": -0.25,
1578          "ub": 0.25,
1579          "factor": "machine_5"
1580        },
1581        {
1582          "lb": -0.25,
1583          "ub": 0.25,
1584          "factor": "machine_6"
1585        },
1586        {
1587          "lb": -0.25,
1588          "ub": 0.25,
1589          "factor": "machine_7"
1590        },
1591        {
1592          "lb": -0.25,
1593          "ub": 0.25,
1594          "factor": "machine_8"
1595        },
1596        {
1597          "lb": -0.25,
1598          "ub": 0.25,
1599          "factor": "machine_9"
1600        },
1601        {
1602          "lb": -0.25,
1603          "ub": 0.25,
1604          "factor": "machine_10"
1605        }
1606      ],
1607      "signalProportionateRisk": false,
1608      "topBottomPercentToTrade": 20,
1609      "useCustomRebalanceDates": false,
1610      "nameBasedSectorWeighting": false,
1611      "percentileFilterBySector": false,
1612      "tradeByPercentOfRankings": false,
1613      "signalSectorNeutralSpread": 1,
1614      "pairsTradingMinCorrelation": 0.5,
1615      "removeBlacklistFromRankings": false,
1616      "stopLossGainSameDayExecution": true,
1617      "excess_return_relative_to_beta": true,
1618      "sectorNeutralEqualWeightBenchmark": false,
1619      "industryNeutralEqualWeightBenchmark": false
1620    }"""
1621    )
1622
1623    def __init__(self, settings: Optional[Dict] = None):
1624        # TODO: i'd rather have kwargs for the different keys and then allow
1625        # for per-key overrides, but we start with this more conservative approach
1626        if settings is None:
1627            self.settings = copy.deepcopy(PortfolioSettings.__default_settings)
1628        else:
1629            self.settings = settings
1630
1631    def toDict(self):
1632        return self.settings
1633
1634    def __str__(self):
1635        return json.dumps(self.settings)
1636
1637    def __repr__(self):
1638        return self.__str__()
1639
1640    def fromDict(settings):
1641        return PortfolioSettings(settings)
1642
1643
1644hedge_experiment_type = Literal["HEDGE", "MIMIC"]
1645
1646
1647@dataclass(frozen=True)  # from _python client's_ perspective, instances will never change!
1648class HedgeExperiment:
1649    id: str  # really a uuid, which we represent as a string
1650    name: str
1651    user_id: str  # see id comment
1652    description: str
1653    experiment_type: hedge_experiment_type  # TODO: enum worth it?
1654    last_calculated: dt.datetime
1655    last_modified: dt.datetime
1656    status: str  # TODO: enum worth it?
1657    portfolio_calculation_status: str  # TODO: enum worth it?
1658    selected_models: List[str]  # see id comment
1659    target_securities: Dict[GbiIdSecurity, float]  # Security is a hashable type because frozen=True
1660    target_portfolios: List[str]
1661    selected_stock_universe_ids: List[str]  # see id comment
1662    baseline_model: Optional[str] = None
1663    baseline_stock_universe_id: Optional[str] = None
1664    baseline_scenario: Optional["HedgeExperimentScenario"] = None
1665
1666    @property
1667    def config(self) -> Dict:
1668        """
1669        Returns a hedge experiment configuration dictionary, ready for JSON-serialization and
1670        submission.
1671        """
1672        weights_by_gbiid = [
1673            {"gbiId": sec.id, "weight": weight} for sec, weight in self.target_securities.items()
1674        ]
1675        return {
1676            "experimentType": self.experiment_type,
1677            "selectedModels": self.selected_models,
1678            "targetSecurities": weights_by_gbiid,
1679            "selectedStockUniverseIds": self._selected_stock_universe_ids,
1680        }
1681
1682    @classmethod
1683    def from_json_dict(cls, d: Dict) -> "HedgeExperiment":
1684        # drafts arent fully populated
1685        if d["status"] == "DRAFT":
1686            weights_by_security = {}
1687            selected_models = []
1688            selected_stock_universe_ids = []
1689        else:
1690            weights_by_security = {
1691                GbiIdSecurity(
1692                    sec_dict["gbiId"],
1693                    None,
1694                    sec_dict["security"]["symbol"],
1695                    sec_dict["security"]["name"],
1696                ): sec_dict["weight"]
1697                for sec_dict in d["targetSecurities"]
1698            }
1699            experiment_config = json.loads(d["config"])
1700            selected_models = experiment_config["selectedModels"]
1701            selected_stock_universe_ids = experiment_config["selectedStockUniverseIds"]
1702        baseline_scenario = None
1703        if d["baselineScenario"] is not None:
1704            baseline_scenario = HedgeExperimentScenario.from_json_dict(d["baselineScenario"])
1705        return cls(
1706            d["hedgeExperimentId"],
1707            d["experimentName"],
1708            d["userId"],
1709            d["description"],
1710            d["experimentType"],
1711            d["lastCalculated"],
1712            d["lastModified"],
1713            d["status"],
1714            d["portfolioCalcStatus"],
1715            selected_models,
1716            weights_by_security,
1717            d.get("targetPortfolios"),
1718            selected_stock_universe_ids or [],
1719            d.get("baselineModel"),
1720            d.get("baselineStockUniverseId"),
1721            baseline_scenario,
1722        )
1723
1724
1725@dataclass(frozen=True)
1726class HedgeExperimentScenario:
1727    id: str  # really a uuid, which we represent as a string;
1728    name: str
1729    description: str
1730    status: str  # TODO: enum worth it?
1731    settings: PortfolioSettings
1732    summary: pd.DataFrame
1733    # we currently a portfolios field. they get wrapped up into the summary table
1734
1735    @classmethod
1736    def from_json_dict(cls, d: Dict) -> "HedgeExperimentScenario":
1737        return cls(
1738            d["hedgeExperimentScenarioId"],
1739            d["scenarioName"],
1740            d["description"],
1741            d.get("status"),
1742            PortfolioSettings(json.loads(d["portfolioSettingsJson"])),
1743            HedgeExperimentScenario._build_portfolio_summary(d.get("hedgeExperimentPortfolios")),
1744        )
1745
1746    @staticmethod
1747    def _build_portfolio_summary(portfolios: Dict) -> pd.DataFrame:
1748        # this happens for added scenarios that havent run yet
1749        if portfolios is None:
1750            return pd.DataFrame()
1751
1752        df_dict = defaultdict(list)
1753        for pf in portfolios:
1754            p = pf["portfolio"]
1755
1756            df_dict["portfolio_id"].append(p["id"])
1757            df_dict["scenario_name"].append(p["name"])
1758            df_dict["model_id"].append(p["modelId"])
1759            df_dict["status"].append(p["status"])
1760
1761            if p["status"] != "READY":  # data isn't yet available
1762                df_dict["1M"].append(np.nan)
1763                df_dict["3M"].append(np.nan)
1764                df_dict["1Y"].append(np.nan)
1765                df_dict["sharpe"].append(np.nan)
1766                df_dict["vol"].append(np.nan)
1767            else:
1768                # NOTE:
1769                # these are the magic indices/keys of these values; inspect passed arg to see
1770                # if upstream changes the order of (sub-)elements in the response...uh oh
1771                # TODO: specific key search? wrap with try/except and fail loudly?
1772                df_dict["1M"].append(p["performanceGrid"][0][4])
1773                df_dict["3M"].append(p["performanceGrid"][0][5])
1774                df_dict["1Y"].append(p["performanceGrid"][0][7])
1775                df_dict["sharpe"].append(p["tearSheet"][0]["members"][1]["value"])
1776                pf_stddev = p["tearSheet"][1]["members"][1]["value"]
1777                bnchmk_stddev = p["tearSheet"][1]["members"][2]["value"]
1778                df_dict["vol"].append((pf_stddev - bnchmk_stddev) * 100)
1779                # TODO: this is how it is done on the front-end,
1780                # should be pulled out of both here and there
1781
1782        df = pd.DataFrame(df_dict)
1783        return df
1784
1785
1786@dataclass(frozen=True)  # from _python client's_ perspective, instances will never change!
1787class HedgeExperimentDetails:
1788    # this will probably lead to violations of the law of demeter, but it's easy to do right now,
1789    # particularly for an unproven API
1790    experiment: HedgeExperiment
1791    scenarios: Dict[str, HedgeExperimentScenario]
1792
1793    @property
1794    def summary(self) -> pd.DataFrame:
1795        """
1796        Returns a dataframe corresponding to the Hedge Experiment "flat view" - all portfolio
1797        options are displayed
1798        """
1799        if len(self.scenarios) == 0 or all(s.status != "READY" for s in self.scenarios.values()):
1800            return pd.DataFrame()
1801        return pd.concat(scenario.summary for scenario in self.scenarios.values()).sort_values(
1802            ["scenario_name"]
1803        )  # sort for stable presentation
1804
1805    @classmethod
1806    def from_json_dict(cls, d: Dict) -> "HedgeExperimentDetails":
1807        he = HedgeExperiment.from_json_dict(d)
1808        scenarios = {
1809            scenario_dict["hedgeExperimentScenarioId"]: HedgeExperimentScenario.from_json_dict(
1810                scenario_dict
1811            )
1812            for scenario_dict in d["hedgeExperimentScenarios"]
1813        }
1814        return cls(he, scenarios)
logger = <Logger boosted.api.api_type (WARNING)>
BoostedDate = typing.Union[datetime.date, str]
class Status(enum.Enum):
23class Status(Enum):
24    UNKNOWN = 0
25    FAIL = 1
26    SUCCESS = 2

An enumeration.

UNKNOWN = <Status.UNKNOWN: 0>
FAIL = <Status.FAIL: 1>
SUCCESS = <Status.SUCCESS: 2>
Inherited Members
enum.Enum
name
value
class DataAddType(enum.Enum):
29class DataAddType(Enum):
30    CREATION = 1
31    HISTORICAL = 2
32    LIVE = 3
33
34    def __str__(self):
35        if self.name == "CREATION":
36            return "CREATION"
37        elif self.name == "HISTORICAL":
38            return "HISTORICAL"
39        elif self.name == "LIVE":
40            return "LIVE"

An enumeration.

CREATION = <DataAddType.CREATION: 1>
HISTORICAL = <DataAddType.HISTORICAL: 2>
LIVE = <DataAddType.LIVE: 3>
Inherited Members
enum.Enum
name
value
class ChunkStatus(enum.Enum):
43class ChunkStatus(Enum):
44    PROCESSING = "PROCESSING"
45    ABORTED = "ABORTED"
46    SUCCESS = "SUCCESS"
47    WARNING = "WARNING"
48    ERROR = "ERROR"

An enumeration.

PROCESSING = <ChunkStatus.PROCESSING: 'PROCESSING'>
ABORTED = <ChunkStatus.ABORTED: 'ABORTED'>
SUCCESS = <ChunkStatus.SUCCESS: 'SUCCESS'>
WARNING = <ChunkStatus.WARNING: 'WARNING'>
ERROR = <ChunkStatus.ERROR: 'ERROR'>
Inherited Members
enum.Enum
name
value
class DataSetType(enum.Enum):
51class DataSetType(Enum):
52    UNKNOWN = 0
53    STOCK = 1
54    GLOBAL = 2
55    STRATEGY = 3
56    SECURITIES_DAILY = 4
57
58    def __str__(self):
59        if self.name == "STOCK":
60            return "STOCK"
61        elif self.name == "GLOBAL":
62            return "GLOBAL"
63        elif self.name == "STRATEGY":
64            return "STRATEGY"
65        elif self.name == "SECURITIES_DAILY":
66            return "SECURITIES_DAILY"
67        else:
68            return "UNKNOWN"

An enumeration.

UNKNOWN = <DataSetType.UNKNOWN: 0>
STOCK = <DataSetType.STOCK: 1>
GLOBAL = <DataSetType.GLOBAL: 2>
STRATEGY = <DataSetType.STRATEGY: 3>
SECURITIES_DAILY = <DataSetType.SECURITIES_DAILY: 4>
Inherited Members
enum.Enum
name
value
class DataSetSubType(enum.Enum):
71class DataSetSubType(Enum):
72    UNKNOWN = 0
73    DENSE = 1
74    SPARSE_HIST = 2
75    SPARSE_FWD = 3
76
77    def __str__(self):
78        if self.name == "DENSE":
79            return "DENSE"
80        elif self.name == "SPARSE_HIST":
81            return "SPARSE_HIST"
82        elif self.name == "SPARSE_FWD":
83            return "SPARSE_FWD"
84        else:
85            return "UNKNOWN"

An enumeration.

UNKNOWN = <DataSetSubType.UNKNOWN: 0>
DENSE = <DataSetSubType.DENSE: 1>
SPARSE_HIST = <DataSetSubType.SPARSE_HIST: 2>
SPARSE_FWD = <DataSetSubType.SPARSE_FWD: 3>
Inherited Members
enum.Enum
name
value
class DataSetFrequency(enum.Enum):
 88class DataSetFrequency(Enum):
 89    UNKNOWN = 0
 90    DAILY = 1
 91    WEEKLY = 2
 92    MONTHLY = 3
 93    QUARTERLY = 4
 94    SEMIANNUAL = 5
 95    ANNUAL = 6
 96
 97    def __str__(self):
 98        if self.name == "DAILY":
 99            return "DAILY"
100        elif self.name == "WEEKLY":
101            return "WEEKLY"
102        elif self.name == "MONTHLY":
103            return "MONTHLY"
104        elif self.name == "QUARTERLY":
105            return "QUARTERLY"
106        elif self.name == "SEMIANNUAL":
107            return "SEMIANNUAL"
108        elif self.name == "ANNUAL":
109            return "ANNUAL"
110        else:
111            return "UNKNOWN"

An enumeration.

UNKNOWN = <DataSetFrequency.UNKNOWN: 0>
DAILY = <DataSetFrequency.DAILY: 1>
WEEKLY = <DataSetFrequency.WEEKLY: 2>
MONTHLY = <DataSetFrequency.MONTHLY: 3>
QUARTERLY = <DataSetFrequency.QUARTERLY: 4>
SEMIANNUAL = <DataSetFrequency.SEMIANNUAL: 5>
ANNUAL = <DataSetFrequency.ANNUAL: 6>
Inherited Members
enum.Enum
name
value
class ThemeUniverse(enum.Enum):
114class ThemeUniverse(Enum):
115    XIC = "XIC Membership (TSX Composite)"  # bd4163fd-ad77-4412-a250-a2c9e0c13802
116    SPY = "SPY Membership (S&P 500)"  # 4e5f2fd3-394e-4db9-aad3-5c20abf9bf3c
117    QQQ = "QQQ Membership (Nasdaq)"  # d532609b-620a-425b-b8f1-e94f51e0394d
118    IWM = "IWM Membership (Russell 2000)"  # 5ad0a5b3-d4e2-439e-af57-6ea5475c19e8
119    IWB = "IWB Membership (Russell 1000)"  # ed851cea-f19a-45b2-8948-f6cc3503d087
120    IWV = "IWV Membership (iShares Russell 3000 ETF)"  # c122c187-bb0d-4207-87a3-e06f0b877bc9
121    EXSA = "EXSA Membership (Stoxx 600)"  # e160fe15-038b-44ea-be12-1cc1054a26d8
122    ASHR = "ASHR Membership (CSI 300)"  # ee1f70fd-1010-4a90-b2fa-be633ad3d163
123
124    @classmethod
125    def get_ticker_from_name(cls, name: str) -> Optional[str]:
126        for key, value in cls.__members__.items():
127            if value.value == name:
128                return key
129
130        return None

An enumeration.

XIC = <ThemeUniverse.XIC: 'XIC Membership (TSX Composite)'>
SPY = <ThemeUniverse.SPY: 'SPY Membership (S&P 500)'>
QQQ = <ThemeUniverse.QQQ: 'QQQ Membership (Nasdaq)'>
IWM = <ThemeUniverse.IWM: 'IWM Membership (Russell 2000)'>
IWB = <ThemeUniverse.IWB: 'IWB Membership (Russell 1000)'>
IWV = <ThemeUniverse.IWV: 'IWV Membership (iShares Russell 3000 ETF)'>
EXSA = <ThemeUniverse.EXSA: 'EXSA Membership (Stoxx 600)'>
ASHR = <ThemeUniverse.ASHR: 'ASHR Membership (CSI 300)'>
@classmethod
def get_ticker_from_name(cls, name: str) -> Union[str, NoneType]:
124    @classmethod
125    def get_ticker_from_name(cls, name: str) -> Optional[str]:
126        for key, value in cls.__members__.items():
127            if value.value == name:
128                return key
129
130        return None
Inherited Members
enum.Enum
name
value
class Language(builtins.str, enum.Enum):
133class Language(str, Enum):
134    ENGLISH = "en"
135    CHINESE = "zh-CN"

An enumeration.

ENGLISH = <Language.ENGLISH: 'en'>
CHINESE = <Language.CHINESE: 'zh-CN'>
Inherited Members
enum.Enum
name
value
builtins.str
encode
replace
split
rsplit
join
capitalize
casefold
title
center
count
expandtabs
find
partition
index
ljust
lower
lstrip
rfind
rindex
rjust
rstrip
rpartition
splitlines
strip
swapcase
translate
upper
startswith
endswith
isascii
islower
isupper
istitle
isspace
isdecimal
isdigit
isnumeric
isalpha
isalnum
isidentifier
isprintable
zfill
format
format_map
maketrans
class NewsHorizon(enum.Enum):
138class NewsHorizon(Enum):
139    ONE_DAY = "1D"
140    ONE_WEEK = "1W"
141    ONE_MONTH = "1M"

An enumeration.

ONE_DAY = <NewsHorizon.ONE_DAY: '1D'>
ONE_WEEK = <NewsHorizon.ONE_WEEK: '1W'>
ONE_MONTH = <NewsHorizon.ONE_MONTH: '1M'>
Inherited Members
enum.Enum
name
value
class ColumnRole(enum.Enum):
144class ColumnRole(Enum):
145    UNKNOWN = 0
146    VARIABLE = 1
147    GOAL = 2
148    METRIC = 3
149    IDENTIFIER = 4
150
151    def __str__(self):
152        if self.name == "VARIABLE":
153            return "VARIABLE"
154        elif self.name == "GOAL":
155            return "GOAL"
156        elif self.name == "METRIC":
157            return "METRIC"
158        elif self.name == "IDENTIFIER":
159            return "IDENTIFIER"
160        else:
161            return "UNKNOWN"

An enumeration.

UNKNOWN = <ColumnRole.UNKNOWN: 0>
VARIABLE = <ColumnRole.VARIABLE: 1>
GOAL = <ColumnRole.GOAL: 2>
METRIC = <ColumnRole.METRIC: 3>
IDENTIFIER = <ColumnRole.IDENTIFIER: 4>
Inherited Members
enum.Enum
name
value
class ColumnSubRole(enum.Enum):
164class ColumnSubRole(Enum):
165    UNKNOWN = 0
166    GBI_ID = 1
167    ISIN = 2
168    GVKEY = 3
169    IID = 4
170    SYMBOL = 5
171    COUNTRY = 6
172    CURRENCY = 7
173    DATE = 8
174    COMPANY_NAME = 9
175    REPORT_DATE = 10
176    REPORT_PERIOD = 11
177    CUSTOM_SECURITY = 12
178
179    def __str__(self):
180        if self.name == "GBI_ID":
181            return "GBI_ID"
182        elif self.name == "ISIN":
183            return "ISIN"
184        elif self.name == "GVKEY":
185            return "GVKEY"
186        elif self.name == "IID":
187            return "IID"
188        elif self.name == "SYMBOL":
189            return "SYMBOL"
190        elif self.name == "COUNTRY":
191            return "COUNTRY"
192        elif self.name == "CURRENCY":
193            return "CURRENCY"
194        elif self.name == "DATE":
195            return "DATE"
196        elif self.name == "COMPANY_NAME":
197            return "COMPANY_NAME"
198        elif self.name == "REPORT_DATE":
199            return "REPORT_DATE"
200        elif self.name == "REPORT_PERIOD":
201            return "REPORT_PERIOD"
202        elif self.name == "CUSTOM_SECURITY":
203            return "CUSTOM_SECURITY"
204        else:
205            return "UNKNOWN"
206
207    def get_match(column_name):
208        def clean_str(name):
209            translation = str.maketrans("", "", string.punctuation)
210            clean_name = name.strip().translate(translation).lower()
211            return re.sub(r"\s+", "", clean_name)
212
213        identifiers = [
214            ColumnSubRole.GBI_ID,
215            ColumnSubRole.ISIN,
216            ColumnSubRole.GVKEY,
217            ColumnSubRole.IID,
218            ColumnSubRole.SYMBOL,
219            ColumnSubRole.COUNTRY,
220            ColumnSubRole.CURRENCY,
221            ColumnSubRole.COMPANY_NAME,
222            ColumnSubRole.REPORT_DATE,
223            ColumnSubRole.REPORT_PERIOD,
224            ColumnSubRole.CUSTOM_SECURITY,
225        ]
226
227        for identifier in identifiers:
228            if clean_str(str(identifier)) == clean_str(column_name):
229                return identifier
230        return None

An enumeration.

UNKNOWN = <ColumnSubRole.UNKNOWN: 0>
GBI_ID = <ColumnSubRole.GBI_ID: 1>
ISIN = <ColumnSubRole.ISIN: 2>
GVKEY = <ColumnSubRole.GVKEY: 3>
IID = <ColumnSubRole.IID: 4>
SYMBOL = <ColumnSubRole.SYMBOL: 5>
COUNTRY = <ColumnSubRole.COUNTRY: 6>
CURRENCY = <ColumnSubRole.CURRENCY: 7>
DATE = <ColumnSubRole.DATE: 8>
COMPANY_NAME = <ColumnSubRole.COMPANY_NAME: 9>
REPORT_DATE = <ColumnSubRole.REPORT_DATE: 10>
REPORT_PERIOD = <ColumnSubRole.REPORT_PERIOD: 11>
CUSTOM_SECURITY = <ColumnSubRole.CUSTOM_SECURITY: 12>
def get_match(column_name):
207    def get_match(column_name):
208        def clean_str(name):
209            translation = str.maketrans("", "", string.punctuation)
210            clean_name = name.strip().translate(translation).lower()
211            return re.sub(r"\s+", "", clean_name)
212
213        identifiers = [
214            ColumnSubRole.GBI_ID,
215            ColumnSubRole.ISIN,
216            ColumnSubRole.GVKEY,
217            ColumnSubRole.IID,
218            ColumnSubRole.SYMBOL,
219            ColumnSubRole.COUNTRY,
220            ColumnSubRole.CURRENCY,
221            ColumnSubRole.COMPANY_NAME,
222            ColumnSubRole.REPORT_DATE,
223            ColumnSubRole.REPORT_PERIOD,
224            ColumnSubRole.CUSTOM_SECURITY,
225        ]
226
227        for identifier in identifiers:
228            if clean_str(str(identifier)) == clean_str(column_name):
229                return identifier
230        return None
Inherited Members
enum.Enum
name
value
class ColumnValueType(enum.Enum):
233class ColumnValueType(Enum):
234    UNKNOWN = 0
235    NUMBER = 1
236    STRING = 2
237
238    def __str__(self):
239        if self.name == "UNKNOWN":
240            return "UNKNOWN"
241        elif self.name == "NUMBER":
242            return "NUMBER"
243        elif self.name == "STRING":
244            return "STRING"
245        else:
246            return "UNKNOWN"

An enumeration.

UNKNOWN = <ColumnValueType.UNKNOWN: 0>
NUMBER = <ColumnValueType.NUMBER: 1>
STRING = <ColumnValueType.STRING: 2>
Inherited Members
enum.Enum
name
value
class CustomNamespaceVariableRole(enum.Enum):
249class CustomNamespaceVariableRole(Enum):
250    UNKNOWN = "UNKNOWN"
251    DAILY_OPEN = "DAILY_OPEN"
252    DAILY_CLOSE = "DAILY_CLOSE"  # mandatory
253    DAILY_VWAP = "DAILY_VWAP"
254    DAILY_VOLUME = "DAILY_VOLUME"
255    DAILY_HIGH = "DAILY_HIGH"
256    DAILY_LOW = "DAILY_LOW"
257    DAILY_BID = "DAILY_BID"
258    DAILY_ASK = "DAILY_ASK"
259    DAILY_MCAP = "DAILY_MCAP"
260
261    def get_match(column_name):
262        def clean_str(name):
263            translation = str.maketrans("", "", string.punctuation)
264            clean_name = name.strip().translate(translation).lower()
265            return re.sub(r"\s+", "", clean_name)
266
267        identifiers = [
268            CustomNamespaceVariableRole.DAILY_OPEN,
269            CustomNamespaceVariableRole.DAILY_CLOSE,
270            CustomNamespaceVariableRole.DAILY_VWAP,
271            CustomNamespaceVariableRole.DAILY_VOLUME,
272            CustomNamespaceVariableRole.DAILY_HIGH,
273            CustomNamespaceVariableRole.DAILY_LOW,
274            CustomNamespaceVariableRole.DAILY_BID,
275            CustomNamespaceVariableRole.DAILY_ASK,
276            CustomNamespaceVariableRole.DAILY_MCAP,
277        ]
278
279        for identifier in identifiers:
280            if clean_str(str(identifier)) == clean_str(column_name):
281                return identifier
282        return None
283
284    def __str__(self):
285        return self.value

An enumeration.

UNKNOWN = <CustomNamespaceVariableRole.UNKNOWN: 'UNKNOWN'>
DAILY_OPEN = <CustomNamespaceVariableRole.DAILY_OPEN: 'DAILY_OPEN'>
DAILY_CLOSE = <CustomNamespaceVariableRole.DAILY_CLOSE: 'DAILY_CLOSE'>
DAILY_VWAP = <CustomNamespaceVariableRole.DAILY_VWAP: 'DAILY_VWAP'>
DAILY_VOLUME = <CustomNamespaceVariableRole.DAILY_VOLUME: 'DAILY_VOLUME'>
DAILY_HIGH = <CustomNamespaceVariableRole.DAILY_HIGH: 'DAILY_HIGH'>
DAILY_LOW = <CustomNamespaceVariableRole.DAILY_LOW: 'DAILY_LOW'>
DAILY_BID = <CustomNamespaceVariableRole.DAILY_BID: 'DAILY_BID'>
DAILY_ASK = <CustomNamespaceVariableRole.DAILY_ASK: 'DAILY_ASK'>
DAILY_MCAP = <CustomNamespaceVariableRole.DAILY_MCAP: 'DAILY_MCAP'>
def get_match(column_name):
261    def get_match(column_name):
262        def clean_str(name):
263            translation = str.maketrans("", "", string.punctuation)
264            clean_name = name.strip().translate(translation).lower()
265            return re.sub(r"\s+", "", clean_name)
266
267        identifiers = [
268            CustomNamespaceVariableRole.DAILY_OPEN,
269            CustomNamespaceVariableRole.DAILY_CLOSE,
270            CustomNamespaceVariableRole.DAILY_VWAP,
271            CustomNamespaceVariableRole.DAILY_VOLUME,
272            CustomNamespaceVariableRole.DAILY_HIGH,
273            CustomNamespaceVariableRole.DAILY_LOW,
274            CustomNamespaceVariableRole.DAILY_BID,
275            CustomNamespaceVariableRole.DAILY_ASK,
276            CustomNamespaceVariableRole.DAILY_MCAP,
277        ]
278
279        for identifier in identifiers:
280            if clean_str(str(identifier)) == clean_str(column_name):
281                return identifier
282        return None
Inherited Members
enum.Enum
name
value
class ColumnConfig:
288class ColumnConfig:
289    def __init__(
290        self,
291        name=None,
292        role=None,
293        sub_role=None,
294        value_type=ColumnValueType.NUMBER,
295        description="",
296        investment_horizon=None,
297        custom_namespace_variable_role=None,
298    ):
299        self.name = name
300        self.role = role
301        self.sub_role = sub_role
302        self.value_type = value_type
303        # More value_types will be supported in the future.
304        self.description = description
305        self.investment_horizon = investment_horizon
306        self.custom_namespace_variable_role = custom_namespace_variable_role
307
308    def __str__(self):
309        return (
310            'Name: "{0}", Role: {1}, SubRole: {2}, Value type: {3}, '
311            + "Desc: {4} IH: {5} CNSRole: {6}."
312        ).format(
313            self.name,
314            self.role,
315            self.sub_role,
316            self.value_type,
317            self.description,
318            self.investment_horizon,
319            self.custom_namespace_variable_role,
320        )
321
322    def __repr__(self):
323        return self.__str__()
ColumnConfig( name=None, role=None, sub_role=None, value_type=<ColumnValueType.NUMBER: 1>, description='', investment_horizon=None, custom_namespace_variable_role=None)
289    def __init__(
290        self,
291        name=None,
292        role=None,
293        sub_role=None,
294        value_type=ColumnValueType.NUMBER,
295        description="",
296        investment_horizon=None,
297        custom_namespace_variable_role=None,
298    ):
299        self.name = name
300        self.role = role
301        self.sub_role = sub_role
302        self.value_type = value_type
303        # More value_types will be supported in the future.
304        self.description = description
305        self.investment_horizon = investment_horizon
306        self.custom_namespace_variable_role = custom_namespace_variable_role
name
role
sub_role
value_type
description
investment_horizon
custom_namespace_variable_role
class StrategyConfig:
326class StrategyConfig:
327    def __init__(self, name=None, source_name=None):
328        self.name = name
329        self.source_name = source_name
330
331    def __str__(self):
332        return 'Name: "{0}", Source Name: {1}.'.format(self.name, self.source_name)
333
334    def __repr__(self):
335        return self.__str__()
StrategyConfig(name=None, source_name=None)
327    def __init__(self, name=None, source_name=None):
328        self.name = name
329        self.source_name = source_name
name
source_name
class BoostedAPIException(builtins.Exception):
338class BoostedAPIException(Exception):
339    def __init__(self, value, data=None):
340        self.value = value
341        self.data = data
342
343    def __str__(self):
344        return repr(self.value)

Common base class for all non-exit exceptions.

BoostedAPIException(value, data=None)
339    def __init__(self, value, data=None):
340        self.value = value
341        self.data = data
value
data
Inherited Members
builtins.BaseException
with_traceback
args
class BoostedDataSetSchemaException(builtins.Exception):
347class BoostedDataSetSchemaException(Exception):
348    def __init__(self, value, data=None):
349        self.value = value
350        self.data = data
351
352    def __str__(self):
353        return repr(self.value)

Common base class for all non-exit exceptions.

BoostedDataSetSchemaException(value, data=None)
348    def __init__(self, value, data=None):
349        self.value = value
350        self.data = data
value
data
Inherited Members
builtins.BaseException
with_traceback
args
class DataSetConfig:
356class DataSetConfig:
357    def __init__(
358        self,
359        name,
360        datasetType=DataSetType.STOCK,
361        datasetSubType=DataSetSubType.DENSE,
362        datasetFrequency=DataSetFrequency.DAILY,
363    ):
364        self.name = name
365        self.type = datasetType
366        self.subtype = datasetSubType
367        self.frequency = datasetFrequency
368        self.columns = []
369        self.strategies = []
370
371    def addColumn(self, columnConfig):
372        self.columns.append(columnConfig)
373
374    def addStrategy(self, strategyConfig):
375        self.strategies.append(strategyConfig)
376
377    def __getColumnByName(self, col_name):
378        for column in self.columns:
379            if col_name == column.name:
380                return column
381        raise BoostedDataSetSchemaException(f"Unable to find column {col_name}")
382
383    def updateColumnToGoal(self, col_name, investment_horizon):
384        column = self.__getColumnByName(col_name)
385        column.role = ColumnRole.GOAL
386        column.investment_horizon = int(investment_horizon)
387
388    def updateColumnToMetric(self, col_name):
389        column = self.__getColumnByName(col_name)
390        column.role = ColumnRole.METRIC
391
392    def updateColumnToVariable(self, col_name):
393        column = self.__getColumnByName(col_name)
394        column.role = ColumnRole.VARIABLE
395
396    def __validateSchema(self):
397        num_goals = 0
398        num_metrics = 0
399        dt = self.type
400        dst = self.subtype
401        dsf = self.frequency
402        if len(self.columns) == 0:
403            msg = "No feature columns exist."
404            raise BoostedDataSetSchemaException(msg)
405
406        if dst in [DataSetSubType.SPARSE_HIST, DataSetSubType.SPARSE_FWD]:
407            if dsf not in [DataSetFrequency.QUARTERLY]:
408                msg = f"{dsf} frequency is not supported for {dst} sub data"
409                raise BoostedDataSetSchemaException(msg)
410            if dt not in [DataSetType.STOCK]:
411                msg = f"{dst} subtype is not supported for {dt} data"
412                raise BoostedDataSetSchemaException(msg)
413
414        for column in self.columns:
415            if column.role == ColumnRole.GOAL:
416                ih = column.investment_horizon
417                gn = column.name
418                if dt == DataSetType.GLOBAL:
419                    msg = f"{dt} data can not have {ColumnRole.GOAL} type"
420                    raise BoostedDataSetSchemaException(msg)
421                if not isinstance(ih, int):
422                    msg = f"Investment horizon for {gn} must be an integer"
423                    raise BoostedDataSetSchemaException(msg)
424                if ih < 1 or ih > 252:
425                    msg = f"Investment horizon must be between 1 and 252 for {gn}"
426                    raise BoostedDataSetSchemaException(msg)
427                num_goals += 1
428            elif column.role == ColumnRole.METRIC:
429                if dt in [DataSetType.GLOBAL, DataSetType.STOCK]:
430                    msg = f"{dt} data can not have {ColumnRole.METRIC} type"
431                    raise BoostedDataSetSchemaException(msg)
432                num_metrics += 1
433
434        if dt == DataSetType.STRATEGY:
435            if num_goals == 0:
436                msg = "Independent data requires at least one goal."
437                raise BoostedDataSetSchemaException(msg)
438            if num_metrics == 0:
439                msg = "Independent data requires at least one metric."
440                raise BoostedDataSetSchemaException(msg)
441            if len(self.strategies) <= 1:
442                msg = "Independent data requires more than 1 strategy"
443                raise BoostedDataSetSchemaException(msg)
444
445    def toDict(self):
446        self.__validateSchema()
447        config = {}
448        config["name"] = self.name
449        config["type"] = str(self.type)
450        config["subType"] = str(self.subtype)
451        config["frequency"] = str(self.frequency)
452        featureList = []
453        for i, f in enumerate(self.columns):
454            fm = {}
455            fm["name"] = f.name
456            fm["description"] = f.description
457            fm["type"] = str(f.role)
458            fm["role"] = str(f.role)
459            fm["subRole"] = str(f.sub_role) if f.sub_role is not None else None
460            fm["valuesType"] = str(f.value_type)
461            fm["columnName"] = f.name
462            fm["investmentHorizon"] = f.investment_horizon
463            fm["customNamespaceDatasetVariableRole"] = (
464                str(f.custom_namespace_variable_role)
465                if f.custom_namespace_variable_role is not None
466                else None
467            )
468            featureList.append(fm)
469        config["features"] = featureList
470        strategyList = []
471        for i, s in enumerate(self.strategies):
472            sm = {}
473            sm["name"] = s.name
474            sm["sourceName"] = s.source_name
475            strategyList.append(sm)
476        config["strategies"] = strategyList
477        return config
478
479    def __str__(self):
480        a = ""
481        a += "Name: {0}\n".format(self.name)
482        a += "Columns: \n"
483        for c in self.columns:
484            a += "  {0}\n".format(c)
485        return a
486
487    def __repr__(self):
488        return self.__str__()
489
490    def fromDict(schema):
491        subtype = DataSetSubType[schema.get("subtype", str(DataSetSubType.DENSE))]
492        frequency = DataSetFrequency[schema.get("frequency", str(DataSetFrequency.DAILY))]
493        config = DataSetConfig(
494            schema["name"],
495            datasetType=DataSetType[schema["type"]],
496            datasetSubType=subtype,
497            datasetFrequency=frequency,
498        )
499        # two things left to fill in - strategies and columns
500        for strategy_data in schema["strategies"]:
501            config.addStrategy(
502                StrategyConfig(name=strategy_data["name"], source_name=strategy_data["sourceName"])
503            )
504
505        for feature_data in schema["features"]:
506            config.addColumn(
507                ColumnConfig(
508                    name=feature_data["columnName"],
509                    description=feature_data["description"] or "",
510                    investment_horizon=feature_data["investmentHorizon"],
511                    value_type=(
512                        ColumnValueType[feature_data["valuesType"]]
513                        if feature_data["valuesType"] is not None
514                        else ColumnValueType.NUMBER
515                    ),
516                    role=(
517                        ColumnRole[feature_data["role"]]
518                        if feature_data["role"] is not None
519                        else None
520                    ),
521                    sub_role=(
522                        ColumnSubRole[feature_data["subRole"]]
523                        if feature_data["subRole"] is not None
524                        else None
525                    ),
526                    custom_namespace_variable_role=(
527                        CustomNamespaceVariableRole[
528                            feature_data["customNamespaceDatasetVariableRole"]
529                        ]
530                        if feature_data["customNamespaceDatasetVariableRole"] is not None
531                        else None
532                    ),
533                )
534            )
535
536        config.__validateSchema()
537        return config
DataSetConfig( name, datasetType=<DataSetType.STOCK: 1>, datasetSubType=<DataSetSubType.DENSE: 1>, datasetFrequency=<DataSetFrequency.DAILY: 1>)
357    def __init__(
358        self,
359        name,
360        datasetType=DataSetType.STOCK,
361        datasetSubType=DataSetSubType.DENSE,
362        datasetFrequency=DataSetFrequency.DAILY,
363    ):
364        self.name = name
365        self.type = datasetType
366        self.subtype = datasetSubType
367        self.frequency = datasetFrequency
368        self.columns = []
369        self.strategies = []
name
type
subtype
frequency
columns
strategies
def addColumn(self, columnConfig):
371    def addColumn(self, columnConfig):
372        self.columns.append(columnConfig)
def addStrategy(self, strategyConfig):
374    def addStrategy(self, strategyConfig):
375        self.strategies.append(strategyConfig)
def updateColumnToGoal(self, col_name, investment_horizon):
383    def updateColumnToGoal(self, col_name, investment_horizon):
384        column = self.__getColumnByName(col_name)
385        column.role = ColumnRole.GOAL
386        column.investment_horizon = int(investment_horizon)
def updateColumnToMetric(self, col_name):
388    def updateColumnToMetric(self, col_name):
389        column = self.__getColumnByName(col_name)
390        column.role = ColumnRole.METRIC
def updateColumnToVariable(self, col_name):
392    def updateColumnToVariable(self, col_name):
393        column = self.__getColumnByName(col_name)
394        column.role = ColumnRole.VARIABLE
def toDict(self):
445    def toDict(self):
446        self.__validateSchema()
447        config = {}
448        config["name"] = self.name
449        config["type"] = str(self.type)
450        config["subType"] = str(self.subtype)
451        config["frequency"] = str(self.frequency)
452        featureList = []
453        for i, f in enumerate(self.columns):
454            fm = {}
455            fm["name"] = f.name
456            fm["description"] = f.description
457            fm["type"] = str(f.role)
458            fm["role"] = str(f.role)
459            fm["subRole"] = str(f.sub_role) if f.sub_role is not None else None
460            fm["valuesType"] = str(f.value_type)
461            fm["columnName"] = f.name
462            fm["investmentHorizon"] = f.investment_horizon
463            fm["customNamespaceDatasetVariableRole"] = (
464                str(f.custom_namespace_variable_role)
465                if f.custom_namespace_variable_role is not None
466                else None
467            )
468            featureList.append(fm)
469        config["features"] = featureList
470        strategyList = []
471        for i, s in enumerate(self.strategies):
472            sm = {}
473            sm["name"] = s.name
474            sm["sourceName"] = s.source_name
475            strategyList.append(sm)
476        config["strategies"] = strategyList
477        return config
def fromDict(schema):
490    def fromDict(schema):
491        subtype = DataSetSubType[schema.get("subtype", str(DataSetSubType.DENSE))]
492        frequency = DataSetFrequency[schema.get("frequency", str(DataSetFrequency.DAILY))]
493        config = DataSetConfig(
494            schema["name"],
495            datasetType=DataSetType[schema["type"]],
496            datasetSubType=subtype,
497            datasetFrequency=frequency,
498        )
499        # two things left to fill in - strategies and columns
500        for strategy_data in schema["strategies"]:
501            config.addStrategy(
502                StrategyConfig(name=strategy_data["name"], source_name=strategy_data["sourceName"])
503            )
504
505        for feature_data in schema["features"]:
506            config.addColumn(
507                ColumnConfig(
508                    name=feature_data["columnName"],
509                    description=feature_data["description"] or "",
510                    investment_horizon=feature_data["investmentHorizon"],
511                    value_type=(
512                        ColumnValueType[feature_data["valuesType"]]
513                        if feature_data["valuesType"] is not None
514                        else ColumnValueType.NUMBER
515                    ),
516                    role=(
517                        ColumnRole[feature_data["role"]]
518                        if feature_data["role"] is not None
519                        else None
520                    ),
521                    sub_role=(
522                        ColumnSubRole[feature_data["subRole"]]
523                        if feature_data["subRole"] is not None
524                        else None
525                    ),
526                    custom_namespace_variable_role=(
527                        CustomNamespaceVariableRole[
528                            feature_data["customNamespaceDatasetVariableRole"]
529                        ]
530                        if feature_data["customNamespaceDatasetVariableRole"] is not None
531                        else None
532                    ),
533                )
534            )
535
536        config.__validateSchema()
537        return config
class GbiIdSecurityException(builtins.Exception):
540class GbiIdSecurityException(Exception):
541    def __init__(self, value, data=None):
542        self.value = value
543        self.data = data
544
545    def __str__(self):
546        return repr(self.value)

Common base class for all non-exit exceptions.

GbiIdSecurityException(value, data=None)
541    def __init__(self, value, data=None):
542        self.value = value
543        self.data = data
value
data
Inherited Members
builtins.BaseException
with_traceback
args
class GbiIdTickerISIN:
549class GbiIdTickerISIN:
550    def __init__(self, gbi_id: int, ticker: str, isin: str):
551        self.gbi_id = int(gbi_id)
552        self.ticker = ticker
553        self.isin = isin
554
555    def __str__(self):
556        return "(gbi_id: {0}, ticker: {1}, isin: {2})".format(self.gbi_id, self.ticker, self.isin)
557
558    def __repr__(self):
559        return self.__str__()
GbiIdTickerISIN(gbi_id: int, ticker: str, isin: str)
550    def __init__(self, gbi_id: int, ticker: str, isin: str):
551        self.gbi_id = int(gbi_id)
552        self.ticker = ticker
553        self.isin = isin
gbi_id
ticker
isin
class GbiIdSecurity:
562class GbiIdSecurity:
563    def __init__(self, gbi_id, isin_country_currency_date, ticker, company_name):
564        self.gbi_id = gbi_id
565        self.isin_info = isin_country_currency_date
566        self.ticker = ticker
567        self.company_name = company_name
568
569    def __str__(self):
570        return '\n(gbi_id: "{0}", isin_info: {1}, ticker: {2}, company_name: {3})'.format(
571            self.gbi_id, self.isin_info.__str__(), self.ticker, self.company_name
572        )
573
574    def __repr__(self):
575        return self.__str__()
576
577    def __eq__(self, __o: object) -> bool:
578        return self.gbi_id == __o.gbi_id
579
580    def __hash__(self):
581        return hash(self.gbi_id)
GbiIdSecurity(gbi_id, isin_country_currency_date, ticker, company_name)
563    def __init__(self, gbi_id, isin_country_currency_date, ticker, company_name):
564        self.gbi_id = gbi_id
565        self.isin_info = isin_country_currency_date
566        self.ticker = ticker
567        self.company_name = company_name
gbi_id
isin_info
ticker
company_name
class DateIdentCountryCurrencyException(builtins.Exception):
584class DateIdentCountryCurrencyException(Exception):
585    def __init__(self, value, data=None):
586        self.value = value
587        self.data = data
588
589    def __str__(self):
590        return repr(self.value)

Common base class for all non-exit exceptions.

DateIdentCountryCurrencyException(value, data=None)
585    def __init__(self, value, data=None):
586        self.value = value
587        self.data = data
value
data
Inherited Members
builtins.BaseException
with_traceback
args
class DateIsinCountryCurrencyException(DateIdentCountryCurrencyException):
594class DateIsinCountryCurrencyException(DateIdentCountryCurrencyException):
595    pass

Common base class for all non-exit exceptions.

Inherited Members
DateIdentCountryCurrencyException
DateIdentCountryCurrencyException
value
data
builtins.BaseException
with_traceback
args
class DateIdentCountryCurrency:
598class DateIdentCountryCurrency:
599    def __init__(
600        self,
601        date: str,
602        identifier: str,
603        country: Optional[str] = None,
604        currency: Optional[str] = None,
605        id_type: ColumnSubRole = ColumnSubRole.ISIN,
606    ):
607        self.date = date
608        self.identifier = identifier
609        self.id_type = id_type
610        self.country = country
611        self.currency = currency
612        self.__validateInput()
613
614    def __str__(self):
615        return '(date: "{0}", identifier: "{1}", country: {2}, currency: {3})'.format(
616            self.date, self.identifier, self.country, self.currency
617        )
618
619    def __repr__(self):
620        return self.__str__()
621
622    def __validateInput(self):
623        # simply ensure that at least isin and date are filled out.
624        # country/currency defaults to ANY if not filled out, but
625        # still is not recommended.
626        if self.identifier is None or self.date is None:
627            raise DateIdentCountryCurrencyException(
628                "ISIN or Date not provided while constructing DateIsinCountryCurrency"
629            )
630
631        if self.id_type not in (ColumnSubRole.ISIN, ColumnSubRole.SYMBOL):
632            raise DateIdentCountryCurrencyException(f"Invalid ID Type: {self.id_type}")
633
634        def check_is_str(value, fieldname):
635            if value is not None and not isinstance(value, str):
636                raise DateIdentCountryCurrencyException(
637                    f"Field {fieldname} in DateIsinCountryCurrency was not a string."
638                )
639
640        check_is_str(self.date, "date")
641        check_is_str(self.identifier, "identifier")
642        check_is_str(self.country, "country")
643        check_is_str(self.currency, "currency")
DateIdentCountryCurrency( date: str, identifier: str, country: Union[str, NoneType] = None, currency: Union[str, NoneType] = None, id_type: boosted.api.api_type.ColumnSubRole = <ColumnSubRole.ISIN: 2>)
599    def __init__(
600        self,
601        date: str,
602        identifier: str,
603        country: Optional[str] = None,
604        currency: Optional[str] = None,
605        id_type: ColumnSubRole = ColumnSubRole.ISIN,
606    ):
607        self.date = date
608        self.identifier = identifier
609        self.id_type = id_type
610        self.country = country
611        self.currency = currency
612        self.__validateInput()
date
identifier
id_type
country
currency
class DateIsinCountryCurrency(DateIdentCountryCurrency):
647class DateIsinCountryCurrency(DateIdentCountryCurrency):
648    def __init__(self, date, isin, country, currency):
649        super().__init__(date, isin, country, currency, ColumnSubRole.ISIN)
650        self.isin = isin
651
652    def __str__(self):
653        return '(date: "{0}", isin: "{1}", country: {2}, currency: {3})'.format(
654            self.date, self.isin, self.country, self.currency
655        )
DateIsinCountryCurrency(date, isin, country, currency)
648    def __init__(self, date, isin, country, currency):
649        super().__init__(date, isin, country, currency, ColumnSubRole.ISIN)
650        self.isin = isin
isin
class IdentifierToColumnMapping:
658class IdentifierToColumnMapping:
659    """
660    IdentifierToColumnMapping is a mapping from string to boosted.api.api_type.ColumnSubRole.
661    The key of the mapping corresponds to a CSV column name, and the value corresponds
662    to the type of identifier that it maps to. Only columns specifying an identifying factor
663    for the stock needs to be specified (e.g. ISIN, Date, Symbol, Currency)
664    """
665
666    def __init__(self, mapping):
667        self.mapping = mapping
668        self.__validateInput()
669
670    def __str__(self):
671        return repr(self.value)
672
673    def __validateInput(self):
674        def validate_key_val(key, val):
675            if not isinstance(key, str):
676                raise Exception(f"key in IdentifierToColumnMapping {key} was not a string!")
677            if not isinstance(val, ColumnSubRole):
678                raise Exception(f"val in IdentifierToColumnMapping {val} was not a ColumnSubRole!")
679
680        for key, val in self.mapping:
681            validate_key_val(key, val)

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)

IdentifierToColumnMapping(mapping)
666    def __init__(self, mapping):
667        self.mapping = mapping
668        self.__validateInput()
mapping
class PortfolioSettings:
 684class PortfolioSettings:
 685    """
 686    This config is a documentation wrapper around a dict representing the portfolio settings json.
 687    Keys missing in the dict provided will fallback to the listed default when updating the
 688    the portfolio settings on the server.
 689
 690    Parameters
 691    ----------
 692    activeRisk: bool
 693        - **Active Risk**:
 694            Set the portfolio optimization parameters to be relative to the score from the stock
 695            universe. i.e. if you set Momentum to 0.5 to 1.0 and the stock universe has a market cap
 696            weighed score of 0.2 then the optimizer will solve for 0.7 to 1.2.
 697        - Constraints:
 698            Boolean value
 699        - Default Value: false
 700
 701    allowCompanyCollisions: bool
 702        - **Allow Multiple Securities Per Company**:
 703            Allows the machine to trade multiple securities (at the same time) for the same company.
 704            If it is ON and a company has listed PREF shares and COMMON shares, both will show up as
 705            tradeable options for that company. If it is OFF then system will try to determine the
 706            "correct" security to trade based on volume, liquidity, and share type.
 707        - Constraints:
 708            Boolean value
 709        - Default Value: true
 710
 711    allowDR: bool
 712        - **Allow Depositary Receipts**:
 713            Companies with tickers ending in .Y and .F will be disallowed from trading within the
 714            model with this setting off. Turn it on to allow trading in these securities.
 715        - Constraints:
 716            Boolean value
 717        - Default Value: true
 718
 719    benchmark: array
 720        - **Benchmark**:
 721            Set one or more benchmarks and the weighting for each.
 722        - Constraints:
 723            Required.
 724            List of dicts with keys:
 725            gbi_id: integer representing the GBI ID of the benchmark index.
 726            weight: floating point value between 0 and 1.
 727            All gbi_id's in the dictionary must be unique, and the benchmark weights must sum to a
 728            number between 0 and 1.
 729        - Default Value: []
 730
 731    benchmarkAllocation: float
 732        - **Benchmark Allocation**:
 733            None
 734        - Constraints:
 735            Floating point value representing a percentage between -100 and 100
 736        - Default Value: 0
 737
 738    beta_neutral: bool
 739        - **Beta Neutral**:
 740            Adjust the weights to get the ex-ante Beta to be the net exposure of the portfolio. i.e.
 741            70% Long / 30% Short = 0.4 Beta"
 742        - Constraints:
 743            Boolean value
 744        - Default Value: false
 745
 746    bounds_method: str | null
 747        - **Optimizer Bounds**:
 748            Tight: The optimizer will only allow the weightings of securities in your portfolio to
 749            stay tight (close) to their initial weighting.
 750            Loose: The optimizer will allow the weightings of securities in your portfolio to move a
 751            medium amount more from their initial weighting.
 752            Wide: The optimizer will allow almost any weight between the minimum and maximum weight
 753            per long or short position.
 754        - Constraints:
 755            String enum of {loose, tight, wide}, or null
 756        - Default Value: null
 757
 758    compounding: bool
 759        - **Compound Returns**:
 760            When toggled on, this will allow the portfolio to compound returns.
 761        - Constraints:
 762            Boolean value
 763        - Default Value: true
 764
 765    currency: str
 766        - **Currency**:
 767            The currency you would like your portfolio to be calculated in. Note that if the
 768            currency differs from its exchange (i.e. GBP on the S&P), the prior close foreign
 769            exchange rate will be used for calculations.
 770        - Constraints:
 771            String representing a 3 digit ISO currency code.
 772        - Default Value: "USD"
 773
 774    equalWeightBenchmark: bool
 775        - **Equal Weight Benchmark**:
 776            Instead of using the defined benchmarks, set the benchmark to be an equal weighted (per
 777            rebalance period) version of the stock universe.
 778        - Constraints:
 779            Boolean value
 780        - Default Value: false
 781
 782    executionDelay: int
 783        - **Execution Delay**:
 784            This option adds the number of days selected to the trade execution. If you select T+1
 785            and your rebalance period is set to Weekly (Monday), it will trade on Tuesday, and so
 786            on.
 787        - Constraints:
 788            Integer value between 0 and 100, inclusive.
 789        - Default Value: 0
 790
 791    factorNeutralizeSignal: bool
 792        - **Signal Optimizer**:
 793            Turn on optimization at the signal level that will adjust signals and rankings according
 794            to the specifications you set. This is done prior to portfolio construction and can help
 795            reduce bias in the model.
 796        - Constraints:
 797            Boolean value
 798        - Default Value: false
 799
 800    investmentHorizon: int
 801        - **Investment Horizon**:
 802            The investment horizon for the portfolio.
 803        - Constraints:
 804            An integer representing a number of days
 805        - Default Value: 21
 806
 807    lower_turnover: bool
 808        - **Lower Turnover**:
 809            If toggled on the optimizer will try to solve the secondary goal of optimizing the
 810            primary goals while limiting turnover.
 811        - Constraints:
 812            Boolean value
 813        - Default Value: false
 814
 815    marketCapBounds: float
 816        - **Maximum Market Cap Variation**:
 817            How far from the target market cap weighted index each stock can deviate (positive or
 818            negative).
 819        - Constraints:
 820            Floating point value representing a percentage between 0 and 10
 821        - Default Value: 1
 822
 823    marketCapConstraint: bool
 824        - **Constrain Market Cap**:
 825            Force weighting to be near the target weights for a market cap weighted index of your
 826            entire stock universe.
 827        - Constraints:
 828            Boolean value
 829        - Default Value: false
 830
 831    marketCapFlex: float
 832        - **Market Cap Flex**:
 833            How far from the target market cap weighted the stock can deviate relative to the
 834            existing weight (i.e. at 75% a 1% position cannot exceed 1.75%).
 835        - Constraints:
 836            Floating point value representing a percentage between 10 and 200
 837        - Default Value: 100
 838
 839    maxLongPosition: float
 840        - **Max Long Position per Stock**:
 841            The maximum weighting of a long position within the portfolio.
 842        - Constraints:
 843            Required. Floating point value representing a percentage between 0.0 and 300.0
 844        - Default Value: 10.0
 845
 846    maxNumStockLong: int
 847        - **Maximum Number of Longs**:
 848            The maximum number of stocks to buy. This is on a per “portfolio” basis, so if you have
 849            sector neutral turned on it will be maximum per sector.
 850        - Constraints:
 851            Integer value between 0 and 1000, inclusive. Must be >= minNumStockLong
 852        - Default Value: 50
 853
 854    maxNumStockShort: int
 855        - **Maximum Number of Shorts**:
 856            The maximum number of stocks to short. This is on a per “portfolio” basis, so if you
 857            have sector neutral turned on it will be maximum per sector.
 858        - Constraints:
 859            Integer value between 0 and 1000, inclusive. Must be >= minNumStockShort
 860        - Default Value: 50
 861
 862    maxOwnership: float
 863        - **Maximum Ownership**:
 864            The maximum percentage of a company the portfolio is allowed to own. For example, if a
 865            company had a market cap of $100MM and this was 5% the portfolio could not own more than
 866            $5MM of that company.
 867        - Constraints:
 868            Floating point value representing a percentage between 0 and 100
 869        - Default Value: 5
 870
 871    maxShortPosition: float
 872        - **Max Short Position per Stock**:
 873            The maximum weighting of a short position within the portfolio.
 874        - Constraints:
 875            Required. Floating point value representing a percentage between 0.0 and 300.0
 876        - Default Value: 5.0
 877
 878    minimumSharePrice: float
 879        - **Minimum Share Price**:
 880            The minimum share price you want the portfolio to allow.
 881        - Constraints:
 882            Floating point value between 0 and 100000
 883        - Default Value: 2
 884
 885    minLongPosition: float
 886        - **Min Long Position per Stock**:
 887            The minimum weighting of a long position within the portfolio.
 888        - Constraints:
 889            Floating point value representing a percentage between 0.0 and 300.0
 890        - Default Value: 0
 891
 892    minNumStockLong: int
 893        - **Minimum Number of Longs**:
 894            The minimum number of stocks to buy. This is on a per “portfolio” basis, so if you have
 895            sector neutral turned on it will be minimum per sector.
 896        - Constraints:
 897            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockLong
 898        - Default Value: 5
 899
 900    minNumStockShort: int
 901        - **Minimum Number of Shorts**:
 902            The minimum number of stocks to short. This is on a per “portfolio” basis, so if you
 903            have sector neutral turned on it will be minimum per sector.
 904        - Constraints:
 905            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockShort
 906        - Default Value: 5
 907
 908    minShortPosition: float
 909        - **Min Short Position per Stock**:
 910            The minimum weighting of a short position within the portfolio.
 911        - Constraints:
 912            Floating point value representing a percentage between 0.0 and 100.0
 913        - Default Value: 0
 914
 915    missingMarketCap: float
 916        - **Missing Market Cap**:
 917            The amount in millions (MM) to replace any missing market capitalization information
 918            with. This will impact the maximum ownership setting.
 919        - Constraints:
 920            Floating point value between 0 and 100
 921        - Default Value: 10
 922
 923    nameBasedSectorWeighting: bool
 924        - **Name Based Sector Weighting**:
 925            Base sector weightings on number of names. For example, if there are 200 names in the
 926            index and financials has 20 companies, the weight of financials should be 10% (20/200).
 927        - Constraints:
 928            Boolean value
 929        - Default Value: false
 930
 931    netBenchmark: bool
 932        - **Adjust Benchmark for Net Exposure**:
 933            If your portfolio has a net exposure greater or less than 100%, this will adjust the
 934            benchmark returns to match that net exposure.
 935        - Constraints:
 936            Boolean value
 937        - Default Value: false
 938
 939    optimizer: str
 940        - **Optimizer Type**:
 941            No Optimization: None
 942            Reduce Risk: Optimizer designed to reduce portfolio volatility. This tends to result in
 943            the best overall performance.
 944            Maximize Sharpe: Optimizer designed to maximize Sharpe using out of sample estimates for
 945            expected return. Estimates for expected return have an outsized impact on the
 946            performance of this optimizer.
 947            Maximize Alpha: Optimizer designed to maximize expected return based on out-of-sample
 948            estimates for expected return. Estimates for expected return have an outsized impact on
 949            the performance of this optimizer.
 950            Min VaR: Minimize Value at Risk by looking back at the proposed portfolio over the last
 951            year and trying to minimize drawdowns.
 952            Max VaR Sharpe: Maximize Value at Risk Sharpe by looking back at the proposed portfolio
 953            over the last year and trying to maximize Sharpe by observed return for the portfolio vs
 954            observed volatility.
 955            Minimize Skew: Minimize the skew of the portfolio by trying to find a set of portfolio
 956            weightings that results in returns that are closer to the mean.
 957        - Constraints:
 958            Required. String enum: one of
 959            default: No Optimization
 960            min_vol: Reduce Risk
 961            max_sharpe: Maximize Sharpe
 962            max_alpha: Maximize Alpha
 963            min_var: Min VaR
 964            max_var_sharpe: Max VaR Sharpe
 965            min_skew: Minimize Skew
 966        - Default Value: "default"
 967
 968    optimizeTurnover: bool
 969        - **Turnover Optimization**:
 970            Turnover optimization will reduce the turnover at the signal level, making that ranked
 971            list more stable.
 972        - Constraints:
 973            Boolean value
 974        - Default Value: false
 975
 976    pairsTrading: bool
 977        - **Pairs Trading**:
 978            Pairs Trading forces the portfolio to be created by going long / short an equal number
 979            of stocks. The portfolio will try to find an offsetting position for every long by
 980            finding securities that are both highly correlated and have a significant difference in
 981            star rating.
 982        - Constraints:
 983            Boolean value
 984        - Default Value: false
 985
 986    percentPortfolioLong: float
 987        - **Percent of Portfolio Long**:
 988            The exact sum of all long position weightings in the portfolio.
 989        - Constraints:
 990            Required. Floating point value representing a percentage between 0.0 and 300.0
 991        - Default Value: 100.0
 992
 993    percentPortfolioShort: float
 994        - **Percent of Portfolio Short**:
 995            The exact sum of all short position weightings in the portfolio.
 996        - Constraints:
 997            Required. Floating point value representing a percentage between 0.0 and 300.0
 998        - Default Value: 0.0
 999
1000    percentToLong: float
1001        - **Percent to Long**:
1002            The machine will attempt to buy the top X% of stocks, subject to the minimum and maximum
1003            specified. This is on a per “portfolio” basis, so if you have sector neutral turned on
1004            it will be top X% per sector. This will be subject to the maximum and minimum number of
1005            securities you specify below.
1006        - Constraints:
1007            Floating point value representing a percentage between 0 and 100
1008        - Default Value: 20.0
1009
1010    percentToShort: float
1011        - **Percent to Short**:
1012            The machine will attempt to short the bottom X% of stocks, subject to the minimum and
1013            maximum specified. This is on a per “portfolio” basis, so if you have sector neutral
1014            turned on it will be bottom X% per sector. This will be subject to the maximum and
1015            minimum number of securities you specify below.
1016        - Constraints:
1017            Floating point value representing a percentage between 0 and 100
1018        - Default Value: 20.0
1019
1020    portfolioStartingValue: float
1021        - **Portfolio Starting Value**:
1022            The value of the portfolio in MM when it begins trading. If its backtest spans from 2005
1023            - 2020, the starting value in 2005 will be whatever you set here.
1024        - Constraints:
1025            Floating point value between 0 and 100000.
1026        - Default Value: 100.0
1027
1028    priceCleaning: bool
1029        - **Price Cleaning**:
1030            Price Cleaning removes any very large price movements from the securities, specifically
1031            with a daily change greater than +500% or -83.33%. There are some legitimate securities
1032            with moves this large that will be impacted by turning this on, but the advantage is it
1033            prevents these securities from overwhelming the backtest.
1034        - Constraints:
1035            Boolean value
1036        - Default Value: true
1037
1038    priceType: str
1039        - **Price Type**:
1040            None
1041        - Constraints:
1042            Required. String enum of {CLOSE, OPEN, VWAP}
1043        - Default Value: "VWAP"
1044
1045    rebalancePeriod: str
1046        - **Rebalance Period**:
1047            When the machine rebalances the portfolio. You can choose a specific day if you prefer
1048            to execute trades on a specific day.
1049        - Constraints:
1050            Required.
1051            An enum value in the following list:
1052            "daily"
1053            "weekly": Rebalances on every Monday.
1054            Chooses the next available trade date to rebalance when Monday is a holiday
1055            "weekly_wednesday": Rebalances on every Wednesday.
1056            Chooses the next available trade date to rebalance when Wednesday is a holiday
1057            "weekly_friday": Rebalances on every Friday.
1058            Chooses the previous available trade date to rebalance when Friday is a holiday
1059            "monthly"
1060            "quarterly"
1061            OR a string in the following format: "custom_x" where x is an integer between 1 and 252
1062            (days).
1063        - Default Value: "weekly"
1064
1065    sectorNeutral: bool
1066        - **Sector Neutral**:
1067            Will be constructed as a series of portfolios - each matching the market cap weighting
1068            of each of the stock universe sectors. This means that min. and max. number of stocks
1069            "per portfolio" becomes "per sector".
1070        - Constraints:
1071            Boolean value
1072        - Default Value: false
1073
1074    sectorNeutralEqualWeightBenchmark: bool
1075        - **Sector Neutral Equal Weight Benchmark**:
1076            Instead of using the defined benchmarks, set the benchmark to be a "sector neutral"
1077            equal weighted (per rebalance period) version of the stock universe. This takes the
1078            sector weighting and divides by the number of stocks in that sector, so each sector will
1079            have a different individual security weighting.
1080        - Constraints:
1081            Boolean value
1082        - Default Value: false
1083
1084    sectorNeutralSpread: float
1085        - **Sector Neutral Spread**:
1086            The sector neutral spread is how far away from the benchmark sector allocation the
1087            machine can deviate. This is adjusted for net exposure, i.e. if your net exposure is 0%
1088            the target sector allocations will be 0%.
1089        - Constraints:
1090            Floating point value representing a percentage between 0 and 100
1091        - Default Value: 0
1092
1093    signalIndustryNeutral: bool
1094        - **Industry Neutral**:
1095            Make the signal industry neutral, plus or minus the signal sector neutral spread.
1096        - Constraints:
1097            Boolean value
1098        - Default Value: false
1099
1100    signalSectorNeutral: bool
1101        - **Sector Neutral**:
1102            Make the signal sector neutral, plus or minus the signal sector neutral spread.
1103        - Constraints:
1104            Boolean value
1105        - Default Value: false
1106
1107    signalSectorNeutralSpread: float
1108        - **Sector Neutral Spread**:
1109            Make the signal sector neutral, plus or minus the signal sector neutral spread.
1110        - Constraints:
1111            Floating point value representing a percentage between 0 and 100
1112        - Default Value: 1.0
1113
1114    smoothPeriods: int
1115        - **Smooth Periods**:
1116            The number of periods over which to smooth the signals
1117        - Constraints:
1118            Integer value between 1 and 100.
1119        - Default Value: 1
1120
1121    smoothWeighting: str
1122        - **Smooth Weighting Type**:
1123            Alpha Weight: The strength of the signal matters in the smoothing. For example, if a
1124            stock had a score of 5 stars in period A, 2 stars in period B, your 2 period smoothing
1125            would be 7 / 2 = 3.5.
1126            Equal Weight: Only the direction of the signal matters here. For example, if you were 5
1127            stars in period A and 2 stars in period B, your 2 period smoothing would be 0 / 2 = 0
1128            (+1 in period 1 and -1 in period 2)
1129        - Constraints:
1130            One of {alpha_weight, equal_weight}
1131        - Default Value: "alpha_weight"
1132
1133    stopGain: bool
1134        - **Enable Stop Gain**:
1135            Turn on stop gains for the portfolio. This forces sales of securities that have exceeded
1136            the stop gain level. All trades will occur at the next trading time (i.e. next day OPEN
1137            for OPEN price models).
1138        - Constraints:
1139            Boolean value
1140        - Default Value: false
1141
1142    stopGainAmount: float
1143        - **Stop Gain Percentage to Sell**:
1144            The percentage of the position the machine will sell if the stop gain is triggered.
1145        - Constraints:
1146            Floating point value representing a percentage between 1 and 100.
1147        - Default Value: 50.0
1148
1149    stopGainLevel: float
1150        - **Stop Gain Threshold**:
1151            Will sell positions after they hit a threshold. For example, 10% / stop gain, when a
1152            position gains 10% the machine will sell a portion of the position (defined by stop gain
1153            percentage).
1154        - Constraints:
1155            Floating point value representing a percentage between 1 and 100.
1156        - Default Value: 10.0
1157
1158    stopLoss: bool
1159        - **Enable Stop Loss**:
1160            Turn on stop losses for the portfolio. This forces sales of securities that have
1161            exceeded the stop loss level. All trades will occur at the next trading time (i.e. next
1162            day OPEN for OPEN price models).
1163        - Constraints:
1164            Boolean value
1165        - Default Value: false
1166
1167    stopLossAmount: float
1168        - **Stop Loss Percentage to Sell**:
1169            The percentage of the position the machine will sell if the stop loss is triggered.
1170        - Constraints:
1171            Floating point value representing a percentage between 1 and 100.
1172        - Default Value: 50.0
1173
1174    stopLossLevel: float
1175        - **Stop Loss Threshold**:
1176            Will sell positions after they hit a threshold. For example, 10% / stop loss, when a
1177            position loses 10% the machine will sell a portion of the position (defined by stop loss
1178            percentage).
1179        - Constraints:
1180            Floating point value representing a percentage between 1 and 100.
1181        - Default Value: 10.0
1182
1183    stopLossReset: bool
1184        - **Allow multiple stop loss/gain between rebalance days**:
1185            Allows the machine to repeatedly trigger stop losses within a rebalance period. For
1186            example, if you trade Mondays and the stock drops 15% per day during the week and the
1187            stop loss trigger is 10%, then every day will trigger the stop loss. If toggled off the
1188            stop loss can only be triggered once per stock per rebalance period.
1189        - Constraints:
1190            Boolean value
1191        - Default Value: false
1192
1193    trackingConstraint: float
1194        - **Maximum Tracking Error (vs Benchmark)**:
1195            Amount of ex-ante tracking error to constrain the optimizer by.
1196        - Constraints:
1197            Floating point value representing a percentage between 0 and 30
1198        - Default Value: 10
1199
1200    trackingError: bool
1201        - **Allocate Weight to Benchmark**:
1202            Allocate a portion (%) of portfolio to the benchmark
1203        - Constraints:
1204            Boolean value
1205        - Default Value: false
1206
1207    trackingErrorOptimizer: bool
1208        - **Tracking Error**:
1209            If toggled on the optimizer will try to solve for an expected tracking error less than
1210            the amount specified. This is done out-of-sample and the ex post tracking error will
1211            likely be higher.
1212        - Constraints:
1213            Boolean value
1214        - Default Value: false
1215
1216    tradingCost: float
1217        - **Trading Cost**:
1218            A cost that will be applied to every trade.
1219        - Constraints:
1220            Required. Floating point value representing a percentage between 0.0 and 5.0
1221        - Default Value: 0.01
1222
1223    turnoverImportance: float
1224        - **Turnover Importance**:
1225            High importance means the algorithm will aim to keep turnover low, whereas low
1226            importance of turnover will let it vary more.
1227        - Constraints:
1228            Floating point value between 0.2 and 5 nonlinearly mapped onto the 1-9 interval.
1229        - Default Value: 1
1230
1231    useHoldingPeriod: bool
1232        - **Use Holding Period**:
1233            Set any covariance or other calculations within the optimizer to use a holding period
1234            return instead of daily return series
1235        - Constraints:
1236            Boolean value
1237        - Default Value: false
1238
1239    weightingType: str
1240        - **Initial Weighting**:
1241            Alpha Weight: The initial weighting will be done such that higher ranked stocks have a
1242            higher weight.
1243            Equal Weight: The initial weighting will be done such that all stocks selected to be in
1244            the portfolio will have an equal weight.
1245            Market Cap Weight: The initial weighting will be done such that all stocks are weighted
1246            according to their previous day’s market capitalization.
1247        - Constraints:
1248            One of {alpha_weight, equal_weight, market_cap_weight}
1249        - Default Value: "alpha_weight"
1250    """
1251
1252    # note, this is just the collection of portfolio settings
1253    # that is exposed to the API:
1254    # SELECT key, default_value
1255    # FROM portfolio_settings_validation
1256    # WHERE exposed_to_client_api;
1257    # TODO: expand to entire settings? why this way initially?
1258    __default_settings = json.loads(
1259        """{
1260      "adv": {
1261        "max": 25,
1262        "min": 0,
1263        "active": false,
1264        "method": "average",
1265        "num_days": 21,
1266        "daily_max": 100
1267      },
1268      "region": [
1269        "USA",
1270        "CAN"
1271      ],
1272      "allowDR": true,
1273      "lotSize": 1,
1274      "currency": "USD",
1275      "stopGain": false,
1276      "stopLoss": false,
1277      "twapDays": 1,
1278      "advFilter": {
1279        "active": false,
1280        "method": "average",
1281        "num_days": 21
1282      },
1283      "benchmark": [],
1284      "blacklist": "none",
1285      "closeEval": true,
1286      "hypermode": 0,
1287      "optimizer": "default",
1288      "priceType": "VWAP",
1289      "activeRisk": false,
1290      "enableTWAP": false,
1291      "mcapFilter": {
1292        "max": 10000,
1293        "min": 100,
1294        "active": false,
1295        "num_days": 21
1296      },
1297      "shortModel": "",
1298      "compounding": true,
1299      "fullOverlay": false,
1300      "model_style": "default",
1301      "recommender": {
1302        "active": false,
1303        "targetPortfolio": "",
1304        "investmentHorizon": "1M",
1305        "allocationMethodology": "recommenderPV"
1306      },
1307      "totalReturn": true,
1308      "tradingCost": 0.01,
1309      "beta_neutral": false,
1310      "combineModel": [],
1311      "denseSignals": {
1312        "addFwdVol": false,
1313        "addRicCode": false,
1314        "addFwdReturns": false,
1315        "addShareCounts": false
1316      },
1317      "macroTrading": {
1318        "active": false,
1319        "daysAbove": 5,
1320        "movingAverageDays": 50,
1321        "portfolioReduction": 30
1322      },
1323      "masterRegion": "NA",
1324      "maxOwnership": 5,
1325      "netBenchmark": false,
1326      "paParameters": {
1327        "data_type": "rank_all"
1328      },
1329      "pairsTrading": false,
1330      "targetEquity": {
1331        "active": false,
1332        "targetDates": []
1333      },
1334      "volTargeting": {
1335        "lb": 5,
1336        "ub": 20,
1337        "active": false
1338      },
1339      "basketTrading": {
1340        "active": false,
1341        "lookback": 252,
1342        "esgWeight": 1,
1343        "optimizer": "min_var",
1344        "betaWeight": 1,
1345        "dataWeight": 1,
1346        "correlation": false,
1347        "equalWeight": true,
1348        "hedgeMethod": "simple",
1349        "hedgeStocks": [],
1350        "longPrimary": true,
1351        "priceWeight": 1,
1352        "factorWeight": 1,
1353        "primaryStocks": [],
1354        "hedgePortfolio": false,
1355        "numHedgeStocks": 20,
1356        "factorTargeting": false,
1357        "maxPositionSize": 0,
1358        "minPositionSize": 0,
1359        "optimizerWeight": 75,
1360        "secondaryStocks": [],
1361        "secondaryWeight": 0,
1362        "worstPercentage": 50,
1363        "numPrimaryStocks": 10,
1364        "restrictedStocks": [],
1365        "signalImportance": 1,
1366        "factorsAndTargets": [],
1367        "numSecondaryStocks": 10,
1368        "positionSizeBounds": "",
1369        "turnoverImportance": 1,
1370        "secondaryBasketType": "hedge",
1371        "hedgePortfolioPicker": [],
1372        "portfolioPointInTime": false
1373      },
1374      "bounds_method": null,
1375      "combineMethod": "overlay",
1376      "explain_model": {
1377        "active": false,
1378        "method": "winsorize",
1379        "threshold": 3
1380      },
1381      "factorFilters": [],
1382      "indexMatching": {
1383        "active": false,
1384        "numStocksToIndex": 1,
1385        "highestRankAllowed": 3
1386      },
1387      "marketCapFlex": 100,
1388      "percentToLong": 20,
1389      "priceCleaning": true,
1390      "rebalanceDays": [
1391        true,
1392        true,
1393        true,
1394        true,
1395        true
1396      ],
1397      "regionNeutral": false,
1398      "sectorNeutral": false,
1399      "sectorWeights": {},
1400      "smoothPeriods": 1,
1401      "stopGainLevel": 10,
1402      "stopLossLevel": 10,
1403      "stopLossReset": false,
1404      "trackingError": false,
1405      "triggerEvents": {
1406        "active": false
1407      },
1408      "weightingType": "equal_weight",
1409      "executionDelay": 0,
1410      "insertUniverse": null,
1411      "lower_turnover": false,
1412      "mlModelFactors": {
1413        "max": 10,
1414        "active": false
1415      },
1416      "percentToShort": 5,
1417      "stopGainAmount": 50,
1418      "stopLossAmount": 50,
1419      "backtestEndDate": "2050-01-01",
1420      "benchmarkAdjust": 2,
1421      "coreModelWeight": 33.34,
1422      "longBufferLimit": 50,
1423      "marketCapBounds": 1,
1424      "maxLongPosition": 10,
1425      "maxNumStockLong": 100,
1426      "minLongPosition": 0,
1427      "minNumStockLong": 20,
1428      "rebalancePeriod": "weekly_monday",
1429      "smoothWeighting": "alpha_weight",
1430      "taxOptimization": {
1431        "active": false,
1432        "num_days": 30,
1433        "taxThreshold": 20,
1434        "useSectorETFs": false,
1435        "readjustToCore": false
1436      },
1437      "tradingSettings": {
1438        "borrowRate": 0.5,
1439        "useBorrowRateData": true,
1440        "forceRebalanceDates": []
1441      },
1442      "factorTradeRules": [],
1443      "heuristicTrading": false,
1444      "holidayTolerance": 0,
1445      "includeDividends": true,
1446      "isDirectIndexing": false,
1447      "macroTradingLong": {
1448        "active": false,
1449        "daysAbove": 5,
1450        "movingAverageDays": 50,
1451        "portfolioReduction": 30
1452      },
1453      "maxNumStockShort": 200,
1454      "maxSectorWeights": {},
1455      "maxShortPosition": 1,
1456      "maximumOwnership": 100,
1457      "minNumStockShort": 20,
1458      "minShortPosition": 0,
1459      "missingMarketCap": 10,
1460      "optimizeTurnover": false,
1461      "percentileFilter": false,
1462      "shortBufferLimit": 50,
1463      "stopLossGainLong": {
1464        "stopGain": false,
1465        "stopLoss": false,
1466        "stopGainLevel": 10,
1467        "stopLossLevel": 10,
1468        "stopGainAmount": 50,
1469        "stopLossAmount": 50
1470      },
1471      "useHoldingPeriod": false,
1472      "backtestStartDate": "2008-01-01",
1473      "benchmarkOperator": "addition",
1474      "benchmarkSettings": {
1475        "convertSignalsToMarketCap": false
1476      },
1477      "combinePortfolios": [],
1478      "factorConstraints": [],
1479      "investmentHorizon": 21,
1480      "longTermTimeFrame": 63,
1481      "macroTradingShort": {
1482        "active": false,
1483        "daysAbove": 5,
1484        "movingAverageDays": 50,
1485        "portfolioReduction": 30
1486      },
1487      "minimumSharePrice": 2,
1488      "proportionateRisk": false,
1489      "recalcRequestTime": "2024-04-02T19:14:59.145Z",
1490      "selectedBenchmark": 10076,
1491      "stopLossGainShort": {
1492        "stopGain": false,
1493        "stopLoss": false,
1494        "stopGainLevel": 10,
1495        "stopLossLevel": 10,
1496        "stopGainAmount": 50,
1497        "stopLossAmount": 50
1498      },
1499      "turnoverBuffering": false,
1500      "benchmarkPortfolio": false,
1501      "factorSegmentation": {
1502        "active": false,
1503        "factorSlices": 10,
1504        "targetFactor": "size"
1505      },
1506      "ignore_constraints": false,
1507      "longTermImportance": 0,
1508      "reduceMinorTrading": {
1509        "min": 1,
1510        "active": false
1511      },
1512      "shortTermTimeFrame": 5,
1513      "signalsByMarketCap": {
1514        "active": false
1515      },
1516      "trackingConstraint": 10,
1517      "turnoverImportance": 5,
1518      "benchmarkAllocation": 0,
1519      "defineRegionWeights": {
1520        "active": false,
1521        "default": null,
1522        "weights": {}
1523      },
1524      "defineSectorWeights": false,
1525      "longBufferNumStocks": 5,
1526      "marketCapConstraint": false,
1527      "mcapWeightBenchmark": false,
1528      "mediumTermTimeFrame": 21,
1529      "regionNeutralSpread": 2,
1530      "sectorNeutralSpread": 3,
1531      "shortTermImportance": 0,
1532      "signalOptimizerType": "max_alpha",
1533      "signalSectorNeutral": false,
1534      "customRebalanceDates": [],
1535      "equalWeightBenchmark": true,
1536      "insertUniverseToggle": false,
1537      "maxStockForPortfolio": 200,
1538      "mediumTermImportance": 50,
1539      "minStockForPortfolio": 5,
1540      "percentPortfolioLong": 100,
1541      "reportingStockFilter": {
1542        "days": 10,
1543        "active": false
1544      },
1545      "secondaryConstraints": [],
1546      "shortBufferNumStocks": 5,
1547      "originalSignalWeights": false,
1548      "percentPortfolioShort": 0,
1549      "signalIndustryNeutral": false,
1550      "allowCompanyCollisions": true,
1551      "defineMaxSectorWeights": false,
1552      "dividendYieldOptimizer": false,
1553      "factorNeutralizeSignal": false,
1554      "portfolioStartingValue": 100,
1555      "trackingErrorOptimizer": false,
1556      "signalFactorConstraints": [
1557        {
1558          "lb": -0.25,
1559          "ub": 0.25,
1560          "factor": "machine_1"
1561        },
1562        {
1563          "lb": -0.25,
1564          "ub": 0.25,
1565          "factor": "machine_2"
1566        },
1567        {
1568          "lb": -0.25,
1569          "ub": 0.25,
1570          "factor": "machine_3"
1571        },
1572        {
1573          "lb": -0.25,
1574          "ub": 0.25,
1575          "factor": "machine_4"
1576        },
1577        {
1578          "lb": -0.25,
1579          "ub": 0.25,
1580          "factor": "machine_5"
1581        },
1582        {
1583          "lb": -0.25,
1584          "ub": 0.25,
1585          "factor": "machine_6"
1586        },
1587        {
1588          "lb": -0.25,
1589          "ub": 0.25,
1590          "factor": "machine_7"
1591        },
1592        {
1593          "lb": -0.25,
1594          "ub": 0.25,
1595          "factor": "machine_8"
1596        },
1597        {
1598          "lb": -0.25,
1599          "ub": 0.25,
1600          "factor": "machine_9"
1601        },
1602        {
1603          "lb": -0.25,
1604          "ub": 0.25,
1605          "factor": "machine_10"
1606        }
1607      ],
1608      "signalProportionateRisk": false,
1609      "topBottomPercentToTrade": 20,
1610      "useCustomRebalanceDates": false,
1611      "nameBasedSectorWeighting": false,
1612      "percentileFilterBySector": false,
1613      "tradeByPercentOfRankings": false,
1614      "signalSectorNeutralSpread": 1,
1615      "pairsTradingMinCorrelation": 0.5,
1616      "removeBlacklistFromRankings": false,
1617      "stopLossGainSameDayExecution": true,
1618      "excess_return_relative_to_beta": true,
1619      "sectorNeutralEqualWeightBenchmark": false,
1620      "industryNeutralEqualWeightBenchmark": false
1621    }"""
1622    )
1623
1624    def __init__(self, settings: Optional[Dict] = None):
1625        # TODO: i'd rather have kwargs for the different keys and then allow
1626        # for per-key overrides, but we start with this more conservative approach
1627        if settings is None:
1628            self.settings = copy.deepcopy(PortfolioSettings.__default_settings)
1629        else:
1630            self.settings = settings
1631
1632    def toDict(self):
1633        return self.settings
1634
1635    def __str__(self):
1636        return json.dumps(self.settings)
1637
1638    def __repr__(self):
1639        return self.__str__()
1640
1641    def fromDict(settings):
1642        return PortfolioSettings(settings)

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"

PortfolioSettings(settings: Union[Dict, NoneType] = None)
1624    def __init__(self, settings: Optional[Dict] = None):
1625        # TODO: i'd rather have kwargs for the different keys and then allow
1626        # for per-key overrides, but we start with this more conservative approach
1627        if settings is None:
1628            self.settings = copy.deepcopy(PortfolioSettings.__default_settings)
1629        else:
1630            self.settings = settings
def toDict(self):
1632    def toDict(self):
1633        return self.settings
def fromDict(settings):
1641    def fromDict(settings):
1642        return PortfolioSettings(settings)
hedge_experiment_type = typing.Literal['HEDGE', 'MIMIC']
class HedgeExperiment:
1649class HedgeExperiment:
1650    id: str  # really a uuid, which we represent as a string
1651    name: str
1652    user_id: str  # see id comment
1653    description: str
1654    experiment_type: hedge_experiment_type  # TODO: enum worth it?
1655    last_calculated: dt.datetime
1656    last_modified: dt.datetime
1657    status: str  # TODO: enum worth it?
1658    portfolio_calculation_status: str  # TODO: enum worth it?
1659    selected_models: List[str]  # see id comment
1660    target_securities: Dict[GbiIdSecurity, float]  # Security is a hashable type because frozen=True
1661    target_portfolios: List[str]
1662    selected_stock_universe_ids: List[str]  # see id comment
1663    baseline_model: Optional[str] = None
1664    baseline_stock_universe_id: Optional[str] = None
1665    baseline_scenario: Optional["HedgeExperimentScenario"] = None
1666
1667    @property
1668    def config(self) -> Dict:
1669        """
1670        Returns a hedge experiment configuration dictionary, ready for JSON-serialization and
1671        submission.
1672        """
1673        weights_by_gbiid = [
1674            {"gbiId": sec.id, "weight": weight} for sec, weight in self.target_securities.items()
1675        ]
1676        return {
1677            "experimentType": self.experiment_type,
1678            "selectedModels": self.selected_models,
1679            "targetSecurities": weights_by_gbiid,
1680            "selectedStockUniverseIds": self._selected_stock_universe_ids,
1681        }
1682
1683    @classmethod
1684    def from_json_dict(cls, d: Dict) -> "HedgeExperiment":
1685        # drafts arent fully populated
1686        if d["status"] == "DRAFT":
1687            weights_by_security = {}
1688            selected_models = []
1689            selected_stock_universe_ids = []
1690        else:
1691            weights_by_security = {
1692                GbiIdSecurity(
1693                    sec_dict["gbiId"],
1694                    None,
1695                    sec_dict["security"]["symbol"],
1696                    sec_dict["security"]["name"],
1697                ): sec_dict["weight"]
1698                for sec_dict in d["targetSecurities"]
1699            }
1700            experiment_config = json.loads(d["config"])
1701            selected_models = experiment_config["selectedModels"]
1702            selected_stock_universe_ids = experiment_config["selectedStockUniverseIds"]
1703        baseline_scenario = None
1704        if d["baselineScenario"] is not None:
1705            baseline_scenario = HedgeExperimentScenario.from_json_dict(d["baselineScenario"])
1706        return cls(
1707            d["hedgeExperimentId"],
1708            d["experimentName"],
1709            d["userId"],
1710            d["description"],
1711            d["experimentType"],
1712            d["lastCalculated"],
1713            d["lastModified"],
1714            d["status"],
1715            d["portfolioCalcStatus"],
1716            selected_models,
1717            weights_by_security,
1718            d.get("targetPortfolios"),
1719            selected_stock_universe_ids or [],
1720            d.get("baselineModel"),
1721            d.get("baselineStockUniverseId"),
1722            baseline_scenario,
1723        )
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[boosted.api.api_type.HedgeExperimentScenario, NoneType] = None)
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[boosted.api.api_type.HedgeExperimentScenario, NoneType] = None
config: Dict

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

@classmethod
def from_json_dict(cls, d: Dict) -> boosted.api.api_type.HedgeExperiment:
1683    @classmethod
1684    def from_json_dict(cls, d: Dict) -> "HedgeExperiment":
1685        # drafts arent fully populated
1686        if d["status"] == "DRAFT":
1687            weights_by_security = {}
1688            selected_models = []
1689            selected_stock_universe_ids = []
1690        else:
1691            weights_by_security = {
1692                GbiIdSecurity(
1693                    sec_dict["gbiId"],
1694                    None,
1695                    sec_dict["security"]["symbol"],
1696                    sec_dict["security"]["name"],
1697                ): sec_dict["weight"]
1698                for sec_dict in d["targetSecurities"]
1699            }
1700            experiment_config = json.loads(d["config"])
1701            selected_models = experiment_config["selectedModels"]
1702            selected_stock_universe_ids = experiment_config["selectedStockUniverseIds"]
1703        baseline_scenario = None
1704        if d["baselineScenario"] is not None:
1705            baseline_scenario = HedgeExperimentScenario.from_json_dict(d["baselineScenario"])
1706        return cls(
1707            d["hedgeExperimentId"],
1708            d["experimentName"],
1709            d["userId"],
1710            d["description"],
1711            d["experimentType"],
1712            d["lastCalculated"],
1713            d["lastModified"],
1714            d["status"],
1715            d["portfolioCalcStatus"],
1716            selected_models,
1717            weights_by_security,
1718            d.get("targetPortfolios"),
1719            selected_stock_universe_ids or [],
1720            d.get("baselineModel"),
1721            d.get("baselineStockUniverseId"),
1722            baseline_scenario,
1723        )
class HedgeExperimentScenario:
1727class HedgeExperimentScenario:
1728    id: str  # really a uuid, which we represent as a string;
1729    name: str
1730    description: str
1731    status: str  # TODO: enum worth it?
1732    settings: PortfolioSettings
1733    summary: pd.DataFrame
1734    # we currently a portfolios field. they get wrapped up into the summary table
1735
1736    @classmethod
1737    def from_json_dict(cls, d: Dict) -> "HedgeExperimentScenario":
1738        return cls(
1739            d["hedgeExperimentScenarioId"],
1740            d["scenarioName"],
1741            d["description"],
1742            d.get("status"),
1743            PortfolioSettings(json.loads(d["portfolioSettingsJson"])),
1744            HedgeExperimentScenario._build_portfolio_summary(d.get("hedgeExperimentPortfolios")),
1745        )
1746
1747    @staticmethod
1748    def _build_portfolio_summary(portfolios: Dict) -> pd.DataFrame:
1749        # this happens for added scenarios that havent run yet
1750        if portfolios is None:
1751            return pd.DataFrame()
1752
1753        df_dict = defaultdict(list)
1754        for pf in portfolios:
1755            p = pf["portfolio"]
1756
1757            df_dict["portfolio_id"].append(p["id"])
1758            df_dict["scenario_name"].append(p["name"])
1759            df_dict["model_id"].append(p["modelId"])
1760            df_dict["status"].append(p["status"])
1761
1762            if p["status"] != "READY":  # data isn't yet available
1763                df_dict["1M"].append(np.nan)
1764                df_dict["3M"].append(np.nan)
1765                df_dict["1Y"].append(np.nan)
1766                df_dict["sharpe"].append(np.nan)
1767                df_dict["vol"].append(np.nan)
1768            else:
1769                # NOTE:
1770                # these are the magic indices/keys of these values; inspect passed arg to see
1771                # if upstream changes the order of (sub-)elements in the response...uh oh
1772                # TODO: specific key search? wrap with try/except and fail loudly?
1773                df_dict["1M"].append(p["performanceGrid"][0][4])
1774                df_dict["3M"].append(p["performanceGrid"][0][5])
1775                df_dict["1Y"].append(p["performanceGrid"][0][7])
1776                df_dict["sharpe"].append(p["tearSheet"][0]["members"][1]["value"])
1777                pf_stddev = p["tearSheet"][1]["members"][1]["value"]
1778                bnchmk_stddev = p["tearSheet"][1]["members"][2]["value"]
1779                df_dict["vol"].append((pf_stddev - bnchmk_stddev) * 100)
1780                # TODO: this is how it is done on the front-end,
1781                # should be pulled out of both here and there
1782
1783        df = pd.DataFrame(df_dict)
1784        return df
HedgeExperimentScenario( id: str, name: str, description: str, status: str, settings: boosted.api.api_type.PortfolioSettings, summary: pandas.core.frame.DataFrame)
id: str
name: str
description: str
status: str
summary: pandas.core.frame.DataFrame
@classmethod
def from_json_dict(cls, d: Dict) -> boosted.api.api_type.HedgeExperimentScenario:
1736    @classmethod
1737    def from_json_dict(cls, d: Dict) -> "HedgeExperimentScenario":
1738        return cls(
1739            d["hedgeExperimentScenarioId"],
1740            d["scenarioName"],
1741            d["description"],
1742            d.get("status"),
1743            PortfolioSettings(json.loads(d["portfolioSettingsJson"])),
1744            HedgeExperimentScenario._build_portfolio_summary(d.get("hedgeExperimentPortfolios")),
1745        )
class HedgeExperimentDetails:
1788class HedgeExperimentDetails:
1789    # this will probably lead to violations of the law of demeter, but it's easy to do right now,
1790    # particularly for an unproven API
1791    experiment: HedgeExperiment
1792    scenarios: Dict[str, HedgeExperimentScenario]
1793
1794    @property
1795    def summary(self) -> pd.DataFrame:
1796        """
1797        Returns a dataframe corresponding to the Hedge Experiment "flat view" - all portfolio
1798        options are displayed
1799        """
1800        if len(self.scenarios) == 0 or all(s.status != "READY" for s in self.scenarios.values()):
1801            return pd.DataFrame()
1802        return pd.concat(scenario.summary for scenario in self.scenarios.values()).sort_values(
1803            ["scenario_name"]
1804        )  # sort for stable presentation
1805
1806    @classmethod
1807    def from_json_dict(cls, d: Dict) -> "HedgeExperimentDetails":
1808        he = HedgeExperiment.from_json_dict(d)
1809        scenarios = {
1810            scenario_dict["hedgeExperimentScenarioId"]: HedgeExperimentScenario.from_json_dict(
1811                scenario_dict
1812            )
1813            for scenario_dict in d["hedgeExperimentScenarios"]
1814        }
1815        return cls(he, scenarios)
HedgeExperimentDetails( experiment: boosted.api.api_type.HedgeExperiment, scenarios: Dict[str, boosted.api.api_type.HedgeExperimentScenario])
summary: pandas.core.frame.DataFrame

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

@classmethod
def from_json_dict(cls, d: Dict) -> boosted.api.api_type.HedgeExperimentDetails:
1806    @classmethod
1807    def from_json_dict(cls, d: Dict) -> "HedgeExperimentDetails":
1808        he = HedgeExperiment.from_json_dict(d)
1809        scenarios = {
1810            scenario_dict["hedgeExperimentScenarioId"]: HedgeExperimentScenario.from_json_dict(
1811                scenario_dict
1812            )
1813            for scenario_dict in d["hedgeExperimentScenarios"]
1814        }
1815        return cls(he, scenarios)