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.
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) |
Changelog
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
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lotusmarket-0.4.0.tar.gz.
File metadata
- Download URL: lotusmarket-0.4.0.tar.gz
- Upload date:
- Size: 42.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f8d1cadf72eaa1094dfc392f906909d206ec9045f0c84d42fdfade2708a8eef3
|
|
| MD5 |
304006bd68a19663937cc5d94bcb656d
|
|
| BLAKE2b-256 |
59319b20b2de909c87da916972ce7a8ce8bd7243d5960e3dd80581c04247671e
|
Provenance
The following attestation bundles were made for lotusmarket-0.4.0.tar.gz:
Publisher:
pypi-release.yml on ducnhd/lotusmarket
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lotusmarket-0.4.0.tar.gz -
Subject digest:
f8d1cadf72eaa1094dfc392f906909d206ec9045f0c84d42fdfade2708a8eef3 - Sigstore transparency entry: 1378457502
- Sigstore integration time:
-
Permalink:
ducnhd/lotusmarket@a2ab7e251826b3b1ce65970553f4d3796c7bd206 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/ducnhd
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@a2ab7e251826b3b1ce65970553f4d3796c7bd206 -
Trigger Event:
push
-
Statement type:
File details
Details for the file lotusmarket-0.4.0-py3-none-any.whl.
File metadata
- Download URL: lotusmarket-0.4.0-py3-none-any.whl
- Upload date:
- Size: 40.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f776246ab1782313c08014959ed7af0f3ced1275353ad3e13bdccf1ad882de09
|
|
| MD5 |
c6cd86054f6cec575ea23116bcf4ea8f
|
|
| BLAKE2b-256 |
fce4a4d5163a32811dab2838a162cdcecdfe795950079d5e9d828598a6179ecd
|
Provenance
The following attestation bundles were made for lotusmarket-0.4.0-py3-none-any.whl:
Publisher:
pypi-release.yml on ducnhd/lotusmarket
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lotusmarket-0.4.0-py3-none-any.whl -
Subject digest:
f776246ab1782313c08014959ed7af0f3ced1275353ad3e13bdccf1ad882de09 - Sigstore transparency entry: 1378457630
- Sigstore integration time:
-
Permalink:
ducnhd/lotusmarket@a2ab7e251826b3b1ce65970553f4d3796c7bd206 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/ducnhd
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@a2ab7e251826b3b1ce65970553f4d3796c7bd206 -
Trigger Event:
push
-
Statement type: