# Polymarket Alpha Signals: Odds Moves Live (`dmitrii_shifo/polymarket-monitor`) Actor

Track Polymarket odds in real time and get only the markets that actually moved. Pay per signal, not per row. A quiet run costs you nothing.

- **URL**: https://apify.com/dmitrii\_shifo/polymarket-monitor.md
- **Developed by:** [Dmitrii Lominskii](https://apify.com/dmitrii_shifo) (community)
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 price move signals

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Polymarket Alpha Signals: Odds Moves Live

This Actor watches live Polymarket markets and returns only the ones where
something happened: a price move past your thresholds, a spike in 24 hour
volume, or a brand new market. Every run compares the current state against
what it saw on the previous run and writes one row per signal. You are charged
per signal, so a run that finds nothing costs nothing beyond the Actor start
fee. Put it on a schedule and it becomes a continuous monitor.

### What you get

Three signal types, each a separate row in the dataset:

- **Price move**: the price changed by at least 0.03 points and at least 15
  percent within your comparison window. Both thresholds must be met.
- **Volume spike**: 24 hour volume grew by at least 25 percent and at least
  $5,000. Both thresholds must be met.
- **New market**: a market appeared since the previous run. Off by default,
  see Limits below.

Every row carries the current and previous price, the change in points and in
percent, current and previous volume, liquidity, best bid, best ask, spread,
the market question, its tags, event title and a link to the market on
Polymarket.

### How it works

The Actor keeps a short price and volume history in a named key-value store on
your account. Each run reads that history, compares it against live data, and
writes the history back.

The first run has nothing to compare against, so it records a baseline and
emits no signals. This is expected. The second run is the first one that can
produce results.

When a run produces no signals, it writes a single free `run_summary` row
instead of leaving the dataset empty. That row tells you how many markets were
checked, how many candidates were suppressed, and why nothing fired. It is not
charged. Filter on `signal_type` if you only want real signals.

Set **Comparison window, minutes** close to how often you schedule the run. If
you run hourly, use 60. If you run every 15 minutes, use 15. The Actor looks
for the stored point closest to that window and reports the age it actually
used in `actual_window_minutes`, which is almost always a few minutes off the
window you asked for. That is the real interval the comparison was made over.

### Quick start

1. Leave the defaults and press Start. This run records a baseline.
2. Wait roughly as long as your comparison window, then press Start again. This
   run returns signals.
3. When the output looks right, create a schedule with the same input and the
   same interval.

Results land in the dataset and can be sent onward through the Integrations
tab: webhooks, Slack, Zapier, Make, n8n and the rest.

### What a run costs

You pay per signal that reaches the dataset. Suppressed candidates, filtered
markets and `run_summary` rows are not charged.

**Max signals per run** is the ceiling, 5 by default. Signals are ranked by
strength first, so the cap keeps the strongest ones. That makes the worst case
of any single run exactly:

`5 x $0.015 = $0.075`

On an hourly schedule that is at most $1.80 per day. Most runs return fewer
than five signals, so the real figure is lower. Raise the cap for full
coverage, lower it to tighten the ceiling.

The Actor start fee is $0.00005 at 512 MB of memory, and one such fee per extra
gigabyte above 1 GB.

### Tuning the output

Too many signals: raise **Minimum price move, percent**, raise **Minimum 24
hour volume** to work on a smaller and more liquid universe, or lower **Max
signals per run**.

Too few signals: lower the move thresholds, lower **Minimum liquidity** and
**Minimum 24 hour volume**, or raise **Max markets per run**.

Only the markets you care about: fill **Market slugs** or **Condition IDs** to
watch a fixed list, or use **Include tags** to keep a theme such as politics or
crypto. In watchlist mode the liquidity and volume filters do not apply, since
you named the markets yourself.

### Limits worth knowing before you start

**Schedule interval and comparison window belong together.** If the interval is
much longer than the window, the Actor may have no usable history point and
return nothing. It accepts points between a quarter and four times the window,
so a missed run or a few minutes of drift will not break the chain.

**Above 180 minutes** the Actor falls back to Polymarket's own 24 hour price
change until it has collected enough history of its own. In those rows
`window_source` is `gamma_1d` and `actual_window_minutes` is 1440, so the
comparison really is against 24 hours ago, not against the window you set.
Volume spikes have no such fallback and simply will not fire until history
builds up.

**The very first run can still find price moves.** With no history yet, the
Actor checks Polymarket's own 1 hour price change and can emit `price_move`
signals from it, with `window_source` set to `gamma_1h` and
`actual_window_minutes` at 60. Volume spikes and new market signals still
need a second run, there is no such field to fall back on for those.

**New market signals are off by default.** In a broad scan they mostly surface
sports sub-markets with no trading yet, which is noise you would be paying for.
Turn them on together with a watchlist or a tag filter.

**Markets near either end of the price scale are skipped.** By default a signal
is suppressed when the old or the new price sits outside 0.05 to 0.95, because
a market that is already settling produces large percentage moves that mean
nothing. Set **Minimum price for a signal** to 0 if you want to see those
collapses.

**Watching by slug is more reliable than by condition ID.** A few markets
cannot be looked up by condition ID directly. The Actor logs them and carries
on with the rest.

**Auto-generated recurring markets are excluded**, such as the five-minute
crypto up or down contracts Polymarket creates around the clock. Turn them back
on with the checkbox if you want them.

### Output fields

| Field | Meaning |
| --- | --- |
| `signal_type` | `price_move`, `volume_spike`, `new_market`, or `run_summary` for a run with no signals |
| `signal_strength` | How many times over its threshold the signal went. Used for ranking |
| `note` | On a `run_summary` row, why the run produced no signals |
| `markets_checked`, `candidates_suppressed` | On a `run_summary` row, how many markets passed the filters and how many candidates were suppressed |
| `price_now`, `price_then` | Price at this run and at the comparison point, on the 0 to 1 scale |
| `price_change_points`, `price_change_pct` | Absolute and relative change |
| `volume_24h_now`, `volume_24h_then`, `volume_change_pct` | Same for 24 hour volume |
| `window_minutes`, `actual_window_minutes` | Requested window and the age of the point actually used |
| `window_source` | `history` for a stored point, `gamma_1d` for the long window fallback, `gamma_1h` for the first-run fallback |
| `liquidity`, `best_bid`, `best_ask`, `spread` | Order book context at the time of the signal |
| `question`, `slug`, `outcome_yes`, `end_date` | Market identity |
| `event_title`, `event_slug`, `tags`, `market_url` | Event context and link |
| `condition_id` | Stable market identifier, use it for a watchlist |
| `detected_at_iso`, `created_at_iso`, `age_hours` | When the signal fired and how old the market is |

### Running several monitors

All runs on your account share one history store by default, which is what you
want: change a threshold and the history stays. If you run two monitors with
different watchlists and want their histories kept apart, give each a different
**State key**.

# Actor input Schema

## `searchQueries` (type: `array`):

Keep only markets whose question text contains at least one of these words. Leave empty to check all markets that pass the other filters. Only used during a general market scan.

## `slugs` (type: `array`):

Watch specific markets by slug instead of scanning all markets. When this is filled in, the general market scan is skipped and only these markets are checked.

## `conditionIds` (type: `array`):

Watch specific markets by condition ID instead of scanning all markets. Works the same way as market slugs, and can be combined with them.

## `includeTags` (type: `array`):

Keep only events tagged with at least one of these tag slugs, for example politics or crypto. Leave empty to skip this filter. Only used during a general market scan.

## `excludeTags` (type: `array`):

Drop events tagged with any of these tag slugs. Only used during a general market scan.

## `minLiquidity` (type: `integer`):

Skip markets with liquidity below this value, in dollars. Not applied when watching specific markets by slug or condition ID.

## `minVolume24h` (type: `integer`):

Skip markets with traded volume in the last 24 hours below this value, in dollars. Not applied when watching specific markets by slug or condition ID.

## `windowMinutes` (type: `integer`):

How far back to compare price and volume against. Set this close to how often you schedule the actor to run. Above 180 minutes, if there is no usable history point yet, price move signals fall back to Polymarket's own 24 hour price change figure; volume spike signals have no such fallback and simply will not fire until enough history has accumulated.

## `minPriceMovePoints` (type: `number`):

Minimum absolute price change, on Polymarket's 0 to 1 price scale, needed to trigger a price move signal.

## `minPriceMovePct` (type: `number`):

Minimum relative price change, in percent of the starting price, needed to trigger a price move signal. Both this and the points threshold must be met.

## `minVolumeSpikePct` (type: `number`):

Minimum relative growth of 24 hour volume, in percent, needed to trigger a volume spike signal.

## `minVolumeSpikeAbs` (type: `integer`):

Minimum growth of 24 hour volume in dollars, needed to trigger a volume spike signal. Both this and the percent threshold must be met.

## `minPriceForSignal` (type: `number`):

Suppress a price move or volume spike signal if price\_now or price\_then falls outside \[this value, 1 minus this value] on Polymarket's 0 to 1 price scale. Either price alone is enough to suppress the signal; both do not need to be outside the range. Does not apply to new market signals.

## `repeatSuppressMinutes` (type: `integer`):

After a signal on a market, wait this many minutes before signaling on it again, unless the price moves further in that time.

## `newMarketMaxAgeHours` (type: `integer`):

Only signal a market as new if it was created less than this many hours ago.

## `newMarketMinLiquidity` (type: `integer`):

Minimum liquidity, in dollars, for the dedicated new market scan. Used instead of Minimum liquidity and Minimum 24 hour volume for that scan, since a market fresh off creation rarely clears those yet.

## `maxMarkets` (type: `integer`):

Maximum number of markets to check in one run. In a general scan, markets are taken in order of highest 24 hour volume first, up to this limit. When new market alerts are on, the dedicated new market scan reserves room within this same limit rather than adding to it. Not applied when watching specific markets by slug or condition ID.

## `maxSignalsPerEvent` (type: `integer`):

Maximum number of signals to keep from one real-world event in a run, the strongest first. When Polymarket splits one match into several events that share the same underlying game (for example a main line and a halftime result), they are grouped and capped together. Keeps one busy event from filling up the whole result list.

## `maxSignalsPerRun` (type: `integer`):

Hard cap on how many signals a single run can emit and charge for. Signals are ranked by strength before the cap is applied, so you always get the strongest ones first, regardless of signal type. Raise it if you want full coverage, lower it to keep the cost of every run predictable.

## `signalTypes` (type: `array`):

Which kinds of signals to look for. New market alerts are off by default. In a broad scan they mostly surface sports sub-markets that have no trading yet. Turn them on together with a watchlist or a tag filter, where they tell you about markets you actually care about.

## `includeAutoGeneratedMarkets` (type: `boolean`):

Polymarket creates short-lived recurring markets, such as five-minute crypto up or down contracts, around the clock. They are excluded by default because they produce a constant stream of low-value signals. Applies to the general scan and the dedicated new market scan; not applied when watching specific markets by slug or condition ID.

## `stateKey` (type: `string`):

Runs that share a state key share price history. Leave empty unless you run several separate monitors and want to keep their histories apart. Only letters, digits and hyphens are kept; anything else is dropped.

## Actor input object example

```json
{
  "searchQueries": [],
  "slugs": [],
  "conditionIds": [],
  "includeTags": [],
  "excludeTags": [],
  "minLiquidity": 1000,
  "minVolume24h": 5000,
  "windowMinutes": 60,
  "minPriceMovePoints": 0.03,
  "minPriceMovePct": 15,
  "minVolumeSpikePct": 25,
  "minVolumeSpikeAbs": 5000,
  "minPriceForSignal": 0.05,
  "repeatSuppressMinutes": 30,
  "newMarketMaxAgeHours": 24,
  "newMarketMinLiquidity": 100,
  "maxMarkets": 300,
  "maxSignalsPerEvent": 2,
  "maxSignalsPerRun": 5,
  "signalTypes": [
    "price_move",
    "volume_spike"
  ],
  "includeAutoGeneratedMarkets": false,
  "stateKey": ""
}
```

# Actor output Schema

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

No description

# 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 = {
    "searchQueries": [],
    "slugs": [],
    "conditionIds": [],
    "includeTags": [],
    "excludeTags": [],
    "minLiquidity": 1000,
    "minVolume24h": 5000,
    "windowMinutes": 60,
    "minPriceMovePoints": 0.03,
    "minPriceMovePct": 15,
    "minVolumeSpikePct": 25,
    "minVolumeSpikeAbs": 5000,
    "minPriceForSignal": 0.05,
    "repeatSuppressMinutes": 30,
    "newMarketMaxAgeHours": 24,
    "newMarketMinLiquidity": 100,
    "maxMarkets": 300,
    "maxSignalsPerEvent": 2,
    "maxSignalsPerRun": 5,
    "signalTypes": [
        "price_move",
        "volume_spike"
    ],
    "stateKey": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("dmitrii_shifo/polymarket-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 = {
    "searchQueries": [],
    "slugs": [],
    "conditionIds": [],
    "includeTags": [],
    "excludeTags": [],
    "minLiquidity": 1000,
    "minVolume24h": 5000,
    "windowMinutes": 60,
    "minPriceMovePoints": 0.03,
    "minPriceMovePct": 15,
    "minVolumeSpikePct": 25,
    "minVolumeSpikeAbs": 5000,
    "minPriceForSignal": 0.05,
    "repeatSuppressMinutes": 30,
    "newMarketMaxAgeHours": 24,
    "newMarketMinLiquidity": 100,
    "maxMarkets": 300,
    "maxSignalsPerEvent": 2,
    "maxSignalsPerRun": 5,
    "signalTypes": [
        "price_move",
        "volume_spike",
    ],
    "stateKey": "",
}

# Run the Actor and wait for it to finish
run = client.actor("dmitrii_shifo/polymarket-monitor").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "searchQueries": [],
  "slugs": [],
  "conditionIds": [],
  "includeTags": [],
  "excludeTags": [],
  "minLiquidity": 1000,
  "minVolume24h": 5000,
  "windowMinutes": 60,
  "minPriceMovePoints": 0.03,
  "minPriceMovePct": 15,
  "minVolumeSpikePct": 25,
  "minVolumeSpikeAbs": 5000,
  "minPriceForSignal": 0.05,
  "repeatSuppressMinutes": 30,
  "newMarketMaxAgeHours": 24,
  "newMarketMinLiquidity": 100,
  "maxMarkets": 300,
  "maxSignalsPerEvent": 2,
  "maxSignalsPerRun": 5,
  "signalTypes": [
    "price_move",
    "volume_spike"
  ],
  "stateKey": ""
}' |
apify call dmitrii_shifo/polymarket-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dmitrii_shifo/polymarket-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/KcjxsTPgvP0XY4KBo/builds/4kqC4PnZItw0TA9T3/openapi.json
