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