Skip to main content

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

Release files for lotusmarket 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lotusmarket 0.5.0
File Size Uploaded
lotusmarket-0.5.0.tar.gz 48.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lotusmarket 0.5.0
File Interpreter ABI Platform
lotusmarket-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 94.4 kB

Release files / lotusmarket-0.5.0.tar.gz

Download URL lotusmarket-0.5.0.tar.gz
Size 48.3 kB
Tags Source
SHA-256 checksum
How to use checksums
164497e4f52224c7bcf612ae42d57202923ae9ced1d00df8b5c5e62957251f4e
BLAKE2b-256 checksum
How to use checksums
c5697e18ea6164fb3a204e41bbfaa1383ac1abf7c9277f4c3b6103d9fa68b01c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 15, 2026.

Transparency log

Release files / lotusmarket-0.5.0-py3-none-any.whl

Download URL lotusmarket-0.5.0-py3-none-any.whl
Size 46.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c7dcd56c774cb706ecfeb38911e151405452e28f4eda949ccafa6a71ec49f5dd
BLAKE2b-256 checksum
How to use checksums
f7b6acaed24a03a255bde9ebce97630258803bcf205f9db1a5142dcd98e57414
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page