19. Sự kiện doanh nghiệp (Corporate Actions)

Xem cổ tức, cổ phiếu thưởng, quyền mua theo ngày hoặc theo khoảng ngày, lọc theo mã và ngôn ngữ

Mục tiêu

Xem sự kiện quyền của doanh nghiệp (cổ tức, cổ phiếu thưởng, quyền mua...) của hôm nay hoặc của một khoảng ngày trong quá khứ, lọc theo mã và ngôn ngữ. Tương ứng Sample 19 trong repo tutorials (SDK v3.3.2). Sample chỉ đọc dữ liệu, không đặt lệnh và không cần OTP.

Luồng xử lý

Auth (token cache) → Market Data API (Corporate Actions) → Danh sách CorporateAction
  1. Gọi get_corporate_actions() để lấy sự kiện của hôm nay
  2. Gọi get_corporate_actions_historical(from_date, to_date) để lấy sự kiện trong một khoảng ngày (tối đa 5 ngày mỗi lần gọi)
  3. Lọc theo mã (symbol="SSI") và ngôn ngữ (language="en")
  4. Khoảng ngày quá rộng bị SDK chặn bằng ValidationError trước khi gọi server; chia khoảng ngày thành các đoạn tối đa 5 ngày rồi gọi từng đoạn

Các mẫu trong repo

Ngôn ngữFile
Pythonpython/sample_19_corporate_actions.py, python/sample_19_corporate_actions_async.py
Node.jsnode/sample_19_corporate_actions.js
.NETdotnet/Sample19CorporateActions.cs
Gogo/sample_19_corporate_actions.go

Trang này minh họa bằng Python. Repo: ssi-fastconnect-v3-tutorials.

Chuẩn bị

  • Credentials trong config.json, xem Tutorial 1. Sample trong repo đọc cấu hình qua config.py (from config import config), nên code không chứa credentials.
  • Token cần scope data:corporateActions:get.
  • auth_helper.py (token cache, Tutorial 1). Sample này chỉ gọi Market Data nên không cần OTP.

Sample Code — Sync

Code dưới đây lấy nguyên văn từ sample trong repo.

python/sample_19_corporate_actions.py
"""
Sample 19 — Sự kiện doanh nghiệp (Corporate actions)
=====================================================
Xem cổ tức, cổ phiếu thưởng, quyền mua... của các mã. Chỉ ĐỌC dữ liệu, không đặt lệnh.

Luồng:
  1. get_corporate_actions(): sự kiện của HÔM NAY
  2. get_corporate_actions_historical(from, to): sự kiện trong một khoảng ngày ở quá khứ
     (server cho tối đa 5 ngày mỗi lần gọi)
  3. Lọc theo mã (symbol) và ngôn ngữ ("vn" / "en")
  4. Khoảng ngày quá rộng bị SDK chặn tại client (ValidationError), không gọi server

Lưu ý:
  - SDK tự lấy hết các trang (pageSize 100), bạn không cần tự phân trang.
  - Mọi trường là chuỗi đúng như server gửi. Ngày có dạng YYYY/MM/DD, ratio KHÔNG được parse
    vì ý nghĩa phụ thuộc loại sự kiện. Trường server để trống (sự kiện mới công bố chưa có
    ngày GDKHQ/tỷ lệ) là "".
  - Ngày truyền vào phải đúng dạng YYYY/MM/DD (không dùng dấu '-').
  - "Hôm nay" lấy theo ngày của máy bạn.
"""

from datetime import datetime, timedelta

from ssi_sdk import Auth, Data, ValidationError
from auth_helper import ensure_auth
from config import config

DATE_FMT = "%Y/%m/%d"


def show(actions, limit=10):
    for a in actions[:limit]:
        print(
            f"  {a.symbol:<8} | {a.exchange:<6} | GDKHQ: {a.exright_date or '-':<10} "
            f"| Chốt: {a.record_date or '-':<10} | Phát hành: {a.issue_date or '-':<10} "
            f"| Tỷ lệ: {a.ratio or '-':<8} | {a.event_title}"
        )
    if len(actions) > limit:
        print(f"  ... còn {len(actions) - limit} sự kiện")


with Auth(config) as auth:
    ensure_auth(auth)

    with Data(auth) as data:
        # --- Bước 1: Sự kiện của hôm nay ---
        print("--- Sự kiện doanh nghiệp hôm nay ---")
        today_actions = data.market_data.get_corporate_actions()
        print(f"Tổng số sự kiện: {len(today_actions)}")
        show(today_actions)

        # --- Bước 2: Sự kiện trong 5 ngày gần nhất (tối đa 5 ngày cho mỗi lần gọi) ---
        today = datetime.now()
        from_date = (today - timedelta(days=4)).strftime(DATE_FMT)
        to_date = today.strftime(DATE_FMT)
        print(f"\n--- Sự kiện từ {from_date} đến {to_date} ---")
        range_actions = data.market_data.get_corporate_actions_historical(from_date, to_date)
        print(f"Tổng số sự kiện: {len(range_actions)}")
        show(range_actions)

        # --- Bước 3: Lọc theo mã, trả tiêu đề bằng tiếng Anh ---
        print("\n--- Sự kiện của mã SSI (tiếng Anh) ---")
        ssi_actions = data.market_data.get_corporate_actions_historical(
            from_date, to_date, symbol="SSI", language="en"
        )
        print(f"Tổng số sự kiện: {len(ssi_actions)}")
        show(ssi_actions)

        # --- Bước 4: Khoảng ngày quá rộng bị chặn tại SDK ---
        print("\n--- Khoảng 30 ngày (vượt giới hạn 5 ngày) ---")
        too_wide_from = (today - timedelta(days=30)).strftime(DATE_FMT)
        try:
            data.market_data.get_corporate_actions_historical(too_wide_from, to_date)
        except ValidationError as e:
            print(f"  SDK từ chối trước khi gọi server: {e}")
            print("  -> chia khoảng ngày thành các đoạn <= 5 ngày rồi gọi từng đoạn.")

        # --- Response Summary ---
        print("\n[Response] today_count|range_count|ssi_range_count")
        print(f"{len(today_actions)}|{len(range_actions)}|{len(ssi_actions)}")

Sample Code — Async

Async dùng cùng luồng; bước 1-3 độc lập nên chạy song song bằng asyncio.gather.

python/sample_19_corporate_actions_async.py
"""
Sample 19 (Async) — Sự kiện doanh nghiệp (Corporate actions)
=============================================================
Xem cổ tức, cổ phiếu thưởng, quyền mua... của các mã. Chỉ ĐỌC dữ liệu, không đặt lệnh.

Luồng:
  1. get_corporate_actions(): sự kiện của HÔM NAY
  2. get_corporate_actions_historical(from, to): sự kiện trong một khoảng ngày ở quá khứ
     (server cho tối đa 5 ngày mỗi lần gọi)
  3. Lọc theo mã (symbol) và ngôn ngữ ("vn" / "en")
  4. Khoảng ngày quá rộng bị SDK chặn tại client (ValidationError), không gọi server

Bước 1-3 độc lập nên chạy song song bằng asyncio.gather.

Lưu ý: SDK tự lấy hết các trang. Mọi trường là chuỗi đúng như server gửi (ngày YYYY/MM/DD,
ratio không được parse, trường để trống là ""). Ngày truyền vào phải đúng dạng YYYY/MM/DD.
"""

import asyncio
from datetime import datetime, timedelta

from ssi_sdk import AsyncAuth, AsyncData, ValidationError
from auth_helper import ensure_auth_async
from config import config

DATE_FMT = "%Y/%m/%d"


def show(actions, limit=10):
    for a in actions[:limit]:
        print(
            f"  {a.symbol:<8} | {a.exchange:<6} | GDKHQ: {a.exright_date or '-':<10} "
            f"| Chốt: {a.record_date or '-':<10} | Phát hành: {a.issue_date or '-':<10} "
            f"| Tỷ lệ: {a.ratio or '-':<8} | {a.event_title}"
        )
    if len(actions) > limit:
        print(f"  ... còn {len(actions) - limit} sự kiện")


async def main():
    async with AsyncAuth(config) as auth:
        await ensure_auth_async(auth)

        async with AsyncData(auth) as data:
            today = datetime.now()
            from_date = (today - timedelta(days=4)).strftime(DATE_FMT)
            to_date = today.strftime(DATE_FMT)

            # --- Bước 1-3: Gọi song song ---
            today_actions, range_actions, ssi_actions = await asyncio.gather(
                data.market_data.get_corporate_actions(),
                data.market_data.get_corporate_actions_historical(from_date, to_date),
                data.market_data.get_corporate_actions_historical(
                    from_date, to_date, symbol="SSI", language="en"
                ),
            )

            print("--- Sự kiện doanh nghiệp hôm nay ---")
            print(f"Tổng số sự kiện: {len(today_actions)}")
            show(today_actions)

            print(f"\n--- Sự kiện từ {from_date} đến {to_date} ---")
            print(f"Tổng số sự kiện: {len(range_actions)}")
            show(range_actions)

            print("\n--- Sự kiện của mã SSI (tiếng Anh) ---")
            print(f"Tổng số sự kiện: {len(ssi_actions)}")
            show(ssi_actions)

            # --- Bước 4: Khoảng ngày quá rộng bị chặn tại SDK ---
            print("\n--- Khoảng 30 ngày (vượt giới hạn 5 ngày) ---")
            too_wide_from = (today - timedelta(days=30)).strftime(DATE_FMT)
            try:
                await data.market_data.get_corporate_actions_historical(too_wide_from, to_date)
            except ValidationError as e:
                print(f"  SDK từ chối trước khi gọi server: {e}")
                print("  -> chia khoảng ngày thành các đoạn <= 5 ngày rồi gọi từng đoạn.")

            # --- Response Summary ---
            print("\n[Response] today_count|range_count|ssi_range_count")
            print(f"{len(today_actions)}|{len(range_actions)}|{len(ssi_actions)}")


asyncio.run(main())

Các trường của CorporateAction

TrườngÝ nghĩa
symbolMã chứng khoán
event_titleTên sự kiện
exright_dateNgày giao dịch không hưởng quyền (GDKHQ)
record_dateNgày đăng ký cuối cùng (chốt danh sách)
issue_dateNgày thực hiện/phát hành
exchangeSàn, ví dụ HOSE
ratioTỷ lệ sự kiện, dạng chuỗi

Lưu ý

  • Tối đa 5 ngày mỗi lần gọi: to_date không được cách from_date quá 5 ngày. Muốn lấy dài hơn, tự chia thành nhiều đoạn tối đa 5 ngày rồi gọi từng đoạn.
  • Mọi trường là chuỗi: SDK giữ nguyên giá trị server gửi. Ngày có dạng YYYY/MM/DD; ratio không được parse vì ý nghĩa phụ thuộc loại sự kiện.
  • Trường có thể là chuỗi rỗng "": sự kiện mới công bố có thể chưa có ngày GDKHQ hoặc tỷ lệ (exright_date, ratio). Sample dùng a.ratio or '-' để in. Đừng giả định trường nào cũng có giá trị.
  • Kiểm tra trước khi gọi server: SDK kiểm tra định dạng ngày (YYYY/MM/DD, không dùng dấu -), from_date không sau to_date, khoảng tối đa 5 ngày, symbol và language. Sai một điều kiện là ValidationError, chưa có request nào được gửi.
  • language: "vn" (mặc định) hoặc "en"; quyết định ngôn ngữ của event_title.
  • Lọc theo mã: symbol là một mã (chỉ chữ và số, tối đa 20 ký tự). Bỏ trống để lấy mọi mã.
  • "Hôm nay" theo ngày của máy bạn: get_corporate_actions() lấy ngày hiện tại từ máy chạy code.
  • SDK tự lấy hết các trang: bạn không cần tự phân trang.
  • Trạng thái kiểm chứng: theo changelog SDK 3.3.2, hai method này được viết theo tài liệu API và đã có unit test, chưa chạy thử với server thật. Nếu kết quả khác tài liệu API, hãy báo lại cho SSI.
  • Mô tả endpoint, tham số và scope: data-corporateActions. Xử lý ValidationError, rate limit và lỗi server: Xử lý lỗi & Rate limit.

Trên trang này