Skip to content

Conversation

@holden
Copy link
Member

@holden holden commented Jan 29, 2026

TL;DR

Replaced the GenServer-based TradeMonitor with Oban workers for more reliable real-time monitoring of suspicious trades.

What changed?

  • Removed the deprecated TradeMonitor GenServer and its associated Mix task (mix polymarket.monitor)
  • Updated documentation to reflect that monitoring is now handled by Oban workers:
    • TradeIngestionWorker (runs every 2 minutes)
    • TradeScoringWorker (runs every 5 minutes)
    • AlertingWorker (runs every 10 minutes)
  • Updated the admin UI to reflect the new monitoring approach
  • Improved error handling in the AlertingWorker to better handle different types of score inputs
  • Fixed string truncation in PredictionWorker to properly handle string length

How to test?

  1. Verify that the system continues to monitor trades and generate alerts without the TradeMonitor
  2. Run mix polymarket.ingest to manually trigger trade ingestion
  3. Check that alerts are still being generated with appropriate severity levels
  4. Verify the admin UI correctly shows monitoring status

Why make this change?

The GenServer-based approach had several limitations:

  • Not persistent across application restarts
  • Difficult to scale horizontally
  • Lacked proper error recovery mechanisms

Oban workers provide several advantages:

  • Persistent job scheduling via database
  • Automatic retries on failure
  • Better monitoring and observability
  • More predictable resource usage
  • Survives application restarts

This change completes the migration to a more robust and maintainable architecture for real-time trade monitoring.

Summary by CodeRabbit

  • Bug Fixes

    • Improved robustness of threshold comparisons and string handling
  • Chores

    • Refactored monitoring system from manual process to automatic Oban worker-based architecture
    • Removed interactive monitoring CLI tool
    • Simplified configuration by eliminating manual polling and adjustable threshold settings
  • Documentation

    • Updated operator runbook reflecting new monitoring workflow and configuration structure

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

This PR removes the TradeMonitor GenServer-based real-time trade monitoring system, including its configuration, CLI interface, and supervision tree setup. Monitoring functionality is transitioned to Oban-based workers. Minor refactoring enhances component flexibility and defensive type handling.

Changes

Cohort / File(s) Summary
Trade Monitor Removal
config/config.exs, lib/volfefe_machine/application.ex, lib/volfefe_machine/polymarket.ex, lib/volfefe_machine_web/live/admin/polymarket_live.ex
Removed TradeMonitor configuration block, supervision tree child, module alias export, and event handler interactions. Replaced monitor status retrieval with hardcoded Oban worker indicator and info toasts.
Trade Monitor Module Deletion
lib/volfefe_machine/polymarket/trade_monitor.ex, lib/mix/tasks/polymarket/monitor.ex
Deleted entire GenServer implementation with polling, scoring, alerting logic and comprehensive CLI monitoring task for status display, configuration, live alerts, and threshold management.
Documentation Update
docs/polymarket/OPERATOR_RUNBOOK.md
Updated real-time monitoring description from TradeMonitor polling to Oban worker-based flow (TradeIngestionWorker → TradeScoringWorker → AlertingWorker). Replaced polling thresholds with ensemble-based threshold configuration.
Worker & Component Enhancements
lib/volfefe_machine/workers/polymarket/alerting_worker.ex, lib/volfefe_machine/workers/polymarket/prediction_worker.ex, lib/volfefe_machine_web/components/catalyst_components.ex, lib/volfefe_machine_web/live/admin/polymarket_live.html.heex
Enhanced AlertingWorker with defensive Decimal conversion, improved PredictionWorker truncate logic with binary guards, made CatalystComponents action_link class attribute flexible and added id support, updated template to use new component attributes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hop hop, the monitor's gone to rest,
Oban workers now know best!
No more polls, just steady flow,
Trade alerts in Oban's glow!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fixes for rabbit' is vague and does not clearly describe the main change. While the PR objectives reveal this is a major migration from GenServer-based TradeMonitor to Oban workers, the title provides no meaningful information about this significant architectural change. Consider updating the title to be more descriptive, such as 'Migrate TradeMonitor from GenServer to Oban workers' or 'Replace TradeMonitor with persistent Oban-based workers', to clearly communicate the primary change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@holden holden marked this pull request as ready for review January 29, 2026 18:13
@holden holden mentioned this pull request Jan 29, 2026
Copy link
Member Author

holden commented Jan 29, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@holden holden mentioned this pull request Jan 29, 2026
@holden holden changed the base branch from 01-29-phase_10 to graphite-base/201 January 29, 2026 18:13
@holden holden force-pushed the 01-29-fixes_for_rabbit branch from ed922e5 to 3b1058f Compare January 29, 2026 18:13
@graphite-app graphite-app bot changed the base branch from graphite-base/201 to main January 29, 2026 18:14
@holden holden force-pushed the 01-29-fixes_for_rabbit branch from 3b1058f to 856c43d Compare January 29, 2026 18:14
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/polymarket/OPERATOR_RUNBOOK.md (1)

40-70: Remove obsolete mix polymarket.monitor commands/flags.

The TradeMonitor task was removed, but the runbook still lists mix polymarket.monitor and its flags (enable/disable/poll and threshold tuning). Please replace these with the Oban-based flow and the current mix polymarket.ingest / config guidance to avoid operator confusion.

Also applies to: 294-300, 404-407

🤖 Fix all issues with AI agents
In `@lib/volfefe_machine/polymarket.ex`:
- Around line 4664-4665: The monitor_status map in polymarket.ex is hard-coded
to enabled: true; change it to reflect the real runtime setting by querying the
app config or existing helper that indicates whether alerting/AlertingWorker is
enabled (e.g., Application.get_env/3 key or a function like alerting_enabled?/0)
and use that boolean for monitor_status[:enabled]; update any related via value
if you rely on Oban workers being present. Ensure you reference the
monitor_status map in polymarket.ex and the AlertingWorker/alerting config when
implementing the replacement.

In `@lib/volfefe_machine/workers/polymarket/alerting_worker.ex`:
- Around line 110-118: In find_alert_candidates, make min_score normalization
robust: if min_score is nil return a safe default Decimal (e.g.,
Decimal.new(0)); if min_score is already a %Decimal{} return it unchanged; for
floats and integers keep Decimal.from_float/Decimal.new; for binaries use
Decimal.parse/1 and handle {:ok, dec} | :error (fall back to default Decimal on
:error); remove the catch-all Decimal.new(min_score) that can crash on
unsupported types and ensure any other unexpected type falls back to the safe
default Decimal.

Comment on lines +4664 to +4665
# Monitoring is handled by Oban workers (TradeIngestionWorker, TradeScoringWorker, AlertingWorker)
monitor_status: %{enabled: true, via: "oban_workers"}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t hard-code monitoring as enabled in the dashboard.

monitor_status now always shows enabled: true, which can mislead ops if AlertingWorker is disabled in config. Consider reflecting the actual config value so the UI matches runtime state.

🛠️ Suggested adjustment
-      monitor_status: %{enabled: true, via: "oban_workers"}
+      monitor_status: %{
+        enabled:
+          Application.get_env(:volfefe_machine, VolfefeMachine.Workers.Polymarket.AlertingWorker, [])
+          |> Keyword.get(:enabled, true),
+        via: "oban_workers"
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Monitoring is handled by Oban workers (TradeIngestionWorker, TradeScoringWorker, AlertingWorker)
monitor_status: %{enabled: true, via: "oban_workers"}
# Monitoring is handled by Oban workers (TradeIngestionWorker, TradeScoringWorker, AlertingWorker)
monitor_status: %{
enabled:
Application.get_env(:volfefe_machine, VolfefeMachine.Workers.Polymarket.AlertingWorker, [])
|> Keyword.get(:enabled, true),
via: "oban_workers"
}
🤖 Prompt for AI Agents
In `@lib/volfefe_machine/polymarket.ex` around lines 4664 - 4665, The
monitor_status map in polymarket.ex is hard-coded to enabled: true; change it to
reflect the real runtime setting by querying the app config or existing helper
that indicates whether alerting/AlertingWorker is enabled (e.g.,
Application.get_env/3 key or a function like alerting_enabled?/0) and use that
boolean for monitor_status[:enabled]; update any related via value if you rely
on Oban workers being present. Ensure you reference the monitor_status map in
polymarket.ex and the AlertingWorker/alerting config when implementing the
replacement.

Comment on lines 110 to +118
def find_alert_candidates(min_score, limit \\ 100) do
# Convert to decimal for comparison
min_score_decimal = Decimal.from_float(min_score)
# Defensively normalize min_score to Decimal (handles floats, integers, strings)
min_score_decimal =
cond do
is_float(min_score) -> Decimal.from_float(min_score)
is_integer(min_score) -> Decimal.new(min_score)
is_binary(min_score) -> Decimal.new(min_score)
true -> Decimal.new(min_score)
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n lib/volfefe_machine/workers/polymarket/alerting_worker.ex | head -150 | tail -100

Repository: razrfly/volfefe

Length of output: 4250


🏁 Script executed:

# Search for how find_alert_candidates is called and where min_score originates
rg "find_alert_candidates" -A 3 -B 3

Repository: razrfly/volfefe

Length of output: 1428


🏁 Script executed:

# Look for config_min_score or similar configuration in the file
rg "config_min_score|config|min_score" lib/volfefe_machine/workers/polymarket/alerting_worker.ex -n

Repository: razrfly/volfefe

Length of output: 1375


🏁 Script executed:

# Check Decimal library behavior - does Decimal.new/1 raise on nil?
python3 << 'EOF'
# Look for Decimal.new behavior in Elixir documentation
# Decimal.new/1 behavior:
# - Decimal.new(nil) -> raises ArgumentError
# - Decimal.new(%Decimal{}) -> raises ArgumentError  
# - Decimal.new("invalid") -> raises ArgumentError
# This is documented behavior in the Decimal library

print("Decimal.new/1 raises on:")
print("- nil")
print("- %Decimal{} struct")
print("- invalid string inputs")
print("\nDecimal.from_float/1 accepts floats and converts them")
print("Decimal.parse/1 returns {:ok, decimal} or :error tuple")
EOF

Repository: razrfly/volfefe

Length of output: 243


🏁 Script executed:

# Search for where this function is called from and what arguments it receives
rg "find_alert_candidates" -B 5 -A 5

Repository: razrfly/volfefe

Length of output: 2129


🏁 Script executed:

# Look for job/task args processing to understand min_score origin
rg "perform|min_score" lib/volfefe_machine/workers/polymarket/alerting_worker.ex -n -A 2 -B 2

Repository: razrfly/volfefe

Length of output: 1636


Harden min_score normalization to prevent Decimal conversion crashes.

Decimal.new/1 raises on nil, invalid strings, or %Decimal{} inputs. Although min_score receives a fallback at line 54, the catch-all on line 117 still accepts any type and will crash the worker on unexpected inputs (e.g., if config returns an invalid value or a %Decimal{} is passed). Handle %Decimal{} pass-through, use Decimal.parse/1 for safe binary conversion, and add an explicit nil check.

🔧 Proposed fix
-    min_score_decimal =
-      cond do
-        is_float(min_score) -> Decimal.from_float(min_score)
-        is_integer(min_score) -> Decimal.new(min_score)
-        is_binary(min_score) -> Decimal.new(min_score)
-        true -> Decimal.new(min_score)
-      end
+    min_score_decimal =
+      cond do
+        is_nil(min_score) -> Decimal.from_float(config_min_score())
+        match?(%Decimal{}, min_score) -> min_score
+        is_float(min_score) -> Decimal.from_float(min_score)
+        is_integer(min_score) -> Decimal.new(min_score)
+        is_binary(min_score) ->
+          case Decimal.parse(min_score) do
+            {d, ""} -> d
+            _ -> Decimal.from_float(config_min_score())
+          end
+        true ->
+          Decimal.new(to_string(min_score))
+      end
🤖 Prompt for AI Agents
In `@lib/volfefe_machine/workers/polymarket/alerting_worker.ex` around lines 110 -
118, In find_alert_candidates, make min_score normalization robust: if min_score
is nil return a safe default Decimal (e.g., Decimal.new(0)); if min_score is
already a %Decimal{} return it unchanged; for floats and integers keep
Decimal.from_float/Decimal.new; for binaries use Decimal.parse/1 and handle
{:ok, dec} | :error (fall back to default Decimal on :error); remove the
catch-all Decimal.new(min_score) that can crash on unsupported types and ensure
any other unexpected type falls back to the safe default Decimal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants