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