# Theo Dõi & Cảnh Báo

> Giá trực tiếp với watch() và cảnh báo với alert().

## watch()

```ts
import { vn } from "vnstock-js";

const stop = vn.watch(["FPT", "VCB"], (t) => {
  console.log(t.symbol, t.price, t.changePct, t.time);
});

const stopHpg = vn.stock("HPG").watch((t) => console.log(t.price));

// Dừng khi không cần nữa, nếu không tiến trình sẽ chạy mãi
setTimeout(() => {
  stop();
  stopHpg();
}, 60000);
```

Callback nhận một [Tick](/docs/data/RealtimeQuote) mỗi khi có lệnh khớp mới: giá bằng đồng, `changePct` là phần trăm, khối lượng và giá trị luỹ kế của phiên, ba bước giá mua bán.

- Mọi watcher của một client dùng **chung một kết nối WebSocket**, mở khi có watcher đầu tiên và đóng khi watcher cuối cùng dừng. Tự kết nối lại khi rớt.
- Mã được kiểm tra trước khi theo dõi; mã sai được báo qua `onError` (mặc định in ra `console.warn`) và không theo dõi gì.
- Chế độ stream chỉ có dữ liệu khi thị trường mở. Node 22 trở lên và trình duyệt có WebSocket sẵn; Node 18 tới 21 cần `npm install ws`.

### Chế độ poll

```ts
const stop = vn.watch(["FPT"], (t) => console.log(t.price), {
  mode: "poll",
  interval: 5000, // ms, tối thiểu 1000, mặc định 5000
  onError: (e) => console.error(e.message),
});
```

`poll` hỏi bảng giá định kỳ và chỉ gọi callback khi giá hoặc khối lượng đổi. Dùng trên serverless và trên trình duyệt đi qua proxy, nơi không giữ được WebSocket lâu. Tick từ poll có `side: null` và `source: "poll"`.

## alert()

```ts
const stopBreakout = vn.stock("FPT").alert({ when: "breakout" }, (e) => {
  console.log(e.time, e.price, e.level);
});

const stopPrice = vn.stock("HPG").alert({ when: (t) => t.price >= 30000, mode: "poll" }, (e) => {
  console.log("HPG chạm 30.000 đồng", e.price);
});
```

- **`when: "breakout"`**: báo **một lần mỗi phiên** khi giá khớp vượt đỉnh cao nhất của `lookback` phiên trước (mặc định 20, từ 2 tới 200, không tính phiên hôm nay). Không lọc khối lượng, vì khối lượng giữa phiên không so được với cả ngày. Cảnh báo chạy qua đêm tự tính lại mức đỉnh khi sang phiên mới.
- **`when: (tick) => boolean`**: chạy mỗi tick, báo khi hàm đổi từ `false` sang `true`. Hàm ném lỗi thì lỗi đi vào `onError`.
- Callback nhận `{ symbol, rule, time, price, level, tick }`; `level` là mức đỉnh với `breakout`, `null` với hàm tự viết.
- Nhận đủ tuỳ chọn của `watch()`: `mode`, `interval`, `onError`.
- Không có gì được lưu lại: cảnh báo sống cùng tiến trình. Muốn chạy liên tục thì giữ tiến trình (server, worker) của bạn chạy.

Mức breakout dùng chung với preset `breakout` của [Backtest](/docs/key-features/advanced/backtest): cảnh báo báo khi giá khớp vượt đỉnh trong phiên, còn backtest xét giá đóng cửa.

## Trong React

```tsx
"use client";
import { useEffect, useState } from "react";
import { createClient, type Tick } from "vnstock-js";

const vn = createClient({ proxy: "/api/vnstock" });

export function LivePrice({ symbol }: { symbol: string }) {
  const [tick, setTick] = useState<Tick | null>(null);
  useEffect(() => vn.stock(symbol).watch(setTick), [symbol]);
  return <span>{tick ? (tick.price / 1000).toFixed(2) : "–"}</span>;
}
```

`watch()` trả hàm dừng, nên trả thẳng nó từ `useEffect` là đủ để dọn dẹp khi component unmount.
