rqfc-dev 1.0.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.
- rqfc_dev-1.0.0/PKG-INFO +114 -0
- rqfc_dev-1.0.0/README.md +19 -0
- rqfc_dev-1.0.0/package/README.md +105 -0
- rqfc_dev-1.0.0/package/rqfc/__init__.py +110 -0
- rqfc_dev-1.0.0/package/rqfc/_session.py +72 -0
- rqfc_dev-1.0.0/package/rqfc/admin.py +58 -0
- rqfc_dev-1.0.0/package/rqfc/client.py +86 -0
- rqfc_dev-1.0.0/package/rqfc_dev.egg-info/PKG-INFO +114 -0
- rqfc_dev-1.0.0/package/rqfc_dev.egg-info/SOURCES.txt +12 -0
- rqfc_dev-1.0.0/package/rqfc_dev.egg-info/dependency_links.txt +1 -0
- rqfc_dev-1.0.0/package/rqfc_dev.egg-info/requires.txt +1 -0
- rqfc_dev-1.0.0/package/rqfc_dev.egg-info/top_level.txt +1 -0
- rqfc_dev-1.0.0/pyproject.toml +21 -0
- rqfc_dev-1.0.0/setup.cfg +4 -0
rqfc_dev-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rqfc-dev
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Thin client for the RQFC fund trading backend
|
|
5
|
+
Author: RQFC
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.28.0
|
|
9
|
+
|
|
10
|
+
# rqfc
|
|
11
|
+
|
|
12
|
+
Thin Python client for the RQFC fund trading backend.
|
|
13
|
+
|
|
14
|
+
Traders log in with credentials or an API key issued by an admin. The backend
|
|
15
|
+
owns authentication, permissions, and each pod's Alpaca credentials, then
|
|
16
|
+
submits trades on the trader's behalf. **No Alpaca keys or Supabase settings
|
|
17
|
+
ever touch this client**. A pod is one Alpaca account; several traders share a
|
|
18
|
+
pod; admins can trade any pod and manage capital/membership.
|
|
19
|
+
|
|
20
|
+
See `../docs/ARCHITECTURE.md` and `../docs/RUNBOOK.md` for the full system.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
From GitHub:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install git+https://github.com/Rutgers-Quant-Finance-Club/fund.git
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
After publishing to PyPI, this becomes:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install rqfc
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For local package development from this repo:
|
|
36
|
+
```bash
|
|
37
|
+
pip install -e .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Trader Login
|
|
41
|
+
By default, `rqfc` talks to the deployed RQFC API:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
https://fund-tkb1.onrender.com
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Admins issue either email/password credentials or a trader API key. Traders do
|
|
48
|
+
not need to configure Supabase or Alpaca.
|
|
49
|
+
|
|
50
|
+
Email/password login:
|
|
51
|
+
```python
|
|
52
|
+
import rqfc
|
|
53
|
+
|
|
54
|
+
rqfc.login("alice@example.com", "password")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
API key login, useful for scripts and strategy runners:
|
|
58
|
+
```python
|
|
59
|
+
import rqfc
|
|
60
|
+
|
|
61
|
+
rqfc.login(api_key="rqfc_...")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Local Dev Override
|
|
65
|
+
To test against a local or staging backend, use `RQFC_BACKEND_URL`:
|
|
66
|
+
```bash
|
|
67
|
+
export RQFC_BACKEND_URL=http://localhost:8000
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or pass it directly:
|
|
71
|
+
```python
|
|
72
|
+
import rqfc
|
|
73
|
+
|
|
74
|
+
rqfc.login("alice@example.com", "password", backend_url="http://localhost:8000")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Trader Usage
|
|
78
|
+
```python
|
|
79
|
+
import rqfc
|
|
80
|
+
|
|
81
|
+
rqfc.login(api_key="rqfc_...")
|
|
82
|
+
|
|
83
|
+
rqfc.whoami() # your profile + assigned pods
|
|
84
|
+
|
|
85
|
+
acct = rqfc.pod("Alpha Equities") # by name or id; must be assigned
|
|
86
|
+
acct.buy("AAPL", 10)
|
|
87
|
+
acct.sell("AAPL", 5)
|
|
88
|
+
acct.short("TSLA", 3)
|
|
89
|
+
acct.dollar_buy("NVDA", 5000)
|
|
90
|
+
acct.account() # live equity / cash / buying power
|
|
91
|
+
acct.positions() # live positions
|
|
92
|
+
acct.price("AAPL")
|
|
93
|
+
acct.sync() # refresh the dashboard (positions, NAV, metrics)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Admin Usage
|
|
97
|
+
```python
|
|
98
|
+
rqfc.login("admin@example.com", "password")
|
|
99
|
+
admin = rqfc.admin()
|
|
100
|
+
|
|
101
|
+
pod = admin.create_pod("Vol Arb", "options", capital=100000,
|
|
102
|
+
alpaca_api_key="PK...", alpaca_api_secret="...")
|
|
103
|
+
admin.list_traders()
|
|
104
|
+
admin.add_trader(pod["id"], trader_id, role="trader")
|
|
105
|
+
admin.allocate_capital(pod["id"], 150000)
|
|
106
|
+
admin.remove_trader(pod["id"], trader_id)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Method Reference
|
|
110
|
+
**Account** (`rqfc.pod(...)`): `buy`, `sell`, `short`, `cover`, `dollar_buy`,
|
|
111
|
+
`dollar_sell`, `cancel`, `account`, `positions`, `price`, `bars`, `sync`.
|
|
112
|
+
|
|
113
|
+
**Admin** (`rqfc.admin()`): `create_pod`, `set_alpaca`, `allocate_capital`,
|
|
114
|
+
`list_traders`, `add_trader`, `remove_trader`.
|
rqfc_dev-1.0.0/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Fund (to be named)
|
|
2
|
+
|
|
3
|
+
## Rahul Rajkumar & James Yazici
|
|
4
|
+
|
|
5
|
+
A transparent, multi-pod paper-trading fund for RQFC.
|
|
6
|
+
|
|
7
|
+
- **Pod** = one Alpaca account (a strategy team). **Trader** = one rqfc account.
|
|
8
|
+
Several traders share a pod; admins manage pods, traders, and capital.
|
|
9
|
+
- Traders authenticate (Supabase Auth) and trade through a backend that holds
|
|
10
|
+
the Alpaca keys — keys never reach a trader's machine.
|
|
11
|
+
|
|
12
|
+
### Repo layout
|
|
13
|
+
| Dir | What |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `app/` | React + Vite dashboard (public, read-only). Deploys to GitHub Pages. |
|
|
16
|
+
| `backend/` | FastAPI service: auth, permissions, Alpaca order execution, DB writes. |
|
|
17
|
+
| `package/` | `rqfc` — thin Python client traders/admins use. |
|
|
18
|
+
| `app/supabase/migrations/` | Postgres schema + demo seed. |
|
|
19
|
+
| `docs/` | `ARCHITECTURE.md` (design), `RUNBOOK.md` (end-to-end setup). |
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# rqfc
|
|
2
|
+
|
|
3
|
+
Thin Python client for the RQFC fund trading backend.
|
|
4
|
+
|
|
5
|
+
Traders log in with credentials or an API key issued by an admin. The backend
|
|
6
|
+
owns authentication, permissions, and each pod's Alpaca credentials, then
|
|
7
|
+
submits trades on the trader's behalf. **No Alpaca keys or Supabase settings
|
|
8
|
+
ever touch this client**. A pod is one Alpaca account; several traders share a
|
|
9
|
+
pod; admins can trade any pod and manage capital/membership.
|
|
10
|
+
|
|
11
|
+
See `../docs/ARCHITECTURE.md` and `../docs/RUNBOOK.md` for the full system.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
From GitHub:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install git+https://github.com/Rutgers-Quant-Finance-Club/fund.git
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
After publishing to PyPI, this becomes:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install rqfc
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For local package development from this repo:
|
|
27
|
+
```bash
|
|
28
|
+
pip install -e .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Trader Login
|
|
32
|
+
By default, `rqfc` talks to the deployed RQFC API:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
https://fund-tkb1.onrender.com
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Admins issue either email/password credentials or a trader API key. Traders do
|
|
39
|
+
not need to configure Supabase or Alpaca.
|
|
40
|
+
|
|
41
|
+
Email/password login:
|
|
42
|
+
```python
|
|
43
|
+
import rqfc
|
|
44
|
+
|
|
45
|
+
rqfc.login("alice@example.com", "password")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
API key login, useful for scripts and strategy runners:
|
|
49
|
+
```python
|
|
50
|
+
import rqfc
|
|
51
|
+
|
|
52
|
+
rqfc.login(api_key="rqfc_...")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Local Dev Override
|
|
56
|
+
To test against a local or staging backend, use `RQFC_BACKEND_URL`:
|
|
57
|
+
```bash
|
|
58
|
+
export RQFC_BACKEND_URL=http://localhost:8000
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or pass it directly:
|
|
62
|
+
```python
|
|
63
|
+
import rqfc
|
|
64
|
+
|
|
65
|
+
rqfc.login("alice@example.com", "password", backend_url="http://localhost:8000")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Trader Usage
|
|
69
|
+
```python
|
|
70
|
+
import rqfc
|
|
71
|
+
|
|
72
|
+
rqfc.login(api_key="rqfc_...")
|
|
73
|
+
|
|
74
|
+
rqfc.whoami() # your profile + assigned pods
|
|
75
|
+
|
|
76
|
+
acct = rqfc.pod("Alpha Equities") # by name or id; must be assigned
|
|
77
|
+
acct.buy("AAPL", 10)
|
|
78
|
+
acct.sell("AAPL", 5)
|
|
79
|
+
acct.short("TSLA", 3)
|
|
80
|
+
acct.dollar_buy("NVDA", 5000)
|
|
81
|
+
acct.account() # live equity / cash / buying power
|
|
82
|
+
acct.positions() # live positions
|
|
83
|
+
acct.price("AAPL")
|
|
84
|
+
acct.sync() # refresh the dashboard (positions, NAV, metrics)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Admin Usage
|
|
88
|
+
```python
|
|
89
|
+
rqfc.login("admin@example.com", "password")
|
|
90
|
+
admin = rqfc.admin()
|
|
91
|
+
|
|
92
|
+
pod = admin.create_pod("Vol Arb", "options", capital=100000,
|
|
93
|
+
alpaca_api_key="PK...", alpaca_api_secret="...")
|
|
94
|
+
admin.list_traders()
|
|
95
|
+
admin.add_trader(pod["id"], trader_id, role="trader")
|
|
96
|
+
admin.allocate_capital(pod["id"], 150000)
|
|
97
|
+
admin.remove_trader(pod["id"], trader_id)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Method Reference
|
|
101
|
+
**Account** (`rqfc.pod(...)`): `buy`, `sell`, `short`, `cover`, `dollar_buy`,
|
|
102
|
+
`dollar_sell`, `cancel`, `account`, `positions`, `price`, `bars`, `sync`.
|
|
103
|
+
|
|
104
|
+
**Admin** (`rqfc.admin()`): `create_pod`, `set_alpaca`, `allocate_capital`,
|
|
105
|
+
`list_traders`, `add_trader`, `remove_trader`.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# rqfc — RQFC fund trading client
|
|
2
|
+
#
|
|
3
|
+
# A thin, authenticated client for the RQFC trading backend. Traders log in with
|
|
4
|
+
# credentials or an API key issued by an admin; the backend holds each pod's
|
|
5
|
+
# Alpaca keys and submits trades on their behalf. No Alpaca keys ever touch this
|
|
6
|
+
# client.
|
|
7
|
+
#
|
|
8
|
+
# Trader usage:
|
|
9
|
+
# import rqfc
|
|
10
|
+
# rqfc.login("alice@example.com", "password")
|
|
11
|
+
# # or: rqfc.login(api_key="rqfc_...")
|
|
12
|
+
# acct = rqfc.pod("Alpha Equities") # a pod you're assigned to
|
|
13
|
+
# acct.buy("AAPL", 10)
|
|
14
|
+
# acct.positions()
|
|
15
|
+
# acct.sync() # refresh the dashboard
|
|
16
|
+
#
|
|
17
|
+
# Admin usage:
|
|
18
|
+
# rqfc.login("admin@example.com", "password")
|
|
19
|
+
# admin = rqfc.admin()
|
|
20
|
+
# pod = admin.create_pod("Vol Arb", "options", capital=100000,
|
|
21
|
+
# alpaca_api_key="PK...", alpaca_api_secret="...")
|
|
22
|
+
# admin.add_trader(pod["id"], trader_id, role="trader")
|
|
23
|
+
# admin.allocate_capital(pod["id"], 150000)
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
|
|
28
|
+
from ._session import Session
|
|
29
|
+
from .client import Account
|
|
30
|
+
from .admin import Admin
|
|
31
|
+
|
|
32
|
+
__version__ = "1.0.0"
|
|
33
|
+
DEFAULT_BACKEND_URL = "https://fund-tkb1.onrender.com"
|
|
34
|
+
|
|
35
|
+
_session: Session | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def configure(backend_url: str = None) -> Session:
|
|
39
|
+
"""Set backend connection details.
|
|
40
|
+
|
|
41
|
+
Defaults to RQFC_BACKEND_URL, then the production RQFC API. Local dev can
|
|
42
|
+
pass backend_url="http://localhost:8000".
|
|
43
|
+
"""
|
|
44
|
+
global _session
|
|
45
|
+
backend_url = backend_url or os.environ.get("RQFC_BACKEND_URL", DEFAULT_BACKEND_URL)
|
|
46
|
+
_session = Session(backend_url)
|
|
47
|
+
return _session
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def login(email: str = None, password: str = None, *, api_key: str = None,
|
|
51
|
+
backend_url: str = None) -> dict:
|
|
52
|
+
"""Authenticate and start a session. Returns your profile."""
|
|
53
|
+
sess = configure(backend_url)
|
|
54
|
+
if api_key:
|
|
55
|
+
if email or password:
|
|
56
|
+
raise ValueError("Use either email/password or api_key, not both.")
|
|
57
|
+
sess.use_api_key(api_key)
|
|
58
|
+
else:
|
|
59
|
+
if not email or not password:
|
|
60
|
+
raise ValueError("Call rqfc.login(email, password) or rqfc.login(api_key='rqfc_...').")
|
|
61
|
+
profile = sess.login(email, password)
|
|
62
|
+
if profile:
|
|
63
|
+
me = profile
|
|
64
|
+
tag = " (admin)" if me.get("is_admin") else ""
|
|
65
|
+
print(
|
|
66
|
+
f"Logged in as {me['display_name']}{tag}. "
|
|
67
|
+
f"Pods: {[p['pods']['name'] for p in me.get('pods', [])] or 'none assigned'}"
|
|
68
|
+
)
|
|
69
|
+
return me
|
|
70
|
+
|
|
71
|
+
me = sess.get("/me")
|
|
72
|
+
tag = " (admin)" if me.get("is_admin") else ""
|
|
73
|
+
print(f"Logged in as {me['display_name']}{tag}. Pods: {[p['pods']['name'] for p in me['pods']] or 'none assigned'}")
|
|
74
|
+
return me
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _require_session() -> Session:
|
|
78
|
+
if _session is None or not _session.access_token:
|
|
79
|
+
raise RuntimeError(
|
|
80
|
+
"Not logged in. Call rqfc.login(email, password) or "
|
|
81
|
+
"rqfc.login(api_key='rqfc_...') first."
|
|
82
|
+
)
|
|
83
|
+
return _session
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def pod(name_or_id: str) -> Account:
|
|
87
|
+
"""Select a pod to trade, by name or id."""
|
|
88
|
+
return Account(_require_session(), name_or_id)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def admin() -> Admin:
|
|
92
|
+
"""Get the admin interface (requires an admin account)."""
|
|
93
|
+
return Admin(_require_session())
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def whoami() -> dict:
|
|
97
|
+
"""Your profile and pod assignments."""
|
|
98
|
+
return _require_session().get("/me")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
__all__ = [
|
|
102
|
+
"DEFAULT_BACKEND_URL",
|
|
103
|
+
"configure",
|
|
104
|
+
"login",
|
|
105
|
+
"pod",
|
|
106
|
+
"admin",
|
|
107
|
+
"whoami",
|
|
108
|
+
"Account",
|
|
109
|
+
"Admin",
|
|
110
|
+
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Authenticated HTTP session against the RQFC backend.
|
|
2
|
+
|
|
3
|
+
The backend owns auth, authorization, and Alpaca credentials. This client stores
|
|
4
|
+
only a backend-issued token or trader API key and sends it as bearer auth.
|
|
5
|
+
"""
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
_UUID_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.I)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def looks_like_uuid(value: str) -> bool:
|
|
14
|
+
return bool(value and _UUID_RE.match(str(value)))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Session:
|
|
18
|
+
def __init__(self, backend_url: str):
|
|
19
|
+
self.backend_url = backend_url.rstrip("/")
|
|
20
|
+
self.access_token = None
|
|
21
|
+
self.profile = None
|
|
22
|
+
|
|
23
|
+
def login(self, email: str, password: str) -> dict:
|
|
24
|
+
r = requests.post(
|
|
25
|
+
f"{self.backend_url}/auth/login",
|
|
26
|
+
json={"email": email, "password": password},
|
|
27
|
+
timeout=30,
|
|
28
|
+
)
|
|
29
|
+
if r.status_code != 200:
|
|
30
|
+
raise RuntimeError(f"Login failed [{r.status_code}]: {r.text}")
|
|
31
|
+
data = r.json()
|
|
32
|
+
self.access_token = data["token"]
|
|
33
|
+
self.profile = data.get("profile")
|
|
34
|
+
return self.profile or {}
|
|
35
|
+
|
|
36
|
+
def use_api_key(self, api_key: str) -> None:
|
|
37
|
+
if not api_key:
|
|
38
|
+
raise ValueError("api_key is required.")
|
|
39
|
+
self.access_token = api_key
|
|
40
|
+
self.profile = None
|
|
41
|
+
|
|
42
|
+
# ── HTTP helpers ─────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
def _headers(self) -> dict:
|
|
45
|
+
if not self.access_token:
|
|
46
|
+
raise RuntimeError(
|
|
47
|
+
"Not logged in. Call rqfc.login(email, password) or "
|
|
48
|
+
"rqfc.login(api_key='rqfc_...') first."
|
|
49
|
+
)
|
|
50
|
+
return {"Authorization": f"Bearer {self.access_token}"}
|
|
51
|
+
|
|
52
|
+
def get(self, path: str, params: dict = None):
|
|
53
|
+
return self._handle(requests.get(
|
|
54
|
+
f"{self.backend_url}{path}", headers=self._headers(), params=params, timeout=60))
|
|
55
|
+
|
|
56
|
+
def post(self, path: str, json: dict = None):
|
|
57
|
+
return self._handle(requests.post(
|
|
58
|
+
f"{self.backend_url}{path}", headers=self._headers(), json=json, timeout=60))
|
|
59
|
+
|
|
60
|
+
def delete(self, path: str, json: dict = None):
|
|
61
|
+
return self._handle(requests.delete(
|
|
62
|
+
f"{self.backend_url}{path}", headers=self._headers(), json=json, timeout=60))
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def _handle(r: requests.Response):
|
|
66
|
+
if r.status_code >= 400:
|
|
67
|
+
try:
|
|
68
|
+
detail = r.json().get("detail", r.text)
|
|
69
|
+
except Exception:
|
|
70
|
+
detail = r.text
|
|
71
|
+
raise RuntimeError(f"[{r.status_code}] {detail}")
|
|
72
|
+
return r.json() if r.content else None
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Admin operations — manage pods, traders, capital. Admins only.
|
|
2
|
+
|
|
3
|
+
All calls hit the backend's /admin endpoints, which re-check is_admin server-side.
|
|
4
|
+
"""
|
|
5
|
+
from ._session import Session
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Admin:
|
|
9
|
+
def __init__(self, session: Session):
|
|
10
|
+
self._s = session
|
|
11
|
+
me = self._s.get("/me")
|
|
12
|
+
if not me.get("is_admin"):
|
|
13
|
+
raise PermissionError("This account does not have admin access.")
|
|
14
|
+
|
|
15
|
+
# ── Pods ─────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
def create_pod(self, name, asset_class, *, benchmark_symbol="SPY", description=None,
|
|
18
|
+
capital=0, alpaca_api_key=None, alpaca_api_secret=None,
|
|
19
|
+
alpaca_account_id=None) -> dict:
|
|
20
|
+
"""Create a pod, optionally attaching its Alpaca paper-account keys."""
|
|
21
|
+
return self._s.post("/admin/pods", {
|
|
22
|
+
"name": name,
|
|
23
|
+
"asset_class": asset_class,
|
|
24
|
+
"benchmark_symbol": benchmark_symbol,
|
|
25
|
+
"description": description,
|
|
26
|
+
"allocated_capital": capital,
|
|
27
|
+
"alpaca_api_key": alpaca_api_key,
|
|
28
|
+
"alpaca_api_secret": alpaca_api_secret,
|
|
29
|
+
"alpaca_account_id": alpaca_account_id,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
def set_alpaca(self, pod_id, *, api_key=None, api_secret=None, account_id=None) -> dict:
|
|
33
|
+
"""Attach or update a pod's Alpaca credentials."""
|
|
34
|
+
return self._s.post(f"/admin/pods/{pod_id}/alpaca", {
|
|
35
|
+
"alpaca_api_key": api_key,
|
|
36
|
+
"alpaca_api_secret": api_secret,
|
|
37
|
+
"alpaca_account_id": account_id,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
def allocate_capital(self, pod_id, amount, note=None) -> dict:
|
|
41
|
+
"""Set a pod's allocated capital (logged to the audit trail)."""
|
|
42
|
+
return self._s.post(f"/admin/pods/{pod_id}/capital", {"amount": amount, "note": note})
|
|
43
|
+
|
|
44
|
+
# ── Memberships ──────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
def list_traders(self) -> list:
|
|
47
|
+
"""All registered traders (id, display_name, is_admin)."""
|
|
48
|
+
return self._s.get("/admin/traders")
|
|
49
|
+
|
|
50
|
+
def add_trader(self, pod_id, trader_id, role="trader") -> dict:
|
|
51
|
+
"""Assign a trader to a pod. role: 'trader' or 'pm'."""
|
|
52
|
+
return self._s.post("/admin/memberships",
|
|
53
|
+
{"pod_id": pod_id, "trader_id": trader_id, "role": role})
|
|
54
|
+
|
|
55
|
+
def remove_trader(self, pod_id, trader_id) -> dict:
|
|
56
|
+
"""Remove a trader from a pod."""
|
|
57
|
+
return self._s.delete("/admin/memberships",
|
|
58
|
+
{"pod_id": pod_id, "trader_id": trader_id, "role": "trader"})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""The trader-facing Account object.
|
|
2
|
+
|
|
3
|
+
Every method is an authenticated call to the backend, which holds the pod's
|
|
4
|
+
Alpaca keys and submits on your behalf. You can only act on pods you're assigned
|
|
5
|
+
to (admins can act on any pod).
|
|
6
|
+
"""
|
|
7
|
+
from ._session import Session, looks_like_uuid
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Account:
|
|
11
|
+
"""A pod you can trade. Obtain one via rqfc.pod(name_or_id)."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, session: Session, pod_ref: str):
|
|
14
|
+
self._s = session
|
|
15
|
+
self.pod_id = self._resolve_pod(pod_ref)
|
|
16
|
+
|
|
17
|
+
def _resolve_pod(self, ref: str) -> str:
|
|
18
|
+
if looks_like_uuid(ref):
|
|
19
|
+
return ref
|
|
20
|
+
for p in self._s.get("/pods"):
|
|
21
|
+
if p["name"].lower() == str(ref).lower():
|
|
22
|
+
return p["id"]
|
|
23
|
+
raise ValueError(f"No pod named '{ref}'. Run rqfc.whoami() to see your pods.")
|
|
24
|
+
|
|
25
|
+
# ── Orders ───────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
def _order(self, **kw):
|
|
28
|
+
return self._s.post("/orders", {"pod_id": self.pod_id, **kw})
|
|
29
|
+
|
|
30
|
+
def buy(self, symbol, qty, order_type="market", limit_price=None, time_in_force="day"):
|
|
31
|
+
"""Buy shares. order_type: 'market' (default) or 'limit'."""
|
|
32
|
+
return self._order(symbol=symbol, side="buy", qty=qty, order_type=order_type,
|
|
33
|
+
order_label=order_type, limit_price=limit_price,
|
|
34
|
+
time_in_force=time_in_force)
|
|
35
|
+
|
|
36
|
+
def sell(self, symbol, qty, order_type="market", limit_price=None, time_in_force="day"):
|
|
37
|
+
"""Sell shares you hold in the pod."""
|
|
38
|
+
return self._order(symbol=symbol, side="sell", qty=qty, order_type=order_type,
|
|
39
|
+
order_label=order_type, limit_price=limit_price,
|
|
40
|
+
time_in_force=time_in_force)
|
|
41
|
+
|
|
42
|
+
def short(self, symbol, qty, time_in_force="day"):
|
|
43
|
+
"""Short sell. Close with cover()."""
|
|
44
|
+
return self._order(symbol=symbol, side="sell", qty=qty, order_label="short",
|
|
45
|
+
time_in_force=time_in_force)
|
|
46
|
+
|
|
47
|
+
def cover(self, symbol, qty, time_in_force="day"):
|
|
48
|
+
"""Buy back a short position."""
|
|
49
|
+
return self._order(symbol=symbol, side="buy", qty=qty, order_label="cover",
|
|
50
|
+
time_in_force=time_in_force)
|
|
51
|
+
|
|
52
|
+
def dollar_buy(self, symbol, amount, time_in_force="day"):
|
|
53
|
+
"""Buy by dollar amount, e.g. dollar_buy('AAPL', 5000)."""
|
|
54
|
+
return self._order(symbol=symbol, side="buy", notional=amount, order_label="dollar_buy",
|
|
55
|
+
time_in_force=time_in_force)
|
|
56
|
+
|
|
57
|
+
def dollar_sell(self, symbol, amount, time_in_force="day"):
|
|
58
|
+
"""Sell by dollar amount."""
|
|
59
|
+
return self._order(symbol=symbol, side="sell", notional=amount, order_label="dollar_sell",
|
|
60
|
+
time_in_force=time_in_force)
|
|
61
|
+
|
|
62
|
+
def cancel(self, order_id):
|
|
63
|
+
"""Cancel an open order by its Alpaca order id."""
|
|
64
|
+
return self._s.post("/orders/cancel", {"pod_id": self.pod_id, "order_id": order_id})
|
|
65
|
+
|
|
66
|
+
# ── Read ─────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
def account(self):
|
|
69
|
+
"""Live equity, cash, and buying power for the pod."""
|
|
70
|
+
return self._s.get(f"/pods/{self.pod_id}/account")
|
|
71
|
+
|
|
72
|
+
def positions(self):
|
|
73
|
+
"""Live open positions for the pod."""
|
|
74
|
+
return self._s.get(f"/pods/{self.pod_id}/positions")
|
|
75
|
+
|
|
76
|
+
def price(self, symbol):
|
|
77
|
+
"""Latest trade price."""
|
|
78
|
+
return self._s.get("/market/price", {"symbol": symbol, "pod_id": self.pod_id})
|
|
79
|
+
|
|
80
|
+
def bars(self, symbol, days=30):
|
|
81
|
+
"""Daily OHLCV bars."""
|
|
82
|
+
return self._s.get("/market/bars", {"symbol": symbol, "pod_id": self.pod_id, "days": days})
|
|
83
|
+
|
|
84
|
+
def sync(self):
|
|
85
|
+
"""Pull the pod's positions + NAV from Alpaca into the dashboard."""
|
|
86
|
+
return self._s.post(f"/sync/{self.pod_id}")
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rqfc-dev
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Thin client for the RQFC fund trading backend
|
|
5
|
+
Author: RQFC
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.28.0
|
|
9
|
+
|
|
10
|
+
# rqfc
|
|
11
|
+
|
|
12
|
+
Thin Python client for the RQFC fund trading backend.
|
|
13
|
+
|
|
14
|
+
Traders log in with credentials or an API key issued by an admin. The backend
|
|
15
|
+
owns authentication, permissions, and each pod's Alpaca credentials, then
|
|
16
|
+
submits trades on the trader's behalf. **No Alpaca keys or Supabase settings
|
|
17
|
+
ever touch this client**. A pod is one Alpaca account; several traders share a
|
|
18
|
+
pod; admins can trade any pod and manage capital/membership.
|
|
19
|
+
|
|
20
|
+
See `../docs/ARCHITECTURE.md` and `../docs/RUNBOOK.md` for the full system.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
From GitHub:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install git+https://github.com/Rutgers-Quant-Finance-Club/fund.git
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
After publishing to PyPI, this becomes:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install rqfc
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For local package development from this repo:
|
|
36
|
+
```bash
|
|
37
|
+
pip install -e .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Trader Login
|
|
41
|
+
By default, `rqfc` talks to the deployed RQFC API:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
https://fund-tkb1.onrender.com
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Admins issue either email/password credentials or a trader API key. Traders do
|
|
48
|
+
not need to configure Supabase or Alpaca.
|
|
49
|
+
|
|
50
|
+
Email/password login:
|
|
51
|
+
```python
|
|
52
|
+
import rqfc
|
|
53
|
+
|
|
54
|
+
rqfc.login("alice@example.com", "password")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
API key login, useful for scripts and strategy runners:
|
|
58
|
+
```python
|
|
59
|
+
import rqfc
|
|
60
|
+
|
|
61
|
+
rqfc.login(api_key="rqfc_...")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Local Dev Override
|
|
65
|
+
To test against a local or staging backend, use `RQFC_BACKEND_URL`:
|
|
66
|
+
```bash
|
|
67
|
+
export RQFC_BACKEND_URL=http://localhost:8000
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or pass it directly:
|
|
71
|
+
```python
|
|
72
|
+
import rqfc
|
|
73
|
+
|
|
74
|
+
rqfc.login("alice@example.com", "password", backend_url="http://localhost:8000")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Trader Usage
|
|
78
|
+
```python
|
|
79
|
+
import rqfc
|
|
80
|
+
|
|
81
|
+
rqfc.login(api_key="rqfc_...")
|
|
82
|
+
|
|
83
|
+
rqfc.whoami() # your profile + assigned pods
|
|
84
|
+
|
|
85
|
+
acct = rqfc.pod("Alpha Equities") # by name or id; must be assigned
|
|
86
|
+
acct.buy("AAPL", 10)
|
|
87
|
+
acct.sell("AAPL", 5)
|
|
88
|
+
acct.short("TSLA", 3)
|
|
89
|
+
acct.dollar_buy("NVDA", 5000)
|
|
90
|
+
acct.account() # live equity / cash / buying power
|
|
91
|
+
acct.positions() # live positions
|
|
92
|
+
acct.price("AAPL")
|
|
93
|
+
acct.sync() # refresh the dashboard (positions, NAV, metrics)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Admin Usage
|
|
97
|
+
```python
|
|
98
|
+
rqfc.login("admin@example.com", "password")
|
|
99
|
+
admin = rqfc.admin()
|
|
100
|
+
|
|
101
|
+
pod = admin.create_pod("Vol Arb", "options", capital=100000,
|
|
102
|
+
alpaca_api_key="PK...", alpaca_api_secret="...")
|
|
103
|
+
admin.list_traders()
|
|
104
|
+
admin.add_trader(pod["id"], trader_id, role="trader")
|
|
105
|
+
admin.allocate_capital(pod["id"], 150000)
|
|
106
|
+
admin.remove_trader(pod["id"], trader_id)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Method Reference
|
|
110
|
+
**Account** (`rqfc.pod(...)`): `buy`, `sell`, `short`, `cover`, `dollar_buy`,
|
|
111
|
+
`dollar_sell`, `cancel`, `account`, `positions`, `price`, `bars`, `sync`.
|
|
112
|
+
|
|
113
|
+
**Admin** (`rqfc.admin()`): `create_pod`, `set_alpaca`, `allocate_capital`,
|
|
114
|
+
`list_traders`, `add_trader`, `remove_trader`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
package/README.md
|
|
4
|
+
package/rqfc/__init__.py
|
|
5
|
+
package/rqfc/_session.py
|
|
6
|
+
package/rqfc/admin.py
|
|
7
|
+
package/rqfc/client.py
|
|
8
|
+
package/rqfc_dev.egg-info/PKG-INFO
|
|
9
|
+
package/rqfc_dev.egg-info/SOURCES.txt
|
|
10
|
+
package/rqfc_dev.egg-info/dependency_links.txt
|
|
11
|
+
package/rqfc_dev.egg-info/requires.txt
|
|
12
|
+
package/rqfc_dev.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.28.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rqfc
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rqfc-dev"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Thin client for the RQFC fund trading backend"
|
|
9
|
+
readme = "package/README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
authors = [{ name = "RQFC" }]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"requests>=2.28.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools]
|
|
17
|
+
package-dir = { "" = "package" }
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["package"]
|
|
21
|
+
include = ["rqfc*"]
|
rqfc_dev-1.0.0/setup.cfg
ADDED