Overview

The Portfolio Optimizer endpoints are used to generate a proposed set of trades based on universe, allocation, average value, and diversification constraints, as well as retrieve the status and orders in the optimization.

Key Concepts

Portfolio Optimizer

The Optimizer is the core engine behind the Portfolio Optimizer API. It takes three inputs:

  1. The investor's target strategy. Strategies include universe constraints, allocation constraints, average value constraints, and diversification constraints.
  2. The investor's profile. This includes the investor's account ID, state of residence, marginal federal tax rate, and marginal state tax rate.
  3. The investor's current portfolio. If the investor is building a new portfolio, the investor's current portfolio is simply cash.

Based on these inputs and the live, executable quotes available through Moment's EMS, the Optimizer produces a proposed Basket of trades to fill the portfolio in alignment with the target strategy. After the Optimizer has finished running, the API user can submit the proposed Basket for execution via the Execution Management System.

Proposed Orders

The result of running the Optimizer is a set of proposed orders to build or rebalance the investor's profile. When the Optimizer has finished running, the API user can view the proposed orders via the Get Optimization Orders Endpoint. After validating the orders, the user can submit the orders to the Execution Management System via FIX or REST.

Strategies

A Strategy represents the investment strategy that the Optimizer should use to build the investor's portfolio. Every strategy consists of one or more constraints, of which there are four varieties.

Constraints

There are several types of constraints: universe, allocation, average value, diversification, and ladder policy. We'll use this section to elaborate on the purpose of each constraint and provide examples.

Universe Constraints

Universe constraints are constraints applied to the entire universe; the generated portfolio must follow all provided universe constraints. For example, if we wanted to generate a portfolio with only municipal bonds graded higher than BBB that mature within the next eight years (assuming an investment date of June 1st, 2024), we would use the following universe constraint:

{
  "type": "universe",
  "filter": {
    "type": {
      "equal_to": "municipal"
    },
    "sp_rating": {
      "greater_than": "BBB"
    },
    "maturity_date": {
      "less_than_or_equal_to": "2032-06-01"
    }
  }
}

Allocation Constraints

Allocation constraints specify that target a specific weight in the portfolio for bonds that meet pre-specified criteria. An allocation constraint includes a set of filters that define the criteria, as well as a minimum, maximum, and target weight for bonds meeting the criteria. For example, if we wanted to generate a portfolio targeting 70% allocation towards municipals maturing in the 2030s, we would use the following constraint:

{
  "type": "allocation",
  "filter": {
    "type": {
      "equal_to": "municipal"
    },
    "maturity_date": {
      "greater_than_or_equal_to": "2030-01-01",
      "less_than": "2040-01-01"
    }
  },
  "min_weight": 0.60,
  "max_weight": 0.80,
  "target_weight": 0.70
}

We might also want to specify our allocation constraint in terms of market value instead of portfolio weight; to do so, we would instead provide the fields min_market_value, max_market_value, and target_market_value.

Also, note that universe constraints are simply allocation constraints with a minimum weight, maximum weight, and target weight of 1.

Average Value Constraints

Average value constraints allow users to target a specific duration, rating, coupon, or any other field across the portfolio. Average value constraints are applied to the entire generated portfolio, guiding the portfolio towards the specified target_average_value. For example, if we wanted to generate a portfolio with an average S&P rating of "A" and an average coupon of 7:

{
  "type": "average_value",
  "field": "sp_rating",
  "target_average_value": "A",
  "min_average_value": "A-",
  "max_average_value": "AAA+"
},
{
  "type": "average_value",
  "field": "coupon",
  "target_average_value": 7,
  "min_average_value": 5,
  "max_average_value": 9.5
}

Diversification Constraints

Diversification constraints are applied to the entire generated portfolio, requiring that each unique value of a field have no more than a pre-specified weight in the portfolio. For example: if we wanted to generate a portfolio with any instrument taking up at most 5% of the portfolio and with any sector taking up at least 5% and at most 15% of the portfolio:

{
  "type": "diversification",
  "field": "isin",
  "max_weight_per_unique_value": 0.05
},
{
  "type": "diversification",
  "field": "sector",
  "max_weight_per_unique_value": 0.15,
  "min_weight_per_unique_value": 0.05
}

Note that min_weight_per_unique_value is optional, and setting it too high can be detrimental to optimization performance depending on available liquidity.

Similarly to allocation constraints, we might also specify these parameters in terms of market value by setting max_market_value_per_unique_value and min_market_value_per_unique_value.

Ladder Policies

Ladder policies allow us to control the maturity buckets for our portfolio. For example: if we want to generate a 10 year treasury ladder, with an equal distribution of treasuries maturing in each of the next 10 years, we would use the following constraints:

{
  "type": "ladder_policy",
  "period_length": 12, // months
  "start_period": 0,
  "end_period": 10,
  "grace_period_months": 1,
  "weight_tolerance": 0.05
},
{
  "type": "universe",
  "filter": {
    "type": {
      "equal_to": "treasury"
    }
  }
}

The weight_tolerance is a constraint representing how far in either direction each bucket's allocation can deviate from the target equal distribution. Similarly to other constraints, we may specify a market_value_tolerance instead.

Moment supports dynamic ladders, which start on a defined date and automatically transitions to new rungs as old rungs mature. To define the start date, specify the assignment_date at the account level. The rungs will always be set relative to the assignment_date, but dynamically adds the (n+1)th rung depending on the circumstances (where n = number of periods).

  • When the portfolio optimizer is ran and the current date is less than assignment_date + grace_period_months, the ladder will not be altered. For example, if the assignment_date=2024-01-01 and grace_period_months=3, as long as today <= 2024-04-01, the ladder will be set relative to 2024-01-01.
  • When the portfolio optimizer is ran and the current date is greater than assignment_date + grace_period_months, the first rung and the (n+1)th rung will be weighted equal to all the other rungs (where n = number of periods). Let's consider a few examples; if the assigment_date=2024-01-01, grace_period_months=3, period_length=12, start_period=0 and end_period=3:
    • If today=2024-05-10, then we have the following equally weighted buckets:
      • (2024-05-10 to 2025-01-01) + (2027-01-01 to 2028-01-01)
      • 2025-01-01 to 2026-01-01
      • 2026-01-01 to 2027-01-01
    • If today=2030-09-01, then we have the following equally weighted buckets:
      • 2030-09-01 to 2031-01-01 + 2033-01-01 to 2034-01-01
      • 2031-01-01 to 2032-01-01
      • 2032-01-01 to 2033-01-01

Under-the-hood, a ladder policy is translated into a universe constraint for the full lifecycle of the ladder, as well as separate allocation constraints representing each of the rungs of the ladder.

Note: constraint priorities

We are able to set constraint_priority on average value, allocation, diversification, and ladder policies! This must take one of the following values: 1, 2, or 3. These constraint priorities determine how strict the constraints are -- for example, a diversification constraint with a constraint_priority of 3 is effectively a hard constraint, meaning the generated portfolio should always turn out a portfolio following the diversification constraint.

{
  "type": "diversification",
  "field": "isin",
  "max_weight_per_unique_value": 0.05,
  "constraint_priority": 3
}

The optimizer will always attempt to generate a portfolio that strictly follows all of the constraints. However, if it is unable to do so, it will begin to relax the constraints, in order of ascending constraint_priority (unless it's equal to 3).

Filters

Both universe constraints and allocation constraints use filters, which are a set of boolean operators that will be all applied. The following operators are available:

  • greater_than
  • greater_than_or_equal_to
  • less_than
  • less_than_or_equal_to
  • equal_to
  • not_equal_to
  • in_list
  • not_in_list

These filters can be combined and an instrument must satisfy all of the filters. For example:

{
  ...
  "filter": {
    "coupon": {
      "greater_than_or_equal_to": 5,
      "less_than": 8
    },
    "sp_rating": {
      "in_list": ["A", "AA", "AAA"]
    },
    "duration": {
      "greater_than": 4.5
    },
    "type": {
      "not_in_list": ["corporate"]
    }
  } 
  ...
}

This will restrict the universe to bonds with a coupon between 5 and 8 (not inclusive of 8), and an S&P rating that is either "A", "AA", or "AAA", and a duration greater than 4.5, and we exclude corporate bonds.

The available fields to filter on, includes:

  • All reference data fields, such as sp_rating, duration, coupon, maturity_date, issuer, sector, and more.
  • All live inventory fields, which includes the following: order_price, order_ytw, fixed_transaction_cost_in_price, fixed_transaction_cost_in_yield.

Finally, we also allow for a combinations of or filters to be applied; meaning we will filter for orders that satisfy any of the or conditions. For example:

{
	...
  "filter": {
  	"or": [
      {
        "coupon": {
        	"greater_than_or_equal_to": 5,
          "less_than": 8
        }
      },
      {
      	"sp_rating": {
        	"in_list": ["A", "AA", "AAA"]
        }
      },
      {
      	"or_list": [
          {
          	"duration": {
            	"greater_than": 4.5
            },
            "type": {
            	"in_list": ["corporate"]
            }
          },
          {
          	"type": {
            	"not_in_list": ["corporate"]
            },
            "order_ytw": {
            	"greater_than_or_equal_to": 5.0
            }
          }
        ],
        "sp_rating": {
        	"less_than": "A"
        }
      }
    ]
  }
}

This will filter for one of the following:

  • Bonds with a coupon greater than or equal to 5, and less than 8.
  • Bonds with an S&P rating that is either "A", "AA", or "AAA".
  • Bonds with an S&P rating that is less than "A", and is either:
    • A corporate bond, with duration greater than 4.5.
    • A non-corporate bond, with an order yield-to-worst greater than or equal to 5.

Filter Fields

Available Reference Data Fields

Users of the API can create filters using any of the following reference data fields:
• accrued_interest
• ai_issuer
• bank_qualified
• bond_warrant
• call_frequency
• call_notification_convention
• call_notification_max
• call_notification_min
• call_type
• callable
• close_price
• close_price_asof
• close_ytw
• continuously_callable_flag
• convertible
• country_domicile
• country_issue
• coupon
• coupon_frequency
• coupon_type
• currency
• cusip
• dated_date
• daycount
• description
• description_short
• duration
• fdic_insured
• first_coupon_date
• has_extraordinary_call
• insured
• is_144a
• isin
• issue_date
• issue_minimum
• issue_price
• issue_size
• issuer
• last_coupon_date
• liquidity_institutional_aggregate
• liquidity_institutional_buy
• liquidity_institutional_sell
• liquidity_micro_aggregate
• liquidity_micro_buy
• liquidity_micro_sell
• liquidity_retail_aggregate
• liquidity_retail_buy
• liquidity_retail_sell
• maturity_date
• municipal_state
• municipal_taxable_federal
• municipal_type
• next_call_date
• next_call_price
• next_coupon_date
• par_value
• perpetual
• private_placement
• purpose
• puttable
• reg_s
• sector
• security_code
• seniority
• settlement_convention
• sinking_fund
• sp_creditwatch
• sp_creditwatch_date
• sp_outlook
• sp_outlook_date
• sp_rating
• sp_rating_date
• status
• ticker
• trace_eligible
• treasury_subtype
• type
• updated_at
• use_of_proceeds
• is_amt_applicable
• is_state_tax_applicable

Available Quote Fields

Users can also create filters using quote-related fields, including:

• order_price
• order_ytw
• tax_effective_limit_ytw
• fixed_transaction_cost_in_price
• fixed_transaction_cost_in_yield

Examples

0-5 Year Treasury Ladder (using a ladder policy)

{
  "accounts": [
    {
      "account_id": "0",
      "federal_tax_rate": 0.37,
      "state_tax_rate": 0.0,
      "cash": 1000000.0,
      "state": "FL",
      "holdings": [],
      "risk_group_id": null,
      "strategy": {
        "schema_version": 1,
        "constraints": [
          {
            "type": "universe",
            "filter": {
              "treasury_subtype": {
                "in_list": ["bill", "note", "bond"]
              }
            }
          },
          {
            "type": "diversification",
            "field": "isin",
            "min_weight_per_unique_value": 0.05,
            "max_weight_per_unique_value": 0.1
          },
          {
            "type": "ladder_policy",
            "period_length": 12, // months,
            "start_period": 0,
            "end_period": 10,
            "weight_tolerance": 0.05
          }
        ],
        "cash_buffer": 0
      }
    }
  ]
}

0-5 Year Treasury Ladder (without using a ladder policy)

{
  "accounts": [
    {
      "account_id": "0",
      "federal_tax_rate": 0.37,
      "state_tax_rate": 0.0,
      "cash": 1000000.0,
      "state": "FL",
      "holdings": [],
      "risk_group_id": null,
      "strategy": {
        "schema_version": 1,
        "constraints": [
          {
            "type": "universe",
            "filter": {
              "treasury_subtype": {
                "in_list": ["bill", "note", "bond"]
              }
            }
          },
          {
            "type": "diversification",
            "field": "isin",
            "min_weight_per_unique_value": 0.05,
            "max_weight_per_unique_value": 0.1
          },
          {
            "type": "allocation",
            "filter": {
              "maturity_date": {
                "greater_than": "2025-01-27",
                "less_than_or_equal_to": "2025-07-27"
              }
            },
            "min_weight": 0.19,
            "target_weight": 0.2,
            "max_weight": 0.21
          },
          {
            "type": "allocation",
            "filter": {
              "maturity_date": {
                "greater_than": "2025-07-27",
                "less_than_or_equal_to": "2026-07-27"
              }
            },
            "min_weight": 0.19,
            "target_weight": 0.2,
            "max_weight": 0.21
          },
          {
            "type": "allocation",
            "filter": {
              "maturity_date": {
                "greater_than": "2026-07-27",
                "less_than_or_equal_to": "2027-07-27"
              }
            },
            "min_weight": 0.19,
            "target_weight": 0.2,
            "max_weight": 0.21
          },
          {
            "type": "allocation",
            "filter": {
              "maturity_date": {
                "greater_than": "2027-07-27",
                "less_than_or_equal_to": "2028-07-27"
              }
            },
            "min_weight": 0.19,
            "target_weight": 0.2,
            "max_weight": 0.21
          },
          {
            "type": "allocation",
            "filter": {
              "maturity_date": {
                "greater_than": "2028-07-27",
                "less_than_or_equal_to": "2029-07-27"
              }
            },
            "min_weight": 0.19,
            "target_weight": 0.2,
            "max_weight": 0.21
          }
        ],
        "cash_buffer": 0
      }
    }
  ]
}