pypsx 2.4.0__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.
- pypsx-2.4.0/MANIFEST.in +36 -0
- pypsx-2.4.0/PKG-INFO +305 -0
- pypsx-2.4.0/README.md +260 -0
- pypsx-2.4.0/pyproject.toml +96 -0
- pypsx-2.4.0/pypsx/__init__.py +133 -0
- pypsx-2.4.0/pypsx/__init__.pyi +109 -0
- pypsx-2.4.0/pypsx/auth.pyi +54 -0
- pypsx-2.4.0/pypsx/cli/__init__.py +5 -0
- pypsx-2.4.0/pypsx/client.pyi +349 -0
- pypsx-2.4.0/pypsx/dashboard/__init__.py +4 -0
- pypsx-2.4.0/pypsx/dashboard/assets/dashboard.css +239 -0
- pypsx-2.4.0/pypsx/dashboard/assets/dashboard.js +319 -0
- pypsx-2.4.0/pypsx/data/__init__.py +34 -0
- pypsx-2.4.0/pypsx/data/cache/__init__.py +6 -0
- pypsx-2.4.0/pypsx/data/provider.pyi +110 -0
- pypsx-2.4.0/pypsx/py.typed +1 -0
- pypsx-2.4.0/pypsx/sdk.pyi +158 -0
- pypsx-2.4.0/pypsx/strategy.pyi +234 -0
- pypsx-2.4.0/pypsx/trader.pyi +84 -0
- pypsx-2.4.0/pypsx/trader_core/__init__.py +36 -0
- pypsx-2.4.0/pypsx/trader_core/backtesting/__init__.py +6 -0
- pypsx-2.4.0/pypsx/trader_core/data/__init__.py +16 -0
- pypsx-2.4.0/pypsx/trader_core/execution/__init__.py +15 -0
- pypsx-2.4.0/pypsx/trader_core/portfolio/__init__.py +8 -0
- pypsx-2.4.0/pypsx/trader_core/strategies/__init__.py +54 -0
- pypsx-2.4.0/pypsx/trader_core/utils/__init__.py +19 -0
- pypsx-2.4.0/pypsx/utils/__init__.py +32 -0
- pypsx-2.4.0/pypsx/utils/enums.py +40 -0
- pypsx-2.4.0/pypsx/utils/enums.pyi +35 -0
- pypsx-2.4.0/pypsx/utils/exceptions.py +50 -0
- pypsx-2.4.0/pypsx/utils/exceptions.pyi +41 -0
- pypsx-2.4.0/pypsx.egg-info/SOURCES.txt +31 -0
- pypsx-2.4.0/setup.cfg +4 -0
- pypsx-2.4.0/setup.py +117 -0
pypsx-2.4.0/MANIFEST.in
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# pypsx SDK Package Manifest
|
|
2
|
+
# This file controls what gets included in the PyPI distribution.
|
|
3
|
+
# For closed-source distribution: only compiled binaries + .pyi stubs are shipped.
|
|
4
|
+
|
|
5
|
+
include README.md
|
|
6
|
+
include LICENSE
|
|
7
|
+
|
|
8
|
+
# Include stub files and py.typed marker (required for IDE autocomplete)
|
|
9
|
+
recursive-include pypsx *.pyi
|
|
10
|
+
recursive-include pypsx py.typed
|
|
11
|
+
|
|
12
|
+
# Keep dashboard assets (HTML/CSS/JS for the local dashboard UI)
|
|
13
|
+
recursive-include pypsx/dashboard/assets *
|
|
14
|
+
|
|
15
|
+
# Include cache SQL schema
|
|
16
|
+
recursive-include pypsx/data/cache *.sql
|
|
17
|
+
|
|
18
|
+
# Exclude all .py implementation files so source code is not leaked, then
|
|
19
|
+
# re-include only what setup.py's EXCLUDE_STEMS keeps as plain Python.
|
|
20
|
+
# These re-includes MUST come after this exclude -- MANIFEST.in processes
|
|
21
|
+
# include/exclude directives in file order, so anything included earlier
|
|
22
|
+
# gets silently undone here otherwise.
|
|
23
|
+
recursive-exclude pypsx *.py
|
|
24
|
+
recursive-include pypsx __init__.py
|
|
25
|
+
include pypsx/utils/enums.py
|
|
26
|
+
include pypsx/utils/exceptions.py
|
|
27
|
+
|
|
28
|
+
# Exclude development/build artefacts
|
|
29
|
+
prune examples
|
|
30
|
+
prune tests
|
|
31
|
+
prune scripts
|
|
32
|
+
prune build
|
|
33
|
+
prune dist
|
|
34
|
+
prune *.egg-info
|
|
35
|
+
recursive-exclude . *.log *.db *.sqlite *.pyc
|
|
36
|
+
exclude .*
|
pypsx-2.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pypsx
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: Pakistan Stock Exchange (PSX) Trading SDK for Paper Trading and Backtesting
|
|
5
|
+
Author: PyPSX Team
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/pypsx
|
|
8
|
+
Project-URL: Documentation, https://github.com/yourusername/pypsx/blob/main/README.md
|
|
9
|
+
Project-URL: Repository, https://github.com/yourusername/pypsx
|
|
10
|
+
Keywords: trading,stock,psx,pakistan,simulation,backtesting,algorithmic-trading
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: License :: Other/Proprietary License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Requires-Dist: httpx>=0.24.0
|
|
26
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
27
|
+
Requires-Dist: pandas<2.3.3,>=1.5.0
|
|
28
|
+
Requires-Dist: numpy<2.3.0,>=1.24.0
|
|
29
|
+
Requires-Dist: ta>=0.11.0
|
|
30
|
+
Requires-Dist: tzdata>=2024.1
|
|
31
|
+
Requires-Dist: fastapi>=0.110.0
|
|
32
|
+
Requires-Dist: uvicorn>=0.23.0
|
|
33
|
+
Requires-Dist: psycopg2-binary>=2.9.0
|
|
34
|
+
Requires-Dist: websockets>=12.0
|
|
35
|
+
Requires-Dist: python-dateutil>=2.8.0
|
|
36
|
+
Requires-Dist: requests>=2.31.0
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
41
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
43
|
+
Provides-Extra: jupyter
|
|
44
|
+
Requires-Dist: nest-asyncio>=1.5.0; extra == "jupyter"
|
|
45
|
+
|
|
46
|
+
# PyPSX SDK
|
|
47
|
+
|
|
48
|
+
API-first trading infrastructure for the Pakistan Stock Exchange.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install pypsx
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Try it in a notebook
|
|
57
|
+
|
|
58
|
+
[](https://colab.research.google.com/drive/1ac6yt0dlTzpBR16qS_-ltYuImVyyr2PG?usp=sharing)
|
|
59
|
+
|
|
60
|
+
## One-liners — no client to construct
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import pypsx
|
|
64
|
+
|
|
65
|
+
df = pypsx.download("OGDC", period="1y")
|
|
66
|
+
result = pypsx.backtest("dual_sma_momentum", "OGDC", period="1y", initial_cash=1_000_000)
|
|
67
|
+
quote = pypsx.get_quote("OGDC")
|
|
68
|
+
depth = pypsx.get_market_depth("OGDC")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
These read `PYPSX_API_KEY_ID`/`PYPSX_API_SECRET_KEY` from the environment automatically. For order placement, positions, and account state, use `TradingClient` below.
|
|
72
|
+
|
|
73
|
+
## Quick Start
|
|
74
|
+
|
|
75
|
+
Start with paper trading. It is the safety-first way to test your strategy, validate your order flow, and watch your dashboard update in real time before risking real capital.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import os
|
|
79
|
+
from dotenv import load_dotenv
|
|
80
|
+
from pypsx import TradingClient
|
|
81
|
+
|
|
82
|
+
load_dotenv()
|
|
83
|
+
|
|
84
|
+
client = TradingClient(
|
|
85
|
+
api_key=os.getenv("PYPSX_API_KEY_ID"),
|
|
86
|
+
secret_key=os.getenv("PYPSX_API_SECRET_KEY"),
|
|
87
|
+
paper=True,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
account = client.get_account()
|
|
91
|
+
print(f"Connected! Current Balance: PKR {account.cash}")
|
|
92
|
+
|
|
93
|
+
order = client.place_manual_order(
|
|
94
|
+
symbol="OGDC",
|
|
95
|
+
side="BUY",
|
|
96
|
+
quantity=10,
|
|
97
|
+
order_type="MARKET",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
print("Submitted:", order["order_id"], order["status"])
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You can also load keys directly from environment variables:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from pypsx import TradingClient
|
|
107
|
+
|
|
108
|
+
client = TradingClient.from_env(paper=True)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Your First Trade
|
|
112
|
+
|
|
113
|
+
**Step 1:** Generate a paper key in the PyPSX dashboard.
|
|
114
|
+
**Step 2:** Copy the script above into `my_bot.py`.
|
|
115
|
+
**Step 3:** Set your own `PYPSX_API_KEY_ID` and `PYPSX_API_SECRET_KEY` in `.env`.
|
|
116
|
+
**Step 4:** Run `python my_bot.py` while the market is open.
|
|
117
|
+
**Step 5:** Watch orders, fills, and positions appear in the dashboard automatically.
|
|
118
|
+
|
|
119
|
+
## The Power of PyPSX
|
|
120
|
+
|
|
121
|
+
PyPSX gives algorithmic traders a clean Python interface for the Pakistan Stock Exchange without exposing them to exchange plumbing.
|
|
122
|
+
|
|
123
|
+
### Paper And Live Modes
|
|
124
|
+
|
|
125
|
+
The SDK automatically routes traffic based on one flag:
|
|
126
|
+
|
|
127
|
+
- `paper=True` sends requests to the paper trading environment at `https://paper-api.pypsx.com`
|
|
128
|
+
- `paper=False` sends requests to the live trading environment at `https://api.pypsx.com`
|
|
129
|
+
|
|
130
|
+
This keeps your code identical across testing and production. Change the credentials, switch the flag, and keep your trading logic the same.
|
|
131
|
+
|
|
132
|
+
### Real-Time Trading Experience
|
|
133
|
+
|
|
134
|
+
With PyPSX you can:
|
|
135
|
+
|
|
136
|
+
- Read positions, orders, and account state from Python
|
|
137
|
+
- Submit orders with a simple REST interface
|
|
138
|
+
- See fills reflected in the web dashboard without manual refresh
|
|
139
|
+
- Build bots around trading logic instead of exchange protocol handling
|
|
140
|
+
|
|
141
|
+
### Developer's Promise
|
|
142
|
+
|
|
143
|
+
PyPSX handles the operational complexity of PSX integration, including request authentication, endpoint routing, and exchange connectivity. You focus on signal generation, risk rules, and execution logic. We handle the FIX-side complexity behind the API.
|
|
144
|
+
|
|
145
|
+
## Authentication
|
|
146
|
+
|
|
147
|
+
### How To Get Your Keys
|
|
148
|
+
|
|
149
|
+
1. Sign in to the PyPSX dashboard.
|
|
150
|
+
2. Open `Settings`.
|
|
151
|
+
3. Select the account you want to trade.
|
|
152
|
+
4. Click `Generate Paper Key` or `Generate Live Key`.
|
|
153
|
+
5. Copy the `Public Key ID` and `Secret Key`.
|
|
154
|
+
|
|
155
|
+
### How The SDK Uses Them
|
|
156
|
+
|
|
157
|
+
Use the credentials directly in `TradingClient(...)`:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from pypsx import TradingClient
|
|
161
|
+
|
|
162
|
+
client = TradingClient(
|
|
163
|
+
api_key=os.getenv("PYPSX_API_KEY_ID"),
|
|
164
|
+
secret_key=os.getenv("PYPSX_API_SECRET_KEY"),
|
|
165
|
+
paper=True,
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Under the hood, the SDK automatically sends:
|
|
170
|
+
|
|
171
|
+
```http
|
|
172
|
+
PYPSX-API-KEY-ID: <your-public-key-id>
|
|
173
|
+
PYPSX-API-SECRET-KEY: <your-secret-key>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
If you are building against the API without the Python SDK, send those same headers yourself.
|
|
177
|
+
|
|
178
|
+
## API Reference
|
|
179
|
+
|
|
180
|
+
| Method | What it does | Returns |
|
|
181
|
+
| --- | --- | --- |
|
|
182
|
+
| `get_account_config()` | Fetches trading permissions and account-level configuration | `dict` |
|
|
183
|
+
| `get_portfolio_valuation()` | Returns the latest equity, cash, positions value, and pricing snapshot | `dict` |
|
|
184
|
+
| `get_positions()` | Returns open positions for the selected paper or live account | `list[dict]` |
|
|
185
|
+
| `get_orders(limit=...)` | Returns recent orders and their current state | `list[dict]` |
|
|
186
|
+
| `place_manual_order(...)` | Submits a market or priced order through the selected environment | `dict` |
|
|
187
|
+
| `get_symbols()` | Fetches available market symbols | `list[dict]` |
|
|
188
|
+
| `get_intraday(symbol, days=...)` | Retrieves recent intraday market data for a symbol | `list[dict]` |
|
|
189
|
+
| `get_historical(symbol, start=..., end=...)` | Retrieves historical daily bars for strategy research and analysis | `list[dict]` |
|
|
190
|
+
| `get_historical_intraday(symbols, start=..., end=..., interval=...)` | Retrieves multi-interval OHLCV candles (1m/5m/15m/1h) | `list[dict]` |
|
|
191
|
+
| `get_portfolio(bot_id=None)` | Raw portfolio dict for the current bot/account scope | `dict` |
|
|
192
|
+
| `get_account_config(account_id=None)` | Account-level configuration | `dict` |
|
|
193
|
+
| `get_fundamentals(symbol)` | `pe_ratio`, `dividend_yield`, `market_cap`, `free_float`, etc. for a symbol | `dict` |
|
|
194
|
+
| `get_dividends(symbol)` | Dividend history: `year`, `amount`, `ex_date`, `payment_date`, `record_date` | `list[dict]` |
|
|
195
|
+
| `get_commission_rate()` | The account's commission rate percentage (cached after first call) | `float` |
|
|
196
|
+
| `add_funds(amount, account_id=None, bot_id=None)` | Add paper cash to an account | `dict` |
|
|
197
|
+
| `get_performance(bot_id=None, limit=100)` | Historical performance snapshots for a bot | `list[dict]` |
|
|
198
|
+
| `get_trades(bot_id=None, limit=100)` | Executed trade history for a bot | `list[dict]` |
|
|
199
|
+
| `get_logs(bot_id=None, limit=200)` | Bot run logs | `list[dict]` |
|
|
200
|
+
| `list_bots()` | List all bots registered under the account | `list[dict]` |
|
|
201
|
+
| `create_bot(bot_id, bot_label=None, strategy_name=None, symbols=None, cycle_minutes=None)` | Register a new cloud bot | `dict` |
|
|
202
|
+
| `place_bracket_order(symbol, side, quantity, stop_loss_price, take_profit_price, entry_type="MARKET", ...)` | Entry order plus a linked stop-loss/take-profit exit pair | `dict` |
|
|
203
|
+
| `place_oco_order(symbol, quantity, stop_loss_price, take_profit_price, ...)` | Attach a linked stop-loss/take-profit pair to an existing position | `dict` |
|
|
204
|
+
| `place_stop_order(symbol, quantity, trigger_price, limit_price=None, ...)` | Standalone stop order | `dict` |
|
|
205
|
+
| `get_order_executions(since=None, limit=1000)` | Raw fill/execution records for the current bot scope | `list[dict]` |
|
|
206
|
+
| `close()` | Closes the underlying HTTP client cleanly | `None` |
|
|
207
|
+
|
|
208
|
+
## Examples
|
|
209
|
+
|
|
210
|
+
### Paper Trading
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from pypsx import TradingClient
|
|
214
|
+
|
|
215
|
+
client = TradingClient(
|
|
216
|
+
api_key=os.getenv("PYPSX_API_KEY_ID"),
|
|
217
|
+
secret_key=os.getenv("PYPSX_API_SECRET_KEY"),
|
|
218
|
+
paper=True,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
valuation = client.get_portfolio_valuation()
|
|
222
|
+
positions = client.get_positions()
|
|
223
|
+
orders = client.get_orders(limit=25)
|
|
224
|
+
|
|
225
|
+
print("Equity:", valuation["equity"])
|
|
226
|
+
print("Positions:", len(positions))
|
|
227
|
+
print("Orders:", len(orders))
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Live Trading
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from pypsx import TradingClient
|
|
234
|
+
|
|
235
|
+
client = TradingClient(
|
|
236
|
+
api_key="PK-LIVE-ABC123456789",
|
|
237
|
+
secret_key="pypsx-secret-live-replace-me",
|
|
238
|
+
paper=False,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
order = client.place_manual_order(
|
|
242
|
+
symbol="OGDC",
|
|
243
|
+
side="BUY",
|
|
244
|
+
quantity=100,
|
|
245
|
+
order_type="MARKET",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
print(order)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Simple Bot Pattern
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from pypsx import TradingClient
|
|
255
|
+
|
|
256
|
+
SYMBOL = "OGDC"
|
|
257
|
+
|
|
258
|
+
client = TradingClient(
|
|
259
|
+
api_key="PK-PAPER-123456",
|
|
260
|
+
secret_key="pypsx-secret-paper-replace-me",
|
|
261
|
+
paper=True,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
positions = client.get_positions()
|
|
265
|
+
already_holding = any(
|
|
266
|
+
position["symbol"] == SYMBOL and float(position["qty"]) > 0
|
|
267
|
+
for position in positions
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if not already_holding:
|
|
271
|
+
client.place_manual_order(
|
|
272
|
+
symbol=SYMBOL,
|
|
273
|
+
side="BUY",
|
|
274
|
+
quantity=10,
|
|
275
|
+
order_type="MARKET",
|
|
276
|
+
)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Best Practices
|
|
280
|
+
|
|
281
|
+
- Use `.env` files or a secrets manager for credentials. Do not hardcode production keys into source control.
|
|
282
|
+
- Start every new strategy with `paper=True`.
|
|
283
|
+
- Treat paper trading as your pre-flight checklist before switching to live.
|
|
284
|
+
- Run execution scripts when the market is open so fills, liquidity, and dashboard feedback reflect real conditions.
|
|
285
|
+
- Add explicit guards in your code for position sizing, duplicate orders, and risk limits.
|
|
286
|
+
- Close clients cleanly with `client.close()` in longer-running scripts or services.
|
|
287
|
+
|
|
288
|
+
## Raw HTTP Example
|
|
289
|
+
|
|
290
|
+
If you are not using the SDK, this is the equivalent request format:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
curl -X POST "https://paper-api.pypsx.com/orders" \
|
|
294
|
+
-H "Content-Type: application/json" \
|
|
295
|
+
-H "PYPSX-API-KEY-ID: $PYPSX_API_KEY_ID" \
|
|
296
|
+
-H "PYPSX-API-SECRET-KEY: $PYPSX_API_SECRET_KEY" \
|
|
297
|
+
-d "{\"symbol\":\"OGDC\",\"side\":\"BUY\",\"quantity\":10,\"order_type\":\"MARKET\",\"mode\":\"PAPER\",\"commission_rate\":0.02}"
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Set `commission_rate` only when you want to override the default fee behavior for a specific order. The value is a percentage, so `0.02` means `0.02%`.
|
|
301
|
+
|
|
302
|
+
## Additional Examples
|
|
303
|
+
|
|
304
|
+
- `examples/pypsx_client_example.py`
|
|
305
|
+
- `examples/example_bot.py`
|
pypsx-2.4.0/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# PyPSX SDK
|
|
2
|
+
|
|
3
|
+
API-first trading infrastructure for the Pakistan Stock Exchange.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install pypsx
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Try it in a notebook
|
|
12
|
+
|
|
13
|
+
[](https://colab.research.google.com/drive/1ac6yt0dlTzpBR16qS_-ltYuImVyyr2PG?usp=sharing)
|
|
14
|
+
|
|
15
|
+
## One-liners — no client to construct
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import pypsx
|
|
19
|
+
|
|
20
|
+
df = pypsx.download("OGDC", period="1y")
|
|
21
|
+
result = pypsx.backtest("dual_sma_momentum", "OGDC", period="1y", initial_cash=1_000_000)
|
|
22
|
+
quote = pypsx.get_quote("OGDC")
|
|
23
|
+
depth = pypsx.get_market_depth("OGDC")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
These read `PYPSX_API_KEY_ID`/`PYPSX_API_SECRET_KEY` from the environment automatically. For order placement, positions, and account state, use `TradingClient` below.
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
Start with paper trading. It is the safety-first way to test your strategy, validate your order flow, and watch your dashboard update in real time before risking real capital.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import os
|
|
34
|
+
from dotenv import load_dotenv
|
|
35
|
+
from pypsx import TradingClient
|
|
36
|
+
|
|
37
|
+
load_dotenv()
|
|
38
|
+
|
|
39
|
+
client = TradingClient(
|
|
40
|
+
api_key=os.getenv("PYPSX_API_KEY_ID"),
|
|
41
|
+
secret_key=os.getenv("PYPSX_API_SECRET_KEY"),
|
|
42
|
+
paper=True,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
account = client.get_account()
|
|
46
|
+
print(f"Connected! Current Balance: PKR {account.cash}")
|
|
47
|
+
|
|
48
|
+
order = client.place_manual_order(
|
|
49
|
+
symbol="OGDC",
|
|
50
|
+
side="BUY",
|
|
51
|
+
quantity=10,
|
|
52
|
+
order_type="MARKET",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
print("Submitted:", order["order_id"], order["status"])
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
You can also load keys directly from environment variables:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from pypsx import TradingClient
|
|
62
|
+
|
|
63
|
+
client = TradingClient.from_env(paper=True)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Your First Trade
|
|
67
|
+
|
|
68
|
+
**Step 1:** Generate a paper key in the PyPSX dashboard.
|
|
69
|
+
**Step 2:** Copy the script above into `my_bot.py`.
|
|
70
|
+
**Step 3:** Set your own `PYPSX_API_KEY_ID` and `PYPSX_API_SECRET_KEY` in `.env`.
|
|
71
|
+
**Step 4:** Run `python my_bot.py` while the market is open.
|
|
72
|
+
**Step 5:** Watch orders, fills, and positions appear in the dashboard automatically.
|
|
73
|
+
|
|
74
|
+
## The Power of PyPSX
|
|
75
|
+
|
|
76
|
+
PyPSX gives algorithmic traders a clean Python interface for the Pakistan Stock Exchange without exposing them to exchange plumbing.
|
|
77
|
+
|
|
78
|
+
### Paper And Live Modes
|
|
79
|
+
|
|
80
|
+
The SDK automatically routes traffic based on one flag:
|
|
81
|
+
|
|
82
|
+
- `paper=True` sends requests to the paper trading environment at `https://paper-api.pypsx.com`
|
|
83
|
+
- `paper=False` sends requests to the live trading environment at `https://api.pypsx.com`
|
|
84
|
+
|
|
85
|
+
This keeps your code identical across testing and production. Change the credentials, switch the flag, and keep your trading logic the same.
|
|
86
|
+
|
|
87
|
+
### Real-Time Trading Experience
|
|
88
|
+
|
|
89
|
+
With PyPSX you can:
|
|
90
|
+
|
|
91
|
+
- Read positions, orders, and account state from Python
|
|
92
|
+
- Submit orders with a simple REST interface
|
|
93
|
+
- See fills reflected in the web dashboard without manual refresh
|
|
94
|
+
- Build bots around trading logic instead of exchange protocol handling
|
|
95
|
+
|
|
96
|
+
### Developer's Promise
|
|
97
|
+
|
|
98
|
+
PyPSX handles the operational complexity of PSX integration, including request authentication, endpoint routing, and exchange connectivity. You focus on signal generation, risk rules, and execution logic. We handle the FIX-side complexity behind the API.
|
|
99
|
+
|
|
100
|
+
## Authentication
|
|
101
|
+
|
|
102
|
+
### How To Get Your Keys
|
|
103
|
+
|
|
104
|
+
1. Sign in to the PyPSX dashboard.
|
|
105
|
+
2. Open `Settings`.
|
|
106
|
+
3. Select the account you want to trade.
|
|
107
|
+
4. Click `Generate Paper Key` or `Generate Live Key`.
|
|
108
|
+
5. Copy the `Public Key ID` and `Secret Key`.
|
|
109
|
+
|
|
110
|
+
### How The SDK Uses Them
|
|
111
|
+
|
|
112
|
+
Use the credentials directly in `TradingClient(...)`:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from pypsx import TradingClient
|
|
116
|
+
|
|
117
|
+
client = TradingClient(
|
|
118
|
+
api_key=os.getenv("PYPSX_API_KEY_ID"),
|
|
119
|
+
secret_key=os.getenv("PYPSX_API_SECRET_KEY"),
|
|
120
|
+
paper=True,
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Under the hood, the SDK automatically sends:
|
|
125
|
+
|
|
126
|
+
```http
|
|
127
|
+
PYPSX-API-KEY-ID: <your-public-key-id>
|
|
128
|
+
PYPSX-API-SECRET-KEY: <your-secret-key>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If you are building against the API without the Python SDK, send those same headers yourself.
|
|
132
|
+
|
|
133
|
+
## API Reference
|
|
134
|
+
|
|
135
|
+
| Method | What it does | Returns |
|
|
136
|
+
| --- | --- | --- |
|
|
137
|
+
| `get_account_config()` | Fetches trading permissions and account-level configuration | `dict` |
|
|
138
|
+
| `get_portfolio_valuation()` | Returns the latest equity, cash, positions value, and pricing snapshot | `dict` |
|
|
139
|
+
| `get_positions()` | Returns open positions for the selected paper or live account | `list[dict]` |
|
|
140
|
+
| `get_orders(limit=...)` | Returns recent orders and their current state | `list[dict]` |
|
|
141
|
+
| `place_manual_order(...)` | Submits a market or priced order through the selected environment | `dict` |
|
|
142
|
+
| `get_symbols()` | Fetches available market symbols | `list[dict]` |
|
|
143
|
+
| `get_intraday(symbol, days=...)` | Retrieves recent intraday market data for a symbol | `list[dict]` |
|
|
144
|
+
| `get_historical(symbol, start=..., end=...)` | Retrieves historical daily bars for strategy research and analysis | `list[dict]` |
|
|
145
|
+
| `get_historical_intraday(symbols, start=..., end=..., interval=...)` | Retrieves multi-interval OHLCV candles (1m/5m/15m/1h) | `list[dict]` |
|
|
146
|
+
| `get_portfolio(bot_id=None)` | Raw portfolio dict for the current bot/account scope | `dict` |
|
|
147
|
+
| `get_account_config(account_id=None)` | Account-level configuration | `dict` |
|
|
148
|
+
| `get_fundamentals(symbol)` | `pe_ratio`, `dividend_yield`, `market_cap`, `free_float`, etc. for a symbol | `dict` |
|
|
149
|
+
| `get_dividends(symbol)` | Dividend history: `year`, `amount`, `ex_date`, `payment_date`, `record_date` | `list[dict]` |
|
|
150
|
+
| `get_commission_rate()` | The account's commission rate percentage (cached after first call) | `float` |
|
|
151
|
+
| `add_funds(amount, account_id=None, bot_id=None)` | Add paper cash to an account | `dict` |
|
|
152
|
+
| `get_performance(bot_id=None, limit=100)` | Historical performance snapshots for a bot | `list[dict]` |
|
|
153
|
+
| `get_trades(bot_id=None, limit=100)` | Executed trade history for a bot | `list[dict]` |
|
|
154
|
+
| `get_logs(bot_id=None, limit=200)` | Bot run logs | `list[dict]` |
|
|
155
|
+
| `list_bots()` | List all bots registered under the account | `list[dict]` |
|
|
156
|
+
| `create_bot(bot_id, bot_label=None, strategy_name=None, symbols=None, cycle_minutes=None)` | Register a new cloud bot | `dict` |
|
|
157
|
+
| `place_bracket_order(symbol, side, quantity, stop_loss_price, take_profit_price, entry_type="MARKET", ...)` | Entry order plus a linked stop-loss/take-profit exit pair | `dict` |
|
|
158
|
+
| `place_oco_order(symbol, quantity, stop_loss_price, take_profit_price, ...)` | Attach a linked stop-loss/take-profit pair to an existing position | `dict` |
|
|
159
|
+
| `place_stop_order(symbol, quantity, trigger_price, limit_price=None, ...)` | Standalone stop order | `dict` |
|
|
160
|
+
| `get_order_executions(since=None, limit=1000)` | Raw fill/execution records for the current bot scope | `list[dict]` |
|
|
161
|
+
| `close()` | Closes the underlying HTTP client cleanly | `None` |
|
|
162
|
+
|
|
163
|
+
## Examples
|
|
164
|
+
|
|
165
|
+
### Paper Trading
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from pypsx import TradingClient
|
|
169
|
+
|
|
170
|
+
client = TradingClient(
|
|
171
|
+
api_key=os.getenv("PYPSX_API_KEY_ID"),
|
|
172
|
+
secret_key=os.getenv("PYPSX_API_SECRET_KEY"),
|
|
173
|
+
paper=True,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
valuation = client.get_portfolio_valuation()
|
|
177
|
+
positions = client.get_positions()
|
|
178
|
+
orders = client.get_orders(limit=25)
|
|
179
|
+
|
|
180
|
+
print("Equity:", valuation["equity"])
|
|
181
|
+
print("Positions:", len(positions))
|
|
182
|
+
print("Orders:", len(orders))
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Live Trading
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from pypsx import TradingClient
|
|
189
|
+
|
|
190
|
+
client = TradingClient(
|
|
191
|
+
api_key="PK-LIVE-ABC123456789",
|
|
192
|
+
secret_key="pypsx-secret-live-replace-me",
|
|
193
|
+
paper=False,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
order = client.place_manual_order(
|
|
197
|
+
symbol="OGDC",
|
|
198
|
+
side="BUY",
|
|
199
|
+
quantity=100,
|
|
200
|
+
order_type="MARKET",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
print(order)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Simple Bot Pattern
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from pypsx import TradingClient
|
|
210
|
+
|
|
211
|
+
SYMBOL = "OGDC"
|
|
212
|
+
|
|
213
|
+
client = TradingClient(
|
|
214
|
+
api_key="PK-PAPER-123456",
|
|
215
|
+
secret_key="pypsx-secret-paper-replace-me",
|
|
216
|
+
paper=True,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
positions = client.get_positions()
|
|
220
|
+
already_holding = any(
|
|
221
|
+
position["symbol"] == SYMBOL and float(position["qty"]) > 0
|
|
222
|
+
for position in positions
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if not already_holding:
|
|
226
|
+
client.place_manual_order(
|
|
227
|
+
symbol=SYMBOL,
|
|
228
|
+
side="BUY",
|
|
229
|
+
quantity=10,
|
|
230
|
+
order_type="MARKET",
|
|
231
|
+
)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Best Practices
|
|
235
|
+
|
|
236
|
+
- Use `.env` files or a secrets manager for credentials. Do not hardcode production keys into source control.
|
|
237
|
+
- Start every new strategy with `paper=True`.
|
|
238
|
+
- Treat paper trading as your pre-flight checklist before switching to live.
|
|
239
|
+
- Run execution scripts when the market is open so fills, liquidity, and dashboard feedback reflect real conditions.
|
|
240
|
+
- Add explicit guards in your code for position sizing, duplicate orders, and risk limits.
|
|
241
|
+
- Close clients cleanly with `client.close()` in longer-running scripts or services.
|
|
242
|
+
|
|
243
|
+
## Raw HTTP Example
|
|
244
|
+
|
|
245
|
+
If you are not using the SDK, this is the equivalent request format:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
curl -X POST "https://paper-api.pypsx.com/orders" \
|
|
249
|
+
-H "Content-Type: application/json" \
|
|
250
|
+
-H "PYPSX-API-KEY-ID: $PYPSX_API_KEY_ID" \
|
|
251
|
+
-H "PYPSX-API-SECRET-KEY: $PYPSX_API_SECRET_KEY" \
|
|
252
|
+
-d "{\"symbol\":\"OGDC\",\"side\":\"BUY\",\"quantity\":10,\"order_type\":\"MARKET\",\"mode\":\"PAPER\",\"commission_rate\":0.02}"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Set `commission_rate` only when you want to override the default fee behavior for a specific order. The value is a percentage, so `0.02` means `0.02%`.
|
|
256
|
+
|
|
257
|
+
## Additional Examples
|
|
258
|
+
|
|
259
|
+
- `examples/pypsx_client_example.py`
|
|
260
|
+
- `examples/example_bot.py`
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel", "Cython>=3.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pypsx"
|
|
7
|
+
version = "2.4.0"
|
|
8
|
+
description = "Pakistan Stock Exchange (PSX) Trading SDK for Paper Trading and Backtesting"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "Proprietary"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "PyPSX Team"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["trading", "stock", "psx", "pakistan", "simulation", "backtesting", "algorithmic-trading"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
20
|
+
"License :: Other/Proprietary License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Core runtime dependencies for the SDK
|
|
31
|
+
dependencies = [
|
|
32
|
+
"pydantic>=2.0.0",
|
|
33
|
+
"httpx>=0.24.0",
|
|
34
|
+
"sqlalchemy>=2.0.0",
|
|
35
|
+
"pandas>=1.5.0,<2.3.3",
|
|
36
|
+
"numpy>=1.24.0,<2.3.0",
|
|
37
|
+
"ta>=0.11.0",
|
|
38
|
+
"tzdata>=2024.1",
|
|
39
|
+
"fastapi>=0.110.0",
|
|
40
|
+
"uvicorn>=0.23.0",
|
|
41
|
+
"psycopg2-binary>=2.9.0",
|
|
42
|
+
"websockets>=12.0",
|
|
43
|
+
"python-dateutil>=2.8.0",
|
|
44
|
+
"requests>=2.31.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
# Development dependencies (for SDK development)
|
|
49
|
+
dev = [
|
|
50
|
+
"pytest>=7.0.0",
|
|
51
|
+
"pytest-cov>=4.0.0",
|
|
52
|
+
"pytest-asyncio>=0.21.0",
|
|
53
|
+
"black>=23.0.0",
|
|
54
|
+
"ruff>=0.1.0",
|
|
55
|
+
]
|
|
56
|
+
# Jupyter/Colab support (recommended for notebook environments)
|
|
57
|
+
jupyter = [
|
|
58
|
+
"nest-asyncio>=1.5.0",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[project.urls]
|
|
62
|
+
Homepage = "https://github.com/yourusername/pypsx"
|
|
63
|
+
Documentation = "https://github.com/yourusername/pypsx/blob/main/README.md"
|
|
64
|
+
Repository = "https://github.com/yourusername/pypsx"
|
|
65
|
+
|
|
66
|
+
[tool.setuptools.packages.find]
|
|
67
|
+
where = ["."]
|
|
68
|
+
include = ["pypsx*", "examples*"]
|
|
69
|
+
exclude = [
|
|
70
|
+
"tests*",
|
|
71
|
+
"logs*",
|
|
72
|
+
"user_data*",
|
|
73
|
+
"scripts*",
|
|
74
|
+
"test bots*",
|
|
75
|
+
"pypsx.egg-info*",
|
|
76
|
+
"*.egg-info*",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
# Ship .pyi stub files, py.typed marker, and assets alongside compiled binaries.
|
|
80
|
+
# Without "*.pyi" here, pip install will NOT include stubs and IDE autocomplete breaks.
|
|
81
|
+
[tool.setuptools.package-data]
|
|
82
|
+
"*" = ["*.pyi", "py.typed"]
|
|
83
|
+
"pypsx.data.cache" = ["*.sql"]
|
|
84
|
+
"pypsx.dashboard.assets" = ["*"]
|
|
85
|
+
|
|
86
|
+
# CLI entry points for SDK
|
|
87
|
+
[project.scripts]
|
|
88
|
+
pypsx-paper = "pypsx.cli.paper_trading:main"
|
|
89
|
+
|
|
90
|
+
[tool.black]
|
|
91
|
+
line-length = 100
|
|
92
|
+
target-version = ['py38']
|
|
93
|
+
|
|
94
|
+
[tool.ruff]
|
|
95
|
+
line-length = 100
|
|
96
|
+
target-version = "py38"
|