Investment restriction
Turnover and trading-frequency limit
A turnover limit caps how much of a portfolio is traded in a period, usually the lesser of purchases and sales over portfolio value. A trading-frequency limit caps the number of executions instead. Acclimetry measures either from the transaction record over a rolling window, such as ‘annual turnover below 50%’ tested over 365 days.
turnover_limitTurnover / trading-frequency limit
How policies word it
Turnover clauses control trading costs and keep a manager to the agreed style. They appear in investment management agreements and in policies that want a long-term, low-cost approach:
- Turnover: annual portfolio turnover is expected to stay below 50%.
- Purchases: purchases in any 12 months may not exceed 30% of the portfolio.
- Frequency: no more than 20 trades a quarter.
‘Expected to stay below’ is softer than ‘may not exceed’. The rule's author records whether such a clause is a limit at all; a soft expectation may fit better as a manual obligation.
- Annual portfolio turnover is expected to remain below 50%.
- Purchases in any rolling 12-month period may not exceed 30% of the portfolio.
- The manager will execute no more than 20 trades in any quarter.
- Turnover, measured as the lesser of purchases and sales, may not exceed 40% in any 12 months.
How it is measured
Turnover is read from the transaction record, never from changes in positions. The window is a rolling number of days ending at the evaluation date.
- Turnover ratio: the lesser of purchases and sales in the window, over NAV.
- Purchase ratio: purchases in the window, over NAV.
- Trade count: the number of executions in the window.
Only purchases and sales count by default. Subscriptions, redemptions, internal transfers and corporate actions are categorised by the organisation and left out unless the rule names them.
The divisor is the NAV at the evaluation date. Many published turnover figures divide by average assets over the period instead, so the two can differ when NAV has moved.
When it is breached and what follows
A ratio or count above the hard maximum is a breach. Because the window rolls, a burst of trading stays in the measure until it ages out.
An execution with an unmapped type string withholds a compliant reading, because nobody yet knows whether it is trading. If the counted trading alone already exceeds the limit, the breach stands.
Breaches join a case with assignment, escalation and service-level clocks. A planned restructuring, such as a change of manager, can be covered in advance by a time-bound waiver.
Data the check reads
- The transaction record for the window: date, amount and direction of each execution
- The organisation's mapping of transaction type strings to categories
- The window in days and the categories the rule counts
- Market values for every position, for NAV
Rule parameters
Portfolio turnover or execution count over a governed window, measured from the transaction record.
| Parameter | In the platform | Options and help |
|---|---|---|
| measure | Measure | |
| windowDays | Window (days) | |
| countedCategories | Categories counted | Purchases and sales by default. Subscriptions, redemptions, internal transfers and corporate actions are categorised under Organisation ▸ Transaction categories and left out unless named here. A type string nobody has mapped withholds the compliant reading until it is mapped. |
Bounds the rule must set: hardMax
Methodology choices
- turnover_ratio — lesser of purchases and sales over NAV.
- purchase_ratio — purchases over NAV.
- trade_count — executions in the window.
Parameter names, methodology text and result-code meanings are shown exactly as the platform states them.
Worked examples, run by the engine
Purchases of 300 and sales of 200 in a 90-day window over a 1,000 book: two-sided turnover takes the lesser, 20%, inside a 25% cap
Rule template:turnover_limit measure: turnover_ratiowindowDays: 90
Bounds: hard max 25%
| Holding | Market value |
|---|---|
| A | 600 |
| B | 400 |
| Date | Type | Amount |
|---|---|---|
| 10 May 2026 | buy | 300 |
| 20 May 2026 | sell | 200 |
| Group | Status | Value | Bounds | Headroom to the hard bound | Code |
|---|---|---|---|---|---|
| Turnover (90d) | Compliant | 20% | ≤ 25% | 5% | WITHIN_LIMITS |
WITHIN_LIMITS: The measured value sits inside every approved bound.
These are the product's own test cases for this template, evaluated by the Acclimetry rule engine (backend commit 8197063). The build fails if any result changes.
Three executions inside a 90-day window breach a limit of two; the one dated before the window is not counted
Rule template:turnover_limit measure: trade_countwindowDays: 90
Bounds: hard max 2%
| Holding | Market value |
|---|---|
| A | 1,000 |
| Date | Type | Amount |
|---|---|---|
| 10 May 2026 | buy | 100 |
| 20 May 2026 | sell | 100 |
| 15 June 2026 | buy | 100 |
| 15 January 2026 | buy | 100 |
| Group | Status | Value | Bounds | Headroom to the hard bound | Code |
|---|---|---|---|---|---|
| Executions (90d) | Breach | 3% | ≤ 2% | -1% | ABOVE_HARD_MAXIMUM |
ABOVE_HARD_MAXIMUM: The measured value exceeds the approved hard maximum, beyond any stated tolerance.
These are the product's own test cases for this template, evaluated by the Acclimetry rule engine (backend commit 8197063). The build fails if any result changes.
Without the transaction record on the context, turnover is unsupported rather than read as zero
Rule template:turnover_limit measure: turnover_ratiowindowDays: 90
Bounds: hard max 25%
| Holding | Market value |
|---|---|
| A | 1,000 |
| Group | Status | Value | Bounds | Headroom to the hard bound | Code |
|---|---|---|---|---|---|
| Turnover (90d) | Not evaluated: unsupported | – | ≤ 25% | – | TRANSACTION_HISTORY_UNAVAILABLE |
TRANSACTION_HISTORY_UNAVAILABLE: Turnover is measured from the execution record, and none was available for the window.
These are the product's own test cases for this template, evaluated by the Acclimetry rule engine (backend commit 8197063). The build fails if any result changes.
An execution whose type string nobody has mapped sits in the window: the counted trading is inside the cap, so the rule is not evaluated rather than read as compliant
Rule template:turnover_limit measure: turnover_ratiowindowDays: 90
Bounds: hard max 25%
| Holding | Market value |
|---|---|
| A | 1,000 |
| Date | Type | Amount |
|---|---|---|
| 10 May 2026 | buy | 100 |
| 12 May 2026 | sell | 100 |
| 20 May 2026 | buy | 400 |
| Group | Status | Value | Bounds | Headroom to the hard bound | Code |
|---|---|---|---|---|---|
| Turnover (90d) | Not evaluated: data quality | – | ≤ 25% | – | TRANSACTION_TYPES_UNMAPPED |
TRANSACTION_TYPES_UNMAPPED: Executions in the window carry type strings the organisation has not mapped to a category, so what counts as trading is not known and the counted trading alone does not prove a breach.
These are the product's own test cases for this template, evaluated by the Acclimetry rule engine (backend commit 8197063). The build fails if any result changes.
The same unmapped execution beside counted purchases of 400 against a 1,000 book: 40% already exceeds a 25% cap, so the breach is proven and stands
Rule template:turnover_limit measure: purchase_ratiowindowDays: 90
Bounds: hard max 25%
| Holding | Market value |
|---|---|
| A | 1,000 |
| Date | Type | Amount |
|---|---|---|
| 10 May 2026 | buy | 400 |
| 20 May 2026 | buy | 100 |
| Group | Status | Value | Bounds | Headroom to the hard bound | Code |
|---|---|---|---|---|---|
| Turnover (90d) | Breach | 40% | ≤ 25% | -15% | ABOVE_HARD_MAXIMUM |
ABOVE_HARD_MAXIMUM: The measured value exceeds the approved hard maximum, beyond any stated tolerance.
These are the product's own test cases for this template, evaluated by the Acclimetry rule engine (backend commit 8197063). The build fails if any result changes.
Interpretation traps
Position changes are not trades
Turnover needs the execution record. Without it the result is not evaluated, and position deltas cannot substitute for it.
Map every transaction type
An unmapped type string leaves the result not evaluated until it is mapped under the organisation's transaction categories. Meanwhile only a breach proven by the counted trades stands.
Current NAV, not average NAV
The ratio divides by NAV at the evaluation date. If the policy defines turnover over average assets, record that as an author decision and set the limit accordingly.
Rolling window, not calendar year
A 365-day window ending today is not the same as turnover for the calendar year. Say which one the policy means.
Result codes this check can return
| Code | State | Meaning | What to do |
|---|---|---|---|
| WITHIN_LIMITS ✓ | Compliant | The measured value sits inside every approved bound. | – |
| ABOVE_WARNING_MAXIMUM | Warning | The measured value has crossed the early-warning maximum but not the hard limit. | No breach yet; the early-warning band exists so the position can be managed before one. |
| BELOW_WARNING_MINIMUM | Warning | The measured value has crossed the early-warning minimum but not the hard limit. | No breach yet; the early-warning band exists so the position can be managed before one. |
| ABOVE_HARD_MAXIMUM ✓ | Breach | The measured value exceeds the approved hard maximum, beyond any stated tolerance. | Remediate the exposure, or record an approved waiver against the case. |
| BELOW_HARD_MINIMUM | Breach | The measured value falls below the approved hard minimum, beyond any stated tolerance. | Remediate the exposure, or record an approved waiver against the case. |
| CONSERVATIVE_MISSING_DATA | Breach | Data was missing and this rule version's approved unknown-data policy is conservative_breach, so the gap is treated as a breach rather than as Not Evaluated. | Supply the missing values; the reading is deliberately pessimistic until you do. |
| EMPTY_PORTFOLIO | Not applicable | The portfolio held no positions at the evaluation moment. | – |
| NOT_IN_SCOPE | Not applicable | This rule version does not apply to this portfolio — the portfolio is outside the version's stated scope. Recorded so that "considered and out of scope" can be told from "never run". | – |
| AGGREGATE_MEMBER_NOT_ACCEPTED | Not evaluated: data quality | A portfolio in the measured set has no authoritative snapshot, so the set-level number cannot be stated. | Accept the named portfolios' snapshots. A partial set produces no aggregate. |
| MISSING_POSITION_DATA | Not evaluated: data quality | One or more positions had no readable value, and the partial numbers do not prove the outcome either way. | Supply the missing valuations. The platform will not fabricate a zero to produce a reading. |
| SNAPSHOT_NOT_ACCEPTED | Not evaluated: data quality | The portfolio's newest snapshot awaits data-quality review; only accepted data may produce an authoritative result. | Accept, override or reject the snapshot on Data & Quality. |
| SNAPSHOT_REJECTED | Not evaluated: data quality | The portfolio's newest snapshot was rejected, so there is no authoritative data to measure. | Load a corrected file. |
| STALE_POSITIONS | Not evaluated: data quality | The newest snapshot is older than the organisation's staleness threshold. | Load current positions, or revisit the threshold on Data & Quality ▸ Policy. |
| STALE_PRICES | Not evaluated: data quality | A holding had no price on the position file and its approved price was older than the organisation's price-staleness threshold, so it was left unvalued. | Load a current price for the holding, price it on the position file, or revisit the threshold on Data & Quality ▸ Policy. |
| TRANSACTION_HISTORY_UNAVAILABLE ✓ | Not evaluated: data quality | Turnover is measured from the execution record, and none was available for the window. | Load the transaction history for the window; position deltas cannot substitute for it. |
| TRANSACTION_TYPES_UNMAPPED ✓ | Not evaluated: data quality | Executions in the window carry type strings the organisation has not mapped to a category, so what counts as trading is not known and the counted trading alone does not prove a breach. | Map the type strings under Organisation ▸ Transaction categories; the next run counts them. |
| AWAITING_EXTERNAL_DATA | Pending external data | An approved input is late but still inside the organisation's grace window. This is not a pass and not an exception. | None yet. If the delivery does not arrive, this becomes a data-quality exception. |
| METHOD_NOT_SUPPORTED | Not evaluated: unsupported | The rule version asks for a measurement method this engine does not implement. Declared rather than approximated. | Choose a supported methodology, or treat this limit as a manual obligation until the method ships. |
| EVALUATOR_ERROR | Not evaluated: system error | The evaluator raised an error. Surfaced as a visible result rather than as silence. | Report it. A system error is never a pass and never a compliant reading. |
✓ seen in the worked examples. The rest come from the platform registry’s own scan of the template source.
Acclimetry evaluates turnover from the transaction record over a rolling window: purchase and sale ratios over current NAV, or a count of executions. It does not infer trades from position changes, compute transaction costs or judge whether trading was in the client's interest.
Questions
How is portfolio turnover calculated?
Commonly as the lesser of purchases and sales in a period, divided by portfolio value. Acclimetry's turnover ratio uses the lesser of the two in the window, over NAV at the evaluation date.
Do subscriptions and redemptions count as turnover?
Not by default. They are categorised separately and left out unless the rule names them, so client flows do not inflate the figure.
Can the rule limit the number of trades?
Yes. The trade-count measure counts executions in the window against a hard maximum, such as 20 a quarter.