bdshare 1.2.2__tar.gz → 1.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bdshare-1.2.2 → bdshare-1.2.3}/CHANGELOG.md +17 -0
- {bdshare-1.2.2/bdshare.egg-info → bdshare-1.2.3}/PKG-INFO +165 -6
- {bdshare-1.2.2 → bdshare-1.2.3}/README.md +143 -5
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/__init__.py +1 -5
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/_version.py +1 -1
- bdshare-1.2.3/bdshare/indicators.py +100 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/mcp_server.py +26 -1
- bdshare-1.2.3/bdshare/portfolio.py +138 -0
- bdshare-1.2.3/bdshare/stream.py +161 -0
- {bdshare-1.2.2 → bdshare-1.2.3/bdshare.egg-info}/PKG-INFO +165 -6
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare.egg-info/SOURCES.txt +6 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare.egg-info/entry_points.txt +1 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare.egg-info/requires.txt +6 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/setup.cfg +6 -1
- bdshare-1.2.3/tests/test_indicators.py +89 -0
- bdshare-1.2.3/tests/test_portfolio.py +110 -0
- bdshare-1.2.3/tests/test_stream.py +131 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/LICENSE +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/stock/__init__.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/stock/market.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/stock/news.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/stock/trading.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/__init__.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/cache.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/cons.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/helper.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/proxy.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/sectigo_dv_r36.pem +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/session.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/store.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/tickers.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/upass.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare/util/vars.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare.egg-info/dependency_links.txt +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare.egg-info/not-zip-safe +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/bdshare.egg-info/top_level.txt +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/pyproject.toml +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/tests/__init__.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/tests/test_market.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/tests/test_news.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/tests/test_polars.py +0 -0
- {bdshare-1.2.2 → bdshare-1.2.3}/tests/test_trading.py +0 -0
|
@@ -6,6 +6,23 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
6
6
|
Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
---
|
|
9
|
+
## [1.2.3] - 2026-07-22
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `bdshare/indicators.py` — technical indicator helpers (`add_sma`, `add_ema`, `add_rsi`, `add_macd`, `add_bollinger_bands`, `add_indicators`) built on the `ta` library, operating on the OHLCV DataFrames returned by `get_basic_historical_data()`; install with `pip install bdshare[ta]`
|
|
13
|
+
- `bdshare/portfolio.py` — `Portfolio`/`Position` classes for tracking cost basis and valuing holdings against live prices (`holdings()`, `valuation()`, `summary()`); one live call for all instruments regardless of portfolio size
|
|
14
|
+
- `bdshare/stream.py` — polling-based WebSocket tick streaming (`stream_ticks()`, `TickServer`); broadcasts changed rows from `get_current_trade_data()` to connected clients so another program can subscribe over `ws://` instead of polling itself. DSE has no push API, so this is poll-and-diff under a WebSocket-shaped interface, not true push. Install with `pip install bdshare[stream]`, run with `bdshare-stream`
|
|
15
|
+
- `demo/streamlit/streamlit_app.py` — full-feature Streamlit dashboard showcasing every public feature in one app: live trading data, historical charts with `bdshare.indicators` overlays, market movers, news, `bdshare.portfolio` tracking, live-tick polling, and an interactive AI-agent (MCP) tool explorer. Runs via `docker compose up --build streamlit` (`demo/streamlit/Dockerfile`, builds bdshare from local source so unreleased features are included) or locally with `pip install -e ".[ta,stream]"` + `streamlit run demo/streamlit/streamlit_app.py`
|
|
16
|
+
- `demo/` reorganized into `demo/flask/`, `demo/streamlit/`, and `demo/node/`, all built from `demo/docker-compose.yml` with a repo-root build context; documented in `demo/README.md` (previously an unrelated generic Flask/Plotly readme with no mention of bdshare or Docker)
|
|
17
|
+
- `bdshare-mcp --transport streamable-http --host 0.0.0.0 --port 8000` — the MCP server can now run over the network instead of only stdio, for clients that can't spawn it as a local child process (a different language, another container). Binding beyond loopback auto-disables DNS-rebinding protection, since every legitimate client then arrives with a non-localhost `Host` header
|
|
18
|
+
- `demo/node/` — a four-service showcase of consuming bdshare from **outside Python**: an Express UI (`demo/node/server`, never imports bdshare) talking to a FastAPI backend (`demo/node/api`, bdshare functions as JSON REST) over REST, to `bdshare-mcp --transport streamable-http` over MCP via the official `@modelcontextprotocol/sdk`, and to `bdshare-stream` over WebSocket (relayed to the browser). Run via `docker compose up --build node node-api mcp stream` or locally per `demo/README.md`
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `bdshare.BDShareError` (the publicly exported/documented exception) was a **different class** from `bdshare.util.helper.BDShareError`, the one every scraping function actually raises — `except bdshare.BDShareError` silently never caught real errors. `bdshare/__init__.py` now re-exports the same class instead of redefining it
|
|
22
|
+
- `demo/app.py`: `convert_to_native_types()` called `pd.isna(obj)` before checking whether `obj` was a list/dict, crashing with `ValueError: The truth value of an array... is ambiguous` on any chart request; container types are now checked first
|
|
23
|
+
- `demo/app.py`: `get_available_tickers()` checked for a `trading_code` column that doesn't exist on `get_current_trade_data()`'s output (`symbol` does), so it always silently fell back to the hardcoded stock list
|
|
24
|
+
- `demo/Dockerfile`: base image `python:3.11-slim-buster` is EOL (Debian Buster archived), so `apt-get update` would fail on a fresh build; switched to `python:3.11-slim`
|
|
25
|
+
|
|
9
26
|
## [1.2.2] - 2026-07-22
|
|
10
27
|
|
|
11
28
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bdshare
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: A utility for crawling historical and Real-time Quotes of DSE(Dhaka Stock Exchange)
|
|
5
5
|
Author: Raisul Islam
|
|
6
6
|
Author-email: raisul.me@gmail.com
|
|
@@ -20,6 +20,10 @@ Provides-Extra: polars
|
|
|
20
20
|
Requires-Dist: polars; extra == "polars"
|
|
21
21
|
Provides-Extra: mcp
|
|
22
22
|
Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
23
|
+
Provides-Extra: ta
|
|
24
|
+
Requires-Dist: ta; extra == "ta"
|
|
25
|
+
Provides-Extra: stream
|
|
26
|
+
Requires-Dist: websockets>=12.0; extra == "stream"
|
|
23
27
|
Dynamic: license-file
|
|
24
28
|
|
|
25
29
|
# bdshare
|
|
@@ -38,6 +42,7 @@ Dynamic: license-file
|
|
|
38
42
|
|
|
39
43
|
- [Installation](#installation)
|
|
40
44
|
- [Quick Start](#quick-start)
|
|
45
|
+
- [Demo App](#demo-app)
|
|
41
46
|
- [Core Concepts](#core-concepts)
|
|
42
47
|
- [Usage Guide](#usage-guide)
|
|
43
48
|
- [Live Trading Data](#live-trading-data)
|
|
@@ -49,6 +54,10 @@ Dynamic: license-file
|
|
|
49
54
|
- [Error Handling](#error-handling)
|
|
50
55
|
- [API Reference](#api-reference)
|
|
51
56
|
- [Examples](#examples)
|
|
57
|
+
- [Advanced Features](#advanced-features)
|
|
58
|
+
- [Technical Indicators](#technical-indicators)
|
|
59
|
+
- [Portfolio Tracking](#portfolio-tracking)
|
|
60
|
+
- [Real-Time Streaming (WebSocket)](#real-time-streaming-websocket)
|
|
52
61
|
- [Using bdshare with AI Agents (MCP Server)](#using-bdshare-with-ai-agents-mcp-server)
|
|
53
62
|
- [Contributing](#contributing)
|
|
54
63
|
- [Roadmap](#roadmap)
|
|
@@ -100,6 +109,29 @@ with BDShare() as bd:
|
|
|
100
109
|
|
|
101
110
|
---
|
|
102
111
|
|
|
112
|
+
## Demo App
|
|
113
|
+
|
|
114
|
+
Three full-feature demos live in [`demo/`](demo/) — live prices, historical charts with
|
|
115
|
+
technical indicators, market movers, news, a portfolio tracker, live tick polling, and
|
|
116
|
+
an interactive AI-agent (MCP) tool explorer:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
cd demo
|
|
120
|
+
docker compose up --build streamlit # pure Python — open http://localhost:8501
|
|
121
|
+
docker compose up --build node node-api mcp stream # cross-language via REST/MCP/WebSocket — open http://localhost:3000
|
|
122
|
+
docker compose up --build app # Flask/Plotly candlestick-only demo — open http://localhost:9999
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The Node.js demo is the interesting one if you're integrating bdshare from **outside
|
|
126
|
+
Python**: it's an Express UI that never imports bdshare — it talks to a FastAPI backend
|
|
127
|
+
over REST, to `bdshare-mcp` over HTTP using the official MCP SDK, and to
|
|
128
|
+
`bdshare-stream` over WebSocket, showing three different ways another program can
|
|
129
|
+
consume this library.
|
|
130
|
+
|
|
131
|
+
See [`demo/README.md`](demo/README.md) for local (non-Docker) setup for each.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
103
135
|
## Core Concepts
|
|
104
136
|
|
|
105
137
|
| Concept | Details |
|
|
@@ -448,6 +480,100 @@ print(big_movers[['symbol', 'close', 'change']])
|
|
|
448
480
|
|
|
449
481
|
---
|
|
450
482
|
|
|
483
|
+
## Advanced Features
|
|
484
|
+
|
|
485
|
+
### Technical Indicators
|
|
486
|
+
|
|
487
|
+
Wraps the [`ta`](https://github.com/bukosabino/ta) library to add indicator columns
|
|
488
|
+
directly onto bdshare's OHLCV DataFrames.
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
pip install "bdshare[ta]"
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
from bdshare import get_basic_historical_data
|
|
496
|
+
from bdshare.indicators import add_indicators, add_rsi
|
|
497
|
+
|
|
498
|
+
df = get_basic_historical_data('2024-01-01', '2024-06-30', 'GP')
|
|
499
|
+
|
|
500
|
+
# Add everything (sma_20, ema_20, rsi_14, macd/macd_signal/macd_diff, bb_high/bb_mid/bb_low)
|
|
501
|
+
df = add_indicators(df)
|
|
502
|
+
|
|
503
|
+
# Or just one, with custom parameters
|
|
504
|
+
df = add_rsi(df, window=21)
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Also available: `add_sma()`, `add_ema()`, `add_macd()`, `add_bollinger_bands()`.
|
|
508
|
+
Each returns a new DataFrame (the input is never mutated).
|
|
509
|
+
|
|
510
|
+
### Portfolio Tracking
|
|
511
|
+
|
|
512
|
+
`Portfolio` tracks cost basis and values holdings against live prices — no extra
|
|
513
|
+
dependency required (pure pandas), and `valuation()` makes exactly **one** live call
|
|
514
|
+
for all instruments regardless of how many positions you hold.
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
from bdshare.portfolio import Portfolio
|
|
518
|
+
|
|
519
|
+
pf = Portfolio()
|
|
520
|
+
pf.add_position('GP', quantity=100, avg_cost=450.50)
|
|
521
|
+
pf.add_position('ACI', quantity=50, avg_cost=225.75)
|
|
522
|
+
|
|
523
|
+
print(pf.holdings().to_string()) # cost basis only, no network call
|
|
524
|
+
print(pf.valuation().to_string()) # + ltp, market_value, pnl, pnl_pct per position
|
|
525
|
+
print(pf.summary()) # {'positions': 2, 'total_cost': ..., 'total_pnl': ...}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Adding to an existing position blends the cost basis like a real buy; a negative
|
|
529
|
+
`quantity` reduces it, and netting to zero drops the position. Unknown/delisted
|
|
530
|
+
symbols get `None` valuation fields instead of raising, so one bad symbol doesn't
|
|
531
|
+
block valuing the rest.
|
|
532
|
+
|
|
533
|
+
### Real-Time Streaming (WebSocket)
|
|
534
|
+
|
|
535
|
+
DSE has no public push/streaming API — this polls `get_current_trade_data()` on an
|
|
536
|
+
interval and broadcasts **changed** rows over WebSocket, so another program can
|
|
537
|
+
subscribe instead of polling bdshare itself.
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
pip install "bdshare[stream]"
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Run the bundled server:
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
bdshare-stream --symbols GP,ACI --interval 5
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Any other program connects as a plain WebSocket client:
|
|
550
|
+
|
|
551
|
+
```python
|
|
552
|
+
import asyncio, json, websockets
|
|
553
|
+
|
|
554
|
+
async def main():
|
|
555
|
+
async with websockets.connect('ws://localhost:8765') as ws:
|
|
556
|
+
async for message in ws:
|
|
557
|
+
print(json.loads(message)) # {"type": "ticks", "data": [...]}
|
|
558
|
+
|
|
559
|
+
asyncio.run(main())
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Or skip the server and use the polling generator directly inside your own asyncio
|
|
563
|
+
program:
|
|
564
|
+
|
|
565
|
+
```python
|
|
566
|
+
from bdshare.stream import stream_ticks
|
|
567
|
+
|
|
568
|
+
async def main():
|
|
569
|
+
async for changed in stream_ticks(symbols=['GP', 'ACI'], interval=5.0):
|
|
570
|
+
print(changed)
|
|
571
|
+
|
|
572
|
+
asyncio.run(main())
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
451
577
|
## Using bdshare with AI Agents (MCP Server)
|
|
452
578
|
|
|
453
579
|
bdshare ships an [MCP](https://modelcontextprotocol.io/) server so AI agents running in
|
|
@@ -493,6 +619,23 @@ claude mcp add bdshare -- bdshare-mcp
|
|
|
493
619
|
Any other MCP client is configured the same way — point it at the `bdshare-mcp` command
|
|
494
620
|
(or `python -m bdshare.mcp_server`), stdio transport.
|
|
495
621
|
|
|
622
|
+
### Running it over the network instead of stdio
|
|
623
|
+
|
|
624
|
+
Stdio only works when the client can spawn the server as a local child process (Claude
|
|
625
|
+
Desktop/Code). For a program in **another container, another machine, or another
|
|
626
|
+
language entirely** — nothing that speaks Python — run it over HTTP instead:
|
|
627
|
+
|
|
628
|
+
```bash
|
|
629
|
+
bdshare-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
Any MCP client library can connect to `http://host:8000/mcp`; see
|
|
633
|
+
[`demo/node/`](demo/node/) for a full example using the official
|
|
634
|
+
`@modelcontextprotocol/sdk` from Node.js. Binding beyond `127.0.0.1`/`localhost`
|
|
635
|
+
automatically disables DNS-rebinding protection, since every legitimate client then
|
|
636
|
+
arrives with a non-localhost `Host` header — only do this on a trusted network (an
|
|
637
|
+
internal Docker network, not the public internet).
|
|
638
|
+
|
|
496
639
|
### What the agent gets
|
|
497
640
|
|
|
498
641
|
15 tools covering the same data this README documents — `market_status`,
|
|
@@ -535,11 +678,10 @@ Please open an issue before submitting a pull request for significant changes. S
|
|
|
535
678
|
|
|
536
679
|
## Roadmap
|
|
537
680
|
|
|
538
|
-
- [
|
|
539
|
-
- [
|
|
540
|
-
- [
|
|
541
|
-
- [
|
|
542
|
-
- [ ] Docker demo examples
|
|
681
|
+
- [x] WebSocket streaming for real-time ticks (polling-based — see [Real-Time Streaming](#real-time-streaming-websocket))
|
|
682
|
+
- [x] Built-in technical indicators (`ta` integration)
|
|
683
|
+
- [x] Portfolio management helpers
|
|
684
|
+
- [x] Docker demo examples
|
|
543
685
|
- [x] Shared session with exponential back-off
|
|
544
686
|
- [x] `lxml`-based fast parsing
|
|
545
687
|
- [x] `BDShareError` for clean error handling
|
|
@@ -572,6 +714,23 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
572
714
|
Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
573
715
|
|
|
574
716
|
---
|
|
717
|
+
## [1.2.3] - 2026-07-22
|
|
718
|
+
|
|
719
|
+
### Added
|
|
720
|
+
- `bdshare/indicators.py` — technical indicator helpers (`add_sma`, `add_ema`, `add_rsi`, `add_macd`, `add_bollinger_bands`, `add_indicators`) built on the `ta` library, operating on the OHLCV DataFrames returned by `get_basic_historical_data()`; install with `pip install bdshare[ta]`
|
|
721
|
+
- `bdshare/portfolio.py` — `Portfolio`/`Position` classes for tracking cost basis and valuing holdings against live prices (`holdings()`, `valuation()`, `summary()`); one live call for all instruments regardless of portfolio size
|
|
722
|
+
- `bdshare/stream.py` — polling-based WebSocket tick streaming (`stream_ticks()`, `TickServer`); broadcasts changed rows from `get_current_trade_data()` to connected clients so another program can subscribe over `ws://` instead of polling itself. DSE has no push API, so this is poll-and-diff under a WebSocket-shaped interface, not true push. Install with `pip install bdshare[stream]`, run with `bdshare-stream`
|
|
723
|
+
- `demo/streamlit/streamlit_app.py` — full-feature Streamlit dashboard showcasing every public feature in one app: live trading data, historical charts with `bdshare.indicators` overlays, market movers, news, `bdshare.portfolio` tracking, live-tick polling, and an interactive AI-agent (MCP) tool explorer. Runs via `docker compose up --build streamlit` (`demo/streamlit/Dockerfile`, builds bdshare from local source so unreleased features are included) or locally with `pip install -e ".[ta,stream]"` + `streamlit run demo/streamlit/streamlit_app.py`
|
|
724
|
+
- `demo/` reorganized into `demo/flask/`, `demo/streamlit/`, and `demo/node/`, all built from `demo/docker-compose.yml` with a repo-root build context; documented in `demo/README.md` (previously an unrelated generic Flask/Plotly readme with no mention of bdshare or Docker)
|
|
725
|
+
- `bdshare-mcp --transport streamable-http --host 0.0.0.0 --port 8000` — the MCP server can now run over the network instead of only stdio, for clients that can't spawn it as a local child process (a different language, another container). Binding beyond loopback auto-disables DNS-rebinding protection, since every legitimate client then arrives with a non-localhost `Host` header
|
|
726
|
+
- `demo/node/` — a four-service showcase of consuming bdshare from **outside Python**: an Express UI (`demo/node/server`, never imports bdshare) talking to a FastAPI backend (`demo/node/api`, bdshare functions as JSON REST) over REST, to `bdshare-mcp --transport streamable-http` over MCP via the official `@modelcontextprotocol/sdk`, and to `bdshare-stream` over WebSocket (relayed to the browser). Run via `docker compose up --build node node-api mcp stream` or locally per `demo/README.md`
|
|
727
|
+
|
|
728
|
+
### Fixed
|
|
729
|
+
- `bdshare.BDShareError` (the publicly exported/documented exception) was a **different class** from `bdshare.util.helper.BDShareError`, the one every scraping function actually raises — `except bdshare.BDShareError` silently never caught real errors. `bdshare/__init__.py` now re-exports the same class instead of redefining it
|
|
730
|
+
- `demo/app.py`: `convert_to_native_types()` called `pd.isna(obj)` before checking whether `obj` was a list/dict, crashing with `ValueError: The truth value of an array... is ambiguous` on any chart request; container types are now checked first
|
|
731
|
+
- `demo/app.py`: `get_available_tickers()` checked for a `trading_code` column that doesn't exist on `get_current_trade_data()`'s output (`symbol` does), so it always silently fell back to the hardcoded stock list
|
|
732
|
+
- `demo/Dockerfile`: base image `python:3.11-slim-buster` is EOL (Debian Buster archived), so `apt-get update` would fail on a fresh build; switched to `python:3.11-slim`
|
|
733
|
+
|
|
575
734
|
## [1.2.2] - 2026-07-22
|
|
576
735
|
|
|
577
736
|
### Added
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
- [Installation](#installation)
|
|
16
16
|
- [Quick Start](#quick-start)
|
|
17
|
+
- [Demo App](#demo-app)
|
|
17
18
|
- [Core Concepts](#core-concepts)
|
|
18
19
|
- [Usage Guide](#usage-guide)
|
|
19
20
|
- [Live Trading Data](#live-trading-data)
|
|
@@ -25,6 +26,10 @@
|
|
|
25
26
|
- [Error Handling](#error-handling)
|
|
26
27
|
- [API Reference](#api-reference)
|
|
27
28
|
- [Examples](#examples)
|
|
29
|
+
- [Advanced Features](#advanced-features)
|
|
30
|
+
- [Technical Indicators](#technical-indicators)
|
|
31
|
+
- [Portfolio Tracking](#portfolio-tracking)
|
|
32
|
+
- [Real-Time Streaming (WebSocket)](#real-time-streaming-websocket)
|
|
28
33
|
- [Using bdshare with AI Agents (MCP Server)](#using-bdshare-with-ai-agents-mcp-server)
|
|
29
34
|
- [Contributing](#contributing)
|
|
30
35
|
- [Roadmap](#roadmap)
|
|
@@ -76,6 +81,29 @@ with BDShare() as bd:
|
|
|
76
81
|
|
|
77
82
|
---
|
|
78
83
|
|
|
84
|
+
## Demo App
|
|
85
|
+
|
|
86
|
+
Three full-feature demos live in [`demo/`](demo/) — live prices, historical charts with
|
|
87
|
+
technical indicators, market movers, news, a portfolio tracker, live tick polling, and
|
|
88
|
+
an interactive AI-agent (MCP) tool explorer:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cd demo
|
|
92
|
+
docker compose up --build streamlit # pure Python — open http://localhost:8501
|
|
93
|
+
docker compose up --build node node-api mcp stream # cross-language via REST/MCP/WebSocket — open http://localhost:3000
|
|
94
|
+
docker compose up --build app # Flask/Plotly candlestick-only demo — open http://localhost:9999
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The Node.js demo is the interesting one if you're integrating bdshare from **outside
|
|
98
|
+
Python**: it's an Express UI that never imports bdshare — it talks to a FastAPI backend
|
|
99
|
+
over REST, to `bdshare-mcp` over HTTP using the official MCP SDK, and to
|
|
100
|
+
`bdshare-stream` over WebSocket, showing three different ways another program can
|
|
101
|
+
consume this library.
|
|
102
|
+
|
|
103
|
+
See [`demo/README.md`](demo/README.md) for local (non-Docker) setup for each.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
79
107
|
## Core Concepts
|
|
80
108
|
|
|
81
109
|
| Concept | Details |
|
|
@@ -424,6 +452,100 @@ print(big_movers[['symbol', 'close', 'change']])
|
|
|
424
452
|
|
|
425
453
|
---
|
|
426
454
|
|
|
455
|
+
## Advanced Features
|
|
456
|
+
|
|
457
|
+
### Technical Indicators
|
|
458
|
+
|
|
459
|
+
Wraps the [`ta`](https://github.com/bukosabino/ta) library to add indicator columns
|
|
460
|
+
directly onto bdshare's OHLCV DataFrames.
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
pip install "bdshare[ta]"
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
```python
|
|
467
|
+
from bdshare import get_basic_historical_data
|
|
468
|
+
from bdshare.indicators import add_indicators, add_rsi
|
|
469
|
+
|
|
470
|
+
df = get_basic_historical_data('2024-01-01', '2024-06-30', 'GP')
|
|
471
|
+
|
|
472
|
+
# Add everything (sma_20, ema_20, rsi_14, macd/macd_signal/macd_diff, bb_high/bb_mid/bb_low)
|
|
473
|
+
df = add_indicators(df)
|
|
474
|
+
|
|
475
|
+
# Or just one, with custom parameters
|
|
476
|
+
df = add_rsi(df, window=21)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Also available: `add_sma()`, `add_ema()`, `add_macd()`, `add_bollinger_bands()`.
|
|
480
|
+
Each returns a new DataFrame (the input is never mutated).
|
|
481
|
+
|
|
482
|
+
### Portfolio Tracking
|
|
483
|
+
|
|
484
|
+
`Portfolio` tracks cost basis and values holdings against live prices — no extra
|
|
485
|
+
dependency required (pure pandas), and `valuation()` makes exactly **one** live call
|
|
486
|
+
for all instruments regardless of how many positions you hold.
|
|
487
|
+
|
|
488
|
+
```python
|
|
489
|
+
from bdshare.portfolio import Portfolio
|
|
490
|
+
|
|
491
|
+
pf = Portfolio()
|
|
492
|
+
pf.add_position('GP', quantity=100, avg_cost=450.50)
|
|
493
|
+
pf.add_position('ACI', quantity=50, avg_cost=225.75)
|
|
494
|
+
|
|
495
|
+
print(pf.holdings().to_string()) # cost basis only, no network call
|
|
496
|
+
print(pf.valuation().to_string()) # + ltp, market_value, pnl, pnl_pct per position
|
|
497
|
+
print(pf.summary()) # {'positions': 2, 'total_cost': ..., 'total_pnl': ...}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Adding to an existing position blends the cost basis like a real buy; a negative
|
|
501
|
+
`quantity` reduces it, and netting to zero drops the position. Unknown/delisted
|
|
502
|
+
symbols get `None` valuation fields instead of raising, so one bad symbol doesn't
|
|
503
|
+
block valuing the rest.
|
|
504
|
+
|
|
505
|
+
### Real-Time Streaming (WebSocket)
|
|
506
|
+
|
|
507
|
+
DSE has no public push/streaming API — this polls `get_current_trade_data()` on an
|
|
508
|
+
interval and broadcasts **changed** rows over WebSocket, so another program can
|
|
509
|
+
subscribe instead of polling bdshare itself.
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
pip install "bdshare[stream]"
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Run the bundled server:
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
bdshare-stream --symbols GP,ACI --interval 5
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Any other program connects as a plain WebSocket client:
|
|
522
|
+
|
|
523
|
+
```python
|
|
524
|
+
import asyncio, json, websockets
|
|
525
|
+
|
|
526
|
+
async def main():
|
|
527
|
+
async with websockets.connect('ws://localhost:8765') as ws:
|
|
528
|
+
async for message in ws:
|
|
529
|
+
print(json.loads(message)) # {"type": "ticks", "data": [...]}
|
|
530
|
+
|
|
531
|
+
asyncio.run(main())
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Or skip the server and use the polling generator directly inside your own asyncio
|
|
535
|
+
program:
|
|
536
|
+
|
|
537
|
+
```python
|
|
538
|
+
from bdshare.stream import stream_ticks
|
|
539
|
+
|
|
540
|
+
async def main():
|
|
541
|
+
async for changed in stream_ticks(symbols=['GP', 'ACI'], interval=5.0):
|
|
542
|
+
print(changed)
|
|
543
|
+
|
|
544
|
+
asyncio.run(main())
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
427
549
|
## Using bdshare with AI Agents (MCP Server)
|
|
428
550
|
|
|
429
551
|
bdshare ships an [MCP](https://modelcontextprotocol.io/) server so AI agents running in
|
|
@@ -469,6 +591,23 @@ claude mcp add bdshare -- bdshare-mcp
|
|
|
469
591
|
Any other MCP client is configured the same way — point it at the `bdshare-mcp` command
|
|
470
592
|
(or `python -m bdshare.mcp_server`), stdio transport.
|
|
471
593
|
|
|
594
|
+
### Running it over the network instead of stdio
|
|
595
|
+
|
|
596
|
+
Stdio only works when the client can spawn the server as a local child process (Claude
|
|
597
|
+
Desktop/Code). For a program in **another container, another machine, or another
|
|
598
|
+
language entirely** — nothing that speaks Python — run it over HTTP instead:
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
bdshare-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Any MCP client library can connect to `http://host:8000/mcp`; see
|
|
605
|
+
[`demo/node/`](demo/node/) for a full example using the official
|
|
606
|
+
`@modelcontextprotocol/sdk` from Node.js. Binding beyond `127.0.0.1`/`localhost`
|
|
607
|
+
automatically disables DNS-rebinding protection, since every legitimate client then
|
|
608
|
+
arrives with a non-localhost `Host` header — only do this on a trusted network (an
|
|
609
|
+
internal Docker network, not the public internet).
|
|
610
|
+
|
|
472
611
|
### What the agent gets
|
|
473
612
|
|
|
474
613
|
15 tools covering the same data this README documents — `market_status`,
|
|
@@ -511,11 +650,10 @@ Please open an issue before submitting a pull request for significant changes. S
|
|
|
511
650
|
|
|
512
651
|
## Roadmap
|
|
513
652
|
|
|
514
|
-
- [
|
|
515
|
-
- [
|
|
516
|
-
- [
|
|
517
|
-
- [
|
|
518
|
-
- [ ] Docker demo examples
|
|
653
|
+
- [x] WebSocket streaming for real-time ticks (polling-based — see [Real-Time Streaming](#real-time-streaming-websocket))
|
|
654
|
+
- [x] Built-in technical indicators (`ta` integration)
|
|
655
|
+
- [x] Portfolio management helpers
|
|
656
|
+
- [x] Docker demo examples
|
|
519
657
|
- [x] Shared session with exponential back-off
|
|
520
658
|
- [x] `lxml`-based fast parsing
|
|
521
659
|
- [x] `BDShareError` for clean error handling
|
|
@@ -52,7 +52,7 @@ from bdshare.util import (
|
|
|
52
52
|
configure_proxy,
|
|
53
53
|
)
|
|
54
54
|
from bdshare.util.cache import _TTLCache
|
|
55
|
-
from bdshare.util.helper import deprecated
|
|
55
|
+
from bdshare.util.helper import deprecated, BDShareError
|
|
56
56
|
|
|
57
57
|
# ---------------------------------------------------------------------------
|
|
58
58
|
# Type aliases
|
|
@@ -87,10 +87,6 @@ def _validate_date_range(start_date: str, end_date: str) -> None:
|
|
|
87
87
|
raise ValueError("Date range cannot exceed 5 years.")
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
class BDShareError(Exception):
|
|
91
|
-
"""Top-level client exception."""
|
|
92
|
-
|
|
93
|
-
|
|
94
90
|
# ---------------------------------------------------------------------------
|
|
95
91
|
# Rate limiter
|
|
96
92
|
# ---------------------------------------------------------------------------
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
version_info = (1, 2,
|
|
1
|
+
version_info = (1, 2, 3)
|
|
2
2
|
__version__ = ".".join(map(str, version_info))
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
bdshare.indicators
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Optional technical-indicator helpers built on the `ta` library, for use with
|
|
5
|
+
the OHLCV DataFrames returned by get_basic_historical_data() and friends.
|
|
6
|
+
|
|
7
|
+
Requires the optional 'ta' extra:
|
|
8
|
+
pip install bdshare[ta]
|
|
9
|
+
"""
|
|
10
|
+
from typing import Iterable, Optional
|
|
11
|
+
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from ta.momentum import RSIIndicator
|
|
16
|
+
from ta.trend import EMAIndicator, MACD, SMAIndicator
|
|
17
|
+
from ta.volatility import BollingerBands
|
|
18
|
+
except ImportError as exc:
|
|
19
|
+
raise ImportError(
|
|
20
|
+
"Technical indicators require the 'ta' package. "
|
|
21
|
+
"Install it with: pip install bdshare[ta]"
|
|
22
|
+
) from exc
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def add_sma(df: pd.DataFrame, window: int = 20, column: str = "close") -> pd.DataFrame:
|
|
26
|
+
"""Add a Simple Moving Average column named ``sma_{window}``."""
|
|
27
|
+
out = df.copy()
|
|
28
|
+
out[f"sma_{window}"] = SMAIndicator(out[column], window=window).sma_indicator()
|
|
29
|
+
return out
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def add_ema(df: pd.DataFrame, window: int = 20, column: str = "close") -> pd.DataFrame:
|
|
33
|
+
"""Add an Exponential Moving Average column named ``ema_{window}``."""
|
|
34
|
+
out = df.copy()
|
|
35
|
+
out[f"ema_{window}"] = EMAIndicator(out[column], window=window).ema_indicator()
|
|
36
|
+
return out
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def add_rsi(df: pd.DataFrame, window: int = 14, column: str = "close") -> pd.DataFrame:
|
|
40
|
+
"""Add a Relative Strength Index column named ``rsi_{window}``."""
|
|
41
|
+
out = df.copy()
|
|
42
|
+
out[f"rsi_{window}"] = RSIIndicator(out[column], window=window).rsi()
|
|
43
|
+
return out
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def add_macd(
|
|
47
|
+
df: pd.DataFrame,
|
|
48
|
+
window_fast: int = 12,
|
|
49
|
+
window_slow: int = 26,
|
|
50
|
+
window_sign: int = 9,
|
|
51
|
+
column: str = "close",
|
|
52
|
+
) -> pd.DataFrame:
|
|
53
|
+
"""Add ``macd``, ``macd_signal``, and ``macd_diff`` (histogram) columns."""
|
|
54
|
+
out = df.copy()
|
|
55
|
+
macd = MACD(
|
|
56
|
+
out[column], window_fast=window_fast, window_slow=window_slow, window_sign=window_sign
|
|
57
|
+
)
|
|
58
|
+
out["macd"] = macd.macd()
|
|
59
|
+
out["macd_signal"] = macd.macd_signal()
|
|
60
|
+
out["macd_diff"] = macd.macd_diff()
|
|
61
|
+
return out
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def add_bollinger_bands(
|
|
65
|
+
df: pd.DataFrame, window: int = 20, window_dev: int = 2, column: str = "close"
|
|
66
|
+
) -> pd.DataFrame:
|
|
67
|
+
"""Add ``bb_high``, ``bb_mid``, and ``bb_low`` Bollinger Band columns."""
|
|
68
|
+
out = df.copy()
|
|
69
|
+
bb = BollingerBands(out[column], window=window, window_dev=window_dev)
|
|
70
|
+
out["bb_high"] = bb.bollinger_hband()
|
|
71
|
+
out["bb_mid"] = bb.bollinger_mavg()
|
|
72
|
+
out["bb_low"] = bb.bollinger_lband()
|
|
73
|
+
return out
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
_INDICATORS = {
|
|
77
|
+
"sma": add_sma,
|
|
78
|
+
"ema": add_ema,
|
|
79
|
+
"rsi": add_rsi,
|
|
80
|
+
"macd": add_macd,
|
|
81
|
+
"bbands": add_bollinger_bands,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def add_indicators(df: pd.DataFrame, indicators: Optional[Iterable[str]] = None) -> pd.DataFrame:
|
|
86
|
+
"""Add several indicators at once, each with its default parameters.
|
|
87
|
+
|
|
88
|
+
For custom parameters (e.g. a different SMA window), call the individual
|
|
89
|
+
``add_*`` function directly instead.
|
|
90
|
+
|
|
91
|
+
:param indicators: Names from 'sma', 'ema', 'rsi', 'macd', 'bbands'.
|
|
92
|
+
Defaults to all of them.
|
|
93
|
+
"""
|
|
94
|
+
names = list(indicators) if indicators is not None else list(_INDICATORS)
|
|
95
|
+
out = df
|
|
96
|
+
for name in names:
|
|
97
|
+
if name not in _INDICATORS:
|
|
98
|
+
raise ValueError(f"Unknown indicator '{name}'. Choose from: {list(_INDICATORS)}")
|
|
99
|
+
out = _INDICATORS[name](out)
|
|
100
|
+
return out
|
|
@@ -11,6 +11,11 @@ Run directly:
|
|
|
11
11
|
Or via the installed console script:
|
|
12
12
|
bdshare-mcp
|
|
13
13
|
|
|
14
|
+
Defaults to stdio transport (what Claude Desktop/Code expect). For a
|
|
15
|
+
network-reachable server — e.g. so a non-Python program in another
|
|
16
|
+
container can connect — pass --transport streamable-http --host 0.0.0.0:
|
|
17
|
+
bdshare-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
18
|
+
|
|
14
19
|
Requires the optional 'mcp' extra:
|
|
15
20
|
pip install bdshare[mcp]
|
|
16
21
|
"""
|
|
@@ -190,7 +195,27 @@ def agm_news() -> list:
|
|
|
190
195
|
|
|
191
196
|
|
|
192
197
|
def main() -> None:
|
|
193
|
-
|
|
198
|
+
import argparse
|
|
199
|
+
|
|
200
|
+
parser = argparse.ArgumentParser(description="bdshare MCP server.")
|
|
201
|
+
parser.add_argument(
|
|
202
|
+
"--transport", choices=["stdio", "sse", "streamable-http"], default="stdio",
|
|
203
|
+
help="MCP transport (default: stdio, for desktop/CLI agent clients like Claude Desktop/Code)",
|
|
204
|
+
)
|
|
205
|
+
parser.add_argument("--host", default="127.0.0.1", help="Host to bind for sse/streamable-http")
|
|
206
|
+
parser.add_argument("--port", type=int, default=8000, help="Port to bind for sse/streamable-http")
|
|
207
|
+
args = parser.parse_args()
|
|
208
|
+
|
|
209
|
+
if args.transport != "stdio":
|
|
210
|
+
mcp.settings.host = args.host
|
|
211
|
+
mcp.settings.port = args.port
|
|
212
|
+
if args.host not in ("127.0.0.1", "localhost"):
|
|
213
|
+
# Binding beyond loopback (e.g. inside a container network) means every
|
|
214
|
+
# legitimate client arrives with a non-localhost Host header — DNS-rebinding
|
|
215
|
+
# protection would otherwise reject all of them.
|
|
216
|
+
mcp.settings.transport_security.enable_dns_rebinding_protection = False
|
|
217
|
+
|
|
218
|
+
mcp.run(transport=args.transport)
|
|
194
219
|
|
|
195
220
|
|
|
196
221
|
if __name__ == "__main__":
|