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
- Controls (
PolymarketNotify.Controls) — the core: a pure data model of per-category defaults (:fills,:resolutions,:errors,:daily_summary,:consensus) with per-strategy overrides. Routefillsfor the AI strategy to Telegram while mutingfillsfor the surge bots, apply amin_valuethreshold, etc. Pure data + pure resolution — no I/O, trivially testable, and a UI/editor can read and write it later. - Channels (
PolymarketNotify.Channel) — pluggable delivery. A channel is any module implementing thedeliver/2behaviour that ships text somewhere. Built-ins:Telegram,Discord,Webhook,Log, and aMultifan-out. Bring your own by implementing the behaviour.
Design notes
- Best-effort.
PolymarketNotify.notify/3consults the controls and, if the(category, strategy, value)passes, fans the message out. Nothing configured / not allowed ⇒ a silent no-op. It never raises into the caller and always returns:ok; a channel that errors or raises is isolated. - No process, no state. The controls are plain maps you own and persist however you like. The library adds no supervision tree.
- Injectable HTTP. Every network channel accepts an
:httpfunction(url, json -> {:ok, status} | {:error, term}), so tests need no real network and you can swap the client. - Secret-aware config. Any option value may be given as
{:system, "VAR"}to read from the environment at delivery time instead of baking a token into config.
Installation
Add polymarket_notify to your deps in mix.exs:
def deps do
[
{:polymarket_notify, "~> 0.1"}
]
endUsage
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
endLicense
MIT. See LICENSE.