# China A-Share Announcement Signal Monitor - CNINFO Alerts (`yongpub/china-announcement-signal-monitor`) Actor

Monitor official A-share company announcements from CNINFO (Shanghai/Shenzhen/Beijing) and get instant classified alerts for insider selling, dividends, earnings, M\&A, buybacks and more. Deterministic Chinese keyword engine, smart baseline diffing, Slack/Feishu/DingTalk/email delivery.

- **URL**: https://apify.com/yongpub/china-announcement-signal-monitor.md
- **Developed by:** [Yong Yu](https://apify.com/yongpub) (community)
- **Categories:** Business, News, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 company monitoreds

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

## China A-Share Announcement Signal Monitor — CNINFO Alerts

> **Get a structured alert the moment a Chinese listed company files an
> official disclosure that matters** — insider share-reduction plans, exchange
> inquiry letters, trading halts, earnings warnings, restructurings,
> investigations, dividend schedules and more. This Actor monitors the
> official disclosure source **CNINFO (cninfo.com.cn)** for the Shanghai,
> Shenzhen and Beijing stock exchanges, diffs new announcements by ID, and
> classifies each one into standardized research/risk events delivered to
> **Slack, Feishu/Lark, DingTalk, a generic webhook, or email.**

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-orange)](https://apify.com/store)
![Python](https://img.shields.io/badge/python-3.10+-blue)

***

### What problem does this solve?

Chinese listed companies publish every material disclosure on **CNINFO**, the
official disclosure platform. The raw feed is high-volume and Chinese-only:
hundreds of filings land each day across thousands of companies, and the
important ones (a controlling shareholder reducing, a regulator asking
questions, a halt before a restructuring) are easy to miss.

This Actor turns that feed into a clean, machine-readable event pipeline:

- You give it a list of **6-digit stock codes** (or names / pinyin).
- It resolves each company, fetches its latest official announcements, and
  **only pushes the ones it has not seen before**.
- Every new announcement is classified into one or more of **20 standardized
  event types** with a severity level, plus a direct link to the official PDF.

No browser, no proxies, no CAPTCHAs — it uses CNINFO's **public JSON
endpoints**, so runs are fast, cheap and low-maintenance.

> For research and risk monitoring only. This Actor provides data and event
> alerts; it does not provide investment advice or buy/sell recommendations.

***

### Example use cases

- **Risk monitoring** — catch insider reductions, regulatory inquiry letters,
  trading halts, investigations and litigation across a watchlist instantly.
- **Investment research** — structured timelines of earnings forecasts,
  reports, dividends, buybacks, M\&A and placements for covered companies.
- **Corporate / IR tracking** — monitor peers, suppliers, holdings or
  counterparties for material events.
- **Data pipelines** — push normalized announcement events into your own
  warehouse, spreadsheet or workflow via a webhook.

***

### Event taxonomy

Each announcement is matched against a deterministic, rule-based Chinese
keyword taxonomy (no LLM, so classification is instant and stable). One
announcement can map to several events; severity is the maximum across hits.

| Event type | Typical disclosure | Default severity |
| --- | --- | --- |
| `EARNINGS_FORECAST` | 业绩预告 / 预增 / 预减 / 修正 | medium (pre-loss/downward → **high**) |
| `EARNINGS_REPORT` | 年报 / 半年报 / 季报 / 业绩快报 | medium |
| `EARNINGS_RESTATED` | 前期会计差错更正 / 追溯重述 | **high** |
| `DIVIDEND` | 权益分派 / 利润分配 / 送转 | medium |
| `INSIDER_SELL` | 减持 | **high** |
| `INSIDER_BUY` | 增持 | medium |
| `SHAREHOLDER_CHANGE` | 权益变动 / 控制权变更 | **high** |
| `EQUITY_INCENTIVE` | 股权激励 / 限制性股票 / 员工持股 | medium |
| `SHARE_REPURCHASE` | 回购 | medium |
| `TRADING_HALT` | 停牌 / 复牌 | **high** |
| `REGULATORY_INQUIRY` | 问询函 / 关注函 / 监管函 | **high** |
| `M&A_RESTRUCTURING` | 重大资产重组 / 吸收合并 | **high** |
| `MANAGEMENT_CHANGE` | 高管辞职 / 换届 / 聘任 | medium |
| `GUARANTEE` | 对外担保 | medium |
| `LITIGATION_ARBITRATION` | 诉讼 / 仲裁 | **high** |
| `INVESTIGATION_PENALTY` | 立案调查 / 行政处罚 | **high** |
| `RELATED_PARTY_TX` | 关联交易 | medium |
| `BOND_FINANCING` | 公司债 / 可转债 / 中票 | medium |
| `PRIVATE_PLACEMENT` | 非公开发行 / 定增 | medium |
| `IPO_LISTING` | 招股说明书 / 上市公告书 | low |
| `OTHER_MATERIAL` | any other unclassified disclosure | low |

The rules live in [`config/event_rules.json`](./config/event_rules.json) and
can be extended without changing code.

***

### Inputs

```json
{
  "stocks": ["600519", "000001", "603291"],
  "lookbackDays": 7,
  "onlyNew": true,
  "severityMin": "all",
  "eventTypes": [],
  "watchId": "default"
}
```

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `stocks` | array | `["600519","000001"]` | 6-digit codes; names/pinyin also accepted |
| `lookbackDays` | integer | `7` | How far back to look when no dates are given |
| `dateFrom` / `dateTo` | string | auto | Explicit `YYYY-MM-DD` window |
| `onlyNew` | boolean | `true` | Diff against the prior run; set `false` to return everything |
| `severityMin` | enum | `all` | `all` / `low` / `medium` / `high` filter |
| `eventTypes` | array | `[]` | Only keep announcements matching these events |
| `searchKeywords` | array | `[]` | Only keep titles containing any keyword |
| `includeSummary` | boolean | `true` | Emit a per-stock summary row |
| `watchId` | string | `default` | Separate state namespace for independent watchlists |
| `snapshotsStoreName` | string | `cninfo-disclosure-snapshots` | Named KVS holding seen-ID state |
| `requestDelay` | number | `0.4` | Polite delay between requests (seconds) |
| `notify` | object | – | Slack / webhook / email targets (see below) |

#### First-run behavior

On the first run for a stock there is no baseline, so the Actor records the
current announcement IDs and pushes nothing (a `company.summary` row with
`baseline: true`). This prevents a flood of historical alerts. Subsequent runs
push only newly-filed announcements.

***

### Output

One dataset item is pushed per new announcement, plus a free per-stock summary
row. Announcement item example:

```json
{
  "signalType": "announcement.new",
  "eventTypes": ["INSIDER_SELL"],
  "severity": "high",
  "stockCode": "603291",
  "stockName": "联合水务",
  "orgId": "9900047645",
  "exchange": "SSE",
  "announcementId": "1225583131",
  "title": "控股股东之一致行动人及持股5%以上股东减持股份计划公告",
  "publishedAt": "2026-09-25T16:00:00Z",
  "pdfUrl": "https://static.cninfo.com.cn/finalpage/2026-09-25/1225583131.PDF",
  "matchedKeywords": ["减持"],
  "isNew": true,
  "baseline": false,
  "detectedAt": "2026-09-27T08:00:00Z"
}
```

The full result set is available under the run's dataset, and a run report is
stored at the `REPORT` key-value store key.

***

### Notifications

Provide a `notify` object. Slack uses Block Kit; the generic webhook
auto-detects **Feishu/Lark** and **DingTalk** formats.

```json
{
  "notify": {
    "slack_webhook_url": "https://hooks.slack.com/services/XXX",
    "webhook_url": "https://open.feishu.cn/open-apis/bot/v2/hook/XXX",
    "email": {
      "smtp_host": "smtp.example.com",
      "smtp_port": 465,
      "account": "you@example.com",
      "to": ["you@example.com"],
      "password": "APP_PASSWORD",
      "useSsl": true
    }
  }
}
```

Notification delivery is best-effort; a notification failure never fails the
run, and delivery statuses are saved at `NOTIFY_STATUSES`.

***

### Scheduling

Save the configured input as a **Schedule** (e.g. every weekday evening after
the CNINFO feed settles) to receive a continuous alert stream. State persists
in the named key-value store between runs, so each scheduled execution only
emits genuinely new disclosures.

***

### Running locally

```bash
pip install -r requirements.txt
python src/main.py --input examples/input_example.json
```

Local runs store seen-ID state in `local_storage/cninfo_disclosure.db` and
write the last report to `local_storage/last_report.json`.

Tests:

```bash
python -m pytest tests/ -q
```

***

### Notes & limitations

- Source is the official CNINFO feed for SSE / SZSE / BSE. Beijing stocks have
  migrated to `920xxx` codes; older codes resolve automatically.
- The Actor links to the official PDF but does not parse PDF bodies by default.
- Data is provided as-is for monitoring and research; coverage and timing
  depend on CNINFO availability. Not investment advice.

# Actor input Schema

## `stocks` (type: `array`):

Six-digit A-share codes, e.g. \["000001", "600519", "603291"]. Shanghai, Shenzhen and Beijing exchange stocks are all covered. Run this Actor on a schedule (e.g. every trading morning) to be alerted as new disclosures are published.

## `stockNames` (type: `array`):

Optional: company Chinese names or pinyin shorthand (e.g. \["贵州茅台", "gzmt"]). Resolved to codes automatically. Ignored when the code is already given in 'stocks'.

## `searchKeywords` (type: `array`):

Scan the whole market (not just your stocks) for announcements whose title contains these keywords, e.g. \["减持", "问询函"]. Leave empty to only monitor the listed stocks.

## `eventTypes` (type: `array`):

Optional: keep only signals classified into these event types. Empty means all event types are returned.

## `severityMin` (type: `string`):

Filter out signals below this severity level. 'high' only returns key risk events.

## `lookbackDays` (type: `integer`):

When no explicit dateFrom is set, scan announcements from the last N days.

## `dateFrom` (type: `string`):

Optional explicit start date (YYYY-MM-DD). Overrides lookbackDays.

## `dateTo` (type: `string`):

Optional explicit end date (YYYY-MM-DD). Defaults to today.

## `onlyNew` (type: `boolean`):

Only output announcements not seen in the previous snapshot. On the first run for a stock with no baseline, a baseline is established and historical announcements are not pushed (avoids flooding). Turn off to output everything in the date range.

## `includeSummary` (type: `boolean`):

Emit one company.summary row per scanned stock with counts and whether any high-severity event was found.

## `watchId` (type: `string`):

Isolates stored baselines for different monitoring sets. Use distinct IDs to run independent watch lists.

## `snapshotsStoreName` (type: `string`):

Named Apify key-value store used to persist seen-announcement state between scheduled runs.

## `extractPdfText` (type: `boolean`):

Download each announcement PDF and extract leading text to aid classification. Off by default; v1 classifies from titles only.

## `requestTimeout` (type: `integer`):

Timeout in seconds for each HTTP request to CNINFO.

## `maxRetries` (type: `integer`):

Number of retry attempts on transient HTTP failures.

## `requestDelay` (type: `number`):

Polite pause between requests to CNINFO.

## `notify` (type: `object`):

Where to send the disclosure digest. All channels optional; results are always in the run dataset. The generic webhook auto-detects Feishu (open.feishu.cn) and DingTalk.

## Actor input object example

```json
{
  "stocks": [
    "600519",
    "000001"
  ],
  "stockNames": [],
  "searchKeywords": [],
  "eventTypes": [],
  "severityMin": "all",
  "lookbackDays": 7,
  "dateFrom": "",
  "dateTo": "",
  "onlyNew": true,
  "includeSummary": true,
  "watchId": "default",
  "snapshotsStoreName": "cninfo-disclosure-snapshots",
  "extractPdfText": false,
  "requestTimeout": 30,
  "maxRetries": 3,
  "requestDelay": 0.4,
  "notify": {
    "slack_webhook_url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
    "webhook_url": "",
    "email": {
      "smtp_host": "",
      "smtp_port": 465,
      "ssl": true,
      "account": "",
      "password": "",
      "to": [
        "you@yourcompany.com"
      ]
    }
  }
}
```

# Actor output Schema

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

All disclosure and risk-event signals emitted in the run, stored in the default dataset. Includes per-stock company.summary rows.

# 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 = {
    "stocks": [
        "600519",
        "000001"
    ],
    "notify": {
        "slack_webhook_url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
        "webhook_url": "",
        "email": {
            "smtp_host": "",
            "smtp_port": 465,
            "ssl": true,
            "account": "",
            "password": "",
            "to": [
                "you@yourcompany.com"
            ]
        }
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yongpub/china-announcement-signal-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 = {
    "stocks": [
        "600519",
        "000001",
    ],
    "notify": {
        "slack_webhook_url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
        "webhook_url": "",
        "email": {
            "smtp_host": "",
            "smtp_port": 465,
            "ssl": True,
            "account": "",
            "password": "",
            "to": ["you@yourcompany.com"],
        },
    },
}

# Run the Actor and wait for it to finish
run = client.actor("yongpub/china-announcement-signal-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 '{
  "stocks": [
    "600519",
    "000001"
  ],
  "notify": {
    "slack_webhook_url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
    "webhook_url": "",
    "email": {
      "smtp_host": "",
      "smtp_port": 465,
      "ssl": true,
      "account": "",
      "password": "",
      "to": [
        "you@yourcompany.com"
      ]
    }
  }
}' |
apify call yongpub/china-announcement-signal-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yongpub/china-announcement-signal-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/o7OifbaMdfbhgBX5c/builds/bPdZGXkhmJD4wqkNM/openapi.json
