Skip to main content

Vietnamese Stock Market Toolkit — real-time quotes, technical analysis, sentiment, money flow signals for HOSE/HNX stocks

Project description

lotusmarket

Vietnamese Stock Market Toolkit for Go & Python.

Real-time quotes, historical OHLCV, technical analysis, sentiment, money flow signals, and more for HOSE/HNX stocks. All public APIs — no API key required.

Go Tests Python Tests PyPI License: MIT

Install

Go:

go get github.com/ducnhd/lotusmarket/go

Python:

pip install lotusmarket

# With fetchers (httpx for API calls):
pip install lotusmarket[fetchers]

# Everything:
pip install lotusmarket[all]

Quick Start

Go

package main

import (
    "context"
    "fmt"

    "github.com/ducnhd/lotusmarket/go/fetchers"
    "github.com/ducnhd/lotusmarket/go/technical"
    "github.com/ducnhd/lotusmarket/go/sentiment"
    "github.com/ducnhd/lotusmarket/go/signals"
    "github.com/ducnhd/lotusmarket/go/market"
)

func main() {
    ctx := context.Background()

    // 1. Real-time quote
    stock, _ := fetchers.VPS(ctx, "ACB")
    fmt.Printf("ACB: %.0f VND, vol: %d, foreign net: %d\n",
        stock.Close, stock.Volume, stock.ForeignNetVol)

    // 2. Multiple stocks at once
    stocks, _ := fetchers.VPSMultiple(ctx, []string{"ACB", "VNM", "FPT", "HPG"})

    // 3. Historical OHLCV (200 days)
    history, _ := fetchers.EntradeHistory(ctx, "ACB", 200)
    closes := make([]float64, len(history))
    for i, h := range history {
        closes[i] = h.Close
    }

    // 4. Technical analysis — all-in-one
    d := technical.Dashboard(closes)
    fmt.Printf("RSI: %.1f, MA20: %.0f, Signal: %s, Score: %.0f\n",
        d.RSI, *d.MA20, d.Signal, d.Score)

    // 5. Individual indicators
    rsi := technical.RSI(closes, 14)
    ma20 := technical.MA(closes, 20)
    mom := technical.Momentum(closes, 20)

    // 6. Weekly candles from daily
    weekly := technical.AggregateWeekly(history)

    // 7. Vietnamese sentiment
    r := sentiment.Analyze("Ngân hàng tăng mạnh, khối ngoại mua ròng")
    fmt.Printf("Sentiment: %s (%.2f)\n", r.Label, r.Score)

    // 8. Domestic flow — buy/sell pressure
    flow := signals.ComputeDomesticFlow(stocks)
    fmt.Printf("Domestic: %s (buy pressure: %.1f%%)\n", flow.Signal, flow.BuyPressure)

    // 9. Sector flow ranking (now includes avg_change_pct, total_volume_vnd, advances/total)
    sectors := market.RankSectorsByFlow(stocks)
    for _, s := range sectors {
        fmt.Printf("#%d %s: %+.2f%% · KL %.0f tỷ · %d/%d tăng\n",
            s.Rank, s.Sector, s.AvgChangePct, s.TotalVolumeVND/1e9,
            s.AdvancesCount, s.TotalCount)
    }

    // 9b. Price driver attribution — why did this stock move today?
    attr := market.AttributeDrivers(market.DriverFeatures{
        PriceChangePct:   2.5,
        ForeignNetVND:    15e9,
        MarketDeltaPct:   0.5,
        SectorDeltaPct:   1.2,
        NewsCount:        3,
        NewsSentimentAvg: 0.6,
        RSI:              58,
        MASignal:         "BUY",
        VolumeRatio:      1.8,
    })
    fmt.Printf("Dominant driver: %s (%.1f%%)\n", attr.Dominant,
        map[string]float64{"NEWS": attr.NewsPct, "FOREIGN": attr.ForeignPct,
            "SECTOR": attr.SectorPct, "MARKET": attr.MarketPct,
            "TECHNICAL": attr.TechnicalPct, "VOLUME": attr.VolumePct}[attr.Dominant])

    // 9c. News clustering — gom tin cùng chủ đề từ nhiều nguồn
    titles := []string{
        "VN-Index tăng 1% phiên cuối tuần",
        "VNIndex đóng cửa tăng 1% phiên cuối",
        "Giá dầu WTI vượt 85 USD",
    }
    groups := sentiment.ClusterTitles(titles, 0.3)
    // groups = [[0, 1], [2]] — first two grouped as same story

    // 10. Fundamentals
    fund, _ := fetchers.KBS(ctx, "ACB")
    fmt.Printf("P/E: %.1f, P/B: %.1f, EPS: %.0f\n", fund.PE, fund.PB, fund.EPS)

    // 11. With fallback (VPS -> Entrade)
    stock2, _ := fetchers.StockWithFallback(ctx, "VNM")

    // 12. AI analysis (requires CLAUDE_API_KEY)
    import "github.com/ducnhd/lotusmarket/go/ai"
    aiClient, err := ai.New(ai.Config{APIKey: os.Getenv("CLAUDE_API_KEY")})
    if err != nil {
        // → ErrNoAPIKey with setup instructions
    }
    // Trend analysis with pre-computed indicators
    analysis, _ := aiClient.AnalyzeTrend(ctx, "ACB", history)
    fmt.Println(analysis.Text) // Vietnamese analysis

    // Q&A
    answer, _ := aiClient.AskQuestion(ctx, "ACB có nên mua không?")
    fmt.Println(answer.Text)

    _ = stock2
    _ = rsi
    _ = ma20
    _ = mom
    _ = weekly
}

Python

import os
os.environ["LOTUSMARKET_QUIET"] = "1"  # suppress startup banner

import pandas as pd
import lotusmarket as lm

# === 1. Real-time quote ===
from lotusmarket.fetchers import vps, vps_multiple, entrade_history, kbs, stock_with_fallback

stock = vps("ACB")
print(f"ACB: {stock.close:.0f} VND, vol: {stock.volume}, foreign net: {stock.foreign_net_vol}")

# Multiple stocks
stocks = vps_multiple(["ACB", "VNM", "FPT", "HPG"])

# === 2. Historical OHLCV ===
history = entrade_history("ACB", days=200)  # returns list of StockData
closes = pd.Series([h.close for h in history])

# === 3. Technical analysis — all-in-one ===
d = lm.technical.dashboard(closes)
print(f"RSI: {d.rsi:.1f}, MA20: {d.ma20:.0f}, Signal: {d.signal}, Score: {d.score:.0f}")

# Individual indicators (pandas Series in/out)
df = pd.DataFrame({"close": closes})
df["rsi"] = lm.technical.rsi(df["close"], period=14)
df["ma20"] = lm.technical.ma(df["close"], period=20)
df["ma50"] = lm.technical.ma(df["close"], period=50)

# Score and signal
score = lm.technical.score(closes)
signal = lm.technical.signal(closes)

# === 4. Weekly candles ===
daily_df = pd.DataFrame({
    "date": [h.date for h in history],
    "open": [h.open for h in history],
    "high": [h.high for h in history],
    "low": [h.low for h in history],
    "close": [h.close for h in history],
    "volume": [h.volume for h in history],
})
weekly = lm.technical.aggregate_weekly(daily_df)

# === 5. Vietnamese sentiment ===
r = lm.sentiment.analyze("Ngân hàng tăng mạnh, khối ngoại mua ròng")
print(f"Sentiment: {r.label} ({r.score:.2f})")

# === 6. Volume surge detection ===
surge = lm.signals.classify_volume_surge(
    current_vol=5000000, avg_vol_20=2000000, stddev_vol_20=500000, price_change=2.5
)
print(f"Surge: {surge.signal} / {surge.intensity}{surge.label}")

# === 7. Domestic flow ===
stocks_df = pd.DataFrame({
    "ticker": ["ACB", "VNM", "FPT"],
    "volume": [1000000, 500000, 800000],
    "bid_vol": [600000, 300000, 500000],
    "ask_vol": [400000, 200000, 300000],
    "foreign_buy_vol": [50000, 30000, 40000],
    "foreign_sell_vol": [30000, 40000, 20000],
})
flow = lm.signals.domestic_flow(stocks_df)
print(f"Domestic: {flow.signal} (buy pressure: {flow.buy_pressure:.1f}%)")

# === 8. Sector flow ranking ===
sector_df = pd.DataFrame({
    "ticker": ["ACB", "VNM", "FPT", "HPG"],
    "close": [25000, 70000, 120000, 28000],
    "volume": [1000000, 500000, 800000, 600000],
    "change_percent": [1.5, -0.5, 2.0, -1.0],
    "foreign_net_vol": [500, -200, 300, -100],
})
sectors = lm.market.sector_flow(sector_df)
# New columns: avg_change_pct, total_volume_vnd, advances_count, total_count
print(sectors[["sector", "rank", "signal", "avg_change_pct", "total_volume_vnd"]])

# === 8b. Price driver attribution ===
attr = lm.market.attribute_drivers(lm.market.DriverFeatures(
    price_change_pct=2.5,
    foreign_net_vnd=15e9,
    market_delta_pct=0.5,
    sector_delta_pct=1.2,
    news_count=3,
    news_sentiment_avg=0.6,
    rsi=58,
    ma_signal="BUY",
    volume_ratio=1.8,
))
print(f"Dominant: {attr.dominant} · News {attr.news_pct}% · Foreign {attr.foreign_pct}%")

# === 8c. News clustering — gom tin cùng chủ đề ===
titles = [
    "VN-Index tăng 1% phiên cuối tuần",
    "VNIndex đóng cửa tăng 1% phiên cuối",
    "Giá dầu WTI vượt 85 USD",
]
groups = lm.sentiment.cluster_titles(titles, threshold=0.3)
# groups = [[0, 1], [2]] — first two grouped as same story

# === 9. Market pulse scoring ===
score_val, signal_str = lm.market.pulse_score(
    breadth_score=70, foreign_flow_score=60, volume_score=55, risk_score=40
)
print(f"Pulse: {score_val} ({signal_str})")

# === 10. Risk indicators ===
print(f"VIX risk: {lm.market.score_vix(25)}")
print(f"Yield curve risk: {lm.market.score_yield_curve(-0.3)}")

# === 11. Fundamentals ===
fund = kbs("ACB")
print(f"P/E: {fund.pe:.1f}, P/B: {fund.pb:.1f}, EPS: {fund.eps:.0f}")

# === 12. Portfolio TWR ===
from lotusmarket.portfolio import twr, ReturnSegment
returns = twr([ReturnSegment(100, 110), ReturnSegment(110, 132)])
print(f"TWR: {returns:.2%}")

# === 13. Vietnamese NLU ===
from lotusmarket.nlu import Parser
parser = Parser()
result = parser.parse("giá ACB hiện tại bao nhiêu")
print(f"Intent: {result.intent}, Tickers: {result.tickers}")

# === 14. Backtest ===
from lotusmarket.backtest import run, BacktestConfig
prices = pd.Series([20000 + i * 50 for i in range(200)], dtype=float)
bt = run(BacktestConfig(strategy="rsi"), prices)
print(f"Return: {bt.total_return:.1f}%, Max DD: {bt.max_drawdown:.1f}%, Trades: {bt.trade_count}")

# === 15. AI analysis (requires CLAUDE_API_KEY) ===
from lotusmarket.ai import AIClient, AIConfig

# Option 1: from env var
# export CLAUDE_API_KEY=sk-ant-...
# client = AIClient()

# Option 2: explicit key
client = AIClient(AIConfig(api_key="sk-ant-..."))

# Trend analysis (pre-computes RSI/MA/momentum before sending to Claude)
analysis = client.analyze_trend("ACB", closes)
print(analysis.text)  # Vietnamese analysis
print(f"Tokens: {analysis.tokens_in} in, {analysis.tokens_out} out")

# Q&A
answer = client.ask_question("ACB có nên mua không?")
print(answer.text)

# Custom model (default: sonnet, can use opus for complex analysis)
client_opus = AIClient(AIConfig(api_key="sk-ant-...", model="claude-opus-4-6", max_tokens=8192))

Modules

Pure computation (no API key, no network)

Module Go import Python import Description
technical technical lotusmarket.technical RSI (Wilder's smoothing), MA20/50/200, Momentum, Signal (BUY/SELL/HOLD), Score (0-100), Dashboard, Weekly aggregation
sentiment sentiment lotusmarket.sentiment Vietnamese financial keyword analysis — 70+ keywords with weights, score -1.0 to +1.0
signals signals lotusmarket.signals Volume surge detection (z-score or ratio), domestic buy/sell pressure (bid/ask ratio)
market market lotusmarket.market Market pulse scoring (0-100, green/yellow/red), sector flow ranking, risk indicators (VIX, yield curve, news sentiment, Fed trend)
portfolio portfolio lotusmarket.portfolio Time-weighted returns (TWR), 6-factor confidence scoring (0-100)
nlu nlu lotusmarket.nlu Vietnamese intent classification (5 intents), ticker extraction, timeframe detection
backtest backtest lotusmarket.backtest Strategy backtesting — RSI, MA cross, combined. Win rate, max drawdown, buy & hold comparison
types types lotusmarket.types StockData, KBSQuote, VN30 list, sector mappings, trading fee constants

AI analysis (requires CLAUDE_API_KEY)

Module Go import Python import Description
ai ai lotusmarket.ai Claude API wrapper — trend analysis (pre-computes indicators), Q&A, custom prompts. Default model: sonnet. Configurable model + max_tokens.

Go: go get github.com/ducnhd/lotusmarket/go/ai (adds anthropic-sdk-go dependency) Python: pip install lotusmarket[ai] (adds anthropic dependency)

Data fetchers (network calls, no API key needed)

Fetcher Go Python Data
VPS fetchers.VPS(ctx, "ACB") fetchers.vps("ACB") Real-time: price, volume, change%, foreign buy/sell, bid/ask
Entrade fetchers.EntradeHistory(ctx, "ACB", 200) fetchers.entrade_history("ACB", 200) Historical daily OHLCV (up to 1000 days)
KBS fetchers.KBS(ctx, "ACB") fetchers.kbs("ACB") Fundamentals: P/E, P/B, EPS, Beta, Dividend Yield, Market Cap
Fallback fetchers.StockWithFallback(ctx, "ACB") fetchers.stock_with_fallback("ACB") VPS primary, Entrade backup (3 retries + backoff)

Python fetchers require httpx: pip install lotusmarket[fetchers]

Data Sources

Source URL Data Auth
VPS bgapidatafeed.vps.com.vn Real-time quotes, foreign flow, bid/ask volumes Public
Entrade services.entrade.com.vn Historical daily OHLCV candles Public
KBS kbbuddywts.kbsec.com.vn Fundamental metrics (P/E, P/B, EPS, Beta) Public
SSI iboard-query.ssi.com.vn VN30/HNX30 index component lists Public

Technical Analysis Details

Indicator Method Parameters
RSI Wilder's exponential smoothing Default period: 14
MA Simple Moving Average Periods: 20, 50, 200
Momentum Rate of change: (current - past) / past * 100 Default period: 20
Signal MA crossover + RSI zones BUY: price > MA20 > MA50, RSI < 70
Score Composite: signal + RSI zones Range: 0-100
Weekly ISO week aggregation from daily OHLCV O=first, H=max, L=min, C=last, V=sum

Vietnamese Sentiment Keywords

The sentiment analyzer includes 70+ Vietnamese financial keywords with weights:

Positive (sample): tăng mạnh (1.5), bùng nổ (2.0), mua ròng (1.0), vượt đỉnh (1.5), lợi nhuận (1.0)

Negative (sample): giảm sâu (2.0), bán tháo (2.0), lao dốc (2.0), nợ xấu (1.5), rút vốn (1.5)

Score formula: (positive_sum - negative_sum) / total_sum, clamped to [-1.0, 1.0].

Domestic Flow Signal

Based on aggregate bid/ask volume ratios across stocks:

Buy Pressure Signal
>= 60% mua manh (strong buy)
>= 53% nghieng mua (leaning buy)
>= 47% can bang (balanced)
>= 40% nghieng ban (leaning sell)
< 40% ban manh (strong sell)

Volume Surge Detection

Uses z-score (preferred) or ratio fallback:

Z-Score / Ratio Intensity
>= 3.0 / 3x avg strong_surge
>= 2.0 / 2x avg surge
< 2.0 (no surge)

Combined with price direction:

  • Price up (>= 0.5%) + surge = accumulation (tien dang do vao)
  • Price down (<= -0.5%) + surge = distribution (dang ban thao)
  • Flat + surge = high_activity (giao dich dot bien)

Go vs Python API Conventions

Aspect Go Python
Input/Output []float64, structs pd.Series, pd.DataFrame, dataclasses
Async context.Context + goroutines asyncio (fetchers have _async variants planned)
Error handling (result, error) return Exceptions (ValueError, ConnectionError)
Config Option functions Keyword arguments
Dependencies Zero (stdlib only for pure modules) pandas, numpy (core), httpx (fetchers)

CLI tool — lmcli

A one-binary CLI for everything in the library. Useful for cron jobs, Telegram bots, or pipe-to-file workflows.

# Build
cd go && go build -o /tmp/lmcli ./cmd/lmcli

# Run
lmcli pulse              # daily VN30 + flow markdown
lmcli quote ACB          # real-time quote
lmcli rate ACB           # 6-dim star ratings
lmcli screen --rsi=30-50 # filter VN30 by RSI band
lmcli sectors            # sector flow leaderboard
lmcli global             # 14 international indices snapshot
lmcli dividends VNM      # corporate action calendar
lmcli report --out=today.md  # full daily report

Automated daily reports (zero-cost)

.github/workflows/daily-report.yml runs lmcli report every weekday at 15:30 VN via GitHub Actions (free for public repos), commits the markdown to docs/reports/YYYY-MM-DD.md, and serves via GitHub Pages — no server, no API key, no operating cost.

Browse the live archive: https://ducnhd.github.io/lotusmarket/

Changelog

v0.5.0

  • fetchers/yahoo (Go) — Yahoo Finance v8 chart API for global indices (S&P 500, Dow, Hang Seng, Nikkei, VIX, gold, oil, USD index) and historical OHLCV (split + dividend adjusted). Includes GlobalIndexRegistry for one-shot multi-symbol fetch.
  • fetchers/vci (Go) — VCI (Vietcap) IQ-Insight API for VN corporate actions: cash dividends, bonus issues, ESOP, rights. Same backend as vnstock python lib.
  • ratings (Go + Python) — 6-dimensional star ratings (price strength, trend, RSI position, money flow, volatility, base range) with overall 0-100 gauge and verdict. Deterministic, no AI.
  • historical (Go + Python) — generic cohort analysis: bucket (ticker, date, features, fwd_returns) rows by RSI / MA trend / MACD / Wyckoff / regime / joint, output markdown leaderboard. DB-agnostic.
  • cmd/lmcli (Go) — one-binary CLI: pulse, quote, rate, screen, sectors, global, dividends, report. Designed for cron / GitHub Actions automation.
  • CI (.github/workflows/daily-report.yml) — daily report generator running on GitHub free tier. Commits to docs/ for GitHub Pages serving.

Python fetchers for Yahoo + VCI are deferred to a follow-up release; Go is the reference implementation for new fetchers since the CLI + daily report workflow are Go-based.

v0.4.0

  • exposure (go/exposure, python/lotusmarket/exposure.py) — per-ticker mapping of external drivers (commodity / FX / peer) with 2-year backtest-validated Pearson r correlations for HPG, 14 VN30 banks, SSI, FPT, MWG, VHM/VIC/VRE, GAS, PLX, MSN, SAB, VNM, DGC, GVR, VJC; analyze(), format_for_prompt(), regime_change_flags(), bounce_signals() via a HistoryProvider seam.
  • market/regime (go/market/regime.go, appended to python/lotusmarket/market.py) — pure deterministic regime classifier: score_regime_signals() / classify_regime() / identify_trigger() covering STABLE / VOLATILE / CRISIS (PANIC vs FUNDAMENTAL) / EUPHORIA from VIX, VN-Index change, foreign flow, news tier, and global contagion inputs.
  • fetchers/cafef (go/fetchers/cafef.go, python/lotusmarket/fetchers/cafef.py) — CafeF AJAX JSON insider-trading fetcher; parses /Date(ms)/ timestamps in VN timezone (UTC+7), strips HTML name wrappers, applies 90-day cutoff; InsiderTransaction type added to types.
  • earnings (go/earnings, python/lotusmarket/earnings.py) — stateless Vietnamese headline parser: extract_profit(), extract_revenue(), extract_annual_target(), detect_period(), extract_tickers(), bctc_deadlines(year) returning 6 regulatory BCTC submission deadlines.

Support

If you find this useful, consider supporting the project:

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lotusmarket-0.5.0.tar.gz (48.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lotusmarket-0.5.0-py3-none-any.whl (46.1 kB view details)

Uploaded Python 3

File details

Details for the file lotusmarket-0.5.0.tar.gz.

File metadata

  • Download URL: lotusmarket-0.5.0.tar.gz
  • Upload date:
  • Size: 48.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for lotusmarket-0.5.0.tar.gz
Algorithm Hash digest
SHA256 164497e4f52224c7bcf612ae42d57202923ae9ced1d00df8b5c5e62957251f4e
MD5 bf9a4046367f5a079e915864511cd3a1
BLAKE2b-256 c5697e18ea6164fb3a204e41bbfaa1383ac1abf7c9277f4c3b6103d9fa68b01c

See more details on using hashes here.

Provenance

The following attestation bundles were made for lotusmarket-0.5.0.tar.gz:

Publisher: pypi-release.yml on ducnhd/lotusmarket

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lotusmarket-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: lotusmarket-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 46.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for lotusmarket-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c7dcd56c774cb706ecfeb38911e151405452e28f4eda949ccafa6a71ec49f5dd
MD5 4e664ec5a5a27aca028f6676f61a2e30
BLAKE2b-256 f7b6acaed24a03a255bde9ebce97630258803bcf205f9db1a5142dcd98e57414

See more details on using hashes here.

Provenance

The following attestation bundles were made for lotusmarket-0.5.0-py3-none-any.whl:

Publisher: pypi-release.yml on ducnhd/lotusmarket

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page