Skip to content

Repository files navigation

bdshare

StyleCI Documentation Status PyPI Python License

bdshare is a Python library for fetching live and historical market data from the Dhaka Stock Exchange (DSE). It handles scraping, retries, caching, and rate limiting so you can focus on your analysis.


Table of Contents


Installation

Requirements: Python 3.7+

pip install bdshare

Install from source (latest development version):

pip install -U git+https://github.com/rochi88/bdshare.git

Dependencies installed automatically: pandas, requests, beautifulsoup4, lxml


Quick Start

from bdshare import get_current_trade_data, get_historical_data

# Live prices for all instruments
df = get_current_trade_data()
print(df.head())

# Historical data for a specific symbol
df = get_historical_data('2024-01-01', '2024-01-31', 'GP')
print(df.head())

Or use the object-oriented client:

from bdshare import BDShare

with BDShare() as bd:
    print(bd.get_market_summary())
    print(bd.get_current_trades('ACI'))

Demo App

Three full-feature demos live in demo/ — live prices, historical charts with technical indicators, market movers, news, a portfolio tracker, live tick polling, and an interactive AI-agent (MCP) tool explorer:

cd demo
docker compose up --build streamlit   # pure Python — open http://localhost:8501
docker compose up --build node node-api mcp stream  # cross-language via REST/MCP/WebSocket — open http://localhost:3000
docker compose up --build app         # Flask/Plotly candlestick-only demo — open http://localhost:9999

The Node.js demo is the interesting one if you're integrating bdshare from outside Python: it's an Express UI that never imports bdshare — it talks to a FastAPI backend over REST, to bdshare-mcp over HTTP using the official MCP SDK, and to bdshare-stream over WebSocket, showing three different ways another program can consume this library.

See demo/README.md for local (non-Docker) setup for each.


Core Concepts

Concept Details
Retries All network calls retry up to 3 times with exponential back-off
Fallback URL Every request has a primary and an alternate DSE endpoint
Caching The BDShare client caches responses automatically (configurable TTL)
Rate limiting Built-in sliding-window limiter (5 calls/second) prevents being blocked
Errors All failures raise BDShareError — never silent

Usage Guide

Live Trading Data

from bdshare import get_current_trade_data, get_dsex_data, get_current_trading_code

# All instruments — returns columns: symbol, ltp, high, low, close, ycp, change, trade, value, volume
df = get_current_trade_data()

# Single instrument (case-insensitive)
df = get_current_trade_data('GP')

# DSEX index entries
df = get_dsex_data()

# Just the list of tradeable symbols
codes = get_current_trading_code()
print(codes['symbol'].tolist())

Historical Data

from bdshare import get_historical_data, get_basic_historical_data, get_close_price_data
import datetime as dt

start = '2024-01-01'
end   = '2024-03-31'

# Full historical data (ltp, open, high, low, close, volume, trade, value…)
# Indexed by date, sorted newest-first
df = get_historical_data(start, end, 'ACI')

# Simplified OHLCV — sorted oldest-first, ready for TA libraries
df = get_basic_historical_data(start, end, 'ACI')

# Set date as index explicitly
df = get_basic_historical_data(start, end, 'ACI', index='date')

# Rolling 2-year window
end   = dt.date.today()
start = end - dt.timedelta(days=2 * 365)
df    = get_basic_historical_data(str(start), str(end), 'GP')

# Close prices only
df = get_close_price_data(start, end, 'ACI')

Column order note: get_basic_historical_data intentionally returns OHLCV in standard order (open, high, low, close, volume) to be compatible with libraries like ta, pandas-ta, and backtrader.

Deprecated aliases: get_hist_data() and get_basic_hist_data() still work but emit a DeprecationWarning — migrate to get_historical_data() / get_basic_historical_data().

Market & Index Data

from bdshare import (
    get_market_status,
    get_market_info,
    get_market_info_more_data,
    get_market_depth_data,
    get_latest_pe,
    get_top_ten_gainers_losers,
    get_top_twenty_shares,
    get_company_info,
)

# Current market status: Open, Closed, Holiday, etc.
status = get_market_status()

# Last 30 days of market summary (DSEX, DSES, DS30, DGEN, volumes, market cap)
df = get_market_info()

# Historical market summary between two dates
df = get_market_info_more_data('2024-01-01', '2024-03-31')

# Order book (buy/sell depth) for a symbol
df = get_market_depth_data('ACI')

# P/E ratios for all listed companies
df = get_latest_pe()

# Top 10 gainers and losers (adjust limit as needed)
df = get_top_ten_gainers_losers(limit=10)

# Top 20 shares by traded volume (adjust limit as needed)
df = get_top_twenty_shares(limit=20)

# Detailed company profile
tables = get_company_info('GP')

News & Announcements

from bdshare import get_news, get_agm_news, get_all_news

# Unified dispatcher — news_type: 'all' | 'agm' | 'corporate' | 'psn'
df = get_news(news_type='all')
df = get_news(news_type='agm')
df = get_news(news_type='corporate', code='GP')
df = get_news(news_type='psn', code='ACI')   # price-sensitive news

# Direct function calls
df = get_agm_news()                          # AGM / dividend declarations
df = get_all_news(code='BEXIMCO')            # All news for one symbol
df = get_all_news('2024-01-01', '2024-03-31', 'GP')  # Filtered by date + symbol

Saving to CSV

from bdshare import get_basic_historical_data, Store
import datetime as dt

end   = dt.date.today()
start = end - dt.timedelta(days=365)

df = get_basic_historical_data(str(start), str(end), 'GP')
Store(df).save()   # saves to current directory as a CSV

OOP Client (BDShare)

The BDShare class wraps all functions with automatic caching and rate limiting.

from bdshare import BDShare

bd = BDShare(cache_enabled=True)   # cache_enabled=True is the default

Context manager (auto-cleans cache and session)

with BDShare() as bd:
    data = bd.get_current_trades('GP')

Market methods

bd.get_market_summary()                    # DSEX/DSES/DS30 indices + stats  (1-min TTL)
bd.get_company_profile('ACI')             # Company profile                  (1-hr TTL)
bd.get_latest_pe_ratios()                 # All P/E ratios                   (1-hr TTL)
bd.get_top_movers(limit=10)               # Top gainers/losers               (5-min TTL)

get_market_status() and get_top_twenty_shares() don't have BDShare wrapper methods yet — call the module-level functions directly (see API Reference).

Trading methods

bd.get_current_trades()                   # All live prices                  (30-sec TTL)
bd.get_current_trades('GP')               # Single symbol
bd.get_dsex_index()                       # DSEX index entries               (1-min TTL)
bd.get_trading_codes()                    # All tradeable symbols            (24-hr TTL)
bd.get_historical_data('GP', '2024-01-01', '2024-03-31')    # OHLCV history

News methods

bd.get_news(news_type='all')              # All news                         (5-min TTL)
bd.get_news(news_type='corporate', code='GP')
bd.get_news(news_type='psn')             # Price-sensitive news

Utility methods

bd.clear_cache()                          # Flush all cached data
bd.configure(proxy_url='http://proxy:8080')
print(bd.version)                         # Package version string

Error Handling

All failures raise BDShareError. Never catch bare Exception — you'll miss bugs.

from bdshare import BDShare, BDShareError

bd = BDShare()

try:
    df = bd.get_historical_data('INVALID', '2024-01-01', '2024-01-31')
except BDShareError as e:
    print(f"DSE error: {e}")
    # safe fallback logic here

Common causes of BDShareError:

  • Symbol not found in the response table
  • DSE site returned a non-200 status after all retries
  • Table structure changed on the DSE page (report as a bug)
  • Network timeout

API Reference

Trading Functions

Function Parameters Returns Description
get_current_trade_data(symbol?) symbol: str DataFrame Live prices (all or one symbol)
get_dsex_data(symbol?) symbol: str DataFrame DSEX index entries
get_current_trading_code() DataFrame All tradeable symbols
get_historical_data(start, end, code?) str, str, str DataFrame Full historical OHLCV
get_basic_historical_data(start, end, code?, index?) str, str, str, str DataFrame Simplified OHLCV (TA-ready)
get_close_price_data(start, end, code?) str, str, str DataFrame Close + prior close
get_last_trade_price_data() DataFrame Last trade from DSE text file

Deprecated aliases (removed in 2.0.0): get_hist_data()get_historical_data(), get_basic_hist_data()get_basic_historical_data().

Market Functions

Function Parameters Returns Description
get_market_status() str Current market status (Open, Closed, Holiday, etc.)
get_market_info() DataFrame 30-day market summary
get_market_info_more_data(start, end) str, str DataFrame Historical market summary
get_market_depth_data(symbol) str DataFrame Order book (buy/sell depth)
get_latest_pe() DataFrame P/E ratios for all companies
get_company_info(symbol) str list[DataFrame] Detailed company tables
get_top_ten_gainers_losers(limit?) int (default 10) DataFrame Top movers by price change
get_top_twenty_shares(limit?) int (default 20) DataFrame Top shares by traded volume

News Functions

Function Parameters Returns Description
get_news(news_type?, code?) str, str DataFrame Unified news dispatcher
get_agm_news() DataFrame AGM / dividend declarations
get_all_news(start?, end?, code?) str, str, str DataFrame All DSE news
get_corporate_announcements(code?) str DataFrame Corporate actions
get_price_sensitive_news(code?) str DataFrame Price-sensitive news

get_news news_type values

Value Equivalent direct function
'all' get_all_news()
'agm' get_agm_news()
'corporate' get_corporate_announcements()
'psn' get_price_sensitive_news()

Examples

Stock performance summary

import datetime as dt
from bdshare import BDShare, BDShareError

def summarize(symbol: str, days: int = 30) -> dict:
    end   = dt.date.today()
    start = end - dt.timedelta(days=days)

    with BDShare() as bd:
        try:
            df = bd.get_historical_data(symbol, str(start), str(end))
        except BDShareError as e:
            print(f"Could not fetch data: {e}")
            return {}

    return {
        'symbol':       symbol,
        'current':      df['close'].iloc[0],
        'period_high':  df['high'].max(),
        'period_low':   df['low'].min(),
        'avg_volume':   df['volume'].mean(),
        'change_pct':   (df['close'].iloc[0] - df['close'].iloc[-1])
                        / df['close'].iloc[-1] * 100,
    }

result = summarize('GP', days=30)
print(f"{result['symbol']}: {result['change_pct']:.2f}% over 30 days")

Simple portfolio tracker

from bdshare import BDShare, BDShareError

PORTFOLIO = {
    'GP':      {'qty': 100, 'cost': 450.50},
    'ACI':     {'qty':  50, 'cost': 225.75},
    'BEXIMCO': {'qty': 200, 'cost': 125.25},
}

with BDShare() as bd:
    total_cost = total_value = 0

    for symbol, pos in PORTFOLIO.items():
        try:
            row = bd.get_current_trades(symbol).iloc[0]
            price        = row['ltp']
            market_value = pos['qty'] * price
            cost         = pos['qty'] * pos['cost']
            pnl          = market_value - cost

            print(f"{symbol:10s}  price={price:8.2f}  P&L={pnl:+10.2f}")
            total_cost  += cost
            total_value += market_value

        except BDShareError as e:
            print(f"{symbol}: fetch error — {e}")

    print(f"\nPortfolio P&L: {total_value - total_cost:+.2f} "
          f"({(total_value/total_cost - 1)*100:+.2f}%)")

Fetch and screen top gainers above 5 %

from bdshare import get_top_ten_gainers_losers

df = get_top_ten_gainers_losers(limit=20)
big_movers = df[df['change'] > 5]
print(big_movers[['symbol', 'close', 'change']])

Advanced Features

Technical Indicators

Wraps the ta library to add indicator columns directly onto bdshare's OHLCV DataFrames.

pip install "bdshare[ta]"
from bdshare import get_basic_historical_data
from bdshare.indicators import add_indicators, add_rsi

df = get_basic_historical_data('2024-01-01', '2024-06-30', 'GP')

# Add everything (sma_20, ema_20, rsi_14, macd/macd_signal/macd_diff, bb_high/bb_mid/bb_low)
df = add_indicators(df)

# Or just one, with custom parameters
df = add_rsi(df, window=21)

Also available: add_sma(), add_ema(), add_macd(), add_bollinger_bands(). Each returns a new DataFrame (the input is never mutated).

Portfolio Tracking

Portfolio tracks cost basis and values holdings against live prices — no extra dependency required (pure pandas), and valuation() makes exactly one live call for all instruments regardless of how many positions you hold.

from bdshare.portfolio import Portfolio

pf = Portfolio()
pf.add_position('GP', quantity=100, avg_cost=450.50)
pf.add_position('ACI', quantity=50, avg_cost=225.75)

print(pf.holdings().to_string())    # cost basis only, no network call
print(pf.valuation().to_string())   # + ltp, market_value, pnl, pnl_pct per position
print(pf.summary())                 # {'positions': 2, 'total_cost': ..., 'total_pnl': ...}

Adding to an existing position blends the cost basis like a real buy; a negative quantity reduces it, and netting to zero drops the position. Unknown/delisted symbols get None valuation fields instead of raising, so one bad symbol doesn't block valuing the rest.

Real-Time Streaming (WebSocket)

DSE has no public push/streaming API — this polls get_current_trade_data() on an interval and broadcasts changed rows over WebSocket, so another program can subscribe instead of polling bdshare itself.

pip install "bdshare[stream]"

Run the bundled server:

bdshare-stream --symbols GP,ACI --interval 5

Any other program connects as a plain WebSocket client:

import asyncio, json, websockets

async def main():
    async with websockets.connect('ws://localhost:8765') as ws:
        async for message in ws:
            print(json.loads(message))   # {"type": "ticks", "data": [...]}

asyncio.run(main())

Or skip the server and use the polling generator directly inside your own asyncio program:

from bdshare.stream import stream_ticks

async def main():
    async for changed in stream_ticks(symbols=['GP', 'ACI'], interval=5.0):
        print(changed)

asyncio.run(main())

Using bdshare with AI Agents (MCP Server)

bdshare ships an MCP server so AI agents running in another program — Claude Desktop, Claude Code, or any other MCP-compatible client — can call live DSE data as tools, without you writing any glue code.

Install

pip install "bdshare[mcp]"

Run

bdshare-mcp
# or
python -m bdshare.mcp_server

By default it speaks MCP over stdio, which is what desktop/CLI agent clients expect.

Connect it to a client

Claude Code:

claude mcp add bdshare -- bdshare-mcp

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "bdshare": {
      "command": "bdshare-mcp"
    }
  }
}

Any other MCP client is configured the same way — point it at the bdshare-mcp command (or python -m bdshare.mcp_server), stdio transport.

Running it over the network instead of stdio

Stdio only works when the client can spawn the server as a local child process (Claude Desktop/Code). For a program in another container, another machine, or another language entirely — nothing that speaks Python — run it over HTTP instead:

bdshare-mcp --transport streamable-http --host 0.0.0.0 --port 8000

Any MCP client library can connect to http://host:8000/mcp; see demo/node/ for a full example using the official @modelcontextprotocol/sdk from Node.js. Binding beyond 127.0.0.1/localhost automatically disables DNS-rebinding protection, since every legitimate client then arrives with a non-localhost Host header — only do this on a trusted network (an internal Docker network, not the public internet).

What the agent gets

15 tools covering the same data this README documents — market_status, market_summary, market_summary_range, market_depth, latest_pe_ratios, top_ten_gainers_losers, top_twenty_shares, company_info, current_trades, dsex_index, trading_codes, historical_data, basic_historical_data, news, agm_news. Each tool returns JSON — DataFrames are converted to lists of row records — and a BDShareError (bad symbol, DSE outage, parse failure) surfaces as a clean tool error message instead of a raw traceback.

Notes for agent use

  • No caching in the MCP server itself. Every tool call scrapes dsebd.org live. If your agent calls the same tool repeatedly in one turn (e.g. checking a price several times), consider fronting it with the BDShare client's caching in a custom wrapper — the bundled server intentionally stays stateless and simple.
  • Rate limits are the agent's responsibility. The BDShare OOP client has a built-in 5 calls/second limiter; the MCP tools call the plain module-level functions, which don't. Avoid tight loops of tool calls.
  • Source: bdshare/mcp_server.py — a plain FastMCP server, easy to fork if you want a different tool surface.

Contributing

Contributions are welcome! To get started:

git clone https://github.com/rochi88/bdshare.git
cd bdshare
pip install -e ".[dev]"
pytest

Please open an issue before submitting a pull request for significant changes. See CONTRIBUTING.md for the full guide.


Roadmap

  • WebSocket streaming for real-time ticks (polling-based — see Real-Time Streaming)
  • Built-in technical indicators (ta integration)
  • Portfolio management helpers
  • Docker demo examples
  • Shared session with exponential back-off
  • lxml-based fast parsing
  • BDShareError for clean error handling
  • Unified get_news() dispatcher
  • Rate limiter and response caching
  • MCP server for AI agent integration

Support


License

MIT — see LICENSE for details.

Disclaimer

bdshare is intended for educational and research use. Always respect DSE's terms of service. The authors are not responsible for financial decisions made using this library.

About

A utility for crawling historical and Real-time Quotes data from Dhaka Stock Exchange (DSE)

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages