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
  56    def __str__(self):
  57        if self.name == "STOCK":
  58            return "STOCK"
  59        elif self.name == "GLOBAL":
  60            return "GLOBAL"
  61        elif self.name == "STRATEGY":
  62            return "STRATEGY"
  63        else:
  64            return "UNKNOWN"
  65
  66
  67class DataSetSubType(Enum):
  68    UNKNOWN = 0
  69    DENSE = 1
  70    SPARSE_HIST = 2
  71    SPARSE_FWD = 3
  72
  73    def __str__(self):
  74        if self.name == "DENSE":
  75            return "DENSE"
  76        elif self.name == "SPARSE_HIST":
  77            return "SPARSE_HIST"
  78        elif self.name == "SPARSE_FWD":
  79            return "SPARSE_FWD"
  80        else:
  81            return "UNKNOWN"
  82
  83
  84class DataSetFrequency(Enum):
  85    UNKNOWN = 0
  86    DAILY = 1
  87    WEEKLY = 2
  88    MONTHLY = 3
  89    QUARTERLY = 4
  90    SEMIANNUAL = 5
  91    ANNUAL = 6
  92
  93    def __str__(self):
  94        if self.name == "DAILY":
  95            return "DAILY"
  96        elif self.name == "WEEKLY":
  97            return "WEEKLY"
  98        elif self.name == "MONTHLY":
  99            return "MONTHLY"
 100        elif self.name == "QUARTERLY":
 101            return "QUARTERLY"
 102        elif self.name == "SEMIANNUAL":
 103            return "SEMIANNUAL"
 104        elif self.name == "ANNUAL":
 105            return "ANNUAL"
 106        else:
 107            return "UNKNOWN"
 108
 109
 110class ColumnRole(Enum):
 111    UNKNOWN = 0
 112    VARIABLE = 1
 113    GOAL = 2
 114    METRIC = 3
 115    IDENTIFIER = 4
 116
 117    def __str__(self):
 118        if self.name == "VARIABLE":
 119            return "VARIABLE"
 120        elif self.name == "GOAL":
 121            return "GOAL"
 122        elif self.name == "METRIC":
 123            return "METRIC"
 124        elif self.name == "IDENTIFIER":
 125            return "IDENTIFIER"
 126        else:
 127            return "UNKNOWN"
 128
 129
 130class ColumnSubRole(Enum):
 131    UNKNOWN = 0
 132    GBI_ID = 1
 133    ISIN = 2
 134    GVKEY = 3
 135    IID = 4
 136    SYMBOL = 5
 137    COUNTRY = 6
 138    CURRENCY = 7
 139    DATE = 8
 140    COMPANY_NAME = 9
 141    REPORT_DATE = 10
 142    REPORT_PERIOD = 11
 143
 144    def __str__(self):
 145        if self.name == "GBI_ID":
 146            return "GBI_ID"
 147        elif self.name == "ISIN":
 148            return "ISIN"
 149        elif self.name == "GVKEY":
 150            return "GVKEY"
 151        elif self.name == "IID":
 152            return "IID"
 153        elif self.name == "SYMBOL":
 154            return "SYMBOL"
 155        elif self.name == "COUNTRY":
 156            return "COUNTRY"
 157        elif self.name == "CURRENCY":
 158            return "CURRENCY"
 159        elif self.name == "DATE":
 160            return "DATE"
 161        elif self.name == "COMPANY_NAME":
 162            return "COMPANY_NAME"
 163        elif self.name == "REPORT_DATE":
 164            return "REPORT_DATE"
 165        elif self.name == "REPORT_PERIOD":
 166            return "REPORT_PERIOD"
 167        else:
 168            return "UNKNOWN"
 169
 170    def get_match(column_name):
 171        def clean_str(name):
 172            translation = str.maketrans("", "", string.punctuation)
 173            clean_name = name.strip().translate(translation).lower()
 174            return re.sub(r"\s+", "", clean_name)
 175
 176        identifiers = [
 177            ColumnSubRole.GBI_ID,
 178            ColumnSubRole.ISIN,
 179            ColumnSubRole.GVKEY,
 180            ColumnSubRole.IID,
 181            ColumnSubRole.SYMBOL,
 182            ColumnSubRole.COUNTRY,
 183            ColumnSubRole.CURRENCY,
 184            ColumnSubRole.COMPANY_NAME,
 185            ColumnSubRole.REPORT_DATE,
 186            ColumnSubRole.REPORT_PERIOD,
 187        ]
 188
 189        for identifier in identifiers:
 190            if clean_str(str(identifier)) == clean_str(column_name):
 191                return identifier
 192        return None
 193
 194
 195class ColumnValueType(Enum):
 196    UNKNOWN = 0
 197    NUMBER = 1
 198    STRING = 2
 199
 200    def __str__(self):
 201        if self.name == "UNKNOWN":
 202            return "UNKNOWN"
 203        elif self.name == "NUMBER":
 204            return "NUMBER"
 205        elif self.name == "STRING":
 206            return "STRING"
 207        else:
 208            return "UNKNOWN"
 209
 210
 211class ColumnConfig:
 212    def __init__(
 213        self,
 214        name=None,
 215        role=None,
 216        sub_role=None,
 217        value_type=ColumnValueType.NUMBER,
 218        description="",
 219        investment_horizon=None,
 220    ):
 221        self.name = name
 222        self.role = role
 223        self.sub_role = sub_role
 224        self.value_type = value_type
 225        # More value_types will be supported in the future.
 226        self.description = description
 227        self.investment_horizon = investment_horizon
 228
 229    def __str__(self):
 230        return 'Name: "{0}", Role: {1}, SubRole: {2}, Value type: {3}, Desc: {4} IH: {5}.'.format(
 231            self.name,
 232            self.role,
 233            self.sub_role,
 234            self.value_type,
 235            self.description,
 236            self.investment_horizon,
 237        )
 238
 239    def __repr__(self):
 240        return self.__str__()
 241
 242
 243class StrategyConfig:
 244    def __init__(self, name=None, source_name=None):
 245        self.name = name
 246        self.source_name = source_name
 247
 248    def __str__(self):
 249        return 'Name: "{0}", Source Name: {1}.'.format(self.name, self.source_name)
 250
 251    def __repr__(self):
 252        return self.__str__()
 253
 254
 255class BoostedDataSetSchemaException(Exception):
 256    def __init__(self, value, data=None):
 257        self.value = value
 258        self.data = data
 259
 260    def __str__(self):
 261        return repr(self.value)
 262
 263
 264class DataSetConfig:
 265    def __init__(
 266        self,
 267        name,
 268        datasetType=DataSetType.STOCK,
 269        datasetSubType=DataSetSubType.DENSE,
 270        datasetFrequency=DataSetFrequency.DAILY,
 271    ):
 272        self.name = name
 273        self.type = datasetType
 274        self.subtype = datasetSubType
 275        self.frequency = datasetFrequency
 276        self.columns = []
 277        self.strategies = []
 278
 279    def addColumn(self, columnConfig):
 280        self.columns.append(columnConfig)
 281
 282    def addStrategy(self, strategyConfig):
 283        self.strategies.append(strategyConfig)
 284
 285    def __getColumnByName(self, col_name):
 286        for column in self.columns:
 287            if col_name == column.name:
 288                return column
 289        raise BoostedDataSetSchemaException(f"Unable to find column {col_name}")
 290
 291    def updateColumnToGoal(self, col_name, investment_horizon):
 292        column = self.__getColumnByName(col_name)
 293        column.role = ColumnRole.GOAL
 294        column.investment_horizon = int(investment_horizon)
 295
 296    def updateColumnToMetric(self, col_name):
 297        column = self.__getColumnByName(col_name)
 298        column.role = ColumnRole.METRIC
 299
 300    def updateColumnToVariable(self, col_name):
 301        column = self.__getColumnByName(col_name)
 302        column.role = ColumnRole.VARIABLE
 303
 304    def __validateSchema(self):
 305        num_goals = 0
 306        num_metrics = 0
 307        dt = self.type
 308        dst = self.subtype
 309        dsf = self.frequency
 310        if len(self.columns) == 0:
 311            msg = "No feature columns exist."
 312            raise BoostedDataSetSchemaException(msg)
 313
 314        if dst in [DataSetSubType.SPARSE_HIST, DataSetSubType.SPARSE_FWD]:
 315            if dsf not in [DataSetFrequency.QUARTERLY]:
 316                msg = f"{dsf} frequency is not supported for {dst} sub data"
 317                raise BoostedDataSetSchemaException(msg)
 318            if dt not in [DataSetType.STOCK]:
 319                msg = f"{dst} subtype is not supported for {dt} data"
 320                raise BoostedDataSetSchemaException(msg)
 321
 322        for column in self.columns:
 323            if column.role == ColumnRole.GOAL:
 324                ih = column.investment_horizon
 325                gn = column.name
 326                if dt == DataSetType.GLOBAL:
 327                    msg = f"{dt} data can not have {ColumnRole.GOAL} type"
 328                    raise BoostedDataSetSchemaException(msg)
 329                if not isinstance(ih, int):
 330                    msg = f"Investment horizon for {gn} must be an integer"
 331                    raise BoostedDataSetSchemaException(msg)
 332                if ih < 1 or ih > 252:
 333                    msg = f"Investment horizon must be between 1 and 252 for {gn}"
 334                    raise BoostedDataSetSchemaException(msg)
 335                num_goals += 1
 336            elif column.role == ColumnRole.METRIC:
 337                if dt in [DataSetType.GLOBAL, DataSetType.STOCK]:
 338                    msg = f"{dt} data can not have {ColumnRole.METRIC} type"
 339                    raise BoostedDataSetSchemaException(msg)
 340                num_metrics += 1
 341
 342        if dt == DataSetType.STRATEGY:
 343            if num_goals == 0:
 344                msg = "Independent data requires at least one goal."
 345                raise BoostedDataSetSchemaException(msg)
 346            if num_metrics == 0:
 347                msg = "Independent data requires at least one metric."
 348                raise BoostedDataSetSchemaException(msg)
 349            if len(self.strategies) <= 1:
 350                msg = "Independent data requires more than 1 strategy"
 351                raise BoostedDataSetSchemaException(msg)
 352
 353    def toDict(self):
 354        self.__validateSchema()
 355        config = {}
 356        config["name"] = self.name
 357        config["type"] = str(self.type)
 358        config["subType"] = str(self.subtype)
 359        config["frequency"] = str(self.frequency)
 360        featureList = []
 361        for i, f in enumerate(self.columns):
 362            fm = {}
 363            fm["name"] = f.name
 364            fm["description"] = f.description
 365            fm["type"] = str(f.role)
 366            fm["role"] = str(f.role)
 367            fm["subRole"] = str(f.sub_role) if f.sub_role is not None else None
 368            fm["valuesType"] = str(f.value_type)
 369            fm["columnName"] = f.name
 370            fm["investmentHorizon"] = f.investment_horizon
 371            featureList.append(fm)
 372        config["features"] = featureList
 373        strategyList = []
 374        for i, s in enumerate(self.strategies):
 375            sm = {}
 376            sm["name"] = s.name
 377            sm["sourceName"] = s.source_name
 378            strategyList.append(sm)
 379        config["strategies"] = strategyList
 380        return config
 381
 382    def __str__(self):
 383        a = ""
 384        a += "Name: {0}\n".format(self.name)
 385        a += "Columns: \n"
 386        for c in self.columns:
 387            a += "  {0}\n".format(c)
 388        return a
 389
 390    def __repr__(self):
 391        return self.__str__()
 392
 393    def fromDict(schema):
 394        subtype = DataSetSubType[schema.get("subtype", str(DataSetSubType.DENSE))]
 395        frequency = DataSetFrequency[schema.get("frequency", str(DataSetFrequency.DAILY))]
 396        config = DataSetConfig(
 397            schema["name"],
 398            datasetType=DataSetType[schema["type"]],
 399            datasetSubType=subtype,
 400            datasetFrequency=frequency,
 401        )
 402        # two things left to fill in - strategies and columns
 403        for strategy_data in schema["strategies"]:
 404            config.addStrategy(
 405                StrategyConfig(name=strategy_data["name"], source_name=strategy_data["sourceName"])
 406            )
 407
 408        for feature_data in schema["features"]:
 409            config.addColumn(
 410                ColumnConfig(
 411                    name=feature_data["columnName"],
 412                    description=feature_data["description"] or "",
 413                    investment_horizon=feature_data["investmentHorizon"],
 414                    value_type=ColumnValueType[feature_data["valuesType"]]
 415                    if feature_data["valuesType"] is not None
 416                    else ColumnValueType.NUMBER,
 417                    role=ColumnRole[feature_data["role"]]
 418                    if feature_data["role"] is not None
 419                    else None,
 420                    sub_role=ColumnSubRole[feature_data["subRole"]]
 421                    if feature_data["subRole"] is not None
 422                    else None,
 423                )
 424            )
 425
 426        config.__validateSchema()
 427        return config
 428
 429
 430class GbiIdSecurityException(Exception):
 431    def __init__(self, value, data=None):
 432        self.value = value
 433        self.data = data
 434
 435    def __str__(self):
 436        return repr(self.value)
 437
 438
 439class GbiIdTickerISIN:
 440    def __init__(self, gbi_id: int, ticker: str, isin: str):
 441        self.gbi_id = int(gbi_id)
 442        self.ticker = ticker
 443        self.isin = isin
 444
 445    def __str__(self):
 446        return "(gbi_id: {0}, ticker: {1}, isin: {2})".format(self.gbi_id, self.ticker, self.isin)
 447
 448    def __repr__(self):
 449        return self.__str__()
 450
 451
 452class GbiIdSecurity:
 453    def __init__(self, gbi_id, isin_country_currency_date, ticker, company_name):
 454        self.gbi_id = gbi_id
 455        self.isin_info = isin_country_currency_date
 456        self.ticker = ticker
 457        self.company_name = company_name
 458
 459    def __str__(self):
 460        return '\n(gbi_id: "{0}", isin_info: {1}, ticker: {2}, company_name: {3})'.format(
 461            self.gbi_id, self.isin_info.__str__(), self.ticker, self.company_name
 462        )
 463
 464    def __repr__(self):
 465        return self.__str__()
 466
 467    def __eq__(self, __o: object) -> bool:
 468        return self.gbi_id == __o.gbi_id
 469
 470    def __hash__(self):
 471        return hash(self.gbi_id)
 472
 473
 474class DateIdentCountryCurrencyException(Exception):
 475    def __init__(self, value, data=None):
 476        self.value = value
 477        self.data = data
 478
 479    def __str__(self):
 480        return repr(self.value)
 481
 482
 483# for backward compatibility
 484class DateIsinCountryCurrencyException(DateIdentCountryCurrencyException):
 485    pass
 486
 487
 488class DateIdentCountryCurrency:
 489    def __init__(
 490        self,
 491        date: str,
 492        identifier: str,
 493        country: Optional[str] = None,
 494        currency: Optional[str] = None,
 495        id_type: ColumnSubRole = ColumnSubRole.ISIN,
 496    ):
 497        self.date = date
 498        self.identifier = identifier
 499        self.id_type = id_type
 500        self.country = country
 501        self.currency = currency
 502        self.__validateInput()
 503
 504    def __str__(self):
 505        return '(date: "{0}", identifier: "{1}", country: {2}, currency: {3})'.format(
 506            self.date, self.identifier, self.country, self.currency
 507        )
 508
 509    def __repr__(self):
 510        return self.__str__()
 511
 512    def __validateInput(self):
 513        # simply ensure that at least isin and date are filled out.
 514        # country/currency defaults to ANY if not filled out, but
 515        # still is not recommended.
 516        if self.identifier is None or self.date is None:
 517            raise DateIdentCountryCurrencyException(
 518                "ISIN or Date not provided while constructing DateIsinCountryCurrency"
 519            )
 520
 521        if self.id_type not in (ColumnSubRole.ISIN, ColumnSubRole.SYMBOL):
 522            raise DateIdentCountryCurrencyException(f"Invalid ID Type: {self.id_type}")
 523
 524        def check_is_str(value, fieldname):
 525            if value is not None and not isinstance(value, str):
 526                raise DateIdentCountryCurrencyException(
 527                    f"Field {fieldname} in DateIsinCountryCurrency was not a string."
 528                )
 529
 530        check_is_str(self.date, "date")
 531        check_is_str(self.identifier, "identifier")
 532        check_is_str(self.country, "country")
 533        check_is_str(self.currency, "currency")
 534
 535
 536# for backwards compatibility
 537class DateIsinCountryCurrency(DateIdentCountryCurrency):
 538    def __init__(self, date, isin, country, currency):
 539        super().__init__(date, isin, country, currency, ColumnSubRole.ISIN)
 540        self.isin = isin
 541
 542    def __str__(self):
 543        return '(date: "{0}", isin: "{1}", country: {2}, currency: {3})'.format(
 544            self.date, self.isin, self.country, self.currency
 545        )
 546
 547
 548class IdentifierToColumnMapping:
 549    """
 550    IdentifierToColumnMapping is a mapping from string to boosted.api.api_type.ColumnSubRole.
 551    The key of the mapping corresponds to a CSV column name, and the value corresponds
 552    to the type of identifier that it maps to. Only columns specifying an identifying factor
 553    for the stock needs to be specified (e.g. ISIN, Date, Symbol, Currency)
 554    """
 555
 556    def __init__(self, mapping):
 557        self.mapping = mapping
 558        self.__validateInput()
 559
 560    def __str__(self):
 561        return repr(self.value)
 562
 563    def __validateInput(self):
 564        def validate_key_val(key, val):
 565            if not isinstance(key, str):
 566                raise Exception(f"key in IdentifierToColumnMapping {key} was not a string!")
 567            if not isinstance(val, ColumnSubRole):
 568                raise Exception(f"val in IdentifierToColumnMapping {val} was not a ColumnSubRole!")
 569
 570        for key, val in self.mapping:
 571            validate_key_val(key, val)
 572
 573
 574class PortfolioSettings:
 575    """
 576    This config is a documentation wrapper around a dict representing the portfolio settings json.
 577    Keys missing in the dict provided will fallback to the listed default when updating the
 578    the portfolio settings on the server.
 579
 580    Parameters
 581    ----------
 582    activeRisk: bool
 583        - **Active Risk**:
 584            Set the portfolio optimization parameters to be relative to the score from the stock
 585            universe. i.e. if you set Momentum to 0.5 to 1.0 and the stock universe has a market cap
 586            weighed score of 0.2 then the optimizer will solve for 0.7 to 1.2.
 587        - Constraints:
 588            Boolean value
 589        - Default Value: false
 590
 591    allowCompanyCollisions: bool
 592        - **Allow Multiple Securities Per Company**:
 593            Allows the machine to trade multiple securities (at the same time) for the same company.
 594            If it is ON and a company has listed PREF shares and COMMON shares, both will show up as
 595            tradeable options for that company. If it is OFF then system will try to determine the
 596            "correct" security to trade based on volume, liquidity, and share type.
 597        - Constraints:
 598            Boolean value
 599        - Default Value: true
 600
 601    allowDR: bool
 602        - **Allow Depositary Receipts**:
 603            Companies with tickers ending in .Y and .F will be disallowed from trading within the
 604            model with this setting off. Turn it on to allow trading in these securities.
 605        - Constraints:
 606            Boolean value
 607        - Default Value: true
 608
 609    benchmark: array
 610        - **Benchmark**:
 611            Set one or more benchmarks and the weighting for each.
 612        - Constraints:
 613            Required.
 614            List of dicts with keys:
 615            gbi_id: integer representing the GBI ID of the benchmark index.
 616            weight: floating point value between 0 and 1.
 617            All gbi_id's in the dictionary must be unique, and the benchmark weights must sum to a
 618            number between 0 and 1.
 619        - Default Value: []
 620
 621    benchmarkAllocation: float
 622        - **Benchmark Allocation**:
 623            None
 624        - Constraints:
 625            Floating point value representing a percentage between -100 and 100
 626        - Default Value: 0
 627
 628    beta_neutral: bool
 629        - **Beta Neutral**:
 630            Adjust the weights to get the ex-ante Beta to be the net exposure of the portfolio. i.e.
 631            70% Long / 30% Short = 0.4 Beta"
 632        - Constraints:
 633            Boolean value
 634        - Default Value: false
 635
 636    bounds_method: str | null
 637        - **Optimizer Bounds**:
 638            Tight: The optimizer will only allow the weightings of securities in your portfolio to
 639            stay tight (close) to their initial weighting.
 640            Loose: The optimizer will allow the weightings of securities in your portfolio to move a
 641            medium amount more from their initial weighting.
 642            Wide: The optimizer will allow almost any weight between the minimum and maximum weight
 643            per long or short position.
 644        - Constraints:
 645            String enum of {loose, tight, wide}, or null
 646        - Default Value: null
 647
 648    compounding: bool
 649        - **Compound Returns**:
 650            When toggled on, this will allow the portfolio to compound returns.
 651        - Constraints:
 652            Boolean value
 653        - Default Value: true
 654
 655    currency: str
 656        - **Currency**:
 657            The currency you would like your portfolio to be calculated in. Note that if the
 658            currency differs from its exchange (i.e. GBP on the S&P), the prior close foreign
 659            exchange rate will be used for calculations.
 660        - Constraints:
 661            String representing a 3 digit ISO currency code.
 662        - Default Value: "USD"
 663
 664    equalWeightBenchmark: bool
 665        - **Equal Weight Benchmark**:
 666            Instead of using the defined benchmarks, set the benchmark to be an equal weighted (per
 667            rebalance period) version of the stock universe.
 668        - Constraints:
 669            Boolean value
 670        - Default Value: false
 671
 672    executionDelay: int
 673        - **Execution Delay**:
 674            This option adds the number of days selected to the trade execution. If you select T+1
 675            and your rebalance period is set to Weekly (Monday), it will trade on Tuesday, and so
 676            on.
 677        - Constraints:
 678            Integer value between 0 and 100, inclusive.
 679        - Default Value: 0
 680
 681    factorNeutralizeSignal: bool
 682        - **Signal Optimizer**:
 683            Turn on optimization at the signal level that will adjust signals and rankings according
 684            to the specifications you set. This is done prior to portfolio construction and can help
 685            reduce bias in the model.
 686        - Constraints:
 687            Boolean value
 688        - Default Value: false
 689
 690    investmentHorizon: int
 691        - **Investment Horizon**:
 692            The investment horizon for the portfolio.
 693        - Constraints:
 694            An integer representing a number of days
 695        - Default Value: 21
 696
 697    lower_turnover: bool
 698        - **Lower Turnover**:
 699            If toggled on the optimizer will try to solve the secondary goal of optimizing the
 700            primary goals while limiting turnover.
 701        - Constraints:
 702            Boolean value
 703        - Default Value: false
 704
 705    marketCapBounds: float
 706        - **Maximum Market Cap Variation**:
 707            How far from the target market cap weighted index each stock can deviate (positive or
 708            negative).
 709        - Constraints:
 710            Floating point value representing a percentage between 0 and 10
 711        - Default Value: 1
 712
 713    marketCapConstraint: bool
 714        - **Constrain Market Cap**:
 715            Force weighting to be near the target weights for a market cap weighted index of your
 716            entire stock universe.
 717        - Constraints:
 718            Boolean value
 719        - Default Value: false
 720
 721    marketCapFlex: float
 722        - **Market Cap Flex**:
 723            How far from the target market cap weighted the stock can deviate relative to the
 724            existing weight (i.e. at 75% a 1% position cannot exceed 1.75%).
 725        - Constraints:
 726            Floating point value representing a percentage between 10 and 200
 727        - Default Value: 100
 728
 729    maxLongPosition: float
 730        - **Max Long Position per Stock**:
 731            The maximum weighting of a long position within the portfolio.
 732        - Constraints:
 733            Required. Floating point value representing a percentage between 0.0 and 300.0
 734        - Default Value: 10.0
 735
 736    maxNumStockLong: int
 737        - **Maximum Number of Longs**:
 738            The maximum number of stocks to buy. This is on a per “portfolio” basis, so if you have
 739            sector neutral turned on it will be maximum per sector.
 740        - Constraints:
 741            Integer value between 0 and 1000, inclusive. Must be >= minNumStockLong
 742        - Default Value: 50
 743
 744    maxNumStockShort: int
 745        - **Maximum Number of Shorts**:
 746            The maximum number of stocks to short. This is on a per “portfolio” basis, so if you
 747            have sector neutral turned on it will be maximum per sector.
 748        - Constraints:
 749            Integer value between 0 and 1000, inclusive. Must be >= minNumStockShort
 750        - Default Value: 50
 751
 752    maxOwnership: float
 753        - **Maximum Ownership**:
 754            The maximum percentage of a company the portfolio is allowed to own. For example, if a
 755            company had a market cap of $100MM and this was 5% the portfolio could not own more than
 756            $5MM of that company.
 757        - Constraints:
 758            Floating point value representing a percentage between 0 and 100
 759        - Default Value: 5
 760
 761    maxShortPosition: float
 762        - **Max Short Position per Stock**:
 763            The maximum weighting of a short position within the portfolio.
 764        - Constraints:
 765            Required. Floating point value representing a percentage between 0.0 and 300.0
 766        - Default Value: 5.0
 767
 768    minimumSharePrice: float
 769        - **Minimum Share Price**:
 770            The minimum share price you want the portfolio to allow.
 771        - Constraints:
 772            Floating point value between 0 and 100000
 773        - Default Value: 2
 774
 775    minLongPosition: float
 776        - **Min Long Position per Stock**:
 777            The minimum weighting of a long position within the portfolio.
 778        - Constraints:
 779            Floating point value representing a percentage between 0.0 and 300.0
 780        - Default Value: 0
 781
 782    minNumStockLong: int
 783        - **Minimum Number of Longs**:
 784            The minimum number of stocks to buy. This is on a per “portfolio” basis, so if you have
 785            sector neutral turned on it will be minimum per sector.
 786        - Constraints:
 787            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockLong
 788        - Default Value: 5
 789
 790    minNumStockShort: int
 791        - **Minimum Number of Shorts**:
 792            The minimum number of stocks to short. This is on a per “portfolio” basis, so if you
 793            have sector neutral turned on it will be minimum per sector.
 794        - Constraints:
 795            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockShort
 796        - Default Value: 5
 797
 798    minShortPosition: float
 799        - **Min Short Position per Stock**:
 800            The minimum weighting of a short position within the portfolio.
 801        - Constraints:
 802            Floating point value representing a percentage between 0.0 and 100.0
 803        - Default Value: 0
 804
 805    missingMarketCap: float
 806        - **Missing Market Cap**:
 807            The amount in millions (MM) to replace any missing market capitalization information
 808            with. This will impact the maximum ownership setting.
 809        - Constraints:
 810            Floating point value between 0 and 100
 811        - Default Value: 10
 812
 813    nameBasedSectorWeighting: bool
 814        - **Name Based Sector Weighting**:
 815            Base sector weightings on number of names. For example, if there are 200 names in the
 816            index and financials has 20 companies, the weight of financials should be 10% (20/200).
 817        - Constraints:
 818            Boolean value
 819        - Default Value: false
 820
 821    netBenchmark: bool
 822        - **Adjust Benchmark for Net Exposure**:
 823            If your portfolio has a net exposure greater or less than 100%, this will adjust the
 824            benchmark returns to match that net exposure.
 825        - Constraints:
 826            Boolean value
 827        - Default Value: false
 828
 829    optimizer: str
 830        - **Optimizer Type**:
 831            No Optimization: None
 832            Reduce Risk: Optimizer designed to reduce portfolio volatility. This tends to result in
 833            the best overall performance.
 834            Maximize Sharpe: Optimizer designed to maximize Sharpe using out of sample estimates for
 835            expected return. Estimates for expected return have an outsized impact on the
 836            performance of this optimizer.
 837            Maximize Alpha: Optimizer designed to maximize expected return based on out-of-sample
 838            estimates for expected return. Estimates for expected return have an outsized impact on
 839            the performance of this optimizer.
 840            Min VaR: Minimize Value at Risk by looking back at the proposed portfolio over the last
 841            year and trying to minimize drawdowns.
 842            Max VaR Sharpe: Maximize Value at Risk Sharpe by looking back at the proposed portfolio
 843            over the last year and trying to maximize Sharpe by observed return for the portfolio vs
 844            observed volatility.
 845            Minimize Skew: Minimize the skew of the portfolio by trying to find a set of portfolio
 846            weightings that results in returns that are closer to the mean.
 847        - Constraints:
 848            Required. String enum: one of
 849            default: No Optimization
 850            min_vol: Reduce Risk
 851            max_sharpe: Maximize Sharpe
 852            max_alpha: Maximize Alpha
 853            min_var: Min VaR
 854            max_var_sharpe: Max VaR Sharpe
 855            min_skew: Minimize Skew
 856        - Default Value: "default"
 857
 858    optimizeTurnover: bool
 859        - **Turnover Optimization**:
 860            Turnover optimization will reduce the turnover at the signal level, making that ranked
 861            list more stable.
 862        - Constraints:
 863            Boolean value
 864        - Default Value: false
 865
 866    pairsTrading: bool
 867        - **Pairs Trading**:
 868            Pairs Trading forces the portfolio to be created by going long / short an equal number
 869            of stocks. The portfolio will try to find an offsetting position for every long by
 870            finding securities that are both highly correlated and have a significant difference in
 871            star rating.
 872        - Constraints:
 873            Boolean value
 874        - Default Value: false
 875
 876    percentPortfolioLong: float
 877        - **Percent of Portfolio Long**:
 878            The exact sum of all long position weightings in the portfolio.
 879        - Constraints:
 880            Required. Floating point value representing a percentage between 0.0 and 300.0
 881        - Default Value: 100.0
 882
 883    percentPortfolioShort: float
 884        - **Percent of Portfolio Short**:
 885            The exact sum of all short position weightings in the portfolio.
 886        - Constraints:
 887            Required. Floating point value representing a percentage between 0.0 and 300.0
 888        - Default Value: 0.0
 889
 890    percentToLong: float
 891        - **Percent to Long**:
 892            The machine will attempt to buy the top X% of stocks, subject to the minimum and maximum
 893            specified. This is on a per “portfolio” basis, so if you have sector neutral turned on
 894            it will be top X% per sector. This will be subject to the maximum and minimum number of
 895            securities you specify below.
 896        - Constraints:
 897            Floating point value representing a percentage between 0 and 100
 898        - Default Value: 20.0
 899
 900    percentToShort: float
 901        - **Percent to Short**:
 902            The machine will attempt to short the bottom X% of stocks, subject to the minimum and
 903            maximum specified. This is on a per “portfolio” basis, so if you have sector neutral
 904            turned on it will be bottom X% per sector. This will be subject to the maximum and
 905            minimum number of securities you specify below.
 906        - Constraints:
 907            Floating point value representing a percentage between 0 and 100
 908        - Default Value: 20.0
 909
 910    portfolioStartingValue: float
 911        - **Portfolio Starting Value**:
 912            The value of the portfolio in MM when it begins trading. If its backtest spans from 2005
 913            - 2020, the starting value in 2005 will be whatever you set here.
 914        - Constraints:
 915            Floating point value between 0 and 100000.
 916        - Default Value: 100.0
 917
 918    priceCleaning: bool
 919        - **Price Cleaning**:
 920            Price Cleaning removes any very large price movements from the securities, specifically
 921            with a daily change greater than +500% or -83.33%. There are some legitimate securities
 922            with moves this large that will be impacted by turning this on, but the advantage is it
 923            prevents these securities from overwhelming the backtest.
 924        - Constraints:
 925            Boolean value
 926        - Default Value: true
 927
 928    priceType: str
 929        - **Price Type**:
 930            None
 931        - Constraints:
 932            Required. String enum of {CLOSE, OPEN, VWAP}
 933        - Default Value: "VWAP"
 934
 935    rebalancePeriod: str
 936        - **Rebalance Period**:
 937            When the machine rebalances the portfolio. You can choose a specific day if you prefer
 938            to execute trades on a specific day.
 939        - Constraints:
 940            Required.
 941            An enum value in the following list:
 942            "daily"
 943            "weekly": Rebalances on every Monday.
 944            Chooses the next available trade date to rebalance when Monday is a holiday
 945            "weekly_wednesday": Rebalances on every Wednesday.
 946            Chooses the next available trade date to rebalance when Wednesday is a holiday
 947            "weekly_friday": Rebalances on every Friday.
 948            Chooses the previous available trade date to rebalance when Friday is a holiday
 949            "monthly"
 950            "quarterly"
 951            OR a string in the following format: "custom_x" where x is an integer between 1 and 252
 952            (days).
 953        - Default Value: "weekly"
 954
 955    sectorNeutral: bool
 956        - **Sector Neutral**:
 957            Will be constructed as a series of portfolios - each matching the market cap weighting
 958            of each of the stock universe sectors. This means that min. and max. number of stocks
 959            "per portfolio" becomes "per sector".
 960        - Constraints:
 961            Boolean value
 962        - Default Value: false
 963
 964    sectorNeutralEqualWeightBenchmark: bool
 965        - **Sector Neutral Equal Weight Benchmark**:
 966            Instead of using the defined benchmarks, set the benchmark to be a "sector neutral"
 967            equal weighted (per rebalance period) version of the stock universe. This takes the
 968            sector weighting and divides by the number of stocks in that sector, so each sector will
 969            have a different individual security weighting.
 970        - Constraints:
 971            Boolean value
 972        - Default Value: false
 973
 974    sectorNeutralSpread: float
 975        - **Sector Neutral Spread**:
 976            The sector neutral spread is how far away from the benchmark sector allocation the
 977            machine can deviate. This is adjusted for net exposure, i.e. if your net exposure is 0%
 978            the target sector allocations will be 0%.
 979        - Constraints:
 980            Floating point value representing a percentage between 0 and 100
 981        - Default Value: 0
 982
 983    signalIndustryNeutral: bool
 984        - **Industry Neutral**:
 985            Make the signal industry neutral, plus or minus the signal sector neutral spread.
 986        - Constraints:
 987            Boolean value
 988        - Default Value: false
 989
 990    signalSectorNeutral: bool
 991        - **Sector Neutral**:
 992            Make the signal sector neutral, plus or minus the signal sector neutral spread.
 993        - Constraints:
 994            Boolean value
 995        - Default Value: false
 996
 997    signalSectorNeutralSpread: float
 998        - **Sector Neutral Spread**:
 999            Make the signal sector neutral, plus or minus the signal sector neutral spread.
1000        - Constraints:
1001            Floating point value representing a percentage between 0 and 100
1002        - Default Value: 1.0
1003
1004    smoothPeriods: int
1005        - **Smooth Periods**:
1006            The number of periods over which to smooth the signals
1007        - Constraints:
1008            Integer value between 1 and 100.
1009        - Default Value: 1
1010
1011    smoothWeighting: str
1012        - **Smooth Weighting Type**:
1013            Alpha Weight: The strength of the signal matters in the smoothing. For example, if a
1014            stock had a score of 5 stars in period A, 2 stars in period B, your 2 period smoothing
1015            would be 7 / 2 = 3.5.
1016            Equal Weight: Only the direction of the signal matters here. For example, if you were 5
1017            stars in period A and 2 stars in period B, your 2 period smoothing would be 0 / 2 = 0
1018            (+1 in period 1 and -1 in period 2)
1019        - Constraints:
1020            One of {alpha_weight, equal_weight}
1021        - Default Value: "alpha_weight"
1022
1023    stopGain: bool
1024        - **Enable Stop Gain**:
1025            Turn on stop gains for the portfolio. This forces sales of securities that have exceeded
1026            the stop gain level. All trades will occur at the next trading time (i.e. next day OPEN
1027            for OPEN price models).
1028        - Constraints:
1029            Boolean value
1030        - Default Value: false
1031
1032    stopGainAmount: float
1033        - **Stop Gain Percentage to Sell**:
1034            The percentage of the position the machine will sell if the stop gain is triggered.
1035        - Constraints:
1036            Floating point value representing a percentage between 1 and 100.
1037        - Default Value: 50.0
1038
1039    stopGainLevel: float
1040        - **Stop Gain Threshold**:
1041            Will sell positions after they hit a threshold. For example, 10% / stop gain, when a
1042            position gains 10% the machine will sell a portion of the position (defined by stop gain
1043            percentage).
1044        - Constraints:
1045            Floating point value representing a percentage between 1 and 100.
1046        - Default Value: 10.0
1047
1048    stopLoss: bool
1049        - **Enable Stop Loss**:
1050            Turn on stop losses for the portfolio. This forces sales of securities that have
1051            exceeded the stop loss level. All trades will occur at the next trading time (i.e. next
1052            day OPEN for OPEN price models).
1053        - Constraints:
1054            Boolean value
1055        - Default Value: false
1056
1057    stopLossAmount: float
1058        - **Stop Loss Percentage to Sell**:
1059            The percentage of the position the machine will sell if the stop loss is triggered.
1060        - Constraints:
1061            Floating point value representing a percentage between 1 and 100.
1062        - Default Value: 50.0
1063
1064    stopLossLevel: float
1065        - **Stop Loss Threshold**:
1066            Will sell positions after they hit a threshold. For example, 10% / stop loss, when a
1067            position loses 10% the machine will sell a portion of the position (defined by stop loss
1068            percentage).
1069        - Constraints:
1070            Floating point value representing a percentage between 1 and 100.
1071        - Default Value: 10.0
1072
1073    stopLossReset: bool
1074        - **Allow multiple stop loss/gain between rebalance days**:
1075            Allows the machine to repeatedly trigger stop losses within a rebalance period. For
1076            example, if you trade Mondays and the stock drops 15% per day during the week and the
1077            stop loss trigger is 10%, then every day will trigger the stop loss. If toggled off the
1078            stop loss can only be triggered once per stock per rebalance period.
1079        - Constraints:
1080            Boolean value
1081        - Default Value: false
1082
1083    trackingConstraint: float
1084        - **Maximum Tracking Error (vs Benchmark)**:
1085            Amount of ex-ante tracking error to constrain the optimizer by.
1086        - Constraints:
1087            Floating point value representing a percentage between 0 and 30
1088        - Default Value: 10
1089
1090    trackingError: bool
1091        - **Allocate Weight to Benchmark**:
1092            Allocate a portion (%) of portfolio to the benchmark
1093        - Constraints:
1094            Boolean value
1095        - Default Value: false
1096
1097    trackingErrorOptimizer: bool
1098        - **Tracking Error**:
1099            If toggled on the optimizer will try to solve for an expected tracking error less than
1100            the amount specified. This is done out-of-sample and the ex post tracking error will
1101            likely be higher.
1102        - Constraints:
1103            Boolean value
1104        - Default Value: false
1105
1106    tradingCost: float
1107        - **Trading Cost**:
1108            A cost that will be applied to every trade.
1109        - Constraints:
1110            Required. Floating point value representing a percentage between 0.0 and 5.0
1111        - Default Value: 0.01
1112
1113    turnoverImportance: float
1114        - **Turnover Importance**:
1115            High importance means the algorithm will aim to keep turnover low, whereas low
1116            importance of turnover will let it vary more.
1117        - Constraints:
1118            Floating point value between 0.2 and 5 nonlinearly mapped onto the 1-9 interval.
1119        - Default Value: 1
1120
1121    useHoldingPeriod: bool
1122        - **Use Holding Period**:
1123            Set any covariance or other calculations within the optimizer to use a holding period
1124            return instead of daily return series
1125        - Constraints:
1126            Boolean value
1127        - Default Value: false
1128
1129    weightingType: str
1130        - **Initial Weighting**:
1131            Alpha Weight: The initial weighting will be done such that higher ranked stocks have a
1132            higher weight.
1133            Equal Weight: The initial weighting will be done such that all stocks selected to be in
1134            the portfolio will have an equal weight.
1135            Market Cap Weight: The initial weighting will be done such that all stocks are weighted
1136            according to their previous day’s market capitalization.
1137        - Constraints:
1138            One of {alpha_weight, equal_weight, market_cap_weight}
1139        - Default Value: "alpha_weight"
1140    """
1141
1142    # note, this is just the collection of portfolio settings
1143    # that is exposed to the API:
1144    # SELECT key, default_value
1145    # FROM portfolio_settings_validation
1146    # WHERE exposed_to_client_api;
1147    # TODO: expand to entire settings? why this way initially?
1148    __default_settings = {
1149        "maxOwnership": "5",
1150        "pairsTrading": "false",
1151        "minNumStockLong": "5",
1152        "stopLossAmount": "50.0",
1153        "stopGainLevel": "10.0",
1154        "smoothWeighting": '"alpha_weight"',
1155        "minimumSharePrice": "2",
1156        "maxShortPosition": "5.0",
1157        "tradingCost": "0.01",
1158        "stopGainAmount": "50.0",
1159        "smoothPeriods": "1",
1160        "executionDelay": "0",
1161        "benchmarkAllocation": "0",
1162        "sectorNeutralSpread": "0",
1163        "percentToLong": "20.0",
1164        "maxNumStockLong": "50",
1165        "trackingConstraint": "10",
1166        "compounding": "true",
1167        "priceCleaning": "true",
1168        "lower_turnover": "false",
1169        "missingMarketCap": "10",
1170        "allowCompanyCollisions": "true",
1171        "allowDR": "true",
1172        "sectorNeutral": "false",
1173        "marketCapFlex": "100",
1174        "marketCapBounds": "1",
1175        "turnoverImportance": "1",
1176        "minNumStockShort": "5",
1177        "maxNumStockShort": "50",
1178        "beta_neutral": "false",
1179        "nameBasedSectorWeighting": "false",
1180        "trackingError": "false",
1181        "stopLoss": "false",
1182        "stopGain": "false",
1183        "stopLossReset": "false",
1184        "netBenchmark": "false",
1185        "equalWeightBenchmark": "false",
1186        "marketCapConstraint": "false",
1187        "signalIndustryNeutral": "false",
1188        "signalSectorNeutral": "false",
1189        "sectorNeutralEqualWeightBenchmark": "false",
1190        "percentToShort": "20.0",
1191        "trackingErrorOptimizer": "false",
1192        "currency": '"USD"',
1193        "rebalancePeriod": '"weekly"',
1194        "benchmark": "[]",
1195        "signalSectorNeutralSpread": "1.0",
1196        "optimizeTurnover": "false",
1197        "minShortPosition": "0",
1198        "minLongPosition": "0",
1199        "percentPortfolioLong": "100.0",
1200        "portfolioStartingValue": "100.0",
1201        "stopLossLevel": "10.0",
1202        "maxLongPosition": "10.0",
1203        "percentPortfolioShort": "0.0",
1204        "bounds_method": None,
1205        "optimizer": '"default"',
1206        "activeRisk": "false",
1207        "useHoldingPeriod": "false",
1208        "weightingType": '"alpha_weight"',
1209        "priceType": '"VWAP"',
1210        "factorNeutralizeSignal": "false",
1211        "investmentHorizon": "21",
1212    }
1213
1214    def __init__(self, settings: Optional[Dict] = None):
1215        # TODO: i'd rather have kwargs for the different keys and then allow
1216        # for per-key overrides, but we start with this more conservative approach
1217        if settings is None:
1218            self.settings = copy.deepcopy(PortfolioSettings.__default_settings)
1219        else:
1220            self.settings = settings
1221
1222    def toDict(self):
1223        return self.settings
1224
1225    def __str__(self):
1226        return json.dumps(self.settings)
1227
1228    def __repr__(self):
1229        return self.__str__()
1230
1231    def fromDict(settings):
1232        return PortfolioSettings(settings)
1233
1234
1235hedge_experiment_type = Literal["HEDGE", "MIMIC"]
1236
1237
1238@dataclass(frozen=True)  # from _python client's_ perspective, instances will never change!
1239class HedgeExperiment:
1240    id: str  # really a uuid, which we represent as a string
1241    name: str
1242    user_id: str  # see id comment
1243    description: str
1244    experiment_type: hedge_experiment_type  # TODO: enum worth it?
1245    last_calculated: dt.datetime
1246    last_modified: dt.datetime
1247    status: str  # TODO: enum worth it?
1248    portfolio_calculation_status: str  # TODO: enum worth it?
1249    selected_models: List[str]  # see id comment
1250    target_securities: Dict[GbiIdSecurity, float]  # Security is a hashable type because frozen=True
1251    target_portfolios: List[str]
1252    selected_stock_universe_ids: List[str]  # see id comment
1253    baseline_model: Optional[str] = None
1254    baseline_stock_universe_id: Optional[str] = None
1255    baseline_scenario: Optional["HedgeExperimentScenario"] = None
1256
1257    @property
1258    def config(self) -> Dict:
1259        """
1260        Returns a hedge experiment configuration dictionary, ready for JSON-serialization and
1261        submission.
1262        """
1263        weights_by_gbiid = [
1264            {"gbiId": sec.id, "weight": weight} for sec, weight in self.target_securities.items()
1265        ]
1266        return {
1267            "experimentType": self.experiment_type,
1268            "selectedModels": self.selected_models,
1269            "targetSecurities": weights_by_gbiid,
1270            "selectedStockUniverseIds": self._selected_stock_universe_ids,
1271        }
1272
1273    @classmethod
1274    def from_json_dict(cls, d: Dict) -> "HedgeExperiment":
1275        # drafts arent fully populated
1276        if d["status"] == "DRAFT":
1277            weights_by_security = {}
1278            selected_models = []
1279            selected_stock_universe_ids = []
1280        else:
1281            weights_by_security = {
1282                GbiIdSecurity(
1283                    sec_dict["gbiId"],
1284                    None,
1285                    sec_dict["security"]["symbol"],
1286                    sec_dict["security"]["name"],
1287                ): sec_dict["weight"]
1288                for sec_dict in d["targetSecurities"]
1289            }
1290            experiment_config = json.loads(d["config"])
1291            selected_models = experiment_config["selectedModels"]
1292            selected_stock_universe_ids = experiment_config["selectedStockUniverseIds"]
1293        baseline_scenario = None
1294        if d["baselineScenario"] is not None:
1295            baseline_scenario = HedgeExperimentScenario.from_json_dict(d["baselineScenario"])
1296        return cls(
1297            d["hedgeExperimentId"],
1298            d["experimentName"],
1299            d["userId"],
1300            d["description"],
1301            d["experimentType"],
1302            d["lastCalculated"],
1303            d["lastModified"],
1304            d["status"],
1305            d["portfolioCalcStatus"],
1306            selected_models,
1307            weights_by_security,
1308            d.get("targetPortfolios"),
1309            selected_stock_universe_ids or [],
1310            d.get("baselineModel"),
1311            d.get("baselineStockUniverseId"),
1312            baseline_scenario,
1313        )
1314
1315
1316@dataclass(frozen=True)
1317class HedgeExperimentScenario:
1318    id: str  # really a uuid, which we represent as a string;
1319    name: str
1320    description: str
1321    status: str  # TODO: enum worth it?
1322    settings: PortfolioSettings
1323    summary: pd.DataFrame
1324    # we currently a portfolios field. they get wrapped up into the summary table
1325
1326    @classmethod
1327    def from_json_dict(cls, d: Dict) -> "HedgeExperimentScenario":
1328        return cls(
1329            d["hedgeExperimentScenarioId"],
1330            d["scenarioName"],
1331            d["description"],
1332            d.get("status"),
1333            PortfolioSettings(json.loads(d["portfolioSettingsJson"])),
1334            HedgeExperimentScenario._build_portfolio_summary(d.get("hedgeExperimentPortfolios")),
1335        )
1336
1337    @staticmethod
1338    def _build_portfolio_summary(portfolios: Dict) -> pd.DataFrame:
1339        # this happens for added scenarios that havent run yet
1340        if portfolios is None:
1341            return pd.DataFrame()
1342
1343        df_dict = defaultdict(list)
1344        for pf in portfolios:
1345            p = pf["portfolio"]
1346
1347            df_dict["portfolio_id"].append(p["id"])
1348            df_dict["scenario_name"].append(p["name"])
1349            df_dict["model_id"].append(p["modelId"])
1350            df_dict["status"].append(p["status"])
1351
1352            if p["status"] != "READY":  # data isn't yet available
1353                df_dict["1M"].append(np.nan)
1354                df_dict["3M"].append(np.nan)
1355                df_dict["1Y"].append(np.nan)
1356                df_dict["sharpe"].append(np.nan)
1357                df_dict["vol"].append(np.nan)
1358            else:
1359                # NOTE:
1360                # these are the magic indices/keys of these values; inspect passed arg to see
1361                # if upstream changes the order of (sub-)elements in the response...uh oh
1362                # TODO: specific key search? wrap with try/except and fail loudly?
1363                df_dict["1M"].append(p["performanceGrid"][0][4])
1364                df_dict["3M"].append(p["performanceGrid"][0][5])
1365                df_dict["1Y"].append(p["performanceGrid"][0][7])
1366                df_dict["sharpe"].append(p["tearSheet"][0]["members"][1]["value"])
1367                pf_stddev = p["tearSheet"][1]["members"][1]["value"]
1368                bnchmk_stddev = p["tearSheet"][1]["members"][2]["value"]
1369                df_dict["vol"].append((pf_stddev - bnchmk_stddev) * 100)
1370                # TODO: this is how it is done on the front-end,
1371                # should be pulled out of both here and there
1372
1373        df = pd.DataFrame(df_dict)
1374        return df
1375
1376
1377@dataclass(frozen=True)  # from _python client's_ perspective, instances will never change!
1378class HedgeExperimentDetails:
1379    # this will probably lead to violations of the law of demeter, but it's easy to do right now,
1380    # particularly for an unproven API
1381    experiment: HedgeExperiment
1382    scenarios: Dict[str, HedgeExperimentScenario]
1383
1384    @property
1385    def summary(self) -> pd.DataFrame:
1386        """
1387        Returns a dataframe corresponding to the Hedge Experiment "flat view" - all portfolio
1388        options are displayed
1389        """
1390        if len(self.scenarios) == 0 or all(s.status != "READY" for s in self.scenarios.values()):
1391            return pd.DataFrame()
1392        return pd.concat(scenario.summary for scenario in self.scenarios.values()).sort_values(
1393            ["scenario_name"]
1394        )  # sort for stable presentation
1395
1396    @classmethod
1397    def from_json_dict(cls, d: Dict) -> "HedgeExperimentDetails":
1398        he = HedgeExperiment.from_json_dict(d)
1399        scenarios = {
1400            scenario_dict["hedgeExperimentScenarioId"]: HedgeExperimentScenario.from_json_dict(
1401                scenario_dict
1402            )
1403            for scenario_dict in d["hedgeExperimentScenarios"]
1404        }
1405        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
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        else:
65            return "UNKNOWN"

An enumeration.

UNKNOWN = <DataSetType.UNKNOWN: 0>
STOCK = <DataSetType.STOCK: 1>
GLOBAL = <DataSetType.GLOBAL: 2>
STRATEGY = <DataSetType.STRATEGY: 3>
Inherited Members
enum.Enum
name
value
class DataSetSubType(enum.Enum):
68class DataSetSubType(Enum):
69    UNKNOWN = 0
70    DENSE = 1
71    SPARSE_HIST = 2
72    SPARSE_FWD = 3
73
74    def __str__(self):
75        if self.name == "DENSE":
76            return "DENSE"
77        elif self.name == "SPARSE_HIST":
78            return "SPARSE_HIST"
79        elif self.name == "SPARSE_FWD":
80            return "SPARSE_FWD"
81        else:
82            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):
 85class DataSetFrequency(Enum):
 86    UNKNOWN = 0
 87    DAILY = 1
 88    WEEKLY = 2
 89    MONTHLY = 3
 90    QUARTERLY = 4
 91    SEMIANNUAL = 5
 92    ANNUAL = 6
 93
 94    def __str__(self):
 95        if self.name == "DAILY":
 96            return "DAILY"
 97        elif self.name == "WEEKLY":
 98            return "WEEKLY"
 99        elif self.name == "MONTHLY":
100            return "MONTHLY"
101        elif self.name == "QUARTERLY":
102            return "QUARTERLY"
103        elif self.name == "SEMIANNUAL":
104            return "SEMIANNUAL"
105        elif self.name == "ANNUAL":
106            return "ANNUAL"
107        else:
108            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 ColumnRole(enum.Enum):
111class ColumnRole(Enum):
112    UNKNOWN = 0
113    VARIABLE = 1
114    GOAL = 2
115    METRIC = 3
116    IDENTIFIER = 4
117
118    def __str__(self):
119        if self.name == "VARIABLE":
120            return "VARIABLE"
121        elif self.name == "GOAL":
122            return "GOAL"
123        elif self.name == "METRIC":
124            return "METRIC"
125        elif self.name == "IDENTIFIER":
126            return "IDENTIFIER"
127        else:
128            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):
131class ColumnSubRole(Enum):
132    UNKNOWN = 0
133    GBI_ID = 1
134    ISIN = 2
135    GVKEY = 3
136    IID = 4
137    SYMBOL = 5
138    COUNTRY = 6
139    CURRENCY = 7
140    DATE = 8
141    COMPANY_NAME = 9
142    REPORT_DATE = 10
143    REPORT_PERIOD = 11
144
145    def __str__(self):
146        if self.name == "GBI_ID":
147            return "GBI_ID"
148        elif self.name == "ISIN":
149            return "ISIN"
150        elif self.name == "GVKEY":
151            return "GVKEY"
152        elif self.name == "IID":
153            return "IID"
154        elif self.name == "SYMBOL":
155            return "SYMBOL"
156        elif self.name == "COUNTRY":
157            return "COUNTRY"
158        elif self.name == "CURRENCY":
159            return "CURRENCY"
160        elif self.name == "DATE":
161            return "DATE"
162        elif self.name == "COMPANY_NAME":
163            return "COMPANY_NAME"
164        elif self.name == "REPORT_DATE":
165            return "REPORT_DATE"
166        elif self.name == "REPORT_PERIOD":
167            return "REPORT_PERIOD"
168        else:
169            return "UNKNOWN"
170
171    def get_match(column_name):
172        def clean_str(name):
173            translation = str.maketrans("", "", string.punctuation)
174            clean_name = name.strip().translate(translation).lower()
175            return re.sub(r"\s+", "", clean_name)
176
177        identifiers = [
178            ColumnSubRole.GBI_ID,
179            ColumnSubRole.ISIN,
180            ColumnSubRole.GVKEY,
181            ColumnSubRole.IID,
182            ColumnSubRole.SYMBOL,
183            ColumnSubRole.COUNTRY,
184            ColumnSubRole.CURRENCY,
185            ColumnSubRole.COMPANY_NAME,
186            ColumnSubRole.REPORT_DATE,
187            ColumnSubRole.REPORT_PERIOD,
188        ]
189
190        for identifier in identifiers:
191            if clean_str(str(identifier)) == clean_str(column_name):
192                return identifier
193        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>
def get_match(column_name):
171    def get_match(column_name):
172        def clean_str(name):
173            translation = str.maketrans("", "", string.punctuation)
174            clean_name = name.strip().translate(translation).lower()
175            return re.sub(r"\s+", "", clean_name)
176
177        identifiers = [
178            ColumnSubRole.GBI_ID,
179            ColumnSubRole.ISIN,
180            ColumnSubRole.GVKEY,
181            ColumnSubRole.IID,
182            ColumnSubRole.SYMBOL,
183            ColumnSubRole.COUNTRY,
184            ColumnSubRole.CURRENCY,
185            ColumnSubRole.COMPANY_NAME,
186            ColumnSubRole.REPORT_DATE,
187            ColumnSubRole.REPORT_PERIOD,
188        ]
189
190        for identifier in identifiers:
191            if clean_str(str(identifier)) == clean_str(column_name):
192                return identifier
193        return None
Inherited Members
enum.Enum
name
value
class ColumnValueType(enum.Enum):
196class ColumnValueType(Enum):
197    UNKNOWN = 0
198    NUMBER = 1
199    STRING = 2
200
201    def __str__(self):
202        if self.name == "UNKNOWN":
203            return "UNKNOWN"
204        elif self.name == "NUMBER":
205            return "NUMBER"
206        elif self.name == "STRING":
207            return "STRING"
208        else:
209            return "UNKNOWN"

An enumeration.

UNKNOWN = <ColumnValueType.UNKNOWN: 0>
NUMBER = <ColumnValueType.NUMBER: 1>
STRING = <ColumnValueType.STRING: 2>
Inherited Members
enum.Enum
name
value
class ColumnConfig:
212class ColumnConfig:
213    def __init__(
214        self,
215        name=None,
216        role=None,
217        sub_role=None,
218        value_type=ColumnValueType.NUMBER,
219        description="",
220        investment_horizon=None,
221    ):
222        self.name = name
223        self.role = role
224        self.sub_role = sub_role
225        self.value_type = value_type
226        # More value_types will be supported in the future.
227        self.description = description
228        self.investment_horizon = investment_horizon
229
230    def __str__(self):
231        return 'Name: "{0}", Role: {1}, SubRole: {2}, Value type: {3}, Desc: {4} IH: {5}.'.format(
232            self.name,
233            self.role,
234            self.sub_role,
235            self.value_type,
236            self.description,
237            self.investment_horizon,
238        )
239
240    def __repr__(self):
241        return self.__str__()
ColumnConfig( name=None, role=None, sub_role=None, value_type=<ColumnValueType.NUMBER: 1>, description='', investment_horizon=None)
213    def __init__(
214        self,
215        name=None,
216        role=None,
217        sub_role=None,
218        value_type=ColumnValueType.NUMBER,
219        description="",
220        investment_horizon=None,
221    ):
222        self.name = name
223        self.role = role
224        self.sub_role = sub_role
225        self.value_type = value_type
226        # More value_types will be supported in the future.
227        self.description = description
228        self.investment_horizon = investment_horizon
name
role
sub_role
value_type
description
investment_horizon
class StrategyConfig:
244class StrategyConfig:
245    def __init__(self, name=None, source_name=None):
246        self.name = name
247        self.source_name = source_name
248
249    def __str__(self):
250        return 'Name: "{0}", Source Name: {1}.'.format(self.name, self.source_name)
251
252    def __repr__(self):
253        return self.__str__()
StrategyConfig(name=None, source_name=None)
245    def __init__(self, name=None, source_name=None):
246        self.name = name
247        self.source_name = source_name
name
source_name
class BoostedDataSetSchemaException(builtins.Exception):
256class BoostedDataSetSchemaException(Exception):
257    def __init__(self, value, data=None):
258        self.value = value
259        self.data = data
260
261    def __str__(self):
262        return repr(self.value)

Common base class for all non-exit exceptions.

BoostedDataSetSchemaException(value, data=None)
257    def __init__(self, value, data=None):
258        self.value = value
259        self.data = data
value
data
Inherited Members
builtins.BaseException
with_traceback
args
class DataSetConfig:
265class DataSetConfig:
266    def __init__(
267        self,
268        name,
269        datasetType=DataSetType.STOCK,
270        datasetSubType=DataSetSubType.DENSE,
271        datasetFrequency=DataSetFrequency.DAILY,
272    ):
273        self.name = name
274        self.type = datasetType
275        self.subtype = datasetSubType
276        self.frequency = datasetFrequency
277        self.columns = []
278        self.strategies = []
279
280    def addColumn(self, columnConfig):
281        self.columns.append(columnConfig)
282
283    def addStrategy(self, strategyConfig):
284        self.strategies.append(strategyConfig)
285
286    def __getColumnByName(self, col_name):
287        for column in self.columns:
288            if col_name == column.name:
289                return column
290        raise BoostedDataSetSchemaException(f"Unable to find column {col_name}")
291
292    def updateColumnToGoal(self, col_name, investment_horizon):
293        column = self.__getColumnByName(col_name)
294        column.role = ColumnRole.GOAL
295        column.investment_horizon = int(investment_horizon)
296
297    def updateColumnToMetric(self, col_name):
298        column = self.__getColumnByName(col_name)
299        column.role = ColumnRole.METRIC
300
301    def updateColumnToVariable(self, col_name):
302        column = self.__getColumnByName(col_name)
303        column.role = ColumnRole.VARIABLE
304
305    def __validateSchema(self):
306        num_goals = 0
307        num_metrics = 0
308        dt = self.type
309        dst = self.subtype
310        dsf = self.frequency
311        if len(self.columns) == 0:
312            msg = "No feature columns exist."
313            raise BoostedDataSetSchemaException(msg)
314
315        if dst in [DataSetSubType.SPARSE_HIST, DataSetSubType.SPARSE_FWD]:
316            if dsf not in [DataSetFrequency.QUARTERLY]:
317                msg = f"{dsf} frequency is not supported for {dst} sub data"
318                raise BoostedDataSetSchemaException(msg)
319            if dt not in [DataSetType.STOCK]:
320                msg = f"{dst} subtype is not supported for {dt} data"
321                raise BoostedDataSetSchemaException(msg)
322
323        for column in self.columns:
324            if column.role == ColumnRole.GOAL:
325                ih = column.investment_horizon
326                gn = column.name
327                if dt == DataSetType.GLOBAL:
328                    msg = f"{dt} data can not have {ColumnRole.GOAL} type"
329                    raise BoostedDataSetSchemaException(msg)
330                if not isinstance(ih, int):
331                    msg = f"Investment horizon for {gn} must be an integer"
332                    raise BoostedDataSetSchemaException(msg)
333                if ih < 1 or ih > 252:
334                    msg = f"Investment horizon must be between 1 and 252 for {gn}"
335                    raise BoostedDataSetSchemaException(msg)
336                num_goals += 1
337            elif column.role == ColumnRole.METRIC:
338                if dt in [DataSetType.GLOBAL, DataSetType.STOCK]:
339                    msg = f"{dt} data can not have {ColumnRole.METRIC} type"
340                    raise BoostedDataSetSchemaException(msg)
341                num_metrics += 1
342
343        if dt == DataSetType.STRATEGY:
344            if num_goals == 0:
345                msg = "Independent data requires at least one goal."
346                raise BoostedDataSetSchemaException(msg)
347            if num_metrics == 0:
348                msg = "Independent data requires at least one metric."
349                raise BoostedDataSetSchemaException(msg)
350            if len(self.strategies) <= 1:
351                msg = "Independent data requires more than 1 strategy"
352                raise BoostedDataSetSchemaException(msg)
353
354    def toDict(self):
355        self.__validateSchema()
356        config = {}
357        config["name"] = self.name
358        config["type"] = str(self.type)
359        config["subType"] = str(self.subtype)
360        config["frequency"] = str(self.frequency)
361        featureList = []
362        for i, f in enumerate(self.columns):
363            fm = {}
364            fm["name"] = f.name
365            fm["description"] = f.description
366            fm["type"] = str(f.role)
367            fm["role"] = str(f.role)
368            fm["subRole"] = str(f.sub_role) if f.sub_role is not None else None
369            fm["valuesType"] = str(f.value_type)
370            fm["columnName"] = f.name
371            fm["investmentHorizon"] = f.investment_horizon
372            featureList.append(fm)
373        config["features"] = featureList
374        strategyList = []
375        for i, s in enumerate(self.strategies):
376            sm = {}
377            sm["name"] = s.name
378            sm["sourceName"] = s.source_name
379            strategyList.append(sm)
380        config["strategies"] = strategyList
381        return config
382
383    def __str__(self):
384        a = ""
385        a += "Name: {0}\n".format(self.name)
386        a += "Columns: \n"
387        for c in self.columns:
388            a += "  {0}\n".format(c)
389        return a
390
391    def __repr__(self):
392        return self.__str__()
393
394    def fromDict(schema):
395        subtype = DataSetSubType[schema.get("subtype", str(DataSetSubType.DENSE))]
396        frequency = DataSetFrequency[schema.get("frequency", str(DataSetFrequency.DAILY))]
397        config = DataSetConfig(
398            schema["name"],
399            datasetType=DataSetType[schema["type"]],
400            datasetSubType=subtype,
401            datasetFrequency=frequency,
402        )
403        # two things left to fill in - strategies and columns
404        for strategy_data in schema["strategies"]:
405            config.addStrategy(
406                StrategyConfig(name=strategy_data["name"], source_name=strategy_data["sourceName"])
407            )
408
409        for feature_data in schema["features"]:
410            config.addColumn(
411                ColumnConfig(
412                    name=feature_data["columnName"],
413                    description=feature_data["description"] or "",
414                    investment_horizon=feature_data["investmentHorizon"],
415                    value_type=ColumnValueType[feature_data["valuesType"]]
416                    if feature_data["valuesType"] is not None
417                    else ColumnValueType.NUMBER,
418                    role=ColumnRole[feature_data["role"]]
419                    if feature_data["role"] is not None
420                    else None,
421                    sub_role=ColumnSubRole[feature_data["subRole"]]
422                    if feature_data["subRole"] is not None
423                    else None,
424                )
425            )
426
427        config.__validateSchema()
428        return config
DataSetConfig( name, datasetType=<DataSetType.STOCK: 1>, datasetSubType=<DataSetSubType.DENSE: 1>, datasetFrequency=<DataSetFrequency.DAILY: 1>)
266    def __init__(
267        self,
268        name,
269        datasetType=DataSetType.STOCK,
270        datasetSubType=DataSetSubType.DENSE,
271        datasetFrequency=DataSetFrequency.DAILY,
272    ):
273        self.name = name
274        self.type = datasetType
275        self.subtype = datasetSubType
276        self.frequency = datasetFrequency
277        self.columns = []
278        self.strategies = []
name
type
subtype
frequency
columns
strategies
def addColumn(self, columnConfig):
280    def addColumn(self, columnConfig):
281        self.columns.append(columnConfig)
def addStrategy(self, strategyConfig):
283    def addStrategy(self, strategyConfig):
284        self.strategies.append(strategyConfig)
def updateColumnToGoal(self, col_name, investment_horizon):
292    def updateColumnToGoal(self, col_name, investment_horizon):
293        column = self.__getColumnByName(col_name)
294        column.role = ColumnRole.GOAL
295        column.investment_horizon = int(investment_horizon)
def updateColumnToMetric(self, col_name):
297    def updateColumnToMetric(self, col_name):
298        column = self.__getColumnByName(col_name)
299        column.role = ColumnRole.METRIC
def updateColumnToVariable(self, col_name):
301    def updateColumnToVariable(self, col_name):
302        column = self.__getColumnByName(col_name)
303        column.role = ColumnRole.VARIABLE
def toDict(self):
354    def toDict(self):
355        self.__validateSchema()
356        config = {}
357        config["name"] = self.name
358        config["type"] = str(self.type)
359        config["subType"] = str(self.subtype)
360        config["frequency"] = str(self.frequency)
361        featureList = []
362        for i, f in enumerate(self.columns):
363            fm = {}
364            fm["name"] = f.name
365            fm["description"] = f.description
366            fm["type"] = str(f.role)
367            fm["role"] = str(f.role)
368            fm["subRole"] = str(f.sub_role) if f.sub_role is not None else None
369            fm["valuesType"] = str(f.value_type)
370            fm["columnName"] = f.name
371            fm["investmentHorizon"] = f.investment_horizon
372            featureList.append(fm)
373        config["features"] = featureList
374        strategyList = []
375        for i, s in enumerate(self.strategies):
376            sm = {}
377            sm["name"] = s.name
378            sm["sourceName"] = s.source_name
379            strategyList.append(sm)
380        config["strategies"] = strategyList
381        return config
def fromDict(schema):
394    def fromDict(schema):
395        subtype = DataSetSubType[schema.get("subtype", str(DataSetSubType.DENSE))]
396        frequency = DataSetFrequency[schema.get("frequency", str(DataSetFrequency.DAILY))]
397        config = DataSetConfig(
398            schema["name"],
399            datasetType=DataSetType[schema["type"]],
400            datasetSubType=subtype,
401            datasetFrequency=frequency,
402        )
403        # two things left to fill in - strategies and columns
404        for strategy_data in schema["strategies"]:
405            config.addStrategy(
406                StrategyConfig(name=strategy_data["name"], source_name=strategy_data["sourceName"])
407            )
408
409        for feature_data in schema["features"]:
410            config.addColumn(
411                ColumnConfig(
412                    name=feature_data["columnName"],
413                    description=feature_data["description"] or "",
414                    investment_horizon=feature_data["investmentHorizon"],
415                    value_type=ColumnValueType[feature_data["valuesType"]]
416                    if feature_data["valuesType"] is not None
417                    else ColumnValueType.NUMBER,
418                    role=ColumnRole[feature_data["role"]]
419                    if feature_data["role"] is not None
420                    else None,
421                    sub_role=ColumnSubRole[feature_data["subRole"]]
422                    if feature_data["subRole"] is not None
423                    else None,
424                )
425            )
426
427        config.__validateSchema()
428        return config
class GbiIdSecurityException(builtins.Exception):
431class GbiIdSecurityException(Exception):
432    def __init__(self, value, data=None):
433        self.value = value
434        self.data = data
435
436    def __str__(self):
437        return repr(self.value)

Common base class for all non-exit exceptions.

GbiIdSecurityException(value, data=None)
432    def __init__(self, value, data=None):
433        self.value = value
434        self.data = data
value
data
Inherited Members
builtins.BaseException
with_traceback
args
class GbiIdTickerISIN:
440class GbiIdTickerISIN:
441    def __init__(self, gbi_id: int, ticker: str, isin: str):
442        self.gbi_id = int(gbi_id)
443        self.ticker = ticker
444        self.isin = isin
445
446    def __str__(self):
447        return "(gbi_id: {0}, ticker: {1}, isin: {2})".format(self.gbi_id, self.ticker, self.isin)
448
449    def __repr__(self):
450        return self.__str__()
GbiIdTickerISIN(gbi_id: int, ticker: str, isin: str)
441    def __init__(self, gbi_id: int, ticker: str, isin: str):
442        self.gbi_id = int(gbi_id)
443        self.ticker = ticker
444        self.isin = isin
gbi_id
ticker
isin
class GbiIdSecurity:
453class GbiIdSecurity:
454    def __init__(self, gbi_id, isin_country_currency_date, ticker, company_name):
455        self.gbi_id = gbi_id
456        self.isin_info = isin_country_currency_date
457        self.ticker = ticker
458        self.company_name = company_name
459
460    def __str__(self):
461        return '\n(gbi_id: "{0}", isin_info: {1}, ticker: {2}, company_name: {3})'.format(
462            self.gbi_id, self.isin_info.__str__(), self.ticker, self.company_name
463        )
464
465    def __repr__(self):
466        return self.__str__()
467
468    def __eq__(self, __o: object) -> bool:
469        return self.gbi_id == __o.gbi_id
470
471    def __hash__(self):
472        return hash(self.gbi_id)
GbiIdSecurity(gbi_id, isin_country_currency_date, ticker, company_name)
454    def __init__(self, gbi_id, isin_country_currency_date, ticker, company_name):
455        self.gbi_id = gbi_id
456        self.isin_info = isin_country_currency_date
457        self.ticker = ticker
458        self.company_name = company_name
gbi_id
isin_info
ticker
company_name
class DateIdentCountryCurrencyException(builtins.Exception):
475class DateIdentCountryCurrencyException(Exception):
476    def __init__(self, value, data=None):
477        self.value = value
478        self.data = data
479
480    def __str__(self):
481        return repr(self.value)

Common base class for all non-exit exceptions.

DateIdentCountryCurrencyException(value, data=None)
476    def __init__(self, value, data=None):
477        self.value = value
478        self.data = data
value
data
Inherited Members
builtins.BaseException
with_traceback
args
class DateIsinCountryCurrencyException(DateIdentCountryCurrencyException):
485class DateIsinCountryCurrencyException(DateIdentCountryCurrencyException):
486    pass

Common base class for all non-exit exceptions.

Inherited Members
DateIdentCountryCurrencyException
DateIdentCountryCurrencyException
value
data
builtins.BaseException
with_traceback
args
class DateIdentCountryCurrency:
489class DateIdentCountryCurrency:
490    def __init__(
491        self,
492        date: str,
493        identifier: str,
494        country: Optional[str] = None,
495        currency: Optional[str] = None,
496        id_type: ColumnSubRole = ColumnSubRole.ISIN,
497    ):
498        self.date = date
499        self.identifier = identifier
500        self.id_type = id_type
501        self.country = country
502        self.currency = currency
503        self.__validateInput()
504
505    def __str__(self):
506        return '(date: "{0}", identifier: "{1}", country: {2}, currency: {3})'.format(
507            self.date, self.identifier, self.country, self.currency
508        )
509
510    def __repr__(self):
511        return self.__str__()
512
513    def __validateInput(self):
514        # simply ensure that at least isin and date are filled out.
515        # country/currency defaults to ANY if not filled out, but
516        # still is not recommended.
517        if self.identifier is None or self.date is None:
518            raise DateIdentCountryCurrencyException(
519                "ISIN or Date not provided while constructing DateIsinCountryCurrency"
520            )
521
522        if self.id_type not in (ColumnSubRole.ISIN, ColumnSubRole.SYMBOL):
523            raise DateIdentCountryCurrencyException(f"Invalid ID Type: {self.id_type}")
524
525        def check_is_str(value, fieldname):
526            if value is not None and not isinstance(value, str):
527                raise DateIdentCountryCurrencyException(
528                    f"Field {fieldname} in DateIsinCountryCurrency was not a string."
529                )
530
531        check_is_str(self.date, "date")
532        check_is_str(self.identifier, "identifier")
533        check_is_str(self.country, "country")
534        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>)
490    def __init__(
491        self,
492        date: str,
493        identifier: str,
494        country: Optional[str] = None,
495        currency: Optional[str] = None,
496        id_type: ColumnSubRole = ColumnSubRole.ISIN,
497    ):
498        self.date = date
499        self.identifier = identifier
500        self.id_type = id_type
501        self.country = country
502        self.currency = currency
503        self.__validateInput()
date
identifier
id_type
country
currency
class DateIsinCountryCurrency(DateIdentCountryCurrency):
538class DateIsinCountryCurrency(DateIdentCountryCurrency):
539    def __init__(self, date, isin, country, currency):
540        super().__init__(date, isin, country, currency, ColumnSubRole.ISIN)
541        self.isin = isin
542
543    def __str__(self):
544        return '(date: "{0}", isin: "{1}", country: {2}, currency: {3})'.format(
545            self.date, self.isin, self.country, self.currency
546        )
DateIsinCountryCurrency(date, isin, country, currency)
539    def __init__(self, date, isin, country, currency):
540        super().__init__(date, isin, country, currency, ColumnSubRole.ISIN)
541        self.isin = isin
isin
class IdentifierToColumnMapping:
549class IdentifierToColumnMapping:
550    """
551    IdentifierToColumnMapping is a mapping from string to boosted.api.api_type.ColumnSubRole.
552    The key of the mapping corresponds to a CSV column name, and the value corresponds
553    to the type of identifier that it maps to. Only columns specifying an identifying factor
554    for the stock needs to be specified (e.g. ISIN, Date, Symbol, Currency)
555    """
556
557    def __init__(self, mapping):
558        self.mapping = mapping
559        self.__validateInput()
560
561    def __str__(self):
562        return repr(self.value)
563
564    def __validateInput(self):
565        def validate_key_val(key, val):
566            if not isinstance(key, str):
567                raise Exception(f"key in IdentifierToColumnMapping {key} was not a string!")
568            if not isinstance(val, ColumnSubRole):
569                raise Exception(f"val in IdentifierToColumnMapping {val} was not a ColumnSubRole!")
570
571        for key, val in self.mapping:
572            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)
557    def __init__(self, mapping):
558        self.mapping = mapping
559        self.__validateInput()
mapping
class PortfolioSettings:
 575class PortfolioSettings:
 576    """
 577    This config is a documentation wrapper around a dict representing the portfolio settings json.
 578    Keys missing in the dict provided will fallback to the listed default when updating the
 579    the portfolio settings on the server.
 580
 581    Parameters
 582    ----------
 583    activeRisk: bool
 584        - **Active Risk**:
 585            Set the portfolio optimization parameters to be relative to the score from the stock
 586            universe. i.e. if you set Momentum to 0.5 to 1.0 and the stock universe has a market cap
 587            weighed score of 0.2 then the optimizer will solve for 0.7 to 1.2.
 588        - Constraints:
 589            Boolean value
 590        - Default Value: false
 591
 592    allowCompanyCollisions: bool
 593        - **Allow Multiple Securities Per Company**:
 594            Allows the machine to trade multiple securities (at the same time) for the same company.
 595            If it is ON and a company has listed PREF shares and COMMON shares, both will show up as
 596            tradeable options for that company. If it is OFF then system will try to determine the
 597            "correct" security to trade based on volume, liquidity, and share type.
 598        - Constraints:
 599            Boolean value
 600        - Default Value: true
 601
 602    allowDR: bool
 603        - **Allow Depositary Receipts**:
 604            Companies with tickers ending in .Y and .F will be disallowed from trading within the
 605            model with this setting off. Turn it on to allow trading in these securities.
 606        - Constraints:
 607            Boolean value
 608        - Default Value: true
 609
 610    benchmark: array
 611        - **Benchmark**:
 612            Set one or more benchmarks and the weighting for each.
 613        - Constraints:
 614            Required.
 615            List of dicts with keys:
 616            gbi_id: integer representing the GBI ID of the benchmark index.
 617            weight: floating point value between 0 and 1.
 618            All gbi_id's in the dictionary must be unique, and the benchmark weights must sum to a
 619            number between 0 and 1.
 620        - Default Value: []
 621
 622    benchmarkAllocation: float
 623        - **Benchmark Allocation**:
 624            None
 625        - Constraints:
 626            Floating point value representing a percentage between -100 and 100
 627        - Default Value: 0
 628
 629    beta_neutral: bool
 630        - **Beta Neutral**:
 631            Adjust the weights to get the ex-ante Beta to be the net exposure of the portfolio. i.e.
 632            70% Long / 30% Short = 0.4 Beta"
 633        - Constraints:
 634            Boolean value
 635        - Default Value: false
 636
 637    bounds_method: str | null
 638        - **Optimizer Bounds**:
 639            Tight: The optimizer will only allow the weightings of securities in your portfolio to
 640            stay tight (close) to their initial weighting.
 641            Loose: The optimizer will allow the weightings of securities in your portfolio to move a
 642            medium amount more from their initial weighting.
 643            Wide: The optimizer will allow almost any weight between the minimum and maximum weight
 644            per long or short position.
 645        - Constraints:
 646            String enum of {loose, tight, wide}, or null
 647        - Default Value: null
 648
 649    compounding: bool
 650        - **Compound Returns**:
 651            When toggled on, this will allow the portfolio to compound returns.
 652        - Constraints:
 653            Boolean value
 654        - Default Value: true
 655
 656    currency: str
 657        - **Currency**:
 658            The currency you would like your portfolio to be calculated in. Note that if the
 659            currency differs from its exchange (i.e. GBP on the S&P), the prior close foreign
 660            exchange rate will be used for calculations.
 661        - Constraints:
 662            String representing a 3 digit ISO currency code.
 663        - Default Value: "USD"
 664
 665    equalWeightBenchmark: bool
 666        - **Equal Weight Benchmark**:
 667            Instead of using the defined benchmarks, set the benchmark to be an equal weighted (per
 668            rebalance period) version of the stock universe.
 669        - Constraints:
 670            Boolean value
 671        - Default Value: false
 672
 673    executionDelay: int
 674        - **Execution Delay**:
 675            This option adds the number of days selected to the trade execution. If you select T+1
 676            and your rebalance period is set to Weekly (Monday), it will trade on Tuesday, and so
 677            on.
 678        - Constraints:
 679            Integer value between 0 and 100, inclusive.
 680        - Default Value: 0
 681
 682    factorNeutralizeSignal: bool
 683        - **Signal Optimizer**:
 684            Turn on optimization at the signal level that will adjust signals and rankings according
 685            to the specifications you set. This is done prior to portfolio construction and can help
 686            reduce bias in the model.
 687        - Constraints:
 688            Boolean value
 689        - Default Value: false
 690
 691    investmentHorizon: int
 692        - **Investment Horizon**:
 693            The investment horizon for the portfolio.
 694        - Constraints:
 695            An integer representing a number of days
 696        - Default Value: 21
 697
 698    lower_turnover: bool
 699        - **Lower Turnover**:
 700            If toggled on the optimizer will try to solve the secondary goal of optimizing the
 701            primary goals while limiting turnover.
 702        - Constraints:
 703            Boolean value
 704        - Default Value: false
 705
 706    marketCapBounds: float
 707        - **Maximum Market Cap Variation**:
 708            How far from the target market cap weighted index each stock can deviate (positive or
 709            negative).
 710        - Constraints:
 711            Floating point value representing a percentage between 0 and 10
 712        - Default Value: 1
 713
 714    marketCapConstraint: bool
 715        - **Constrain Market Cap**:
 716            Force weighting to be near the target weights for a market cap weighted index of your
 717            entire stock universe.
 718        - Constraints:
 719            Boolean value
 720        - Default Value: false
 721
 722    marketCapFlex: float
 723        - **Market Cap Flex**:
 724            How far from the target market cap weighted the stock can deviate relative to the
 725            existing weight (i.e. at 75% a 1% position cannot exceed 1.75%).
 726        - Constraints:
 727            Floating point value representing a percentage between 10 and 200
 728        - Default Value: 100
 729
 730    maxLongPosition: float
 731        - **Max Long Position per Stock**:
 732            The maximum weighting of a long position within the portfolio.
 733        - Constraints:
 734            Required. Floating point value representing a percentage between 0.0 and 300.0
 735        - Default Value: 10.0
 736
 737    maxNumStockLong: int
 738        - **Maximum Number of Longs**:
 739            The maximum number of stocks to buy. This is on a per “portfolio” basis, so if you have
 740            sector neutral turned on it will be maximum per sector.
 741        - Constraints:
 742            Integer value between 0 and 1000, inclusive. Must be >= minNumStockLong
 743        - Default Value: 50
 744
 745    maxNumStockShort: int
 746        - **Maximum Number of Shorts**:
 747            The maximum number of stocks to short. This is on a per “portfolio” basis, so if you
 748            have sector neutral turned on it will be maximum per sector.
 749        - Constraints:
 750            Integer value between 0 and 1000, inclusive. Must be >= minNumStockShort
 751        - Default Value: 50
 752
 753    maxOwnership: float
 754        - **Maximum Ownership**:
 755            The maximum percentage of a company the portfolio is allowed to own. For example, if a
 756            company had a market cap of $100MM and this was 5% the portfolio could not own more than
 757            $5MM of that company.
 758        - Constraints:
 759            Floating point value representing a percentage between 0 and 100
 760        - Default Value: 5
 761
 762    maxShortPosition: float
 763        - **Max Short Position per Stock**:
 764            The maximum weighting of a short position within the portfolio.
 765        - Constraints:
 766            Required. Floating point value representing a percentage between 0.0 and 300.0
 767        - Default Value: 5.0
 768
 769    minimumSharePrice: float
 770        - **Minimum Share Price**:
 771            The minimum share price you want the portfolio to allow.
 772        - Constraints:
 773            Floating point value between 0 and 100000
 774        - Default Value: 2
 775
 776    minLongPosition: float
 777        - **Min Long Position per Stock**:
 778            The minimum weighting of a long position within the portfolio.
 779        - Constraints:
 780            Floating point value representing a percentage between 0.0 and 300.0
 781        - Default Value: 0
 782
 783    minNumStockLong: int
 784        - **Minimum Number of Longs**:
 785            The minimum number of stocks to buy. This is on a per “portfolio” basis, so if you have
 786            sector neutral turned on it will be minimum per sector.
 787        - Constraints:
 788            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockLong
 789        - Default Value: 5
 790
 791    minNumStockShort: int
 792        - **Minimum Number of Shorts**:
 793            The minimum number of stocks to short. This is on a per “portfolio” basis, so if you
 794            have sector neutral turned on it will be minimum per sector.
 795        - Constraints:
 796            Integer value between 0 and 1000, inclusive. Must be <= maxNumStockShort
 797        - Default Value: 5
 798
 799    minShortPosition: float
 800        - **Min Short Position per Stock**:
 801            The minimum weighting of a short position within the portfolio.
 802        - Constraints:
 803            Floating point value representing a percentage between 0.0 and 100.0
 804        - Default Value: 0
 805
 806    missingMarketCap: float
 807        - **Missing Market Cap**:
 808            The amount in millions (MM) to replace any missing market capitalization information
 809            with. This will impact the maximum ownership setting.
 810        - Constraints:
 811            Floating point value between 0 and 100
 812        - Default Value: 10
 813
 814    nameBasedSectorWeighting: bool
 815        - **Name Based Sector Weighting**:
 816            Base sector weightings on number of names. For example, if there are 200 names in the
 817            index and financials has 20 companies, the weight of financials should be 10% (20/200).
 818        - Constraints:
 819            Boolean value
 820        - Default Value: false
 821
 822    netBenchmark: bool
 823        - **Adjust Benchmark for Net Exposure**:
 824            If your portfolio has a net exposure greater or less than 100%, this will adjust the
 825            benchmark returns to match that net exposure.
 826        - Constraints:
 827            Boolean value
 828        - Default Value: false
 829
 830    optimizer: str
 831        - **Optimizer Type**:
 832            No Optimization: None
 833            Reduce Risk: Optimizer designed to reduce portfolio volatility. This tends to result in
 834            the best overall performance.
 835            Maximize Sharpe: Optimizer designed to maximize Sharpe using out of sample estimates for
 836            expected return. Estimates for expected return have an outsized impact on the
 837            performance of this optimizer.
 838            Maximize Alpha: Optimizer designed to maximize expected return based on out-of-sample
 839            estimates for expected return. Estimates for expected return have an outsized impact on
 840            the performance of this optimizer.
 841            Min VaR: Minimize Value at Risk by looking back at the proposed portfolio over the last
 842            year and trying to minimize drawdowns.
 843            Max VaR Sharpe: Maximize Value at Risk Sharpe by looking back at the proposed portfolio
 844            over the last year and trying to maximize Sharpe by observed return for the portfolio vs
 845            observed volatility.
 846            Minimize Skew: Minimize the skew of the portfolio by trying to find a set of portfolio
 847            weightings that results in returns that are closer to the mean.
 848        - Constraints:
 849            Required. String enum: one of
 850            default: No Optimization
 851            min_vol: Reduce Risk
 852            max_sharpe: Maximize Sharpe
 853            max_alpha: Maximize Alpha
 854            min_var: Min VaR
 855            max_var_sharpe: Max VaR Sharpe
 856            min_skew: Minimize Skew
 857        - Default Value: "default"
 858
 859    optimizeTurnover: bool
 860        - **Turnover Optimization**:
 861            Turnover optimization will reduce the turnover at the signal level, making that ranked
 862            list more stable.
 863        - Constraints:
 864            Boolean value
 865        - Default Value: false
 866
 867    pairsTrading: bool
 868        - **Pairs Trading**:
 869            Pairs Trading forces the portfolio to be created by going long / short an equal number
 870            of stocks. The portfolio will try to find an offsetting position for every long by
 871            finding securities that are both highly correlated and have a significant difference in
 872            star rating.
 873        - Constraints:
 874            Boolean value
 875        - Default Value: false
 876
 877    percentPortfolioLong: float
 878        - **Percent of Portfolio Long**:
 879            The exact sum of all long position weightings in the portfolio.
 880        - Constraints:
 881            Required. Floating point value representing a percentage between 0.0 and 300.0
 882        - Default Value: 100.0
 883
 884    percentPortfolioShort: float
 885        - **Percent of Portfolio Short**:
 886            The exact sum of all short position weightings in the portfolio.
 887        - Constraints:
 888            Required. Floating point value representing a percentage between 0.0 and 300.0
 889        - Default Value: 0.0
 890
 891    percentToLong: float
 892        - **Percent to Long**:
 893            The machine will attempt to buy the top X% of stocks, subject to the minimum and maximum
 894            specified. This is on a per “portfolio” basis, so if you have sector neutral turned on
 895            it will be top X% per sector. This will be subject to the maximum and minimum number of
 896            securities you specify below.
 897        - Constraints:
 898            Floating point value representing a percentage between 0 and 100
 899        - Default Value: 20.0
 900
 901    percentToShort: float
 902        - **Percent to Short**:
 903            The machine will attempt to short the bottom X% of stocks, subject to the minimum and
 904            maximum specified. This is on a per “portfolio” basis, so if you have sector neutral
 905            turned on it will be bottom X% per sector. This will be subject to the maximum and
 906            minimum number of securities you specify below.
 907        - Constraints:
 908            Floating point value representing a percentage between 0 and 100
 909        - Default Value: 20.0
 910
 911    portfolioStartingValue: float
 912        - **Portfolio Starting Value**:
 913            The value of the portfolio in MM when it begins trading. If its backtest spans from 2005
 914            - 2020, the starting value in 2005 will be whatever you set here.
 915        - Constraints:
 916            Floating point value between 0 and 100000.
 917        - Default Value: 100.0
 918
 919    priceCleaning: bool
 920        - **Price Cleaning**:
 921            Price Cleaning removes any very large price movements from the securities, specifically
 922            with a daily change greater than +500% or -83.33%. There are some legitimate securities
 923            with moves this large that will be impacted by turning this on, but the advantage is it
 924            prevents these securities from overwhelming the backtest.
 925        - Constraints:
 926            Boolean value
 927        - Default Value: true
 928
 929    priceType: str
 930        - **Price Type**:
 931            None
 932        - Constraints:
 933            Required. String enum of {CLOSE, OPEN, VWAP}
 934        - Default Value: "VWAP"
 935
 936    rebalancePeriod: str
 937        - **Rebalance Period**:
 938            When the machine rebalances the portfolio. You can choose a specific day if you prefer
 939            to execute trades on a specific day.
 940        - Constraints:
 941            Required.
 942            An enum value in the following list:
 943            "daily"
 944            "weekly": Rebalances on every Monday.
 945            Chooses the next available trade date to rebalance when Monday is a holiday
 946            "weekly_wednesday": Rebalances on every Wednesday.
 947            Chooses the next available trade date to rebalance when Wednesday is a holiday
 948            "weekly_friday": Rebalances on every Friday.
 949            Chooses the previous available trade date to rebalance when Friday is a holiday
 950            "monthly"
 951            "quarterly"
 952            OR a string in the following format: "custom_x" where x is an integer between 1 and 252
 953            (days).
 954        - Default Value: "weekly"
 955
 956    sectorNeutral: bool
 957        - **Sector Neutral**:
 958            Will be constructed as a series of portfolios - each matching the market cap weighting
 959            of each of the stock universe sectors. This means that min. and max. number of stocks
 960            "per portfolio" becomes "per sector".
 961        - Constraints:
 962            Boolean value
 963        - Default Value: false
 964
 965    sectorNeutralEqualWeightBenchmark: bool
 966        - **Sector Neutral Equal Weight Benchmark**:
 967            Instead of using the defined benchmarks, set the benchmark to be a "sector neutral"
 968            equal weighted (per rebalance period) version of the stock universe. This takes the
 969            sector weighting and divides by the number of stocks in that sector, so each sector will
 970            have a different individual security weighting.
 971        - Constraints:
 972            Boolean value
 973        - Default Value: false
 974
 975    sectorNeutralSpread: float
 976        - **Sector Neutral Spread**:
 977            The sector neutral spread is how far away from the benchmark sector allocation the
 978            machine can deviate. This is adjusted for net exposure, i.e. if your net exposure is 0%
 979            the target sector allocations will be 0%.
 980        - Constraints:
 981            Floating point value representing a percentage between 0 and 100
 982        - Default Value: 0
 983
 984    signalIndustryNeutral: bool
 985        - **Industry Neutral**:
 986            Make the signal industry neutral, plus or minus the signal sector neutral spread.
 987        - Constraints:
 988            Boolean value
 989        - Default Value: false
 990
 991    signalSectorNeutral: bool
 992        - **Sector Neutral**:
 993            Make the signal sector neutral, plus or minus the signal sector neutral spread.
 994        - Constraints:
 995            Boolean value
 996        - Default Value: false
 997
 998    signalSectorNeutralSpread: float
 999        - **Sector Neutral Spread**:
1000            Make the signal sector neutral, plus or minus the signal sector neutral spread.
1001        - Constraints:
1002            Floating point value representing a percentage between 0 and 100
1003        - Default Value: 1.0
1004
1005    smoothPeriods: int
1006        - **Smooth Periods**:
1007            The number of periods over which to smooth the signals
1008        - Constraints:
1009            Integer value between 1 and 100.
1010        - Default Value: 1
1011
1012    smoothWeighting: str
1013        - **Smooth Weighting Type**:
1014            Alpha Weight: The strength of the signal matters in the smoothing. For example, if a
1015            stock had a score of 5 stars in period A, 2 stars in period B, your 2 period smoothing
1016            would be 7 / 2 = 3.5.
1017            Equal Weight: Only the direction of the signal matters here. For example, if you were 5
1018            stars in period A and 2 stars in period B, your 2 period smoothing would be 0 / 2 = 0
1019            (+1 in period 1 and -1 in period 2)
1020        - Constraints:
1021            One of {alpha_weight, equal_weight}
1022        - Default Value: "alpha_weight"
1023
1024    stopGain: bool
1025        - **Enable Stop Gain**:
1026            Turn on stop gains for the portfolio. This forces sales of securities that have exceeded
1027            the stop gain level. All trades will occur at the next trading time (i.e. next day OPEN
1028            for OPEN price models).
1029        - Constraints:
1030            Boolean value
1031        - Default Value: false
1032
1033    stopGainAmount: float
1034        - **Stop Gain Percentage to Sell**:
1035            The percentage of the position the machine will sell if the stop gain is triggered.
1036        - Constraints:
1037            Floating point value representing a percentage between 1 and 100.
1038        - Default Value: 50.0
1039
1040    stopGainLevel: float
1041        - **Stop Gain Threshold**:
1042            Will sell positions after they hit a threshold. For example, 10% / stop gain, when a
1043            position gains 10% the machine will sell a portion of the position (defined by stop gain
1044            percentage).
1045        - Constraints:
1046            Floating point value representing a percentage between 1 and 100.
1047        - Default Value: 10.0
1048
1049    stopLoss: bool
1050        - **Enable Stop Loss**:
1051            Turn on stop losses for the portfolio. This forces sales of securities that have
1052            exceeded the stop loss level. All trades will occur at the next trading time (i.e. next
1053            day OPEN for OPEN price models).
1054        - Constraints:
1055            Boolean value
1056        - Default Value: false
1057
1058    stopLossAmount: float
1059        - **Stop Loss Percentage to Sell**:
1060            The percentage of the position the machine will sell if the stop loss is triggered.
1061        - Constraints:
1062            Floating point value representing a percentage between 1 and 100.
1063        - Default Value: 50.0
1064
1065    stopLossLevel: float
1066        - **Stop Loss Threshold**:
1067            Will sell positions after they hit a threshold. For example, 10% / stop loss, when a
1068            position loses 10% the machine will sell a portion of the position (defined by stop loss
1069            percentage).
1070        - Constraints:
1071            Floating point value representing a percentage between 1 and 100.
1072        - Default Value: 10.0
1073
1074    stopLossReset: bool
1075        - **Allow multiple stop loss/gain between rebalance days**:
1076            Allows the machine to repeatedly trigger stop losses within a rebalance period. For
1077            example, if you trade Mondays and the stock drops 15% per day during the week and the
1078            stop loss trigger is 10%, then every day will trigger the stop loss. If toggled off the
1079            stop loss can only be triggered once per stock per rebalance period.
1080        - Constraints:
1081            Boolean value
1082        - Default Value: false
1083
1084    trackingConstraint: float
1085        - **Maximum Tracking Error (vs Benchmark)**:
1086            Amount of ex-ante tracking error to constrain the optimizer by.
1087        - Constraints:
1088            Floating point value representing a percentage between 0 and 30
1089        - Default Value: 10
1090
1091    trackingError: bool
1092        - **Allocate Weight to Benchmark**:
1093            Allocate a portion (%) of portfolio to the benchmark
1094        - Constraints:
1095            Boolean value
1096        - Default Value: false
1097
1098    trackingErrorOptimizer: bool
1099        - **Tracking Error**:
1100            If toggled on the optimizer will try to solve for an expected tracking error less than
1101            the amount specified. This is done out-of-sample and the ex post tracking error will
1102            likely be higher.
1103        - Constraints:
1104            Boolean value
1105        - Default Value: false
1106
1107    tradingCost: float
1108        - **Trading Cost**:
1109            A cost that will be applied to every trade.
1110        - Constraints:
1111            Required. Floating point value representing a percentage between 0.0 and 5.0
1112        - Default Value: 0.01
1113
1114    turnoverImportance: float
1115        - **Turnover Importance**:
1116            High importance means the algorithm will aim to keep turnover low, whereas low
1117            importance of turnover will let it vary more.
1118        - Constraints:
1119            Floating point value between 0.2 and 5 nonlinearly mapped onto the 1-9 interval.
1120        - Default Value: 1
1121
1122    useHoldingPeriod: bool
1123        - **Use Holding Period**:
1124            Set any covariance or other calculations within the optimizer to use a holding period
1125            return instead of daily return series
1126        - Constraints:
1127            Boolean value
1128        - Default Value: false
1129
1130    weightingType: str
1131        - **Initial Weighting**:
1132            Alpha Weight: The initial weighting will be done such that higher ranked stocks have a
1133            higher weight.
1134            Equal Weight: The initial weighting will be done such that all stocks selected to be in
1135            the portfolio will have an equal weight.
1136            Market Cap Weight: The initial weighting will be done such that all stocks are weighted
1137            according to their previous day’s market capitalization.
1138        - Constraints:
1139            One of {alpha_weight, equal_weight, market_cap_weight}
1140        - Default Value: "alpha_weight"
1141    """
1142
1143    # note, this is just the collection of portfolio settings
1144    # that is exposed to the API:
1145    # SELECT key, default_value
1146    # FROM portfolio_settings_validation
1147    # WHERE exposed_to_client_api;
1148    # TODO: expand to entire settings? why this way initially?
1149    __default_settings = {
1150        "maxOwnership": "5",
1151        "pairsTrading": "false",
1152        "minNumStockLong": "5",
1153        "stopLossAmount": "50.0",
1154        "stopGainLevel": "10.0",
1155        "smoothWeighting": '"alpha_weight"',
1156        "minimumSharePrice": "2",
1157        "maxShortPosition": "5.0",
1158        "tradingCost": "0.01",
1159        "stopGainAmount": "50.0",
1160        "smoothPeriods": "1",
1161        "executionDelay": "0",
1162        "benchmarkAllocation": "0",
1163        "sectorNeutralSpread": "0",
1164        "percentToLong": "20.0",
1165        "maxNumStockLong": "50",
1166        "trackingConstraint": "10",
1167        "compounding": "true",
1168        "priceCleaning": "true",
1169        "lower_turnover": "false",
1170        "missingMarketCap": "10",
1171        "allowCompanyCollisions": "true",
1172        "allowDR": "true",
1173        "sectorNeutral": "false",
1174        "marketCapFlex": "100",
1175        "marketCapBounds": "1",
1176        "turnoverImportance": "1",
1177        "minNumStockShort": "5",
1178        "maxNumStockShort": "50",
1179        "beta_neutral": "false",
1180        "nameBasedSectorWeighting": "false",
1181        "trackingError": "false",
1182        "stopLoss": "false",
1183        "stopGain": "false",
1184        "stopLossReset": "false",
1185        "netBenchmark": "false",
1186        "equalWeightBenchmark": "false",
1187        "marketCapConstraint": "false",
1188        "signalIndustryNeutral": "false",
1189        "signalSectorNeutral": "false",
1190        "sectorNeutralEqualWeightBenchmark": "false",
1191        "percentToShort": "20.0",
1192        "trackingErrorOptimizer": "false",
1193        "currency": '"USD"',
1194        "rebalancePeriod": '"weekly"',
1195        "benchmark": "[]",
1196        "signalSectorNeutralSpread": "1.0",
1197        "optimizeTurnover": "false",
1198        "minShortPosition": "0",
1199        "minLongPosition": "0",
1200        "percentPortfolioLong": "100.0",
1201        "portfolioStartingValue": "100.0",
1202        "stopLossLevel": "10.0",
1203        "maxLongPosition": "10.0",
1204        "percentPortfolioShort": "0.0",
1205        "bounds_method": None,
1206        "optimizer": '"default"',
1207        "activeRisk": "false",
1208        "useHoldingPeriod": "false",
1209        "weightingType": '"alpha_weight"',
1210        "priceType": '"VWAP"',
1211        "factorNeutralizeSignal": "false",
1212        "investmentHorizon": "21",
1213    }
1214
1215    def __init__(self, settings: Optional[Dict] = None):
1216        # TODO: i'd rather have kwargs for the different keys and then allow
1217        # for per-key overrides, but we start with this more conservative approach
1218        if settings is None:
1219            self.settings = copy.deepcopy(PortfolioSettings.__default_settings)
1220        else:
1221            self.settings = settings
1222
1223    def toDict(self):
1224        return self.settings
1225
1226    def __str__(self):
1227        return json.dumps(self.settings)
1228
1229    def __repr__(self):
1230        return self.__str__()
1231
1232    def fromDict(settings):
1233        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)
1215    def __init__(self, settings: Optional[Dict] = None):
1216        # TODO: i'd rather have kwargs for the different keys and then allow
1217        # for per-key overrides, but we start with this more conservative approach
1218        if settings is None:
1219            self.settings = copy.deepcopy(PortfolioSettings.__default_settings)
1220        else:
1221            self.settings = settings
def toDict(self):
1223    def toDict(self):
1224        return self.settings
def fromDict(settings):
1232    def fromDict(settings):
1233        return PortfolioSettings(settings)
hedge_experiment_type = typing.Literal['HEDGE', 'MIMIC']
class HedgeExperiment:
1240class HedgeExperiment:
1241    id: str  # really a uuid, which we represent as a string
1242    name: str
1243    user_id: str  # see id comment
1244    description: str
1245    experiment_type: hedge_experiment_type  # TODO: enum worth it?
1246    last_calculated: dt.datetime
1247    last_modified: dt.datetime
1248    status: str  # TODO: enum worth it?
1249    portfolio_calculation_status: str  # TODO: enum worth it?
1250    selected_models: List[str]  # see id comment
1251    target_securities: Dict[GbiIdSecurity, float]  # Security is a hashable type because frozen=True
1252    target_portfolios: List[str]
1253    selected_stock_universe_ids: List[str]  # see id comment
1254    baseline_model: Optional[str] = None
1255    baseline_stock_universe_id: Optional[str] = None
1256    baseline_scenario: Optional["HedgeExperimentScenario"] = None
1257
1258    @property
1259    def config(self) -> Dict:
1260        """
1261        Returns a hedge experiment configuration dictionary, ready for JSON-serialization and
1262        submission.
1263        """
1264        weights_by_gbiid = [
1265            {"gbiId": sec.id, "weight": weight} for sec, weight in self.target_securities.items()
1266        ]
1267        return {
1268            "experimentType": self.experiment_type,
1269            "selectedModels": self.selected_models,
1270            "targetSecurities": weights_by_gbiid,
1271            "selectedStockUniverseIds": self._selected_stock_universe_ids,
1272        }
1273
1274    @classmethod
1275    def from_json_dict(cls, d: Dict) -> "HedgeExperiment":
1276        # drafts arent fully populated
1277        if d["status"] == "DRAFT":
1278            weights_by_security = {}
1279            selected_models = []
1280            selected_stock_universe_ids = []
1281        else:
1282            weights_by_security = {
1283                GbiIdSecurity(
1284                    sec_dict["gbiId"],
1285                    None,
1286                    sec_dict["security"]["symbol"],
1287                    sec_dict["security"]["name"],
1288                ): sec_dict["weight"]
1289                for sec_dict in d["targetSecurities"]
1290            }
1291            experiment_config = json.loads(d["config"])
1292            selected_models = experiment_config["selectedModels"]
1293            selected_stock_universe_ids = experiment_config["selectedStockUniverseIds"]
1294        baseline_scenario = None
1295        if d["baselineScenario"] is not None:
1296            baseline_scenario = HedgeExperimentScenario.from_json_dict(d["baselineScenario"])
1297        return cls(
1298            d["hedgeExperimentId"],
1299            d["experimentName"],
1300            d["userId"],
1301            d["description"],
1302            d["experimentType"],
1303            d["lastCalculated"],
1304            d["lastModified"],
1305            d["status"],
1306            d["portfolioCalcStatus"],
1307            selected_models,
1308            weights_by_security,
1309            d.get("targetPortfolios"),
1310            selected_stock_universe_ids or [],
1311            d.get("baselineModel"),
1312            d.get("baselineStockUniverseId"),
1313            baseline_scenario,
1314        )
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:
1274    @classmethod
1275    def from_json_dict(cls, d: Dict) -> "HedgeExperiment":
1276        # drafts arent fully populated
1277        if d["status"] == "DRAFT":
1278            weights_by_security = {}
1279            selected_models = []
1280            selected_stock_universe_ids = []
1281        else:
1282            weights_by_security = {
1283                GbiIdSecurity(
1284                    sec_dict["gbiId"],
1285                    None,
1286                    sec_dict["security"]["symbol"],
1287                    sec_dict["security"]["name"],
1288                ): sec_dict["weight"]
1289                for sec_dict in d["targetSecurities"]
1290            }
1291            experiment_config = json.loads(d["config"])
1292            selected_models = experiment_config["selectedModels"]
1293            selected_stock_universe_ids = experiment_config["selectedStockUniverseIds"]
1294        baseline_scenario = None
1295        if d["baselineScenario"] is not None:
1296            baseline_scenario = HedgeExperimentScenario.from_json_dict(d["baselineScenario"])
1297        return cls(
1298            d["hedgeExperimentId"],
1299            d["experimentName"],
1300            d["userId"],
1301            d["description"],
1302            d["experimentType"],
1303            d["lastCalculated"],
1304            d["lastModified"],
1305            d["status"],
1306            d["portfolioCalcStatus"],
1307            selected_models,
1308            weights_by_security,
1309            d.get("targetPortfolios"),
1310            selected_stock_universe_ids or [],
1311            d.get("baselineModel"),
1312            d.get("baselineStockUniverseId"),
1313            baseline_scenario,
1314        )
class HedgeExperimentScenario:
1318class HedgeExperimentScenario:
1319    id: str  # really a uuid, which we represent as a string;
1320    name: str
1321    description: str
1322    status: str  # TODO: enum worth it?
1323    settings: PortfolioSettings
1324    summary: pd.DataFrame
1325    # we currently a portfolios field. they get wrapped up into the summary table
1326
1327    @classmethod
1328    def from_json_dict(cls, d: Dict) -> "HedgeExperimentScenario":
1329        return cls(
1330            d["hedgeExperimentScenarioId"],
1331            d["scenarioName"],
1332            d["description"],
1333            d.get("status"),
1334            PortfolioSettings(json.loads(d["portfolioSettingsJson"])),
1335            HedgeExperimentScenario._build_portfolio_summary(d.get("hedgeExperimentPortfolios")),
1336        )
1337
1338    @staticmethod
1339    def _build_portfolio_summary(portfolios: Dict) -> pd.DataFrame:
1340        # this happens for added scenarios that havent run yet
1341        if portfolios is None:
1342            return pd.DataFrame()
1343
1344        df_dict = defaultdict(list)
1345        for pf in portfolios:
1346            p = pf["portfolio"]
1347
1348            df_dict["portfolio_id"].append(p["id"])
1349            df_dict["scenario_name"].append(p["name"])
1350            df_dict["model_id"].append(p["modelId"])
1351            df_dict["status"].append(p["status"])
1352
1353            if p["status"] != "READY":  # data isn't yet available
1354                df_dict["1M"].append(np.nan)
1355                df_dict["3M"].append(np.nan)
1356                df_dict["1Y"].append(np.nan)
1357                df_dict["sharpe"].append(np.nan)
1358                df_dict["vol"].append(np.nan)
1359            else:
1360                # NOTE:
1361                # these are the magic indices/keys of these values; inspect passed arg to see
1362                # if upstream changes the order of (sub-)elements in the response...uh oh
1363                # TODO: specific key search? wrap with try/except and fail loudly?
1364                df_dict["1M"].append(p["performanceGrid"][0][4])
1365                df_dict["3M"].append(p["performanceGrid"][0][5])
1366                df_dict["1Y"].append(p["performanceGrid"][0][7])
1367                df_dict["sharpe"].append(p["tearSheet"][0]["members"][1]["value"])
1368                pf_stddev = p["tearSheet"][1]["members"][1]["value"]
1369                bnchmk_stddev = p["tearSheet"][1]["members"][2]["value"]
1370                df_dict["vol"].append((pf_stddev - bnchmk_stddev) * 100)
1371                # TODO: this is how it is done on the front-end,
1372                # should be pulled out of both here and there
1373
1374        df = pd.DataFrame(df_dict)
1375        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:
1327    @classmethod
1328    def from_json_dict(cls, d: Dict) -> "HedgeExperimentScenario":
1329        return cls(
1330            d["hedgeExperimentScenarioId"],
1331            d["scenarioName"],
1332            d["description"],
1333            d.get("status"),
1334            PortfolioSettings(json.loads(d["portfolioSettingsJson"])),
1335            HedgeExperimentScenario._build_portfolio_summary(d.get("hedgeExperimentPortfolios")),
1336        )
class HedgeExperimentDetails:
1379class HedgeExperimentDetails:
1380    # this will probably lead to violations of the law of demeter, but it's easy to do right now,
1381    # particularly for an unproven API
1382    experiment: HedgeExperiment
1383    scenarios: Dict[str, HedgeExperimentScenario]
1384
1385    @property
1386    def summary(self) -> pd.DataFrame:
1387        """
1388        Returns a dataframe corresponding to the Hedge Experiment "flat view" - all portfolio
1389        options are displayed
1390        """
1391        if len(self.scenarios) == 0 or all(s.status != "READY" for s in self.scenarios.values()):
1392            return pd.DataFrame()
1393        return pd.concat(scenario.summary for scenario in self.scenarios.values()).sort_values(
1394            ["scenario_name"]
1395        )  # sort for stable presentation
1396
1397    @classmethod
1398    def from_json_dict(cls, d: Dict) -> "HedgeExperimentDetails":
1399        he = HedgeExperiment.from_json_dict(d)
1400        scenarios = {
1401            scenario_dict["hedgeExperimentScenarioId"]: HedgeExperimentScenario.from_json_dict(
1402                scenario_dict
1403            )
1404            for scenario_dict in d["hedgeExperimentScenarios"]
1405        }
1406        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:
1397    @classmethod
1398    def from_json_dict(cls, d: Dict) -> "HedgeExperimentDetails":
1399        he = HedgeExperiment.from_json_dict(d)
1400        scenarios = {
1401            scenario_dict["hedgeExperimentScenarioId"]: HedgeExperimentScenario.from_json_dict(
1402                scenario_dict
1403            )
1404            for scenario_dict in d["hedgeExperimentScenarios"]
1405        }
1406        return cls(he, scenarios)