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