polily 0.11.1__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.
- polily-0.11.1/.envrc.example +24 -0
- polily-0.11.1/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- polily-0.11.1/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
- polily-0.11.1/.github/PULL_REQUEST_TEMPLATE.md +12 -0
- polily-0.11.1/.github/workflows/ci.yml +56 -0
- polily-0.11.1/.github/workflows/publish-to-pypi.yml +94 -0
- polily-0.11.1/.github/workflows/sync-master-to-dev.yml +95 -0
- polily-0.11.1/.gitignore +49 -0
- polily-0.11.1/AGENTS.md +168 -0
- polily-0.11.1/CHANGELOG.md +636 -0
- polily-0.11.1/CLAUDE.md +168 -0
- polily-0.11.1/CODE_OF_CONDUCT.md +39 -0
- polily-0.11.1/CONTRIBUTING.md +155 -0
- polily-0.11.1/LICENSE +21 -0
- polily-0.11.1/PKG-INFO +217 -0
- polily-0.11.1/README.md +177 -0
- polily-0.11.1/config/phrases.yaml +418 -0
- polily-0.11.1/docs/architecture.md +286 -0
- polily-0.11.1/docs/ui-guide.md +229 -0
- polily-0.11.1/polily/__init__.py +44 -0
- polily-0.11.1/polily/agents/__init__.py +0 -0
- polily-0.11.1/polily/agents/base.py +378 -0
- polily-0.11.1/polily/agents/narrative_writer.py +192 -0
- polily-0.11.1/polily/agents/narrator_registry.py +73 -0
- polily-0.11.1/polily/agents/prompts/narrative_writer.md +295 -0
- polily-0.11.1/polily/agents/schemas.py +134 -0
- polily-0.11.1/polily/analysis_store.py +88 -0
- polily-0.11.1/polily/api.py +351 -0
- polily-0.11.1/polily/cli.py +524 -0
- polily-0.11.1/polily/core/__init__.py +0 -0
- polily-0.11.1/polily/core/clob.py +122 -0
- polily-0.11.1/polily/core/config.py +511 -0
- polily-0.11.1/polily/core/config_docs/__init__.py +8 -0
- polily-0.11.1/polily/core/config_docs/_loader.py +148 -0
- polily-0.11.1/polily/core/config_docs/mispricing.md +57 -0
- polily-0.11.1/polily/core/config_docs/movement.md +262 -0
- polily-0.11.1/polily/core/config_docs/scoring.md +41 -0
- polily-0.11.1/polily/core/config_docs/wallet.md +21 -0
- polily-0.11.1/polily/core/config_store.py +494 -0
- polily-0.11.1/polily/core/config_yaml.py +77 -0
- polily-0.11.1/polily/core/db.py +493 -0
- polily-0.11.1/polily/core/event_store.py +402 -0
- polily-0.11.1/polily/core/events.py +95 -0
- polily-0.11.1/polily/core/fees.py +48 -0
- polily-0.11.1/polily/core/lifecycle.py +152 -0
- polily-0.11.1/polily/core/migration_v0_11_0.py +155 -0
- polily-0.11.1/polily/core/models.py +243 -0
- polily-0.11.1/polily/core/monitor_store.py +49 -0
- polily-0.11.1/polily/core/paths.py +123 -0
- polily-0.11.1/polily/core/positions.py +173 -0
- polily-0.11.1/polily/core/trade_engine.py +296 -0
- polily-0.11.1/polily/core/wallet.py +204 -0
- polily-0.11.1/polily/core/wallet_reset.py +45 -0
- polily-0.11.1/polily/daemon/__init__.py +0 -0
- polily-0.11.1/polily/daemon/auto_monitor.py +48 -0
- polily-0.11.1/polily/daemon/close_event.py +32 -0
- polily-0.11.1/polily/daemon/launchctl_query.py +111 -0
- polily-0.11.1/polily/daemon/poll_job.py +1050 -0
- polily-0.11.1/polily/daemon/resolution.py +191 -0
- polily-0.11.1/polily/daemon/scheduler.py +622 -0
- polily-0.11.1/polily/daemon/score_refresh.py +180 -0
- polily-0.11.1/polily/doctor.py +172 -0
- polily-0.11.1/polily/market_types/__init__.py +15 -0
- polily-0.11.1/polily/market_types/crypto_threshold.py +51 -0
- polily-0.11.1/polily/market_types/protocol.py +39 -0
- polily-0.11.1/polily/market_types/registry.py +65 -0
- polily-0.11.1/polily/match.py +83 -0
- polily-0.11.1/polily/monitor/__init__.py +0 -0
- polily-0.11.1/polily/monitor/event_metrics.py +90 -0
- polily-0.11.1/polily/monitor/models.py +70 -0
- polily-0.11.1/polily/monitor/scorer.py +59 -0
- polily-0.11.1/polily/monitor/signals.py +148 -0
- polily-0.11.1/polily/monitor/store.py +158 -0
- polily-0.11.1/polily/orderbook.py +85 -0
- polily-0.11.1/polily/pnl.py +35 -0
- polily-0.11.1/polily/price_feeds.py +160 -0
- polily-0.11.1/polily/scan/__init__.py +0 -0
- polily-0.11.1/polily/scan/commentary.py +167 -0
- polily-0.11.1/polily/scan/event_scoring.py +302 -0
- polily-0.11.1/polily/scan/mispricing.py +209 -0
- polily-0.11.1/polily/scan/pipeline.py +324 -0
- polily-0.11.1/polily/scan/reporting.py +111 -0
- polily-0.11.1/polily/scan/scoring.py +450 -0
- polily-0.11.1/polily/scan/tag_classifier.py +56 -0
- polily-0.11.1/polily/scan_log.py +333 -0
- polily-0.11.1/polily/tui/__init__.py +0 -0
- polily-0.11.1/polily/tui/_dispatch.py +128 -0
- polily-0.11.1/polily/tui/app.py +116 -0
- polily-0.11.1/polily/tui/bindings.py +54 -0
- polily-0.11.1/polily/tui/components/__init__.py +19 -0
- polily-0.11.1/polily/tui/components/analysis_panel.py +213 -0
- polily-0.11.1/polily/tui/components/binary_structure_panel.py +111 -0
- polily-0.11.1/polily/tui/components/event_header.py +175 -0
- polily-0.11.1/polily/tui/components/event_kpi.py +228 -0
- polily-0.11.1/polily/tui/components/movement_sparkline.py +79 -0
- polily-0.11.1/polily/tui/components/position_panel.py +102 -0
- polily-0.11.1/polily/tui/components/sub_market_table.py +174 -0
- polily-0.11.1/polily/tui/css/app.tcss +125 -0
- polily-0.11.1/polily/tui/css/tokens.tcss +56 -0
- polily-0.11.1/polily/tui/i18n.py +38 -0
- polily-0.11.1/polily/tui/icons.py +53 -0
- polily-0.11.1/polily/tui/monitor_format.py +162 -0
- polily-0.11.1/polily/tui/screens/__init__.py +0 -0
- polily-0.11.1/polily/tui/screens/main.py +595 -0
- polily-0.11.1/polily/tui/service.py +976 -0
- polily-0.11.1/polily/tui/terminal_cleanup.py +86 -0
- polily-0.11.1/polily/tui/theme.py +73 -0
- polily-0.11.1/polily/tui/utils.py +68 -0
- polily-0.11.1/polily/tui/views/__init__.py +0 -0
- polily-0.11.1/polily/tui/views/_config_fatal_screen.py +89 -0
- polily-0.11.1/polily/tui/views/_trade_preview.py +70 -0
- polily-0.11.1/polily/tui/views/_wallet_overview.py +74 -0
- polily-0.11.1/polily/tui/views/archived_events.py +178 -0
- polily-0.11.1/polily/tui/views/changelog.py +161 -0
- polily-0.11.1/polily/tui/views/config.py +954 -0
- polily-0.11.1/polily/tui/views/config_modals.py +282 -0
- polily-0.11.1/polily/tui/views/config_weight_modal.py +446 -0
- polily-0.11.1/polily/tui/views/event_detail.py +408 -0
- polily-0.11.1/polily/tui/views/history.py +212 -0
- polily-0.11.1/polily/tui/views/monitor_list.py +306 -0
- polily-0.11.1/polily/tui/views/monitor_modals.py +84 -0
- polily-0.11.1/polily/tui/views/paper_status.py +367 -0
- polily-0.11.1/polily/tui/views/scan_log.py +778 -0
- polily-0.11.1/polily/tui/views/scan_modals.py +84 -0
- polily-0.11.1/polily/tui/views/score_result.py +192 -0
- polily-0.11.1/polily/tui/views/trade_dialog.py +778 -0
- polily-0.11.1/polily/tui/views/wallet.py +334 -0
- polily-0.11.1/polily/tui/views/wallet_modals.py +409 -0
- polily-0.11.1/polily/tui/widgets/__init__.py +0 -0
- polily-0.11.1/polily/tui/widgets/amount_input.py +136 -0
- polily-0.11.1/polily/tui/widgets/buy_sell_action_row.py +114 -0
- polily-0.11.1/polily/tui/widgets/cards.py +45 -0
- polily-0.11.1/polily/tui/widgets/confirm_cancel_bar.py +71 -0
- polily-0.11.1/polily/tui/widgets/empty_state.py +16 -0
- polily-0.11.1/polily/tui/widgets/field_row.py +82 -0
- polily-0.11.1/polily/tui/widgets/kv_row.py +57 -0
- polily-0.11.1/polily/tui/widgets/loading_state.py +20 -0
- polily-0.11.1/polily/tui/widgets/polily_card.py +53 -0
- polily-0.11.1/polily/tui/widgets/polily_zone.py +54 -0
- polily-0.11.1/polily/tui/widgets/quick_amount_row.py +97 -0
- polily-0.11.1/polily/tui/widgets/section_header.py +18 -0
- polily-0.11.1/polily/tui/widgets/sidebar.py +187 -0
- polily-0.11.1/polily/tui/widgets/status_badge.py +31 -0
- polily-0.11.1/polily/url_parser.py +29 -0
- polily-0.11.1/polily/utils.py +56 -0
- polily-0.11.1/pyproject.toml +105 -0
- polily-0.11.1/scripts/audit_config_usage.py +234 -0
- polily-0.11.1/scripts/check_changelog.py +163 -0
- polily-0.11.1/scripts/generate_snapshots.py +749 -0
- polily-0.11.1/scripts/validate_db.py +664 -0
- polily-0.11.1/tests/__init__.py +0 -0
- polily-0.11.1/tests/agents/__init__.py +0 -0
- polily-0.11.1/tests/agents/test_narrative_writer_feedback_log.py +125 -0
- polily-0.11.1/tests/conftest.py +205 -0
- polily-0.11.1/tests/test_agent_debug_log_path.py +50 -0
- polily-0.11.1/tests/test_agent_subprocess_env.py +123 -0
- polily-0.11.1/tests/test_agents_base.py +359 -0
- polily-0.11.1/tests/test_agents_narrative.py +179 -0
- polily-0.11.1/tests/test_analysis_store.py +94 -0
- polily-0.11.1/tests/test_analyze_event_lifecycle.py +174 -0
- polily-0.11.1/tests/test_analyze_event_noactive_error.py +78 -0
- polily-0.11.1/tests/test_analyze_event_single_active.py +105 -0
- polily-0.11.1/tests/test_api.py +421 -0
- polily-0.11.1/tests/test_api_trades.py +71 -0
- polily-0.11.1/tests/test_archived_events_service.py +91 -0
- polily-0.11.1/tests/test_archived_events_view.py +244 -0
- polily-0.11.1/tests/test_archived_events_view_v080.py +110 -0
- polily-0.11.1/tests/test_audit_script.py +137 -0
- polily-0.11.1/tests/test_auto_monitor_toggle.py +70 -0
- polily-0.11.1/tests/test_binary_structure_panel.py +162 -0
- polily-0.11.1/tests/test_bus_publishers.py +319 -0
- polily-0.11.1/tests/test_cancel_running_scan.py +96 -0
- polily-0.11.1/tests/test_changelog_view.py +394 -0
- polily-0.11.1/tests/test_check_changelog_script.py +186 -0
- polily-0.11.1/tests/test_cli.py +263 -0
- polily-0.11.1/tests/test_cli_doctor.py +114 -0
- polily-0.11.1/tests/test_cli_path_flags.py +116 -0
- polily-0.11.1/tests/test_clob.py +160 -0
- polily-0.11.1/tests/test_close_event.py +58 -0
- polily-0.11.1/tests/test_commentary.py +282 -0
- polily-0.11.1/tests/test_config.py +56 -0
- polily-0.11.1/tests/test_config_cli_escape.py +65 -0
- polily-0.11.1/tests/test_config_cli_reset.py +102 -0
- polily-0.11.1/tests/test_config_docs_coverage.py +58 -0
- polily-0.11.1/tests/test_config_docs_loader.py +175 -0
- polily-0.11.1/tests/test_config_field_consumption.py +209 -0
- polily-0.11.1/tests/test_config_field_resolution.py +186 -0
- polily-0.11.1/tests/test_config_field_resolution_direct.py +114 -0
- polily-0.11.1/tests/test_config_loader.py +172 -0
- polily-0.11.1/tests/test_config_loader_transaction.py +99 -0
- polily-0.11.1/tests/test_config_save_batch.py +195 -0
- polily-0.11.1/tests/test_config_store.py +713 -0
- polily-0.11.1/tests/test_config_store_unsupported_types.py +68 -0
- polily-0.11.1/tests/test_config_yaml_atomic.py +101 -0
- polily-0.11.1/tests/test_config_yaml_generation.py +70 -0
- polily-0.11.1/tests/test_confirm_unmonitor_modal.py +82 -0
- polily-0.11.1/tests/test_css_tokens_exist.py +20 -0
- polily-0.11.1/tests/test_css_utility_classes_extended.py +33 -0
- polily-0.11.1/tests/test_daemon_dispatcher_wiring.py +95 -0
- polily-0.11.1/tests/test_daemon_orphan_cleanup.py +45 -0
- polily-0.11.1/tests/test_daemon_plist_migration.py +270 -0
- polily-0.11.1/tests/test_daemon_plist_v0_11_0.py +133 -0
- polily-0.11.1/tests/test_daemon_scheduler_banner.py +96 -0
- polily-0.11.1/tests/test_daemon_shutdown_marker.py +168 -0
- polily-0.11.1/tests/test_daemon_working_dir.py +55 -0
- polily-0.11.1/tests/test_db.py +152 -0
- polily-0.11.1/tests/test_db_schema.py +60 -0
- polily-0.11.1/tests/test_db_seed.py +161 -0
- polily-0.11.1/tests/test_db_wal_race.py +112 -0
- polily-0.11.1/tests/test_default_db_path_uses_paths.py +39 -0
- polily-0.11.1/tests/test_detail_views_open_link.py +160 -0
- polily-0.11.1/tests/test_dust_filter.py +146 -0
- polily-0.11.1/tests/test_enrichment_modules.py +53 -0
- polily-0.11.1/tests/test_event_bus.py +56 -0
- polily-0.11.1/tests/test_event_detail_market_zone.py +78 -0
- polily-0.11.1/tests/test_event_detail_toggle_monitor.py +180 -0
- polily-0.11.1/tests/test_event_detail_trade_guard.py +114 -0
- polily-0.11.1/tests/test_event_detail_view_v080.py +169 -0
- polily-0.11.1/tests/test_event_header.py +101 -0
- polily-0.11.1/tests/test_event_kpi.py +96 -0
- polily-0.11.1/tests/test_event_metrics.py +118 -0
- polily-0.11.1/tests/test_event_scoring.py +211 -0
- polily-0.11.1/tests/test_event_store.py +187 -0
- polily-0.11.1/tests/test_fees.py +89 -0
- polily-0.11.1/tests/test_fetch_trades.py +128 -0
- polily-0.11.1/tests/test_first_launch_migration.py +242 -0
- polily-0.11.1/tests/test_global_bindings.py +12 -0
- polily-0.11.1/tests/test_history_view.py +148 -0
- polily-0.11.1/tests/test_history_view_v080.py +100 -0
- polily-0.11.1/tests/test_icon_glossary.py +23 -0
- polily-0.11.1/tests/test_intelligence_layer.py +140 -0
- polily-0.11.1/tests/test_launchctl_query.py +94 -0
- polily-0.11.1/tests/test_lifecycle.py +217 -0
- polily-0.11.1/tests/test_main_screen_v080.py +251 -0
- polily-0.11.1/tests/test_match.py +53 -0
- polily-0.11.1/tests/test_migration_visibility.py +310 -0
- polily-0.11.1/tests/test_mispricing.py +380 -0
- polily-0.11.1/tests/test_models.py +239 -0
- polily-0.11.1/tests/test_monitor_format.py +272 -0
- polily-0.11.1/tests/test_monitor_list_next_check_source.py +65 -0
- polily-0.11.1/tests/test_monitor_list_service.py +161 -0
- polily-0.11.1/tests/test_monitor_list_view.py +328 -0
- polily-0.11.1/tests/test_monitor_list_view_v080.py +137 -0
- polily-0.11.1/tests/test_monitor_modals_v080.py +68 -0
- polily-0.11.1/tests/test_monitor_store.py +58 -0
- polily-0.11.1/tests/test_monitor_toggle_supersede.py +72 -0
- polily-0.11.1/tests/test_movement.py +159 -0
- polily-0.11.1/tests/test_movement_config.py +10 -0
- polily-0.11.1/tests/test_movement_scorer.py +37 -0
- polily-0.11.1/tests/test_movement_signals.py +179 -0
- polily-0.11.1/tests/test_movement_sparkline.py +56 -0
- polily-0.11.1/tests/test_movement_store.py +145 -0
- polily-0.11.1/tests/test_movement_trigger.py +120 -0
- polily-0.11.1/tests/test_narrator_registry.py +59 -0
- polily-0.11.1/tests/test_negrisk_scoring.py +67 -0
- polily-0.11.1/tests/test_once_per_tick.py +357 -0
- polily-0.11.1/tests/test_orderbook.py +101 -0
- polily-0.11.1/tests/test_paper_status_shim.py +186 -0
- polily-0.11.1/tests/test_paper_status_view_v080.py +219 -0
- polily-0.11.1/tests/test_paths.py +137 -0
- polily-0.11.1/tests/test_paths_linux_xdg.py +43 -0
- polily-0.11.1/tests/test_phase_7_smoke.py +87 -0
- polily-0.11.1/tests/test_pipeline.py +1 -0
- polily-0.11.1/tests/test_pnl.py +53 -0
- polily-0.11.1/tests/test_polily_theme.py +37 -0
- polily-0.11.1/tests/test_poll_dispatcher.py +201 -0
- polily-0.11.1/tests/test_poll_job.py +660 -0
- polily-0.11.1/tests/test_poll_job_log_path.py +61 -0
- polily-0.11.1/tests/test_poll_log_hygiene.py +61 -0
- polily-0.11.1/tests/test_poll_log_path.py +53 -0
- polily-0.11.1/tests/test_poll_resolution.py +631 -0
- polily-0.11.1/tests/test_position_manager.py +270 -0
- polily-0.11.1/tests/test_price_feeds.py +167 -0
- polily-0.11.1/tests/test_public_api_v090.py +34 -0
- polily-0.11.1/tests/test_r_refresh_integration.py +300 -0
- polily-0.11.1/tests/test_realized_history.py +185 -0
- polily-0.11.1/tests/test_realtime_scores.py +77 -0
- polily-0.11.1/tests/test_reporting.py +119 -0
- polily-0.11.1/tests/test_resolution_handler.py +341 -0
- polily-0.11.1/tests/test_scan_log.py +98 -0
- polily-0.11.1/tests/test_scan_log_lifecycle.py +223 -0
- polily-0.11.1/tests/test_scan_log_timezone.py +441 -0
- polily-0.11.1/tests/test_scan_log_v070_migration.py +165 -0
- polily-0.11.1/tests/test_scan_log_view_v080.py +280 -0
- polily-0.11.1/tests/test_scan_log_view_zones.py +52 -0
- polily-0.11.1/tests/test_scan_modals_v080.py +66 -0
- polily-0.11.1/tests/test_scheduler_daemon.py +106 -0
- polily-0.11.1/tests/test_scheduler_plist_heal.py +317 -0
- polily-0.11.1/tests/test_schemas_v2.py +250 -0
- polily-0.11.1/tests/test_score_refresh.py +397 -0
- polily-0.11.1/tests/test_score_result_view_v080.py +141 -0
- polily-0.11.1/tests/test_scoring.py +158 -0
- polily-0.11.1/tests/test_scoring_v2.py +214 -0
- polily-0.11.1/tests/test_service.py +293 -0
- polily-0.11.1/tests/test_service_add_event.py +66 -0
- polily-0.11.1/tests/test_service_emits_events.py +72 -0
- polily-0.11.1/tests/test_service_monitor.py +84 -0
- polily-0.11.1/tests/test_service_monitor_count.py +61 -0
- polily-0.11.1/tests/test_service_movement.py +53 -0
- polily-0.11.1/tests/test_service_wallet_wiring.py +112 -0
- polily-0.11.1/tests/test_single_event.py +91 -0
- polily-0.11.1/tests/test_sub_market_table.py +70 -0
- polily-0.11.1/tests/test_tag_classifier.py +40 -0
- polily-0.11.1/tests/test_territory_a_allowlist.py +103 -0
- polily-0.11.1/tests/test_three_scores.py +89 -0
- polily-0.11.1/tests/test_toggle_monitor_guard.py +105 -0
- polily-0.11.1/tests/test_trade_dialog_v080.py +206 -0
- polily-0.11.1/tests/test_trade_dialog_widget.py +263 -0
- polily-0.11.1/tests/test_trade_engine.py +298 -0
- polily-0.11.1/tests/test_trade_engine_monitor_guard.py +105 -0
- polily-0.11.1/tests/test_trade_preview.py +135 -0
- polily-0.11.1/tests/test_tui.py +270 -0
- polily-0.11.1/tests/test_tui_utils.py +47 -0
- polily-0.11.1/tests/test_two_pass.py +155 -0
- polily-0.11.1/tests/test_type_scoring.py +88 -0
- polily-0.11.1/tests/test_ui_layout_regression.py +189 -0
- polily-0.11.1/tests/test_update_next_check_at_removal.py +30 -0
- polily-0.11.1/tests/test_url_parser.py +37 -0
- polily-0.11.1/tests/test_version.py +137 -0
- polily-0.11.1/tests/test_wallet_config.py +24 -0
- polily-0.11.1/tests/test_wallet_e2e.py +211 -0
- polily-0.11.1/tests/test_wallet_modals_v080.py +104 -0
- polily-0.11.1/tests/test_wallet_overview.py +122 -0
- polily-0.11.1/tests/test_wallet_reset.py +250 -0
- polily-0.11.1/tests/test_wallet_schema.py +169 -0
- polily-0.11.1/tests/test_wallet_service.py +252 -0
- polily-0.11.1/tests/test_wallet_view.py +563 -0
- polily-0.11.1/tests/test_wallet_view_v080.py +202 -0
- polily-0.11.1/tests/test_watch_poller_jobs.py +5 -0
- polily-0.11.1/tests/test_watch_scheduler.py +42 -0
- polily-0.11.1/tests/test_widget_amount_input.py +133 -0
- polily-0.11.1/tests/test_widget_buy_sell_action_row.py +133 -0
- polily-0.11.1/tests/test_widget_confirm_cancel_bar.py +76 -0
- polily-0.11.1/tests/test_widget_field_row.py +123 -0
- polily-0.11.1/tests/test_widget_kv_row.py +24 -0
- polily-0.11.1/tests/test_widget_polily_card.py +53 -0
- polily-0.11.1/tests/test_widget_polily_zone.py +67 -0
- polily-0.11.1/tests/test_widget_quick_amount_row.py +86 -0
- polily-0.11.1/tests/test_widget_status_badge.py +31 -0
- polily-0.11.1/tests/test_widgets_state_atoms.py +37 -0
- polily-0.11.1/tests/test_yaml_path_v0_11_0.py +72 -0
- polily-0.11.1/tests/tui/__init__.py +0 -0
- polily-0.11.1/tests/tui/test_config_edit_modal.py +646 -0
- polily-0.11.1/tests/tui/test_config_view.py +1245 -0
- polily-0.11.1/tests/tui/test_config_weight_modal.py +483 -0
- polily-0.11.1/tests/tui/test_event_detail_in_place.py +145 -0
- polily-0.11.1/tests/tui/test_fatal_config_screen.py +101 -0
- polily-0.11.1/tests/tui/test_sidebar_config_entry.py +18 -0
- polily-0.11.1/tests/tui/test_terminal_cleanup.py +119 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# .envrc.example — Copy to .envrc and run `direnv allow` to activate.
|
|
2
|
+
# Provides per-developer overrides so polily uses repo-local data dir
|
|
3
|
+
# ($REPO_ROOT/data) instead of the v0.11.0 default
|
|
4
|
+
# ~/Library/Application Support/polily.
|
|
5
|
+
|
|
6
|
+
# Setup:
|
|
7
|
+
# 1. Install direnv: `brew install direnv` (or your distro's package manager)
|
|
8
|
+
# 2. Hook into your shell: https://direnv.net/docs/hook.html
|
|
9
|
+
# 3. Copy this file: `cp .envrc.example .envrc`
|
|
10
|
+
# 4. Activate: `direnv allow`
|
|
11
|
+
#
|
|
12
|
+
# Now `cd $REPO_ROOT` auto-loads these env vars; `cd` away auto-unloads.
|
|
13
|
+
|
|
14
|
+
export POLILY_DATA_DIR="$(git rev-parse --show-toplevel)/data"
|
|
15
|
+
export POLILY_LOG_DIR="$POLILY_DATA_DIR/logs"
|
|
16
|
+
|
|
17
|
+
# Use a distinct launchd label so dev daemon and prod daemon coexist.
|
|
18
|
+
# Without this, `polily scheduler restart` would clobber the user's
|
|
19
|
+
# real ~/Library/LaunchAgents/com.polily.scheduler.plist with one
|
|
20
|
+
# pointing at the dev clone's data dir.
|
|
21
|
+
export POLILY_LAUNCHD_LABEL="com.polily.scheduler.dev"
|
|
22
|
+
|
|
23
|
+
# Optional shell alias fallback if you don't want direnv at all:
|
|
24
|
+
# alias polily-dev='POLILY_DATA_DIR=$(pwd)/data POLILY_LAUNCHD_LABEL=com.polily.scheduler.dev .venv/bin/polily'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug Report
|
|
3
|
+
about: Report a bug
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Describe the bug**
|
|
8
|
+
A clear description of what the bug is.
|
|
9
|
+
|
|
10
|
+
**To Reproduce**
|
|
11
|
+
Steps to reproduce:
|
|
12
|
+
1. Run `polily ...`
|
|
13
|
+
2. Press '...'
|
|
14
|
+
3. See error
|
|
15
|
+
|
|
16
|
+
**Expected behavior**
|
|
17
|
+
What you expected to happen.
|
|
18
|
+
|
|
19
|
+
**Environment**
|
|
20
|
+
- OS: [e.g. macOS 14, Ubuntu 22.04]
|
|
21
|
+
- Python version: [e.g. 3.11.8]
|
|
22
|
+
- Polily version: [e.g. 0.1.0]
|
|
23
|
+
- Claude CLI installed: [yes/no]
|
|
24
|
+
|
|
25
|
+
**Logs / Screenshots**
|
|
26
|
+
If applicable, add logs or screenshots.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature Request
|
|
3
|
+
about: Suggest a new feature
|
|
4
|
+
labels: enhancement
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Problem**
|
|
8
|
+
What problem does this solve?
|
|
9
|
+
|
|
10
|
+
**Proposed Solution**
|
|
11
|
+
How should it work?
|
|
12
|
+
|
|
13
|
+
**Alternatives Considered**
|
|
14
|
+
Any other approaches you've thought about.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
Brief description of changes.
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] Tests pass (`pytest tests/ -q`)
|
|
8
|
+
- [ ] Lint passes (`ruff check polily/ tests/`)
|
|
9
|
+
- [ ] Type check passes (`pyright polily/`)
|
|
10
|
+
- [ ] New features have tests
|
|
11
|
+
- [ ] Code comments in English
|
|
12
|
+
- [ ] No definitive trade signals added (project red line)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master, dev]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
# hatch-vcs needs full history + tags to derive version from
|
|
20
|
+
# the nearest git tag. fetch-depth: 0 = full history.
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
|
|
28
|
+
- name: Install system dependencies
|
|
29
|
+
run: sudo apt-get update && sudo apt-get install -y build-essential
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: pip install -e ".[dev]"
|
|
33
|
+
|
|
34
|
+
- name: Lint
|
|
35
|
+
run: ruff check polily/ tests/
|
|
36
|
+
|
|
37
|
+
- name: Type check
|
|
38
|
+
run: pyright polily/ || true # tracked as follow-up, not blocking CI yet
|
|
39
|
+
|
|
40
|
+
- name: Test
|
|
41
|
+
run: pytest tests/ -q --tb=short
|
|
42
|
+
|
|
43
|
+
# Release-PR discipline gate. Only runs on dev → master PRs; feature PRs
|
|
44
|
+
# into dev are expected to be [Unreleased]-topped (CHANGELOG rename
|
|
45
|
+
# happens in the release PR, not the feature PR).
|
|
46
|
+
changelog-check:
|
|
47
|
+
if: github.event_name == 'pull_request' && github.base_ref == 'master'
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
- name: Set up Python 3.11
|
|
52
|
+
uses: actions/setup-python@v5
|
|
53
|
+
with:
|
|
54
|
+
python-version: "3.11"
|
|
55
|
+
- name: Validate CHANGELOG release discipline
|
|
56
|
+
run: python scripts/check_changelog.py CHANGELOG.md
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Why this workflow exists
|
|
4
|
+
# ------------------------
|
|
5
|
+
# Automatically publish polily to PyPI on every stable release. Triggered
|
|
6
|
+
# by the `released` event (when `gh release create vX.Y.Z` flips the
|
|
7
|
+
# release out of draft into "published"); skipped for prereleases.
|
|
8
|
+
#
|
|
9
|
+
# Authentication is via PyPI Trusted Publishing (OIDC) — no long-lived
|
|
10
|
+
# API token. PyPI verifies the OIDC token from GitHub identifies the
|
|
11
|
+
# expected repo + workflow + environment, and issues a short-lived
|
|
12
|
+
# upload token automatically. See https://docs.pypi.org/trusted-publishers/
|
|
13
|
+
#
|
|
14
|
+
# Pre-flight requirement: the project owner registers a "pending
|
|
15
|
+
# publisher" on PyPI (https://pypi.org/manage/account/publishing/) with
|
|
16
|
+
# matching repo / workflow / environment names BEFORE the first release.
|
|
17
|
+
#
|
|
18
|
+
# Note on action pinning: actions are pinned to major version (@v4, @v5)
|
|
19
|
+
# rather than commit SHA. Trade-off: floating-tag (lower friction, auto
|
|
20
|
+
# security patches in v4.x) vs. SHA-pin (supply-chain hardening). Major-
|
|
21
|
+
# pin matches the existing ci.yml convention. Future hardening: pin to
|
|
22
|
+
# SHA via dependabot when GH supply-chain risk profile justifies it.
|
|
23
|
+
|
|
24
|
+
on:
|
|
25
|
+
release:
|
|
26
|
+
types: [released]
|
|
27
|
+
|
|
28
|
+
# Single in-flight publish. Defends against the rare case where a release
|
|
29
|
+
# is flipped draft→published twice (e.g., GH UI hiccup) and two workflow
|
|
30
|
+
# runs race the actual upload. cancel-in-progress: false because PyPI
|
|
31
|
+
# rejects double-uploads anyway — better to let the first one finish
|
|
32
|
+
# than half-cancel and confuse logs.
|
|
33
|
+
concurrency:
|
|
34
|
+
group: pypi-publish
|
|
35
|
+
cancel-in-progress: false
|
|
36
|
+
|
|
37
|
+
permissions:
|
|
38
|
+
id-token: write # required for PyPI Trusted Publishing OIDC + sigstore attestations
|
|
39
|
+
contents: read
|
|
40
|
+
|
|
41
|
+
jobs:
|
|
42
|
+
publish:
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
timeout-minutes: 10 # OIDC handshake + build + upload finishes well under this
|
|
45
|
+
environment: pypi-publish # matches PyPI pending publisher registration
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
with:
|
|
49
|
+
# hatch-vcs derives version from the nearest git tag.
|
|
50
|
+
# fetch-depth: 0 = full history, required for clean version.
|
|
51
|
+
fetch-depth: 0
|
|
52
|
+
|
|
53
|
+
- name: Set up Python 3.11
|
|
54
|
+
uses: actions/setup-python@v5
|
|
55
|
+
with:
|
|
56
|
+
python-version: "3.11"
|
|
57
|
+
|
|
58
|
+
- name: Install build tooling
|
|
59
|
+
run: python -m pip install --upgrade pip build
|
|
60
|
+
|
|
61
|
+
- name: Build wheel + sdist
|
|
62
|
+
run: python -m build
|
|
63
|
+
|
|
64
|
+
- name: Verify built version matches release tag
|
|
65
|
+
# Defense against tag/version drift: if hatch-vcs produces something
|
|
66
|
+
# other than the clean release tag, fail fast before publishing.
|
|
67
|
+
# Example: tag v0.11.1 must produce polily-0.11.1-py3-none-any.whl.
|
|
68
|
+
#
|
|
69
|
+
# Extraction strategy: parse wheel filename via sed regex anchored
|
|
70
|
+
# on the canonical "-py3-none-any.whl" suffix. This handles PEP 440
|
|
71
|
+
# local versions (e.g. "0.11.1+local.foo") that the prior
|
|
72
|
+
# `re.match(r'polily-([^-]+)-')` would have truncated incorrectly.
|
|
73
|
+
run: |
|
|
74
|
+
tag="${GITHUB_REF#refs/tags/v}"
|
|
75
|
+
# ls -1 to one-per-line; head -1 to handle the (rare) multi-wheel case.
|
|
76
|
+
wheel=$(ls -1 dist/polily-*-py3-none-any.whl | head -1)
|
|
77
|
+
built=$(echo "$wheel" | sed -E 's|dist/polily-(.+)-py3-none-any\.whl|\1|')
|
|
78
|
+
echo "Release tag (without 'v' prefix): $tag"
|
|
79
|
+
echo "Built wheel version: $built"
|
|
80
|
+
echo "Built wheel filename: $wheel"
|
|
81
|
+
if [ "$tag" != "$built" ]; then
|
|
82
|
+
echo "::error::Version mismatch — tag=$tag built=$built. Aborting publish."
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
- name: Publish to PyPI via Trusted Publishing
|
|
87
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
88
|
+
with:
|
|
89
|
+
# Sigstore attestations: cryptographic proof that this artifact was
|
|
90
|
+
# built by THIS workflow run on THIS repo. PyPI auto-attaches the
|
|
91
|
+
# bundle to the release. Free with OIDC, and impossible to retrofit
|
|
92
|
+
# to existing releases later — turn it on at first publish.
|
|
93
|
+
# See https://docs.pypi.org/attestations/
|
|
94
|
+
attestations: true
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
name: Sync master → dev
|
|
2
|
+
|
|
3
|
+
# Why this workflow exists
|
|
4
|
+
# ------------------------
|
|
5
|
+
# When a release PR (dev → master) is merged via "Create a merge commit"
|
|
6
|
+
# (per CLAUDE.md release discipline, NOT squash), the merge commit is
|
|
7
|
+
# added to master but NOT to dev. Dev then drifts behind master by one
|
|
8
|
+
# commit. The next release PR flags "branch out-of-date with base"
|
|
9
|
+
# until a manual sync PR is opened.
|
|
10
|
+
#
|
|
11
|
+
# This workflow runs that sync automatically: on every push to master,
|
|
12
|
+
# open a sync/master-into-dev-<timestamp> branch, merge master, open
|
|
13
|
+
# a PR to dev, enable auto-merge. Once CI on the sync PR goes green,
|
|
14
|
+
# GitHub merges it with a merge-commit, restoring dev as a clean
|
|
15
|
+
# descendant of master.
|
|
16
|
+
#
|
|
17
|
+
# Respects all branch protection rules — the bot goes through a PR
|
|
18
|
+
# like any other contributor, and auto-merge only kicks in after the
|
|
19
|
+
# protection rules (CI green, etc.) are satisfied.
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
push:
|
|
23
|
+
branches: [master]
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: write
|
|
27
|
+
pull-requests: write
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
sync:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
with:
|
|
35
|
+
fetch-depth: 0
|
|
36
|
+
# SYNC_PAT (fine-grained PAT, Contents+PRs on polily) so
|
|
37
|
+
# the later `git push` and `gh pr create/merge` authenticate
|
|
38
|
+
# as a real user — triggers CI on the sync PR. GITHUB_TOKEN
|
|
39
|
+
# would create the PR as github-actions[bot], which GitHub
|
|
40
|
+
# intentionally does NOT trigger workflows for (prevents
|
|
41
|
+
# recursive workflow invocation).
|
|
42
|
+
token: ${{ secrets.SYNC_PAT }}
|
|
43
|
+
|
|
44
|
+
- name: Check whether dev is already up-to-date with master
|
|
45
|
+
id: check
|
|
46
|
+
run: |
|
|
47
|
+
git fetch origin dev master
|
|
48
|
+
BEHIND=$(git rev-list --count origin/dev..origin/master)
|
|
49
|
+
echo "Dev is behind master by $BEHIND commit(s)."
|
|
50
|
+
echo "behind=$BEHIND" >> "$GITHUB_OUTPUT"
|
|
51
|
+
|
|
52
|
+
- name: Skip — dev already contains master
|
|
53
|
+
if: steps.check.outputs.behind == '0'
|
|
54
|
+
run: echo "No sync needed; dev is already a descendant of master."
|
|
55
|
+
|
|
56
|
+
- name: Create sync branch, merge master, open + auto-merge PR
|
|
57
|
+
if: steps.check.outputs.behind != '0'
|
|
58
|
+
env:
|
|
59
|
+
# See Task 3.2 rationale: SYNC_PAT so the created PR triggers CI.
|
|
60
|
+
GH_TOKEN: ${{ secrets.SYNC_PAT }}
|
|
61
|
+
run: |
|
|
62
|
+
set -euo pipefail
|
|
63
|
+
|
|
64
|
+
SYNC_BRANCH="sync/master-into-dev-$(date +%Y%m%d-%H%M%S)"
|
|
65
|
+
|
|
66
|
+
# Bot identity for the merge commit.
|
|
67
|
+
git config user.name "github-actions[bot]"
|
|
68
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
69
|
+
|
|
70
|
+
# Branch off current dev, then merge master in with --no-ff to
|
|
71
|
+
# guarantee a merge commit (preserves ancestry even if dev
|
|
72
|
+
# happens to be a strict ancestor of master).
|
|
73
|
+
git checkout -b "$SYNC_BRANCH" origin/dev
|
|
74
|
+
git merge --no-ff origin/master \
|
|
75
|
+
-m "Sync master → dev (auto after release)"
|
|
76
|
+
|
|
77
|
+
git push origin "$SYNC_BRANCH"
|
|
78
|
+
|
|
79
|
+
gh pr create \
|
|
80
|
+
--base dev \
|
|
81
|
+
--head "$SYNC_BRANCH" \
|
|
82
|
+
--title "sync: master → dev (auto)" \
|
|
83
|
+
--body "$(cat <<'BODY'
|
|
84
|
+
Auto-generated by the `Sync master → dev` workflow after a push
|
|
85
|
+
to master. Keeps dev a strict descendant of master so the next
|
|
86
|
+
release PR lands as a clean fast-forward.
|
|
87
|
+
|
|
88
|
+
No code changes — merge commit only. CI runs as a sanity check
|
|
89
|
+
before auto-merge.
|
|
90
|
+
BODY
|
|
91
|
+
)"
|
|
92
|
+
|
|
93
|
+
# Auto-merge with merge-commit strategy (matches CLAUDE.md
|
|
94
|
+
# release discipline; squash would defeat the whole purpose).
|
|
95
|
+
gh pr merge --merge --auto "$SYNC_BRANCH"
|
polily-0.11.1/.gitignore
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg
|
|
8
|
+
|
|
9
|
+
# Virtual environment
|
|
10
|
+
.venv/
|
|
11
|
+
|
|
12
|
+
# IDE
|
|
13
|
+
.vscode/
|
|
14
|
+
.idea/
|
|
15
|
+
*.swp
|
|
16
|
+
*.swo
|
|
17
|
+
.DS_Store
|
|
18
|
+
|
|
19
|
+
# Runtime data (all user-generated, never committed)
|
|
20
|
+
data/
|
|
21
|
+
|
|
22
|
+
# Auto-generated by polily from db on every startup; users should not
|
|
23
|
+
# edit or commit this file. Edit via TUI → ⚙ 配置.
|
|
24
|
+
/config.yaml
|
|
25
|
+
|
|
26
|
+
# Environment
|
|
27
|
+
.env*
|
|
28
|
+
# v0.11.0 — .envrc.example is a dev-workflow template that must be in git;
|
|
29
|
+
# .envrc itself stays ignored via .env* above
|
|
30
|
+
!.envrc.example
|
|
31
|
+
|
|
32
|
+
# Internal docs (not for public repo)
|
|
33
|
+
companion-note.md
|
|
34
|
+
docs/internal/
|
|
35
|
+
|
|
36
|
+
# Temp files
|
|
37
|
+
*.tmp
|
|
38
|
+
/tmp/
|
|
39
|
+
|
|
40
|
+
# Test/coverage
|
|
41
|
+
.pytest_cache/
|
|
42
|
+
.coverage
|
|
43
|
+
htmlcov/
|
|
44
|
+
|
|
45
|
+
# Claude Code
|
|
46
|
+
.claude/
|
|
47
|
+
*.db-shm
|
|
48
|
+
*.db-wal
|
|
49
|
+
docs/internal/
|
polily-0.11.1/AGENTS.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Instructions for Codex when working on this codebase.
|
|
4
|
+
|
|
5
|
+
## Product Identity
|
|
6
|
+
|
|
7
|
+
**Polily — A Polymarket Monitoring Agent That Actually Works**
|
|
8
|
+
|
|
9
|
+
Finds structure, surfaces risk, sizes friction, watches positions. The user pulls the trigger today. Autopilot is on the roadmap but not the current default — design new features so they remain compatible with both modes.
|
|
10
|
+
|
|
11
|
+
## Design Context
|
|
12
|
+
|
|
13
|
+
Not a user-profile spec — these are facts about how the product is used, useful when deciding trade-offs:
|
|
14
|
+
|
|
15
|
+
- **Small-account usage pattern.** $5 in friction cost is not a rounding error at this tier. Surface every fee / slippage component explicitly; never silently absorb costs into a "net" number.
|
|
16
|
+
- **Manual operation, not automation.** Users click buttons; sub-second latency isn't the pressure. But signals must be scannable — a user should decide "look closer vs. skip" in ≤5s.
|
|
17
|
+
- **Daily review cadence.** ≥5s refresh intervals are fine; no tick-level streaming UI needed. Heartbeat-driven refresh (see `polily/tui/screens/main.py::_bus_heartbeat`) is the canonical pattern.
|
|
18
|
+
- **URL-driven depth, not scan breadth.** Users bring their own events. The pipeline is deep due-diligence on one event at a time, not shallow breadth across thousands. If a feature requires iterating 8000+ markets, it's likely a misunderstanding of the product shape.
|
|
19
|
+
- **Due diligence, not signal generation.** Output is "here's what the numbers say, here's the risk, here's the friction" — conditional framing, never unconditional commands like "buy YES".
|
|
20
|
+
|
|
21
|
+
## Architecture (Event-First)
|
|
22
|
+
|
|
23
|
+
**Data model:** Events (parent) → Markets (children). All state in unified SQLite (`data/polily.db`). No scan archives, no JSON files. Events carry tier/score/monitor state; markets carry prices/orderbook.
|
|
24
|
+
|
|
25
|
+
**Entry flow:** **URL-driven, single-event.** User pastes a Polymarket event URL into the TUI. `polily.url_parser` extracts the event slug. `polily.scan.pipeline.fetch_and_score_event` fetches the event + child markets from Gamma API → applies hard filters → computes structure score (5-dim) → runs mispricing detection → optionally calls NarrativeWriter agent → assigns tier → persists to events/markets tables. **There is no batch scan over 8000+ markets** — that pattern was removed in v0.5.0.
|
|
26
|
+
|
|
27
|
+
**Poll architecture:** Single global poll job runs every **30 seconds** on a dedicated 1-thread executor. Fetches prices for all markets the user has added to monitoring. Movement detection runs inline per tick (magnitude + quality scoring). If significant movement detected, triggers AI analysis on the `ai` executor (5 threads).
|
|
28
|
+
|
|
29
|
+
**Daemon:** Dual-executor APScheduler daemon (`polily/daemon/scheduler.py`). Poll executor (1 thread) for price polling. AI executor (5 threads) for concurrent analysis jobs. Managed via `polily scheduler run/stop/restart/status`. Started via launchd in production.
|
|
30
|
+
|
|
31
|
+
**Why "monitoring agent" rather than research assistant / signal generator?**
|
|
32
|
+
Users don't want data dumps (research) or commands (signals). They want something that keeps watching on their behalf and surfaces what actually changed — price moves, structure shifts, position risk, end-dates coming up. Conditional advice ("if you're bullish, this may have edge") is OK. Definitive signals are not.
|
|
33
|
+
|
|
34
|
+
**Why Structure Score ≠ trade quality?**
|
|
35
|
+
Score measures tradability (spread, depth, objectivity, time, friction). Not profitability. Always communicate it that way to users.
|
|
36
|
+
|
|
37
|
+
**Why URL-driven instead of batch scanning?**
|
|
38
|
+
Users have domain edge in specific events — they already know what they want to look at. Batch scanning produced noise and rate-limit issues; deep single-event analysis is what actually informs decisions.
|
|
39
|
+
|
|
40
|
+
**Why Binance (ccxt) not CoinGecko?**
|
|
41
|
+
CoinGecko free tier rate-limits aggressively (429 errors). Binance via ccxt has 6000 weight/min, no API key needed, first-party exchange data.
|
|
42
|
+
|
|
43
|
+
**Why AI agents use `Codex -p` CLI?**
|
|
44
|
+
Included in Codex subscription, no per-token cost. Response parsed from `result` field (not `structured_output`). JSON extracted from markdown code blocks via regex fallback.
|
|
45
|
+
|
|
46
|
+
## Coding Conventions
|
|
47
|
+
|
|
48
|
+
- Python 3.11+, type hints everywhere
|
|
49
|
+
- Pydantic for data models and config
|
|
50
|
+
- `async` for I/O (HTTP, ccxt), `sync` for pipeline orchestration
|
|
51
|
+
- TDD: write test first (red), implement (green), refactor
|
|
52
|
+
- Chinese for all user-facing output (terminal, narratives, prompts)
|
|
53
|
+
- English for code, variable names, comments
|
|
54
|
+
- No unnecessary abstractions — three similar lines beats a premature abstraction
|
|
55
|
+
- Config-driven: thresholds, weights, behavior live in `polily.db` `config` table (canonical since v0.10.0). TUI ⚙ 配置 edits live values via `save_knob`; `config.yaml` on disk is a read-only auto-generated snapshot regenerated after each save. Do NOT edit `config.yaml` and expect it to load — it's overwritten on every TUI launch / save / `polily config reset`. The CLI escape hatch is `polily config reset --all` or `polily config reset <key>`. Pre-v0.10.0 `config.yaml` files are auto-migrated to db on first boot (invalid yaml is preserved as `config.yaml.bak`).
|
|
56
|
+
- Single AI agent (NarrativeWriter). No silent fallback — CLI failures raise and land as `failed` scan_logs rows.
|
|
57
|
+
- **All AI calls go through `Codex -p` CLI**, never the `anthropic` SDK. Reason: uses the user's Codex subscription instead of per-token billing. `BaseAgent` at `polily/agents/base.py` is the only place that shells out — don't add a second integration path.
|
|
58
|
+
|
|
59
|
+
## Key Files
|
|
60
|
+
|
|
61
|
+
| File | Role |
|
|
62
|
+
|------|------|
|
|
63
|
+
| `polily/__init__.py` | Public API surface (v0.6.0) |
|
|
64
|
+
| `polily/cli.py` | CLI: TUI launch + `scheduler` subcommands + `reset` (`--wallet-only`) |
|
|
65
|
+
| `polily/url_parser.py` | Polymarket URL → event slug |
|
|
66
|
+
| `polily/scan_log.py` | Scan log entries (per-event run history) |
|
|
67
|
+
| `polily/analysis_store.py` | NarrativeWriter analysis versions per event |
|
|
68
|
+
| `polily/core/db.py` | Unified SQLite database (PolilyDB) |
|
|
69
|
+
| `polily/core/config.py` | All Pydantic config models |
|
|
70
|
+
| `polily/core/models.py` | Market, BookLevel, Trade models |
|
|
71
|
+
| `polily/core/event_store.py` | EventRow, MarketRow, upsert/query |
|
|
72
|
+
| `polily/core/lifecycle.py` | Market / Event lifecycle state derivation (v0.8.5). `MarketState` (TRADING / PENDING_SETTLEMENT / SETTLING / SETTLED) + `EventState` (ACTIVE / AWAITING_FULL_SETTLEMENT / RESOLVED) derived from `closed` + `end_date` + `resolved_outcome` — no DB column, derive-on-read |
|
|
73
|
+
| `polily/core/monitor_store.py` | Event monitor state (v0.7.0: user-intent flag only — `auto_monitor` / `price_snapshot` / `notes`) |
|
|
74
|
+
| `polily/core/wallet.py` | WalletService — cash + ledger + atomicity contract (commit=False) |
|
|
75
|
+
| `polily/core/positions.py` | PositionManager — aggregated (market_id, side) positions, weighted-avg cost |
|
|
76
|
+
| `polily/core/trade_engine.py` | TradeEngine — atomic buy/sell (wallet + position + fee in one BEGIN/COMMIT) |
|
|
77
|
+
| `polily/core/fees.py` | Polymarket category-based taker fee curve |
|
|
78
|
+
| `polily/core/wallet_reset.py` | Hard reset util (requires no concurrent writer — see docstring) |
|
|
79
|
+
| `polily/scan/pipeline.py` | **Single-event** orchestrator: fetch → filter → score → mispricing → AI → tier |
|
|
80
|
+
| `polily/scan/scoring.py` | Structure score (5-dimension) |
|
|
81
|
+
| `polily/scan/event_scoring.py` | Event-level aggregation + tier assignment |
|
|
82
|
+
| `polily/scan/filters.py` | Hard filters |
|
|
83
|
+
| `polily/scan/mispricing.py` | Crypto vol-implied mispricing detection |
|
|
84
|
+
| `polily/scan/commentary.py` | Per-dimension score commentary |
|
|
85
|
+
| `polily/scan/tag_classifier.py` | Market type / tag classification |
|
|
86
|
+
| `polily/monitor/scorer.py` | Movement scorer (magnitude + quality) |
|
|
87
|
+
| `polily/monitor/signals.py` | Movement signal computation |
|
|
88
|
+
| `polily/monitor/drift.py` | CUSUM drift detector |
|
|
89
|
+
| `polily/monitor/store.py` | Movement records storage |
|
|
90
|
+
| `polily/monitor/event_metrics.py` | Per-event movement metrics |
|
|
91
|
+
| `polily/daemon/scheduler.py` | APScheduler daemon: dual executor + launchd; wires scheduler into `_ctx` so `global_poll`'s Step 3.5 dispatcher can submit. Plist auto-heal in `ensure_daemon_running` (v0.9.0) |
|
|
92
|
+
| `polily/daemon/launchctl_query.py` | `launchctl list com.polily.scheduler` parser + `kill_daemon(sig)` helper (v0.9.0). Replaced `data/scheduler.pid` as source of truth for "is daemon alive" |
|
|
93
|
+
| `polily/daemon/poll_job.py` | Global poll job (30s): fetch prices → auto-resolution → score refresh → **Step 3.5 dispatcher (drain overdue `scan_logs` pending rows)** → intelligence layer |
|
|
94
|
+
| `polily/daemon/resolution.py` | ResolutionHandler — atomic per-market settle on Gamma outcomePrices |
|
|
95
|
+
| `polily/daemon/close_event.py` | Event archival / close handling |
|
|
96
|
+
| `polily/daemon/auto_monitor.py` | Auto-monitor toggle logic |
|
|
97
|
+
| `polily/daemon/score_refresh.py` | Periodic structure-score refresh |
|
|
98
|
+
| `polily/agents/narrator_registry.py` | In-process narrator cancel registry (scope: process-local; see docstring for cross-process limitation) |
|
|
99
|
+
| `polily/agents/base.py` | BaseAgent: Codex CLI invoke + retry + JSON parsing |
|
|
100
|
+
| `polily/agents/narrative_writer.py` | NarrativeWriter agent (decision advisor) |
|
|
101
|
+
| `polily/agents/schemas.py` | Pydantic schemas for agent I/O |
|
|
102
|
+
| `polily/agents/prompts/` | Markdown prompt files for agents |
|
|
103
|
+
| `polily/tui/app.py` | Textual TUI entry point |
|
|
104
|
+
| `polily/tui/screens/main.py` | Main screen: sidebar + content + worker (menu: tasks/monitor/paper/wallet/history/notifications); 5s `_bus_heartbeat` bridges daemon-side DB writes to TUI subscribers |
|
|
105
|
+
| `polily/tui/_dispatch.py` | `dispatch_to_ui(app, fn)` thread-hop helper + `@once_per_tick` coalescing decorator (React-style batching). Load-bearing for heartbeat-driven refresh — see v0.9.0 `call_later` signature fix |
|
|
106
|
+
| `polily/tui/service.py` | `PolilyService` — bridge between TUI views and backend; owns wallet/positions/trade_engine |
|
|
107
|
+
| `polily/tui/views/` | Per-pane views (scan_log, monitor_list, paper_status, event_detail, wallet, history, archived_events, changelog) |
|
|
108
|
+
| `polily/tui/views/changelog.py` | ChangelogView — renders CHANGELOG.md via Markdown widget; reads from repo root in dev or from packaged resource (see `pyproject.toml` `force-include`) in installed wheels |
|
|
109
|
+
| `polily/tui/views/trade_dialog.py` | Modal with Buy/Sell tabs — calls TradeEngine.execute_buy/sell |
|
|
110
|
+
| `polily/tui/views/wallet.py` | WalletView — balance + transactions ledger + topup/withdraw/reset |
|
|
111
|
+
| `polily/tui/views/wallet_modals.py` | TopupModal / WithdrawModal / WalletResetModal |
|
|
112
|
+
|
|
113
|
+
## Common Pitfalls
|
|
114
|
+
|
|
115
|
+
- **No batch scan.** If a feature seems to require iterating all 8000 markets, it's likely a misunderstanding — the pipeline is single-event by URL. Batch poll only covers markets the user has explicitly added to monitoring.
|
|
116
|
+
- NarrativeWriter agent uses `Codex -p --allowedTools Read,Bash,Grep,WebSearch,StructuredOutput` — agent autonomously reads DB, searches web, then outputs via StructuredOutput. Prompt rules in `polily/agents/prompts/narrative_writer.md`.
|
|
117
|
+
- `Codex -p --output-format json` (CLI v2.1+) returns a **JSON array**: `[{"type":"system",...}, {"type":"assistant",...}, {"type":"result","result":"..."}]`. Find the `{"type":"result"}` element (last in array), then parse `result` field.
|
|
118
|
+
- TUI exit uses `os._exit(0)` because `Codex -p` spawns Node.js subprocesses that survive normal shutdown.
|
|
119
|
+
- Textual workers: pass function reference (no parentheses), not coroutine. All UI updates from worker threads must use `call_from_thread`.
|
|
120
|
+
- Global poll runs every **30s** on a dedicated single-thread executor. Movement detection is inline (no separate job). AI analysis is triggered on the ai executor (5 threads).
|
|
121
|
+
- Daemon aliveness + PID lookup all go through `launchctl list com.polily.scheduler` (see `polily/daemon/launchctl_query.py`). The legacy `data/scheduler.pid` file was dropped in v0.9.0 — launchctl is the single source of truth. `SIGUSR1` still triggers job reload from DB.
|
|
122
|
+
- **CLOB /book API returns distorted books for negRisk markets** (GitHub Issue #180): returns the raw token book with bid=0.01 / ask=0.99, which does not reflect the real liquidity provided by complement matching. Use `/midpoint` for the true price and the difference between `/price?side=BUY` and `/price?side=SELL` for the true spread. `/book` depth data is unreliable for negRisk markets.
|
|
123
|
+
- **`paper_trades` no longer exists.** Dropped in v0.6.1 — `positions` + `wallet_transactions` are the only trade-state tables. On DB init, `PolilyDB._init_schema` executes `DROP TABLE IF EXISTS paper_trades` so old installs auto-clean. All writes go through `TradeEngine.execute_buy/sell`; history reads go through `PolilyService.get_realized_history` (SELL + RESOLVE ledger rows).
|
|
124
|
+
- **`wallet.credit(commit=False)` defers the cash write to the outer transaction.** `ResolutionHandler.resolve_market` wraps one BEGIN around the credit + position delete + ledger insert, so passing `commit=True` would split them and re-credit on retry. Any new "bulk close" code path must preserve this contract (see `polily/core/wallet.py:113-154`).
|
|
125
|
+
- **`reset_wallet` has no built-in writer lock.** The CLI path stops the scheduler daemon first; the TUI `WalletResetModal` sends SIGTERM + 1s grace before calling reset (on a worker thread so the event loop doesn't freeze). Any new caller MUST guarantee no concurrent writer — otherwise DELETE races with a mid-flight poll INSERT.
|
|
126
|
+
- **`cumulative_realized_pnl` on the wallet snapshot is derived**, not stored: `SUM(wallet_transactions.realized_pnl) WHERE realized_pnl IS NOT NULL`. Goes to 0 automatically after reset (wallet_transactions is cleared). Don't try to mirror it into a stored column.
|
|
127
|
+
|
|
128
|
+
## Release Process
|
|
129
|
+
|
|
130
|
+
Polily is open source; releases need to follow a standard. Always use `gh release create` (which creates the tag and release page together); don't run `git tag` standalone.
|
|
131
|
+
|
|
132
|
+
| Stage | Branch | Command | GitHub label |
|
|
133
|
+
|-------|--------|---------|--------------|
|
|
134
|
+
| Early access | dev | `gh release create v0.6.0-beta.1 --target dev --prerelease` | Pre-release |
|
|
135
|
+
| Stable | master | `gh release create v0.6.0 --target master` | Latest |
|
|
136
|
+
|
|
137
|
+
**Cadence:**
|
|
138
|
+
1. Feature-complete on dev → ship `vX.Y.0-beta.N` (prerelease)
|
|
139
|
+
2. Collect feedback, fix bugs → ship `vX.Y.0-beta.N+1` (prerelease)
|
|
140
|
+
3. Once stable, merge to master → ship `vX.Y.0` (latest)
|
|
141
|
+
|
|
142
|
+
**CHANGELOG.md workflow** (Keep a Changelog + SemVer):
|
|
143
|
+
- Update `[Unreleased]` as part of every PR that has user-visible change. Don't let it drift — it's the source of truth for what ships next.
|
|
144
|
+
- Before `gh release create`, diff dev since the last tag and cross-check that every commit with user-visible impact is already in `[Unreleased]` (or the version section). Commits made after the PR merged (hotfixes, follow-ups, docs-only cleanups) are the usual gap.
|
|
145
|
+
- On release, rename `[Unreleased]` → `[X.Y.Z] — YYYY-MM-DD` and update the compare/tag links at the bottom. Don't open a new section for the same version across beta→stable — keep accumulating in one section so the stable release notes reflect the cumulative truth.
|
|
146
|
+
- If you realize post-release the section missed something, patch that version's section directly (commit to dev titled `docs(changelog): catch up [X.Y.Z]`), don't start a new section.
|
|
147
|
+
|
|
148
|
+
**Branch channel discipline:**
|
|
149
|
+
- **dev is the only channel to master.** Every PR into master MUST have `head=dev`. Never open a PR to master from a release branch, feature branch, or any other source — even if the content would be identical to dev.
|
|
150
|
+
|
|
151
|
+
**Merge strategy per PR type:**
|
|
152
|
+
- **Feature PR → dev**: squash merge (keep dev's log granular, one commit per feature).
|
|
153
|
+
- **Release PR `dev → master`**: **"Create a merge commit"** — NOT squash. The merge commit preserves dev's commits as ancestors of master, so the next release PR is a clean fast-forward.
|
|
154
|
+
- **Sync PR (one-time fix when ancestry is broken)**: merge commit. Same reason.
|
|
155
|
+
- **Why this matters:** v0.6.0 + v0.6.1 both had 5-file conflicts on `dev → master` because prior syncs/releases were squashed — that collapses master's history into a single commit on dev (or vice versa), losing the ancestry link. v0.6.1's PR #43 + #44 both used merge commits to establish proper ancestry; v0.6.2 and forward should be clean fast-forwards.
|
|
156
|
+
- **Repo setting**: `allow_merge_commit=true` (enabled 2026-04-19 as part of v0.6.1 release). `allow_squash_merge=true` stays on for feature PRs.
|
|
157
|
+
|
|
158
|
+
**Auto-sync master → dev (post-v0.9.0)**:
|
|
159
|
+
- `.github/workflows/sync-master-to-dev.yml` triggers on every push to master (including release merge-commits).
|
|
160
|
+
- Workflow opens `sync/master-into-dev-<timestamp>` PR and enables auto-merge; once CI passes, GitHub merges it with a merge-commit, restoring dev as a strict descendant of master.
|
|
161
|
+
- Net effect: after a release PR merges, the next release PR is a clean fast-forward within a few minutes. **No manual sync work needed.**
|
|
162
|
+
- If the auto-sync ever fails (CI red on the sync PR, merge conflict, workflow disabled), fall back to the manual steps below.
|
|
163
|
+
|
|
164
|
+
**Manual sync fallback** (only if auto-sync fails):
|
|
165
|
+
1. Branch `sync/master-into-dev` from dev.
|
|
166
|
+
2. `git merge origin/master`, resolve conflicts (usually take dev's version — it's the newer state).
|
|
167
|
+
3. PR that branch → **dev** (not master). **Merge via "Create a merge commit"**, not squash.
|
|
168
|
+
4. After merge, dev is a clean descendant of master. Open the release PR dev → master (clean fast-forward).
|