# ETF Holdings Diff Monitor (`junipr/etf-holdings-diff-monitor`) Actor

Monitor public ETF holdings files for adds, removals, weight changes, and exposure shifts.

- **URL**: https://apify.com/junipr/etf-holdings-diff-monitor.md
- **Developed by:** [junipr](https://apify.com/junipr) (community)
- **Categories:** Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.50 / 1,000 record checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## ETF Holdings Diff Monitor

Compare two ETF holdings files and return security-level additions, removals, allocation moves, share changes, market-value changes, and rank shifts.

### What It Does

The actor accepts prior and current holdings as delimited text or structured records. It normalizes common issuer headers such as `Ticker`, `Symbol`, `CUSIP`, `ISIN`, `Weight`, `Shares`, and `Market Value`, then joins both files by the strongest available security identifier.

Each dataset row represents one holding check. By default, unchanged rows are omitted so the dataset is a focused change feed. Set `includeUnchanged` to `true` when you need a complete reconciliation.

### Input

- `targets`: Up to 50 fund comparisons in one run.
- `previousSnapshot` / `currentSnapshot`: CSV, TSV, semicolon-delimited, or pipe-delimited holdings text.
- `previousRecords` / `currentRecords`: Structured alternatives to text snapshots.
- `sourceUrl`: Public current-file URL. The actor fetches it only when `fetchUrls` is `true` and `currentSnapshot` is empty.
- `fundTicker`: Fund ticker copied to every result row.
- `maxHoldingsPerTarget`: Per-side row cap, from 1 to 5,000.
- `minWeightChange`: Percentage-point threshold for increased or decreased positions. Additions and removals are never filtered by this threshold.
- `includeUnchanged`: Include securities whose comparable values did not change.
- `includeReport`: Write JSON and Markdown report artifacts.

### Example Input

```json
{
  "targets": [
    {
      "sourceId": "jgrw-holdings",
      "sourceUrl": "https://example.com/funds/jgrw-holdings.csv",
      "fundTicker": "JGRW",
      "previousSnapshot": "Ticker,Name,CUSIP,Weight,Shares,Market Value\nACME,\"Acme Cloud, Inc.\",001122334,4.20%,1000,\"$42,000\"\nBRIO,Brio Retail,009988776,3.10%,800,\"$31,000\"",
      "currentSnapshot": "Ticker,Name,CUSIP,Weight,Shares,Market Value\nACME,\"Acme Cloud, Inc.\",001122334,5.80%,1200,\"$58,000\"\nNOVA,Nova AI,555666777,2.40%,600,\"$24,000\""
    }
  ],
  "maxTargets": 1,
  "maxHoldingsPerTarget": 500,
  "includeUnchanged": false,
  "includeReport": true
}
```

### Dataset Fields

Identity and source fields:

- `rowId`, `sourceId`, `sourceUrl`, `sourceType`, `checkedAt`
- `fundTicker`, `holdingTicker`, `holdingName`, `cusip`, `isin`

Comparison fields:

- `oldWeight`, `newWeight`, `weightDelta`
- `oldShares`, `newShares`, `sharesDelta`
- `oldMarketValue`, `newMarketValue`, `marketValueDelta`
- `oldRank`, `newRank`, `rankDelta`, `changeType`
- `status`, `severity`, `score`, `summary`, `recommendation`

`changeType` is one of `added`, `removed`, `increased`, `decreased`, or `unchanged`.

If a fetch fails or neither snapshot contains a valid security, the actor emits a blocked or attention diagnostic row with `diagnosticCode` and `sourceError` instead of silently returning an empty dataset.

### Example Output

```json
{
  "fundTicker": "JGRW",
  "holdingTicker": "ACME",
  "holdingName": "Acme Cloud, Inc.",
  "cusip": "001122334",
  "oldWeight": 4.2,
  "newWeight": 5.8,
  "weightDelta": 1.6,
  "oldShares": 1000,
  "newShares": 1200,
  "sharesDelta": 200,
  "oldMarketValue": 42000,
  "newMarketValue": 58000,
  "marketValueDelta": 16000,
  "oldRank": 1,
  "newRank": 1,
  "rankDelta": 0,
  "changeType": "increased"
}
```

### PPE Pricing

Event prices include Apify platform usage for the configured fixed-inclusive model.

- `actor-start`: $0.02000 once per paid run
- `record-checked`: $0.00908 per emitted holding row
- `event-detected`: $0.01300 per emitted changed holding
- `report-generated`: $0.05000 when report artifacts are written

The actor stops before additional output when the run charge limit cannot cover the next paid event.

### Limitations

- Header normalization covers common issuer names but cannot infer undocumented columns reliably.
- Percentage weights are returned as percentage points: `4.20%` becomes `4.2`.
- A current URL fetch still requires a supplied prior snapshot or prior records for comparison.
- The actor does not provide investment advice or validate an issuer's published data.

# Actor input Schema

## `targets` (type: `array`):

Capped fund comparisons. Supply previous/current snapshots or record arrays.

## `sourceUrl` (type: `string`):

Optional public URL for the current holdings CSV. Used only when Fetch URLs is enabled.

## `fundTicker` (type: `string`):

Ticker attached to output rows.

## `previousSnapshot` (type: `string`):

Prior issuer holdings file as CSV, TSV, semicolon, or pipe-delimited text.

## `currentSnapshot` (type: `string`):

Current issuer holdings file as CSV, TSV, semicolon, or pipe-delimited text.

## `previousRecords` (type: `array`):

Optional prior structured holdings.

## `currentRecords` (type: `array`):

Optional current structured holdings.

## `fetchUrls` (type: `boolean`):

Fetch sourceUrl as the current holdings file when currentSnapshot is omitted.

## `fetchTimeoutMs` (type: `integer`):

Per-request timeout in milliseconds.

## `maxTargets` (type: `integer`):

Maximum fund comparisons.

## `maxHoldingsPerTarget` (type: `integer`):

Maximum rows read from each side of a comparison.

## `minWeightChange` (type: `number`):

Suppress increased/decreased rows below this percentage-point change. Adds and removals are always retained.

## `includeUnchanged` (type: `boolean`):

Emit unchanged securities as checked rows.

## `maxTextBytes` (type: `integer`):

Maximum bytes retained for each snapshot.

## `includeReport` (type: `boolean`):

Write JSON result, JSON summary, and Markdown report artifacts.

## `dryRun` (type: `boolean`):

Validate input without PPE charges or dataset rows.

## `debug` (type: `boolean`):

Enable debug logging.

## Actor input object example

```json
{
  "targets": [],
  "sourceUrl": "",
  "fundTicker": "",
  "previousSnapshot": "",
  "currentSnapshot": "",
  "previousRecords": [],
  "currentRecords": [],
  "fetchUrls": false,
  "fetchTimeoutMs": 10000,
  "maxTargets": 1,
  "maxHoldingsPerTarget": 500,
  "minWeightChange": 0,
  "includeUnchanged": false,
  "maxTextBytes": 120000,
  "includeReport": true,
  "dryRun": false,
  "debug": false
}
```

# Actor output Schema

## `results` (type: `string`):

Holding-level additions, removals, and value changes.

## `resultsJson` (type: `string`):

Complete result rows as JSON.

## `summary` (type: `string`):

Run counts and truncation state.

## `markdownReport` (type: `string`):

Readable fund holdings change digest.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("junipr/etf-holdings-diff-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("junipr/etf-holdings-diff-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call junipr/etf-holdings-diff-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=junipr/etf-holdings-diff-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/mXIdCDeFci7k6OTXN/builds/EMmT6gYrE89pSUAMB/openapi.json
