analysis-poly 0.1.2__tar.gz → 0.1.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.
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/PKG-INFO +1 -1
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/activity_discovery.py +11 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/analyzer.py +17 -1
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/profit_engine.py +7 -2
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly.egg-info/PKG-INFO +1 -1
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/pyproject.toml +1 -1
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_analyzer_discovery.py +135 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_profit_engine.py +46 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/README.md +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/__init__.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/cli.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/logging_config.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/main.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/market_cache.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/market_result_cache.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/models.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/open_with_params.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/polymarket_client.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/run_manager.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/slugs.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/static/dist/app.css +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/static/dist/app.js +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/templates/index.html +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly/web.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly.egg-info/SOURCES.txt +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly.egg-info/dependency_links.txt +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly.egg-info/entry_points.txt +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly.egg-info/requires.txt +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/analysis_poly.egg-info/top_level.txt +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/setup.cfg +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_analyzer_chunking.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_analyzer_csv.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_analyzer_filter_empty_market.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_analyzer_market_cache.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_analyzer_result_cache_compat.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_market_cache.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_market_result_cache.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_models_defaults.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_open_with_params.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_run_manager.py +0 -0
- {analysis_poly-0.1.2 → analysis_poly-0.1.3}/tests/test_slugs.py +0 -0
|
@@ -150,6 +150,9 @@ def summarize_discovered_markets(records: list, warnings: list[WarningItem]) ->
|
|
|
150
150
|
warned_missing_slug: set[str] = set()
|
|
151
151
|
|
|
152
152
|
for record in records:
|
|
153
|
+
if _is_zero_value_redeem(record):
|
|
154
|
+
continue
|
|
155
|
+
|
|
153
156
|
slug = str(record.slug or "").strip()
|
|
154
157
|
condition_id = str(record.condition_id or "").strip()
|
|
155
158
|
if not slug:
|
|
@@ -187,6 +190,14 @@ def summarize_discovered_markets(records: list, warnings: list[WarningItem]) ->
|
|
|
187
190
|
)
|
|
188
191
|
|
|
189
192
|
|
|
193
|
+
def _is_zero_value_redeem(record) -> bool:
|
|
194
|
+
if str(getattr(record, "type", "")).upper() != "REDEEM":
|
|
195
|
+
return False
|
|
196
|
+
size = float(getattr(record, "size", 0) or 0)
|
|
197
|
+
usdc_size = float(getattr(record, "usdc_size", 0) or 0)
|
|
198
|
+
return size <= 0 and usdc_size <= 0
|
|
199
|
+
|
|
200
|
+
|
|
190
201
|
def dedupe_activity_records(records: list) -> list:
|
|
191
202
|
deduped: dict[str, object] = {}
|
|
192
203
|
for record in records:
|
|
@@ -147,6 +147,8 @@ class PolymarketProfitAnalyzer:
|
|
|
147
147
|
)
|
|
148
148
|
discovered_markets = _filter_discovered_markets(
|
|
149
149
|
discovered_markets,
|
|
150
|
+
start_ts=req.start_ts,
|
|
151
|
+
end_ts=req.end_ts,
|
|
150
152
|
keywords=req.keywords,
|
|
151
153
|
)
|
|
152
154
|
total_markets = len(discovered_markets)
|
|
@@ -783,9 +785,23 @@ def _is_market_result_cache_eligible(slug: str, now_ts: int, recent_window_sec:
|
|
|
783
785
|
|
|
784
786
|
def _filter_discovered_markets(
|
|
785
787
|
discovered_markets: list[DiscoveredMarket],
|
|
788
|
+
start_ts: int,
|
|
789
|
+
end_ts: int,
|
|
786
790
|
keywords: list[str],
|
|
787
791
|
) -> list[DiscoveredMarket]:
|
|
788
|
-
return [
|
|
792
|
+
return [
|
|
793
|
+
item
|
|
794
|
+
for item in discovered_markets
|
|
795
|
+
if _slug_in_time_range(item.slug, start_ts=start_ts, end_ts=end_ts)
|
|
796
|
+
and _slug_matches_filters(item.slug, keywords=keywords)
|
|
797
|
+
]
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def _slug_in_time_range(slug: str, start_ts: int, end_ts: int) -> bool:
|
|
801
|
+
market_ts = _market_ts(slug)
|
|
802
|
+
if market_ts is None:
|
|
803
|
+
return True
|
|
804
|
+
return start_ts <= market_ts <= end_ts
|
|
789
805
|
|
|
790
806
|
|
|
791
807
|
def _slug_matches_filters(slug: str, keywords: list[str]) -> bool:
|
|
@@ -128,6 +128,11 @@ class ProfitEngine:
|
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
for redeem in redeem_activities:
|
|
131
|
+
redeem_size = float(redeem.size)
|
|
132
|
+
redeem_usdc_size = float(redeem.usdc_size)
|
|
133
|
+
if redeem_size <= 0 and redeem_usdc_size <= 0:
|
|
134
|
+
continue
|
|
135
|
+
|
|
131
136
|
winner_token = _resolve_winner_token(market)
|
|
132
137
|
if not winner_token:
|
|
133
138
|
warnings.append(
|
|
@@ -146,8 +151,8 @@ class ProfitEngine:
|
|
|
146
151
|
tx=redeem.transaction_hash,
|
|
147
152
|
kind="REDEEM",
|
|
148
153
|
token_id=winner_token,
|
|
149
|
-
size=
|
|
150
|
-
usdc_size=
|
|
154
|
+
size=redeem_size,
|
|
155
|
+
usdc_size=redeem_usdc_size,
|
|
151
156
|
)
|
|
152
157
|
)
|
|
153
158
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
|
+
from analysis_poly.activity_discovery import summarize_discovered_markets
|
|
3
4
|
from analysis_poly.analyzer import PolymarketProfitAnalyzer
|
|
4
5
|
from analysis_poly.models import ActivityRecord, AnalysisRequest, MarketReport, PolymarketMarket, TokenReport
|
|
5
6
|
from analysis_poly.profit_engine import PnlDelta
|
|
@@ -122,6 +123,107 @@ def test_run_discovers_daily_markets_and_filters_keywords(monkeypatch):
|
|
|
122
123
|
asyncio.run(runner())
|
|
123
124
|
|
|
124
125
|
|
|
126
|
+
def test_run_filters_discovered_markets_by_slug_timestamp(monkeypatch):
|
|
127
|
+
class FakeClient:
|
|
128
|
+
async def get_user_activity_page(
|
|
129
|
+
self,
|
|
130
|
+
user,
|
|
131
|
+
activity_types=None,
|
|
132
|
+
start_ts=None,
|
|
133
|
+
end_ts=None,
|
|
134
|
+
limit=500,
|
|
135
|
+
offset=0,
|
|
136
|
+
sort_direction="ASC",
|
|
137
|
+
):
|
|
138
|
+
activity_key = tuple(activity_types or [])
|
|
139
|
+
if activity_key == ("TRADE", "SPLIT", "REDEEM"):
|
|
140
|
+
return [
|
|
141
|
+
ActivityRecord.model_validate(
|
|
142
|
+
{
|
|
143
|
+
"transactionHash": "0xold_redeem",
|
|
144
|
+
"timestamp": 1776432712,
|
|
145
|
+
"type": "REDEEM",
|
|
146
|
+
"conditionId": "cond_old",
|
|
147
|
+
"slug": "btc-updown-15m-1767960900",
|
|
148
|
+
"size": 1,
|
|
149
|
+
"usdcSize": 1,
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
ActivityRecord.model_validate(
|
|
153
|
+
{
|
|
154
|
+
"transactionHash": "0xlive_trade",
|
|
155
|
+
"timestamp": 1776432800,
|
|
156
|
+
"type": "TRADE",
|
|
157
|
+
"conditionId": "cond_live",
|
|
158
|
+
"slug": "btc-updown-15m-1776432800",
|
|
159
|
+
"size": 1,
|
|
160
|
+
"usdcSize": 0.5,
|
|
161
|
+
}
|
|
162
|
+
),
|
|
163
|
+
]
|
|
164
|
+
return []
|
|
165
|
+
|
|
166
|
+
async def aclose(self):
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
fetched_slugs = []
|
|
170
|
+
|
|
171
|
+
async def fake_fetch_markets_with_status(_client, slugs, concurrency):
|
|
172
|
+
fetched_slugs.extend(slugs)
|
|
173
|
+
market = PolymarketMarket(
|
|
174
|
+
slug="btc-updown-15m-1776432800",
|
|
175
|
+
condition_id="cond_live",
|
|
176
|
+
up_token_id="up_live",
|
|
177
|
+
down_token_id="down_live",
|
|
178
|
+
outcomes=["Up", "Down"],
|
|
179
|
+
outcome_prices=[0.5, 0.5],
|
|
180
|
+
)
|
|
181
|
+
return [(slug, market if slug == market.slug else None) for slug in slugs]
|
|
182
|
+
|
|
183
|
+
async def fake_process_single_market(client, engine, engine_no_fee, address, address_market_cache, req, market):
|
|
184
|
+
report = MarketReport(
|
|
185
|
+
market_slug=market.slug,
|
|
186
|
+
condition_id=market.condition_id,
|
|
187
|
+
up_token_id=market.up_token_id,
|
|
188
|
+
down_token_id=market.down_token_id,
|
|
189
|
+
realized_pnl_usdc=1.0,
|
|
190
|
+
tokens=[TokenReport(token_id=market.up_token_id, outcome="Up", realized_pnl_usdc=1.0, trade_count=1)],
|
|
191
|
+
)
|
|
192
|
+
delta = PnlDelta(timestamp=1776432800, market_slug=market.slug, token_id=market.up_token_id, delta_pnl_usdc=1.0)
|
|
193
|
+
from analysis_poly.analyzer import _MarketProcessResult
|
|
194
|
+
|
|
195
|
+
return _MarketProcessResult(
|
|
196
|
+
market_slug=market.slug,
|
|
197
|
+
market_report=report,
|
|
198
|
+
market_report_no_fee=report,
|
|
199
|
+
deltas=[delta],
|
|
200
|
+
deltas_no_fee=[delta],
|
|
201
|
+
warnings=[],
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
async def runner():
|
|
205
|
+
monkeypatch.setattr("analysis_poly.analyzer.PolymarketApiClient", lambda timeout_sec=20: FakeClient())
|
|
206
|
+
analyzer = PolymarketProfitAnalyzer()
|
|
207
|
+
monkeypatch.setattr(analyzer, "_fetch_markets_with_status", fake_fetch_markets_with_status)
|
|
208
|
+
monkeypatch.setattr(analyzer, "_process_single_market", fake_process_single_market)
|
|
209
|
+
|
|
210
|
+
report = await analyzer.run(
|
|
211
|
+
AnalysisRequest(
|
|
212
|
+
address="0xb4ef1257f7ac40c26f4dfaaad69ed75f6458682f",
|
|
213
|
+
start_ts=1774972800,
|
|
214
|
+
end_ts=1776679380,
|
|
215
|
+
keywords=[],
|
|
216
|
+
page_limit=1000,
|
|
217
|
+
concurrency=5,
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
assert fetched_slugs == ["btc-updown-15m-1776432800"]
|
|
222
|
+
assert [market.market_slug for market in report.markets] == ["btc-updown-15m-1776432800"]
|
|
223
|
+
|
|
224
|
+
asyncio.run(runner())
|
|
225
|
+
|
|
226
|
+
|
|
125
227
|
def test_run_adds_daily_maker_rebate_to_summary_and_total_curve(monkeypatch):
|
|
126
228
|
class FakeClient:
|
|
127
229
|
async def get_user_activity_page(
|
|
@@ -226,3 +328,36 @@ def test_run_adds_daily_maker_rebate_to_summary_and_total_curve(monkeypatch):
|
|
|
226
328
|
assert [item.model_dump() for item in report.maker_rebates] == [{"timestamp": 200, "usdc_size": 23.3977}]
|
|
227
329
|
|
|
228
330
|
asyncio.run(runner())
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def test_discovery_ignores_zero_value_redeem_activity():
|
|
334
|
+
warnings = []
|
|
335
|
+
records = [
|
|
336
|
+
ActivityRecord.model_validate(
|
|
337
|
+
{
|
|
338
|
+
"transactionHash": "0xzero",
|
|
339
|
+
"timestamp": 1776432712,
|
|
340
|
+
"type": "REDEEM",
|
|
341
|
+
"conditionId": "cond_old",
|
|
342
|
+
"slug": "btc-updown-15m-1767960900",
|
|
343
|
+
"size": 0,
|
|
344
|
+
"usdcSize": 0,
|
|
345
|
+
}
|
|
346
|
+
),
|
|
347
|
+
ActivityRecord.model_validate(
|
|
348
|
+
{
|
|
349
|
+
"transactionHash": "0xtrade",
|
|
350
|
+
"timestamp": 1776432800,
|
|
351
|
+
"type": "TRADE",
|
|
352
|
+
"conditionId": "cond_live",
|
|
353
|
+
"slug": "btc-updown-15m-1776432800",
|
|
354
|
+
"size": 1,
|
|
355
|
+
"usdcSize": 0.5,
|
|
356
|
+
}
|
|
357
|
+
),
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
discovered = summarize_discovered_markets(records, warnings)
|
|
361
|
+
|
|
362
|
+
assert [item.slug for item in discovered] == ["btc-updown-15m-1776432800"]
|
|
363
|
+
assert warnings == []
|
|
@@ -438,6 +438,52 @@ def test_closed_market_without_redeem_losing_position_counts_as_loss():
|
|
|
438
438
|
assert not any(w.code == "CLOSED_MARKET_UNKNOWN_OUTCOME" for w in warnings)
|
|
439
439
|
|
|
440
440
|
|
|
441
|
+
def test_zero_value_redeem_does_not_shift_closed_market_settlement_timestamp():
|
|
442
|
+
market = PolymarketMarket(
|
|
443
|
+
slug="btc-updown-15m-1767960900",
|
|
444
|
+
condition_id="cond_zero_redeem",
|
|
445
|
+
up_token_id="up_token",
|
|
446
|
+
down_token_id="down_token",
|
|
447
|
+
outcomes=["Up", "Down"],
|
|
448
|
+
outcome_prices=[0, 1],
|
|
449
|
+
closed=True,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
taker_buy = TradeRecord.model_validate(
|
|
453
|
+
{
|
|
454
|
+
"transactionHash": "0xbuy",
|
|
455
|
+
"timestamp": 1767960918,
|
|
456
|
+
"side": "BUY",
|
|
457
|
+
"asset": "up_token",
|
|
458
|
+
"conditionId": "cond_zero_redeem",
|
|
459
|
+
"size": 10,
|
|
460
|
+
"price": 0.52,
|
|
461
|
+
}
|
|
462
|
+
)
|
|
463
|
+
zero_redeem = ActivityRecord.model_validate(
|
|
464
|
+
{
|
|
465
|
+
"transactionHash": "0xzero",
|
|
466
|
+
"timestamp": 1776432712,
|
|
467
|
+
"type": "REDEEM",
|
|
468
|
+
"conditionId": "cond_zero_redeem",
|
|
469
|
+
"size": 0,
|
|
470
|
+
"usdcSize": 0,
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
engine = ProfitEngine(fee_rate_bps=1000, missing_cost_warn_qty=0.5)
|
|
475
|
+
report, deltas, _ = engine.process_market(
|
|
476
|
+
market=market,
|
|
477
|
+
taker_trades=[taker_buy],
|
|
478
|
+
all_trades=[taker_buy],
|
|
479
|
+
split_activities=[],
|
|
480
|
+
redeem_activities=[zero_redeem],
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
assert report.realized_pnl_usdc == -5.2
|
|
484
|
+
assert [delta.timestamp for delta in deltas] == [1767960918]
|
|
485
|
+
|
|
486
|
+
|
|
441
487
|
def test_closed_market_without_resolved_outcome_prices_warns_and_keeps_position():
|
|
442
488
|
market = PolymarketMarket(
|
|
443
489
|
slug="btc-updown-5m-4000",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|