PolymarketNotify

Notification controls + channels for Elixir trading bots. A small, dependency-light layer that decides what to notify about, for which strategy, and where to send it — then fans a plain-text message out to pluggable delivery channels (Telegram, Discord, generic webhook, or the Logger).

It was extracted from a Polymarket prediction-market trading bot and generalized: nothing here is Polymarket-specific. Strategies are identified by an arbitrary string tag, so a user-written strategy gets its own notification settings with no code change.

This package is part of the Polymarket Elixir ecosystem (alongside paper_ex and friends), but it has no dependency on any of the others and works with any bot.

Two layers

Design notes

Installation

Add polymarket_notify to your deps in mix.exs:

def deps do
[
{:polymarket_notify, "~> 0.1"}
]
end

Usage

Configure channels and (optionally) a default control set:

config :polymarket_notify,
channels: %{
telegram: {PolymarketNotify.Telegram,
[bot_token: {:system, "TELEGRAM_BOT_TOKEN"},
chat_id: {:system, "TELEGRAM_CHAT_ID"}]},
log: {PolymarketNotify.Log, []}
},
controls: PolymarketNotify.Controls.default()

Then notify from anywhere in your bot:

# Simple: consults app config for controls + channels.
PolymarketNotify.notify(:fills, "Filled 100 @ 0.62 (BTC-up)")
# With a strategy tag (for per-strategy overrides) and a magnitude
# (for the category's :min_value threshold):
PolymarketNotify.notify(:fills, "Big fill!", strategy: "ai", value: 250.0)
# Inject controls/channels directly (handy in tests):
PolymarketNotify.notify(:errors, "boom",
controls: my_controls,
channels: %{log: {PolymarketNotify.Log, [level: :error]}})

The control model

%{
categories: %{
fills: %{enabled: true, min_value: 25.0, channels: [:telegram]},
resolutions: %{enabled: true, channels: [:telegram]},
errors: %{enabled: true, channels: [:telegram, :log]},
daily_summary: %{enabled: true, channels: [:telegram]}
},
# per-strategy overrides: tag => %{category => partial pref}
strategies: %{
"surge" => %{fills: %{enabled: false}},
"ai" => %{fills: %{enabled: true, min_value: 0.0}}
},
default_channels: [:log]
}

Resolution merges the category default with any per-strategy override for that (strategy, category). Start from PolymarketNotify.Controls.default/0 (every category on, routed to default_channels), or seed one entry per strategy from a catalog:

PolymarketNotify.Controls.from_catalog(["surge", %{tag: "ai"}, "my_custom_strat"])

Writing a channel

defmodule MyApp.SlackChannel do
@behaviour PolymarketNotify.Channel
@impl true
def deliver(message, opts) do
url = PolymarketNotify.resolve(Keyword.get(opts, :webhook_url))
# ... POST message, return :ok or {:error, reason}
:ok
end
end

License

MIT. See LICENSE.