When creating a custom basket, Marquee by default will publish up to five years of pricing and composition history based
on the composition entered during creation. If you choose to bypass this behavior by setting the default_backcast
parameter to False
, you may upload your own custom history any time after the basket has been created. This tutorial
will show you how to submit a basket backcast, including best practices to avoid mapping errors, and more!
Note: You must specify this setting during creation in order to perform the below steps. If you'd like to submit custom history for a basket that has already been created and did not originally select this option, you'll need to create a new basket.
First you will import the necessary modules and add your client id and client secret.
import datetime as dt
from gs_quant.api.gs.reports import GsReportApi
from gs_quant.markets.baskets import Basket
from gs_quant.markets.position_set import Currency, Position, PositionSet, PositionSetWeightingStrategy
from gs_quant.session import Environment, GsSession
client = 'CLIENT ID'
secret = 'CLIENT SECRET'
GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('modify_product_data',))
Next you'll need to create a PositionSet for each date your basket had position changes historically. Examples of position changes are anything that affect the price or composition of your basket. For example, periodic weighted rebalances, identifier changes or other corporate actions, adds/drops, etc.
When Marquee goes to upload your history, we'll convert your position weights into quantities and carry those forward until the next date with position changes occurs. If you do not have position weights and only have quantities at the time of upload, make sure to follow step #4.
For the sake of simplicity in this tutorial, we'll show a very basic example of position sets that may represent a basket's composition history. However based on your own personal Input/Output preferences and setup, you'll likely want to make some adjustments in order to aggregate your positions into unique PositionSet objects per date. See position_set examples for alternate methods.
positions = [Position(identifier='AAPL UW', weight=0.5), Position(identifier='MSFT UW', weight=0.5)]
pos_set_1 = PositionSet(positions, dt.date(2021, 6, 3))
pos_set_2 = PositionSet(positions, dt.date(2022, 1, 2))
pos_set_3 = PositionSet(positions, dt.date(2022, 6, 1))
pos_set_4 = PositionSet(positions, dt.date(2023, 1, 4))
position_sets = [pos_set_1, pos_set_2, pos_set_3, pos_set_4]
Now that you have your position sets created, it's time to resolve your positions historically. This will help confirm that each asset identifier can be mapped as of the provided position date.
If you face any mapping issues, double check that your identifiers are valid. Consider identifier changes (e.g., FB UW -> META UW), listed status, IPOs, etc. If you believe you have corrected all assets with these issues and are still experiencing problems mapping your positions, you may remove these identifiers or email the baskets support team for assistance.
for position_set in position_sets:
position_set.resolve()
if position_set.unresolved_positions is not None and len(position_set.unresolved_positions):
print(f'Error resolving assets on {position_set.date.strftime("%Y-%m-%d")}: {position_set.unresolved_positions}')
""" Uncomment the below to removed unresolved positions from your position set """
# position_set.remove_unresolved_positions()
Basket backcasts require each position to have a specified weight. If you only have historical quantities, you may
call the price
function on each position set in order to extract these weights via Marquee. Otherwise you may skip
this step.
Similar to step #3, you can double check if any assets are unable to be priced and either remove these from your position set or contact the baskets support team for help.
currency = Currency.USD # replace with desired currency
for position_set in position_sets:
position_set.price(currency=currency, use_unadjusted_close_price=False, weighting_strategy=PositionSetWeightingStrategy.Quantity)
if position_set.unpriced_positions is not None and len(position_set.unpriced_positions):
print(f'Error pricing assets on {position_set.date.strftime("%Y-%m-%d")}: {position_set.unpriced_positions}')
""" Uncomment the below to removed unpriced positions from your position set """
# position_set.remove_unpriced_positions()
If you had to remove positions from any position set for one of the reasons described above, the total weight of
each position might no longer add up to 1. If this is the case, you can call redistribute_weights
on any misweighted
position sets, which will redistribute the remaining weights proportionally among each position.
for position_set in position_sets:
position_set.redistribute_weights()
Once your positions have all been mapped and assigned weights, you're ready to submit to Marquee! Fetch your basket
and call upload_position_history
using the position sets from above.
basket = Basket.get('GSMBXXXX')
basket.upload_position_history(position_sets)
There are several cases that can cause a backcast report to fail, including long history length, large number of positions/rebalance frequency, historical errors for assets in Marquee, and more. The above script will catch as many issues as possible prior to upload but there are sometimes still failures that require investigation from the support team to fix.
If you find that your backcasts are failing, you can reschedule any failed report jobs using the below snippet to catch for intermittent errors such as timeouts or memory issues on Marquee side. If the errors persist, you may reach out to the baskets support team for further information.
If your backcast reports are failing due a non-Marquee issue and you need to re-upload your position history, you'll need to cancel any failed report jobs in order to resubmit your adjusted positions. Then you may repeat steps #1-5.
backcast_reports = GsReportApi.get_reports(position_source_id=basket.get_marquee_id(), report_type='Basket Backcast')
report_ids, report_job_ids = [r.id for r in backcast_reports], []
for report_id in report_ids:
report_jobs = GsReportApi.get_report_jobs(report_id)
report_job_ids = [rj.get('id') for rj in report_jobs if rj.get('status') == 'error']
""" Use this to RESCHEDULE failed report jobs """
# for report_job in report_job_ids:
# print(f'Rescheduling report job {report_job.get("id")}')
# GsReportApi.reschedule_report_job(report_job.get('id'))
""" Use this to CANCEL failed report jobs """
# for report_job in report_job_ids:
# print(f'Cancelling report job {report_job.get("id")}')
# GsReportApi.cancel_report_job(report_job.get('id'))