> ## Documentation Index
> Fetch the complete documentation index at: https://docs.noonum.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Iterate a strategy

> Refine a Noonum strategy with Python. Enhance the objective, review holdings and evidence, exclude what you don't want, resubmit, and generate historical results.

This guide continues from [Build a strategy](/v1/guides/build-strategy) and covers the refinement loop:

1. Start with a rough objective.
2. Enhance the objective with `POST /enhance-objective`.
3. Create and submit a strategy.
4. Poll until processing is complete (`status == 100`).
5. Review holdings.
6. Look up a company id from a symbol with `POST /helper/companies/search`.
7. Fetch reasoning and evidence for a company.
8. Craft exclusion phrases grounded in the holdings, then `PATCH` them onto the strategy.
9. Resubmit and repeat. Optionally generate an inverse objective with `POST /reverse-objective` and run the same loop.

[Build a strategy](/v1/guides/build-strategy) shows this loop as curl one-liners. Here it is scripted in Python and extended with the refine, exclude, and resubmit cycle.

```mermaid theme={null}
flowchart TD
    A["Start with raw objective"] --> B["POST /enhance-objective"]
    B --> C["POST /strategies"]
    C --> D["POST /strategies/{strategyId}/submit"]
    D --> E{"GET /strategies/{strategyId}<br/>status hits 100?"}
    E -->|"No"| F["wait, then poll again"]
    F --> E
    E -->|"Yes"| G["GET /strategies/{strategyId}/companies<br/>review holdings"]
    G --> G2["POST /helper/companies/search<br/>look up a company id"]
    G2 --> H["GET /strategies/{strategyId}/companies/{companyId}<br/>review reasoning and evidence"]
    H --> I["POST /exclusion-phrases<br/>get suggestions, choose phrases"]
    I --> J["PATCH /strategies/{strategyId}<br/>apply exclusions"]
    J --> K{"Satisfied?"}
    K -->|"No, resubmit"| D
    K -->|"Yes"| L["Done"]
    L -.->|"optional"| M["POST /reverse-objective<br/>repeat loop for inverse strategy"]
```

The examples use Python and the [`requests`](https://requests.readthedocs.io/) library.
All requests target the REST API base URL `https://api.noonum.ai/v1` and authenticate with an
`Authorization: Bearer <token>` header, where the token is your Noonum API key.
See [Authentication](/v1/getting-started/authentication) for details.

<Note>
  Premade strategies are read-only and cannot be refined.
</Note>

## Setup

```python theme={null}
from time import sleep

import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.noonum.ai/v1"

headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
```

## Iterate a strategy until you're happy

### 1. Start with an objective, then enhance it

```python theme={null}
name = "European defense sector"
raw_objective = "Invest in companies that would benefit from increased spend in the European defense sector."

enhance_resp = requests.post(
    f"{BASE_URL}/enhance-objective",
    json={"objective": raw_objective},
    headers=headers,
)
enhance_resp.raise_for_status()

enhanced = enhance_resp.json()
objective = enhanced["enhancedObjective"]

print("Enhanced objective:", objective)
print("Improvements summary:", enhanced.get("improvementsSummary"))
```

### 2. Create a strategy from the enhanced objective

```python theme={null}
create_resp = requests.post(
    f"{BASE_URL}/strategies",
    json={"name": name, "objective": objective},
    headers=headers,
)
create_resp.raise_for_status()

strategy = create_resp.json()
strategy_id = strategy["id"]
print("Strategy id:", strategy_id)
```

### 3. Submit the strategy for processing

```python theme={null}
submit_resp = requests.post(f"{BASE_URL}/strategies/{strategy_id}/submit", headers=headers)
submit_resp.raise_for_status()
```

### 4. Poll until processing is complete

See [Build a strategy](/v1/guides/build-strategy) for how submit-and-poll works (`status == 100`
means processing finished).

```python theme={null}
while True:
    strategy_resp = requests.get(f"{BASE_URL}/strategies/{strategy_id}", headers=headers)
    strategy_resp.raise_for_status()

    status = strategy_resp.json()["status"]
    print("status:", status)

    if status == 100:
        break

    sleep(10)
```

### 5. Review companies in the strategy

```python theme={null}
companies_resp = requests.get(
    f"{BASE_URL}/strategies/{strategy_id}/companies",
    headers=headers,
)
companies_resp.raise_for_status()

companies = companies_resp.json()
print("Companies returned:", len(companies))
print("First company:", companies[0])
```

Each company entry includes an `id` for fetching its reasoning and evidence.

### 6. Look up a company id from a symbol

If you have a symbol in mind (e.g. `"ABC"`) but don't know the Noonum company id,
use `POST /helper/companies/search`.

```python theme={null}
# Use a known-in-strategy symbol to start (then replace with any symbol/ISIN/FIGI/name fragment).
symbol_query = companies[0].get("symbol") or "ABC"

search_resp = requests.post(
    f"{BASE_URL}/helper/companies/search",
    json={"query": symbol_query},
    headers=headers,
)
search_resp.raise_for_status()

search_results = search_resp.json()["results"]
company = search_results[0]
company_id = company["id"]
print("Matched company:", company["name"], company_id)
```

### 7. Fetch evidence for a company in your strategy

This endpoint explains why a company is or isn't in your strategy results:

* If the company fits the strategy, you'll see `isIncluded: true`, a human-readable
  `reasoning`, and `summaries` containing the evidence excerpts.
* If the company does not fit (or there isn't enough supporting evidence), you'll still
  get a response with `isIncluded: false`, a short "why not" message in `reasoning`, and
  typically an empty `summaries` list.

```python theme={null}
evidence_resp = requests.get(
    f"{BASE_URL}/strategies/{strategy_id}/companies/{company_id}",
    headers=headers,
)
evidence_resp.raise_for_status()

company_evidence = evidence_resp.json()
print("isIncluded:", company_evidence.get("isIncluded"))
print("reasoning:", company_evidence.get("reasoning"))
print("summaries (count):", len(company_evidence.get("summaries", [])))
```

To pull reasoning and evidence for all companies at once:

```python theme={null}
all_evidences_resp = requests.get(
    f"{BASE_URL}/strategies/{strategy_id}/evidences",
    headers=headers,
)
all_evidences_resp.raise_for_status()

all_evidences = all_evidences_resp.json()["evidences"]
print("Companies with evidences:", len(all_evidences))
```

### 8. Craft exclusion phrases grounded in the holdings

An exclusion is a short phrase describing a category of companies you want removed.
After you add exclusions and resubmit, the matching companies drop out and new companies
may surface to replace them. Add an exclusion to remove a specific company you don't want
even if it's otherwise relevant, or to remove a type of company such as a sector or business
activity.

Write each phrase as a short description of what a company is (2–6 words, no negation,
no "exclude" prefix, no company names). See [Build a strategy](/v1/guides/build-strategy) for
the full exclusion rules. Ground the phrase in the holdings: look at the inclusion reasoning
of the companies you want to remove, and describe the shared pattern.

`POST /exclusion-phrases` turns a company's evidence and reasoning into suggested phrases.
You can also write your own. Pass:

* your strategy `objective`
* the `companyId` you're reviewing
* the company's `inclusionReason` (use the `reasoning` field you just fetched)

```python theme={null}
inclusion_reason = company_evidence.get("reasoning") or ""

exclusion_resp = requests.post(
    f"{BASE_URL}/exclusion-phrases",
    json={
        "objective": objective,
        "companyId": company_id,
        "inclusionReason": inclusion_reason,
    },
    headers=headers,
)
exclusion_resp.raise_for_status()

suggestions = exclusion_resp.json()["phrases"]
print("Suggested phrases:")
for item in suggestions:
    print("-", item["phrase"], "=>", item["reason"])
```

Now choose which phrases to apply as exclusions. Start with 2–4; you can always add more.

```python theme={null}
# Example: pick the first 2 suggested phrases (or choose any subset)
exclusions = [suggestions[0]["phrase"], suggestions[1]["phrase"]]
print("Chosen exclusions:", exclusions)
```

### 9. Update the strategy with your exclusions, then resubmit

```python theme={null}
patch_resp = requests.patch(
    f"{BASE_URL}/strategies/{strategy_id}",
    json={"exclusions": exclusions},
    headers=headers,
)
patch_resp.raise_for_status()

updated_strategy = patch_resp.json()
print("Updated exclusions:", updated_strategy.get("exclusions"))
```

Resubmit and repeat the review loop.

```python theme={null}
requests.post(f"{BASE_URL}/strategies/{strategy_id}/submit", headers=headers).raise_for_status()

while True:
    strategy_resp = requests.get(f"{BASE_URL}/strategies/{strategy_id}", headers=headers)
    strategy_resp.raise_for_status()
    if strategy_resp.json()["status"] == 100:
        break
    sleep(10)

companies_resp = requests.get(f"{BASE_URL}/strategies/{strategy_id}/companies", headers=headers)
companies_resp.raise_for_status()
companies = companies_resp.json()
print("New companies returned:", len(companies))
```

After resubmission, check that your exclusions didn't remove companies you wanted to keep,
then keep iterating.

## Create an inverse strategy objective

Use `POST /reverse-objective` to generate an "inverse" strategy, then repeat the loop above.

```python theme={null}
reverse_resp = requests.post(
    f"{BASE_URL}/reverse-objective",
    json={"objective": objective},
    headers=headers,
)
reverse_resp.raise_for_status()

reverse = reverse_resp.json()
inverse_objective = reverse["reverseObjective"]
inverse_name = reverse.get("reverseName") or f"Inverse of {name}"

print("Inverse name:", inverse_name)
print("Inverse objective:", inverse_objective)
```

From here, repeat the same steps:

* `POST /strategies` with `inverse_name` and `inverse_objective`
* `POST /strategies/{strategyId}/submit`
* poll `GET /strategies/{strategyId}` until `status == 100`
* review companies and evidence
* craft exclusions and `PATCH /strategies/{strategyId}` with the updated `exclusions`
* resubmit and repeat

## Generate historical results

### 1. Request historical data for specific month-end dates

```python theme={null}
historical_request_resp = requests.post(
    f"{BASE_URL}/strategies/{strategy_id}/historical-data",
    json={"dates": ["2025-07-31", "2025-06-30"]},
    headers=headers,
)
historical_request_resp.raise_for_status()

print(historical_request_resp.json())
```

### 2. Get available dates for download

```python theme={null}
available_dates_resp = requests.get(
    f"{BASE_URL}/strategies/{strategy_id}/historical-data/dates",
    headers=headers,
)
available_dates_resp.raise_for_status()

available_dates = available_dates_resp.json()["available_dates"]
print("Available dates:", available_dates)
```

### 3. Download historical results for one date

```python theme={null}
date = available_dates[0]

historical_results_resp = requests.get(
    f"{BASE_URL}/strategies/{strategy_id}/historical-data",
    headers=headers,
    params={"date": date},
)
historical_results_resp.raise_for_status()

historical_companies = historical_results_resp.json()
print("Historical companies returned:", len(historical_companies))
print("First historical row:", historical_companies[0])
```

## Where to go next

After refinement, `GET /strategies/{strategyId}/factsheet` (MCP server tool
`get_strategy_factsheet`) returns a presigned URL to a PDF summary of the updated strategy.

* [Analyze strategies](/v1/guides/analyze-strategies): filter, sort, and compare holdings.
* [REST integration](/v1/guides/rest-integration): wire the loop into your own application.
* [MCP server](/base/agentic/mcp-server): drive the same workflow from an agent.
