crowdwisdomtrading 0.2.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.
- crowdwisdomtrading-0.2.0/PKG-INFO +148 -0
- crowdwisdomtrading-0.2.0/README.md +122 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading/__init__.py +5 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading/aihf.py +138 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading/cli.py +71 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading/client.py +262 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading/payment.py +91 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading.egg-info/PKG-INFO +148 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading.egg-info/SOURCES.txt +16 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading.egg-info/dependency_links.txt +1 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading.egg-info/entry_points.txt +2 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading.egg-info/requires.txt +13 -0
- crowdwisdomtrading-0.2.0/crowdwisdomtrading.egg-info/top_level.txt +1 -0
- crowdwisdomtrading-0.2.0/pyproject.toml +38 -0
- crowdwisdomtrading-0.2.0/setup.cfg +4 -0
- crowdwisdomtrading-0.2.0/tests/test_aihf_model.py +108 -0
- crowdwisdomtrading-0.2.0/tests/test_client.py +197 -0
- crowdwisdomtrading-0.2.0/tests/test_payment_scope.py +73 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crowdwisdomtrading
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Client for CrowdWisdom Trading crowd data: free weekly crowd score, pay per call with x402 or use a Bearer key; includes an ai-hedge-fund alpha model.
|
|
5
|
+
Author: CrowdWisdom Trading
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://crowdwisdomtrading.com/x402?utm_source=pypi&utm_medium=package&utm_campaign=crowdwisdomtrading
|
|
8
|
+
Project-URL: Catalog, https://api.crowdwisdomtrading.com/x402/catalog
|
|
9
|
+
Project-URL: Docs, https://api.crowdwisdomtrading.com/docs
|
|
10
|
+
Keywords: x402,trading,crowd,sentiment,ai-hedge-fund,alpha
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: requests>=2.31
|
|
17
|
+
Provides-Extra: x402
|
|
18
|
+
Requires-Dist: x402[evm,requests]>=2.26; extra == "x402"
|
|
19
|
+
Requires-Dist: eth-account>=0.13; extra == "x402"
|
|
20
|
+
Provides-Extra: aihf
|
|
21
|
+
Requires-Dist: aihf>=2.7; extra == "aihf"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
24
|
+
Requires-Dist: build; extra == "dev"
|
|
25
|
+
Requires-Dist: twine; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# crowdwisdomtrading
|
|
28
|
+
|
|
29
|
+
Python client for [CrowdWisdom Trading](https://crowdwisdomtrading.com/x402?utm_source=pypi&utm_medium=readme&utm_campaign=crowdwisdomtrading) crowd data on US stocks:
|
|
30
|
+
free daily and weekly crowd scores, the per-source crowd consensus, the newest crowd setup (direction, entry,
|
|
31
|
+
targets, stops), today's setups, CrowdMap (crowd support / resistance levels) and point-in-time setup history.
|
|
32
|
+
|
|
33
|
+
Start free with no account. Pay per call with [x402](https://x402.org) (USDC on Base, no account) or use a
|
|
34
|
+
Bearer key from a plan. Includes a ready-made alpha model for [virattt/ai-hedge-fund](https://github.com/virattt/ai-hedge-fund).
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install crowdwisdomtrading # free daily + weekly scores, Bearer-key mode
|
|
40
|
+
pip install "crowdwisdomtrading[x402]" # pay per call with a wallet
|
|
41
|
+
pip install "crowdwisdomtrading[aihf]" # + ai-hedge-fund model
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Try it free
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from crowdwisdomtrading import Client
|
|
48
|
+
|
|
49
|
+
Client().daily_sentiment("NVDA") # today's crowd score
|
|
50
|
+
Client().weekly_sentiment("NVDA") # this week's
|
|
51
|
+
# {"data": {"cycle": "weekly", "week_of": "2026-11-02", "score": 42.0, "value": 0.42, "direction": "BULLISH",
|
|
52
|
+
# "confidence": 70, "mentions": 380, "sources_included": ["youtube", "x", "reddit"]}, ...}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
No key, no wallet. One crowd score from YouTube, X, StockTwits, Reddit, brokers, podcasts and newsletters: of the people
|
|
56
|
+
who took a side, long versus short (-100 to +100). Direction, confidence and the score per source are paid.
|
|
57
|
+
50 free calls a day per IP address for each (daily and weekly counted separately), any tickers.
|
|
58
|
+
|
|
59
|
+
## What it costs
|
|
60
|
+
|
|
61
|
+
| Call | Price | What you get |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `daily_sentiment(ticker)` | free | Today's `ProCrowdSentimentScore` (-100 to +100) and `ProTraders` (distinct traders behind it; `low_coverage` under 10) |
|
|
64
|
+
| `weekly_sentiment(ticker)` | free | The same for the week (newest Monday) |
|
|
65
|
+
| `consensus(ticker)` | $0.10 | The score per source (YouTube, X, Reddit, brokers, podcasts, newsletters), mention breakdown, point-in-time history (`as_of`) |
|
|
66
|
+
| `setup_latest(ticker)` | $0.50 | Newest setup: direction, entry, 2 targets, 2 stops, confidence, risk/reward |
|
|
67
|
+
| `setups_today()` | $10.00 | Every setup of the newest day |
|
|
68
|
+
| `crowdmap(ticker)` | $0.30 | CrowdMap, the crowd's support / resistance levels, newest day: every level, role, trader count, impact, horizons |
|
|
69
|
+
| `history(ticker, from, to)` | $0.10 | Up to 200 past setups, as published at the time (backtest-safe) |
|
|
70
|
+
| `catalog()`, `sample()` | free | Price list; one setup at least 7 days old |
|
|
71
|
+
|
|
72
|
+
Live prices: `https://api.crowdwisdomtrading.com/x402/catalog`.
|
|
73
|
+
|
|
74
|
+
## Paying
|
|
75
|
+
|
|
76
|
+
| Mode | Set | What it uses |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| wallet (pay per call) | `X402_WALLET_PRIVATE_KEY=0x...` with USDC on Base | `/x402/*` routes, receipt (tx hash) on every answer |
|
|
79
|
+
| key (plan) | `CWT_API_KEY=cwt_live_...` | `/v1/*` routes you already pay for monthly |
|
|
80
|
+
|
|
81
|
+
A key wins over a wallet. Fund the wallet with small amounts; the `exact` scheme pays only the quoted
|
|
82
|
+
price and nothing else.
|
|
83
|
+
|
|
84
|
+
## Use
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from crowdwisdomtrading import Client
|
|
88
|
+
|
|
89
|
+
c = Client() # reads the env vars above
|
|
90
|
+
c.daily_sentiment("NVDA") # free
|
|
91
|
+
c.weekly_sentiment("NVDA") # free
|
|
92
|
+
c.catalog() # free: prices, schemas, examples
|
|
93
|
+
c.sample() # free: one setup at least 7 days old
|
|
94
|
+
c.consensus("NVDA") # {"data": {"value": 0.42, "direction": "BULLISH", ...}, "as_of": ...}
|
|
95
|
+
c.consensus("NVDA", as_of="2026-11-04") # point-in-time: latest session on or before that day
|
|
96
|
+
c.setup_latest("NVDA") # direction, last_close, targets, stops, confidence, rr
|
|
97
|
+
c.setups_today()
|
|
98
|
+
c.crowdmap("NVDA") # CrowdMap: crowd support / resistance levels
|
|
99
|
+
c.history("NVDA", "2026-10-01", "2026-10-31") # only rows published by `to` (backtest-safe)
|
|
100
|
+
c.last_receipt # {"transaction": "0x...", "payer": ..., "network": ...} in wallet mode
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Every answer is `{"data": ..., "as_of": ..., "meta": {...}}`. Answers with `as_of` / `to` are cached on disk
|
|
104
|
+
(`~/.crowdwisdomtrading/cache`), live answers for 10 minutes, so a backtest pays once per (ticker, date). Errors
|
|
105
|
+
are never cached. `Client(cache=False)` disables it.
|
|
106
|
+
|
|
107
|
+
## ai-hedge-fund
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
# hedge_fund/signals/__init__.py
|
|
111
|
+
from crowdwisdomtrading.aihf import CrowdConsensusModel
|
|
112
|
+
ALPHA_MODEL_REGISTRY["crowd"] = CrowdConsensusModel
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
# hedge_fund/strategies/crowd-tilt.yaml
|
|
117
|
+
name: crowd-tilt
|
|
118
|
+
display_name: Crowd Tilt
|
|
119
|
+
models:
|
|
120
|
+
- name: crowd
|
|
121
|
+
weight: 1.0
|
|
122
|
+
- name: pead
|
|
123
|
+
blend:
|
|
124
|
+
mode: long_short
|
|
125
|
+
method: conviction_weighted
|
|
126
|
+
gross_target: 1.0
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`CrowdConsensusModel.predict(ticker, date, data_client)` returns a `Signal` with `value` in [-1, +1]
|
|
130
|
+
(crowd score / 100), `components` per source, and abstains (`value 0`, `metadata.abstained`) when the
|
|
131
|
+
latest crowd session is older than 10 days. It only asks for sessions dated on or before `date`.
|
|
132
|
+
|
|
133
|
+
## Command line
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
crowdwisdomtrading daily NVDA # free
|
|
137
|
+
crowdwisdomtrading weekly NVDA # free
|
|
138
|
+
crowdwisdomtrading catalog
|
|
139
|
+
crowdwisdomtrading consensus NVDA --as-of 2026-11-04
|
|
140
|
+
crowdwisdomtrading history NVDA --from 2026-10-01 --to 2026-10-31
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Notes
|
|
144
|
+
|
|
145
|
+
- Data is aggregated crowd opinion for research. Not investment advice. Payments are final; a failed
|
|
146
|
+
response is not charged.
|
|
147
|
+
- Networks: Base mainnet `eip155:8453` (default). `X402_NETWORK=eip155:84532` for Base Sepolia testing.
|
|
148
|
+
- Docs, prices and the paid plans: https://crowdwisdomtrading.com/x402?utm_source=pypi&utm_medium=readme&utm_campaign=crowdwisdomtrading
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# crowdwisdomtrading
|
|
2
|
+
|
|
3
|
+
Python client for [CrowdWisdom Trading](https://crowdwisdomtrading.com/x402?utm_source=pypi&utm_medium=readme&utm_campaign=crowdwisdomtrading) crowd data on US stocks:
|
|
4
|
+
free daily and weekly crowd scores, the per-source crowd consensus, the newest crowd setup (direction, entry,
|
|
5
|
+
targets, stops), today's setups, CrowdMap (crowd support / resistance levels) and point-in-time setup history.
|
|
6
|
+
|
|
7
|
+
Start free with no account. Pay per call with [x402](https://x402.org) (USDC on Base, no account) or use a
|
|
8
|
+
Bearer key from a plan. Includes a ready-made alpha model for [virattt/ai-hedge-fund](https://github.com/virattt/ai-hedge-fund).
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install crowdwisdomtrading # free daily + weekly scores, Bearer-key mode
|
|
14
|
+
pip install "crowdwisdomtrading[x402]" # pay per call with a wallet
|
|
15
|
+
pip install "crowdwisdomtrading[aihf]" # + ai-hedge-fund model
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Try it free
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from crowdwisdomtrading import Client
|
|
22
|
+
|
|
23
|
+
Client().daily_sentiment("NVDA") # today's crowd score
|
|
24
|
+
Client().weekly_sentiment("NVDA") # this week's
|
|
25
|
+
# {"data": {"cycle": "weekly", "week_of": "2026-11-02", "score": 42.0, "value": 0.42, "direction": "BULLISH",
|
|
26
|
+
# "confidence": 70, "mentions": 380, "sources_included": ["youtube", "x", "reddit"]}, ...}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
No key, no wallet. One crowd score from YouTube, X, StockTwits, Reddit, brokers, podcasts and newsletters: of the people
|
|
30
|
+
who took a side, long versus short (-100 to +100). Direction, confidence and the score per source are paid.
|
|
31
|
+
50 free calls a day per IP address for each (daily and weekly counted separately), any tickers.
|
|
32
|
+
|
|
33
|
+
## What it costs
|
|
34
|
+
|
|
35
|
+
| Call | Price | What you get |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `daily_sentiment(ticker)` | free | Today's `ProCrowdSentimentScore` (-100 to +100) and `ProTraders` (distinct traders behind it; `low_coverage` under 10) |
|
|
38
|
+
| `weekly_sentiment(ticker)` | free | The same for the week (newest Monday) |
|
|
39
|
+
| `consensus(ticker)` | $0.10 | The score per source (YouTube, X, Reddit, brokers, podcasts, newsletters), mention breakdown, point-in-time history (`as_of`) |
|
|
40
|
+
| `setup_latest(ticker)` | $0.50 | Newest setup: direction, entry, 2 targets, 2 stops, confidence, risk/reward |
|
|
41
|
+
| `setups_today()` | $10.00 | Every setup of the newest day |
|
|
42
|
+
| `crowdmap(ticker)` | $0.30 | CrowdMap, the crowd's support / resistance levels, newest day: every level, role, trader count, impact, horizons |
|
|
43
|
+
| `history(ticker, from, to)` | $0.10 | Up to 200 past setups, as published at the time (backtest-safe) |
|
|
44
|
+
| `catalog()`, `sample()` | free | Price list; one setup at least 7 days old |
|
|
45
|
+
|
|
46
|
+
Live prices: `https://api.crowdwisdomtrading.com/x402/catalog`.
|
|
47
|
+
|
|
48
|
+
## Paying
|
|
49
|
+
|
|
50
|
+
| Mode | Set | What it uses |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| wallet (pay per call) | `X402_WALLET_PRIVATE_KEY=0x...` with USDC on Base | `/x402/*` routes, receipt (tx hash) on every answer |
|
|
53
|
+
| key (plan) | `CWT_API_KEY=cwt_live_...` | `/v1/*` routes you already pay for monthly |
|
|
54
|
+
|
|
55
|
+
A key wins over a wallet. Fund the wallet with small amounts; the `exact` scheme pays only the quoted
|
|
56
|
+
price and nothing else.
|
|
57
|
+
|
|
58
|
+
## Use
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from crowdwisdomtrading import Client
|
|
62
|
+
|
|
63
|
+
c = Client() # reads the env vars above
|
|
64
|
+
c.daily_sentiment("NVDA") # free
|
|
65
|
+
c.weekly_sentiment("NVDA") # free
|
|
66
|
+
c.catalog() # free: prices, schemas, examples
|
|
67
|
+
c.sample() # free: one setup at least 7 days old
|
|
68
|
+
c.consensus("NVDA") # {"data": {"value": 0.42, "direction": "BULLISH", ...}, "as_of": ...}
|
|
69
|
+
c.consensus("NVDA", as_of="2026-11-04") # point-in-time: latest session on or before that day
|
|
70
|
+
c.setup_latest("NVDA") # direction, last_close, targets, stops, confidence, rr
|
|
71
|
+
c.setups_today()
|
|
72
|
+
c.crowdmap("NVDA") # CrowdMap: crowd support / resistance levels
|
|
73
|
+
c.history("NVDA", "2026-10-01", "2026-10-31") # only rows published by `to` (backtest-safe)
|
|
74
|
+
c.last_receipt # {"transaction": "0x...", "payer": ..., "network": ...} in wallet mode
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Every answer is `{"data": ..., "as_of": ..., "meta": {...}}`. Answers with `as_of` / `to` are cached on disk
|
|
78
|
+
(`~/.crowdwisdomtrading/cache`), live answers for 10 minutes, so a backtest pays once per (ticker, date). Errors
|
|
79
|
+
are never cached. `Client(cache=False)` disables it.
|
|
80
|
+
|
|
81
|
+
## ai-hedge-fund
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# hedge_fund/signals/__init__.py
|
|
85
|
+
from crowdwisdomtrading.aihf import CrowdConsensusModel
|
|
86
|
+
ALPHA_MODEL_REGISTRY["crowd"] = CrowdConsensusModel
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
# hedge_fund/strategies/crowd-tilt.yaml
|
|
91
|
+
name: crowd-tilt
|
|
92
|
+
display_name: Crowd Tilt
|
|
93
|
+
models:
|
|
94
|
+
- name: crowd
|
|
95
|
+
weight: 1.0
|
|
96
|
+
- name: pead
|
|
97
|
+
blend:
|
|
98
|
+
mode: long_short
|
|
99
|
+
method: conviction_weighted
|
|
100
|
+
gross_target: 1.0
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`CrowdConsensusModel.predict(ticker, date, data_client)` returns a `Signal` with `value` in [-1, +1]
|
|
104
|
+
(crowd score / 100), `components` per source, and abstains (`value 0`, `metadata.abstained`) when the
|
|
105
|
+
latest crowd session is older than 10 days. It only asks for sessions dated on or before `date`.
|
|
106
|
+
|
|
107
|
+
## Command line
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
crowdwisdomtrading daily NVDA # free
|
|
111
|
+
crowdwisdomtrading weekly NVDA # free
|
|
112
|
+
crowdwisdomtrading catalog
|
|
113
|
+
crowdwisdomtrading consensus NVDA --as-of 2026-11-04
|
|
114
|
+
crowdwisdomtrading history NVDA --from 2026-10-01 --to 2026-10-31
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Notes
|
|
118
|
+
|
|
119
|
+
- Data is aggregated crowd opinion for research. Not investment advice. Payments are final; a failed
|
|
120
|
+
response is not charged.
|
|
121
|
+
- Networks: Base mainnet `eip155:8453` (default). `X402_NETWORK=eip155:84532` for Base Sepolia testing.
|
|
122
|
+
- Docs, prices and the paid plans: https://crowdwisdomtrading.com/x402?utm_source=pypi&utm_medium=readme&utm_campaign=crowdwisdomtrading
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""crowdwisdomtrading: client for CrowdWisdom Trading crowd data (free weekly crowd score, x402 pay-per-call or Bearer key)."""
|
|
2
|
+
from crowdwisdomtrading.client import Client, CrowdWisdomError, PaymentFailed
|
|
3
|
+
|
|
4
|
+
__version__ = "0.2.0"
|
|
5
|
+
__all__ = ["Client", "CrowdWisdomError", "PaymentFailed", "__version__"]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""CrowdConsensusModel: an ai-hedge-fund AlphaModel that buys CrowdWisdom crowd consensus per call.
|
|
2
|
+
|
|
3
|
+
Register it in hedge_fund/signals/__init__.py:
|
|
4
|
+
from crowdwisdomtrading.aihf import CrowdConsensusModel
|
|
5
|
+
ALPHA_MODEL_REGISTRY["crowd"] = CrowdConsensusModel
|
|
6
|
+
and add it to a strategy YAML (models: - name: crowd). It pays with the wallet in
|
|
7
|
+
X402_WALLET_PRIVATE_KEY (the same one ai-hedge-fund uses for Financial Datasets) or uses a
|
|
8
|
+
CWT_API_KEY Bearer key. predict() only ever asks for data dated on or before `date`
|
|
9
|
+
(point-in-time), and disk caching means one payment per (ticker, date) in a backtest.
|
|
10
|
+
|
|
11
|
+
Works without ai-hedge-fund installed (a small shim replaces Signal / QuantModel) so the
|
|
12
|
+
package can be tested standalone; with aihf installed the real classes are used.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from datetime import date, datetime
|
|
17
|
+
from typing import Any, ClassVar
|
|
18
|
+
|
|
19
|
+
from crowdwisdomtrading.client import Client
|
|
20
|
+
|
|
21
|
+
try: # real ai-hedge-fund classes
|
|
22
|
+
from hedge_fund.models import Signal # type: ignore
|
|
23
|
+
from hedge_fund.signals.base import QuantModel # type: ignore
|
|
24
|
+
|
|
25
|
+
HAVE_AIHF = True
|
|
26
|
+
except Exception: # pragma: no cover - shim path is tested explicitly below
|
|
27
|
+
HAVE_AIHF = False
|
|
28
|
+
from dataclasses import dataclass, field
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Signal: # type: ignore[no-redef]
|
|
32
|
+
model_name: str
|
|
33
|
+
ticker: str
|
|
34
|
+
date: str
|
|
35
|
+
value: float
|
|
36
|
+
reasoning: str | None = None
|
|
37
|
+
components: dict[str, float] = field(default_factory=dict)
|
|
38
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
39
|
+
horizon_days: int | None = None
|
|
40
|
+
|
|
41
|
+
class QuantModel: # type: ignore[no-redef]
|
|
42
|
+
investment_approach: ClassVar[str] = "long_short"
|
|
43
|
+
horizon_days: ClassVar[int] = 5
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _as_day(value: date | str) -> str:
|
|
47
|
+
if isinstance(value, datetime):
|
|
48
|
+
return value.date().isoformat()
|
|
49
|
+
if isinstance(value, date):
|
|
50
|
+
return value.isoformat()
|
|
51
|
+
return str(value)[:10]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class CrowdConsensusModel(QuantModel):
|
|
55
|
+
"""Crowd consensus (-1..+1) from YouTube, X and Reddit mentions, as of the backtest date."""
|
|
56
|
+
|
|
57
|
+
investment_approach = "long_short"
|
|
58
|
+
horizon_days = 5
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
client: Client | None = None,
|
|
63
|
+
*,
|
|
64
|
+
cycle: str | None = None,
|
|
65
|
+
max_staleness_days: int = 10,
|
|
66
|
+
neutral_band: float = 0.0,
|
|
67
|
+
) -> None:
|
|
68
|
+
self._client = client
|
|
69
|
+
self._cycle = cycle
|
|
70
|
+
self._max_staleness_days = max_staleness_days
|
|
71
|
+
self._neutral_band = neutral_band
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def name(self) -> str:
|
|
75
|
+
return "crowd"
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def client(self) -> Client:
|
|
79
|
+
if self._client is None:
|
|
80
|
+
self._client = Client()
|
|
81
|
+
return self._client
|
|
82
|
+
|
|
83
|
+
def predict(self, ticker: str, date: date | str, data_client: Any = None) -> Signal: # noqa: A002
|
|
84
|
+
"""Signal for `ticker` using only crowd sessions dated on or before `date`. data_client is unused."""
|
|
85
|
+
day = _as_day(date)
|
|
86
|
+
try:
|
|
87
|
+
answer = self.client.consensus(ticker, as_of=day, cycle=self._cycle)
|
|
88
|
+
except Exception as exc:
|
|
89
|
+
return self._abstain(ticker, day, f"crowd data unavailable: {type(exc).__name__}")
|
|
90
|
+
data = answer.get("data") or {}
|
|
91
|
+
session = data.get("session") or {}
|
|
92
|
+
value = data.get("value")
|
|
93
|
+
if value is None or not session.get("date"):
|
|
94
|
+
return self._abstain(ticker, day, "no crowd session on or before this date")
|
|
95
|
+
try:
|
|
96
|
+
age = (datetime.strptime(day, "%Y-%m-%d").date() - datetime.strptime(str(session["date"]), "%Y-%m-%d").date()).days
|
|
97
|
+
except ValueError:
|
|
98
|
+
return self._abstain(ticker, day, "malformed crowd session date")
|
|
99
|
+
if age > self._max_staleness_days:
|
|
100
|
+
return self._abstain(ticker, day, f"latest crowd session {session['date']} is {age} days old")
|
|
101
|
+
if abs(float(value)) < self._neutral_band:
|
|
102
|
+
value = 0.0
|
|
103
|
+
sources = data.get("sources") or {}
|
|
104
|
+
components = {k: float(v) / 100.0 for k, v in sources.items() if v is not None}
|
|
105
|
+
mentions = data.get("mentions") or {}
|
|
106
|
+
receipt = (answer.get("meta") or {}).get("payment") or {}
|
|
107
|
+
return Signal(
|
|
108
|
+
model_name=self.name,
|
|
109
|
+
ticker=ticker,
|
|
110
|
+
date=day,
|
|
111
|
+
value=max(-1.0, min(1.0, float(value))),
|
|
112
|
+
reasoning=(
|
|
113
|
+
f"Crowd {data.get('direction') or 'view'} {float(data.get('score') or 0):+.0f} on {session['date']} "
|
|
114
|
+
f"({session.get('cycle')}) from {mentions.get('total', '?')} mentions"
|
|
115
|
+
),
|
|
116
|
+
components=components,
|
|
117
|
+
metadata={
|
|
118
|
+
"as_of": session["date"],
|
|
119
|
+
"cycle": session.get("cycle"),
|
|
120
|
+
"confidence": data.get("confidence"),
|
|
121
|
+
"mentions": mentions,
|
|
122
|
+
"latest_setup": data.get("latest_setup"),
|
|
123
|
+
"payment_tx": receipt.get("transaction"),
|
|
124
|
+
"cached": (answer.get("meta") or {}).get("cached"),
|
|
125
|
+
},
|
|
126
|
+
horizon_days=self.horizon_days,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def _abstain(self, ticker: str, day: str, reason: str) -> Signal:
|
|
130
|
+
return Signal(
|
|
131
|
+
model_name=self.name,
|
|
132
|
+
ticker=ticker,
|
|
133
|
+
date=day,
|
|
134
|
+
value=0.0,
|
|
135
|
+
reasoning=reason,
|
|
136
|
+
metadata={"abstained": True},
|
|
137
|
+
horizon_days=self.horizon_days,
|
|
138
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""crowdwisdomtrading command line: quick checks and demos.
|
|
2
|
+
|
|
3
|
+
crowdwisdomtrading catalog
|
|
4
|
+
crowdwisdomtrading sample
|
|
5
|
+
crowdwisdomtrading daily NVDA (free: daily crowd score)
|
|
6
|
+
crowdwisdomtrading weekly NVDA (free: weekly crowd score)
|
|
7
|
+
crowdwisdomtrading consensus NVDA [--as-of 2026-11-04] [--cycle daily]
|
|
8
|
+
crowdwisdomtrading latest NVDA
|
|
9
|
+
crowdwisdomtrading crowdmap NVDA
|
|
10
|
+
crowdwisdomtrading history NVDA --from 2026-10-01 --to 2026-10-31
|
|
11
|
+
Pays with X402_WALLET_PRIVATE_KEY or uses CWT_API_KEY; --base overrides the API host.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
from crowdwisdomtrading.client import Client, CrowdWisdomError
|
|
20
|
+
from crowdwisdomtrading.payment import PaymentFailed
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main(argv: list[str] | None = None) -> int:
|
|
24
|
+
parser = argparse.ArgumentParser(prog="crowdwisdomtrading", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
25
|
+
parser.add_argument("command", choices=["catalog", "sample", "daily", "weekly", "consensus", "latest", "today", "crowdmap", "history"])
|
|
26
|
+
parser.add_argument("ticker", nargs="?")
|
|
27
|
+
parser.add_argument("--as-of", dest="as_of")
|
|
28
|
+
parser.add_argument("--cycle")
|
|
29
|
+
parser.add_argument("--from", dest="date_from")
|
|
30
|
+
parser.add_argument("--to", dest="date_to")
|
|
31
|
+
parser.add_argument("--base")
|
|
32
|
+
parser.add_argument("--no-cache", action="store_true")
|
|
33
|
+
args = parser.parse_args(argv)
|
|
34
|
+
|
|
35
|
+
client = Client(args.base, cache=not args.no_cache)
|
|
36
|
+
try:
|
|
37
|
+
if args.command == "catalog":
|
|
38
|
+
out = client.catalog()
|
|
39
|
+
elif args.command == "sample":
|
|
40
|
+
out = client.sample()
|
|
41
|
+
elif args.command == "today":
|
|
42
|
+
out = client.setups_today(args.cycle)
|
|
43
|
+
else:
|
|
44
|
+
if not args.ticker:
|
|
45
|
+
parser.error("ticker required")
|
|
46
|
+
if args.command == "daily":
|
|
47
|
+
out = client.daily_sentiment(args.ticker)
|
|
48
|
+
elif args.command == "weekly":
|
|
49
|
+
out = client.weekly_sentiment(args.ticker)
|
|
50
|
+
elif args.command == "consensus":
|
|
51
|
+
out = client.consensus(args.ticker, as_of=args.as_of, cycle=args.cycle)
|
|
52
|
+
elif args.command == "latest":
|
|
53
|
+
out = client.setup_latest(args.ticker, cycle=args.cycle)
|
|
54
|
+
elif args.command == "crowdmap":
|
|
55
|
+
out = client.crowdmap(args.ticker)
|
|
56
|
+
else:
|
|
57
|
+
if not (args.date_from and args.date_to):
|
|
58
|
+
parser.error("--from and --to required")
|
|
59
|
+
out = client.history(args.ticker, args.date_from, args.date_to, cycle=args.cycle)
|
|
60
|
+
except PaymentFailed as exc:
|
|
61
|
+
print(f"payment failed: {exc}", file=sys.stderr)
|
|
62
|
+
return 2
|
|
63
|
+
except CrowdWisdomError as exc:
|
|
64
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
65
|
+
return 1
|
|
66
|
+
print(json.dumps(out, indent=2, default=str))
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
sys.exit(main())
|