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.
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(addsanthropic-sdk-godependency) Python:pip install lotusmarket[ai](addsanthropicdependency)
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
GlobalIndexRegistryfor 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 todocs/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 aHistoryProviderseam. - market/regime (
go/market/regime.go, appended topython/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;InsiderTransactiontype added totypes. - 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:
- PayPal
- Telegram Stars: @vnlotusmarketbot
/donate
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)
| File | Size | Uploaded | |
|---|---|---|---|
| lotusmarket-0.5.0.tar.gz | 48.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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