<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nyria</title>
    <description>The latest articles on DEV Community by Nyria (@nyriaautomation).</description>
    <link>https://dev.to/nyriaautomation</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4196492%2Fd15af64b-39cb-4f28-8bd7-51bd8fbb1d50.png</url>
      <title>DEV Community: Nyria</title>
      <link>https://dev.to/nyriaautomation</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nyriaautomation"/>
    <language>en</language>
    <item>
      <title>TradingView alert to a real options order: picking the strike when the alert doesn't say</title>
      <dc:creator>Nyria</dc:creator>
      <pubDate>Fri, 23 Oct 2026 09:41:00 +0000</pubDate>
      <link>https://dev.to/nyriaautomation/tradingview-alert-to-a-real-options-order-picking-the-strike-when-the-alert-doesnt-say-1a54</link>
      <guid>https://dev.to/nyriaautomation/tradingview-alert-to-a-real-options-order-picking-the-strike-when-the-alert-doesnt-say-1a54</guid>
      <description>&lt;h1&gt;
  
  
  TradingView alert to a real options order: picking the strike when the alert doesn't say
&lt;/h1&gt;

&lt;p&gt;A TradingView strategy knows when to go long. It does not know which contract. The alert it fires says &lt;code&gt;SPY&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, maybe a price, and then it is someone's job to turn that into &lt;code&gt;BTO SPY 23 Oct 770C&lt;/code&gt;. Most of the time that someone is a human with a mouse, and the contract they pick at 9:31 is whatever their eye lands on in the chain.&lt;/p&gt;

&lt;p&gt;This post is about taking that decision out of the moment and into a rule set, so an alert that says nothing about strikes still comes back as a specific, consistent order. I build Nyria, which is where I run this; the method is general.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the alert carries, and what it doesn't
&lt;/h2&gt;

&lt;p&gt;A minimal alert from a TradingView strategy, as the webhook sends it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"long"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"{{ticker}}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three fields. No strike, no expiry, no quantity, no order type. You could put them in: Pine can emit &lt;code&gt;{{close}}&lt;/code&gt;, and you can hard-code an offset into the message. But a strike written into an alert goes stale the moment the underlying moves, and a hard-coded expiry is wrong by Tuesday. The alert is the right place for the signal and the wrong place for the contract.&lt;/p&gt;

&lt;p&gt;So the division of labour is: the alert says &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;which way&lt;/em&gt;; a strategy-level rule set says &lt;em&gt;which contract&lt;/em&gt;; the bot says &lt;em&gt;how much&lt;/em&gt; and &lt;em&gt;in which account&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule set
&lt;/h2&gt;

&lt;p&gt;On Nyria these sit on the strategy as option settings, set once. Equivalent knobs exist in any serious self-built system; these are the ones that matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strike selection.&lt;/strong&gt; Either an offset from the money, such as 1 OTM, or a target delta, such as 0.30. Offset is simple and predictable. Delta adapts to volatility and is what most people actually mean when they say "a little out of the money". Pick one and stop choosing by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expiration.&lt;/strong&gt; A range in trading days, 0 to 31 out. A 0DTE strategy sets it to 0. A swing strategy sets a window and takes the nearest listed expiry inside it. The alert never names a date; the rule does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order type.&lt;/strong&gt; Market, or limit at the price the alert names. If the alert carries no price, market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sizing.&lt;/strong&gt; This belongs on the bot, not the strategy, because the same strategy runs in a paper account and a live one at different sizes. A budget per entry does it: at $1.85 a contract, a $500 budget buys 2 contracts (one contract is 100 shares, and the bot never rounds up). The same strategy, deployed by a second person with a $2,000 budget, buys 10.&lt;/p&gt;

&lt;p&gt;With those four set, the three-field alert above becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BTO SPY 23 Oct 770C @ 0.52  x2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the next alert tomorrow, with SPY at a different price, becomes a different strike and the same rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cases where it should refuse
&lt;/h2&gt;

&lt;p&gt;A strike-picker that always finds a contract is a bug. The rules have edges, and the order should not go out when the data cannot satisfy them. The refusals I want written down, in plain words, next to the alert that caused them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No delta available.&lt;/strong&gt; The chain has no delta for the strikes in range, so a delta target cannot be met. Nothing ordered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nearest contract too far from target.&lt;/strong&gt; The strategy asks for 0.30 delta and the closest available is 0.62. Common in the last hour of a 0DTE session. Nothing ordered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No live quote.&lt;/strong&gt; The contract the rule picked has no bid and ask. Nothing ordered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outside hours.&lt;/strong&gt; The alert arrives before 9:30 ET, after 4:00 ET, or on a holiday.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One contract costs more than the budget per entry.&lt;/strong&gt; "One SPY contract costs $185, more than this bot's $150 budget. Raise it to at least $185."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is a decision the human used to make silently and badly at 9:31. Writing them as refusals means you see them, on paper, before any of them costs money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exits
&lt;/h2&gt;

&lt;p&gt;Exits come from exit alerts, matched leg for leg against what the bot holds. The strategy's exit fires &lt;code&gt;{"action": "close", "direction": "long", "ticker": "SPY"}&lt;/code&gt; and the bot sells the contract it opened, not a fresh pick from the chain. &lt;code&gt;STC SPY 770c&lt;/code&gt;, written by a person in a Discord channel, does the same thing.&lt;/p&gt;

&lt;p&gt;Nothing waits at the broker between alerts. For a 0DTE strategy that has one hard consequence: a contract still open at 4 PM ET expires at the broker, and the exit alert has to arrive before the bell. Build the exit into the strategy's own logic, where the entry already lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spreads
&lt;/h2&gt;

&lt;p&gt;The same rule set extends to multi-leg orders: up to four legs on one expiration, each leg the same quantity, placed as one order. Verticals, iron condors, straddles. Ratio spreads, calendars and diagonals are not in scope, and I would be suspicious of any alert-driven system that claimed to do them from a three-field message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it, paper first
&lt;/h2&gt;

&lt;p&gt;The order of operations that has saved me the most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a strategy that trades options, TradingView as the source. Copy the webhook URL it shows into the TradingView alert.&lt;/li&gt;
&lt;li&gt;Set the option settings: strike rule, expiry range, order type.&lt;/li&gt;
&lt;li&gt;Paste a real alert, press Check format, read back the ticker, strike, expiry and side it found. Fix the strategy, not the alert, if any of them is wrong.&lt;/li&gt;
&lt;li&gt;Deploy a bot on Nyria Paper with a budget per entry. Free, no card.&lt;/li&gt;
&lt;li&gt;Let a week of alerts fire. Read every fill and every refusal on the Logs tab.&lt;/li&gt;
&lt;li&gt;Deploy a second bot on the broker account with the same sizing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 5 is the one people skip. It is also the only step that tells you whether your strike rule picks the contract you would have picked, or the one you should have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;Free account at nyria.io, a strategy with TradingView as the source, option settings set once, a paper bot. The first alert that says only &lt;code&gt;long&lt;/code&gt; and comes back as a specific contract is the whole argument.&lt;/p&gt;

</description>
      <category>tradingview</category>
      <category>options</category>
      <category>trading</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to auto-trade the calls from a Discord trading server (without a bot token)</title>
      <dc:creator>Nyria</dc:creator>
      <pubDate>Fri, 23 Oct 2026 04:51:47 +0000</pubDate>
      <link>https://dev.to/nyriaautomation/how-to-auto-trade-the-calls-from-a-discord-trading-server-without-a-bot-token-12ig</link>
      <guid>https://dev.to/nyriaautomation/how-to-auto-trade-the-calls-from-a-discord-trading-server-without-a-bot-token-12ig</guid>
      <description>&lt;h1&gt;
  
  
  How to auto-trade the calls from a Discord trading server (without a bot token)
&lt;/h1&gt;

&lt;p&gt;Most people who trade from a Discord server do the same thing: a call lands, the phone buzzes, they open the broker app, type the ticket, and get filled somewhere between thirty seconds and never. The server is the signal. The human is the bottleneck.&lt;/p&gt;

&lt;p&gt;I spent a while trying to close that gap with my own code, and I build Nyria now, which does it as a product. This post covers what I learned the hard way with a self-written bot, and then the setup I use today, which takes no bot token, no server and no code. Disclosure up front so you can weigh the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The do-it-yourself route, and where it leaks
&lt;/h2&gt;

&lt;p&gt;The obvious plan: create a Discord application, get a bot token, invite the bot to the server, listen for &lt;code&gt;on_message&lt;/code&gt;, parse the text, hit the broker API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@client.event&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;CALLS_CHANNEL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;embed_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;place_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works for an afternoon. Then the problems arrive in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Permissions.&lt;/strong&gt; You can only invite a bot to a server where you have Manage Server. A paid room you merely belong to will not let you add one, and reading it through your own user account is against Discord's terms and gets accounts banned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formats.&lt;/strong&gt; Callers post plain text, embeds, screenshots, quoted tweets. Your &lt;code&gt;parse()&lt;/code&gt; grows a case for each and still misses the one written at 9:31 in a hurry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Noise.&lt;/strong&gt; The same channel carries "nice fill", questions and someone's lunch. Every false positive is a real order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting.&lt;/strong&gt; The listener has to be awake at 3 AM when the crypto call hits. Laptops sleep. Free tiers restart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exits.&lt;/strong&gt; An entry is one message; the exit is another, hours later, in different words. Matching them against what the account is actually holding is the part most home-grown bots never finish.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are hard individually. Together they are a part-time job that pays nothing until the trading does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup I use now
&lt;/h2&gt;

&lt;p&gt;Nyria has its own Discord bot. You invite it to a server you run and it asks for one permission, View Channels. There is no token to manage, nothing to host, and it never reads messages on your personal account. The steps, in the order the app walks you through them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Connect Discord.&lt;/strong&gt; Integrations, Discord card, Connect Discord. Discord asks for two scopes: &lt;code&gt;identify&lt;/code&gt;, to map your Discord user to your Nyria account, and &lt;code&gt;guilds&lt;/code&gt;, to list the servers you belong to. That is all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Create a strategy with Discord as the source.&lt;/strong&gt; On the channel step, select Invite bot to server. You need Manage Server in that server, which is the same rule as any Discord bot. If the caller you follow runs the room and you don't, the fix is at the end of this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Pick the channel and narrow it.&lt;/strong&gt; One channel per strategy. Add an Author filter when several people post in the same channel, and a Keyword filter such as &lt;code&gt;ALERT&lt;/code&gt; when calls share the channel with chatter. The preview shows the last 25 messages so you can see exactly what the bot will read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Teach it the format from real messages.&lt;/strong&gt; On the strategy's Alert format card, assign one past message to each trade type you enable: long entry, long exit, shorts if you trade them. Nyria shows what each message would have traded. If it reads one wrong, assign a different message or change the strategy before any bot runs. Nobody in the channel changes how they write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Deploy on paper first.&lt;/strong&gt; Deploy bot, choose Nyria Paper, set a budget per entry. Paper is free and needs no card. Let a few calls fire and read each fill on the bot's Logs tab, next to the message that caused it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Then the broker.&lt;/strong&gt; When the paper fills track the posts, deploy a second bot on your Alpaca, Tradier, tastytrade or Charles Schwab account with the same sizing. The next message that passes the filters places a trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a call needs to contain
&lt;/h2&gt;

&lt;p&gt;A ticker, a direction and an action. The wording is the caller's own. These all read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Entering BTC long at support
BTO AAPL 345c 11/20 @ 12.50
long SPY
STC SPY 772c
sold half, trimming the rest at 780
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Embeds are read first, then a quoted tweet, then the plain text. A message with no size, no price and no venue, like &lt;code&gt;long SPY&lt;/code&gt;, is still a complete alert: the strategy and the bot supply the size (budget per entry), the order type (market, or limit at the alert's price if it names one) and the account.&lt;/p&gt;

&lt;h2&gt;
  
  
  How exits work
&lt;/h2&gt;

&lt;p&gt;Nothing waits at the broker between messages. An exit is an exit alert: &lt;code&gt;STC SPY 772c&lt;/code&gt;, or a plain "sold" with no contract, closes what the entry opened in the same instrument, matched against what the bot is holding. The next move is always the next message.&lt;/p&gt;

&lt;p&gt;This is the design choice that made my own bot hard: it forces you to think of the channel as the whole state machine, which is also why it holds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to watch on the Logs tab
&lt;/h2&gt;

&lt;p&gt;Every order sits beside the alert that caused it, with the receive time and the fill time to the millisecond. Every refusal does too, written in plain words: the budget per entry is lower than one unit costs, the message passed the filters but named no ticker, the market is closed for that asset. Nothing is ordered on a refusal. Read the log for a week on paper and you will know exactly which messages in that channel are trades and which are talk.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you don't run the server
&lt;/h2&gt;

&lt;p&gt;The bot can't join a paid Discord you only belong to, and nothing legitimate can. Two honest options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask the caller to publish their calls as a strategy on Nyria. Members subscribe and deploy a bot in their own broker account; the caller gets paid per tier, through Nyria, Stripe or Whop. Many callers want this once they see their members are retyping every entry.&lt;/li&gt;
&lt;li&gt;Run your own server for your own alerts: TradingView webhooks, a script, your own posts. Same setup, no permission problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;Make a free account at nyria.io, create a strategy with Discord as the source, point it at a channel you run and deploy a paper bot. No card, no token, no server to keep awake. When the paper fills match what the channel posts, you'll know whether it is worth pointing at a real account.&lt;/p&gt;

</description>
      <category>trading</category>
      <category>discord</category>
      <category>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>Evaluate an options alert service before you pay</title>
      <dc:creator>Nyria</dc:creator>
      <pubDate>Thu, 22 Oct 2026 20:41:20 +0000</pubDate>
      <link>https://dev.to/nyriaautomation/evaluate-an-options-alert-service-before-you-pay-2b98</link>
      <guid>https://dev.to/nyriaautomation/evaluate-an-options-alert-service-before-you-pay-2b98</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nyria.io/guides/evaluate-options-alert-services" rel="noopener noreferrer"&gt;nyria.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every options alert service looks the same from the outside: a price, a screenshot, a channel you cannot see into. This guide is how to grade one from the inside, with its own posts, on a paper account that costs nothing, before a month's fee or a single live order. It ranks no services and names none.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a usable alert contains
&lt;/h2&gt;

&lt;p&gt;Five fields. An alert missing any of them is a trade idea, and a bot cannot place a trade idea.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ticker&lt;/strong&gt; The underlying, written as the exchange lists it: SPY, not "spiders". A call with no ticker is commentary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Side and action&lt;/strong&gt; Call or put, and whether the post opens or closes. BTO, STC and the rest, or plain words such as "out of SPY calls here". An exit that is implied, not posted, cannot be counted and cannot be traded.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Strike&lt;/strong&gt; The exact strike, as listed. A strike that is not on the chain gets no contract, and a bot checks the chain rather than rounding for the caller.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expiry&lt;/strong&gt; Month and day. A call with no expiry takes the nearest listed expiration, which can be today's, even late in the day. If the caller leaves the date off, that is the trade they are posting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Price&lt;/strong&gt; The price the caller got. Without it there is nothing to compare a fill against, and the record is a list of tickers.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The notation does not matter; the fields do. Paste a week of the room's posts into the &lt;a href="https://nyria.io/tools/alert-tester" rel="noopener noreferrer"&gt;alert tester&lt;/a&gt; and see which ones read as an order and which get a refusal. Nothing is traded and no account is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: one call, graded
&lt;/h2&gt;

&lt;p&gt;An entry and its exit as a room would post them, and the three numbers a paper bot gives you for each.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Entry, posted at 9:47&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BTO SPY 775c 11/20 @ 1.85
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Exit, posted at 10:12&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STC SPY 775c 11/20 @ 2.40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The paper bot reads the first as buy to open the SPY 775 call expiring Nov 20, sized from its own budget per entry, and the second as sell to close. Its Logs tab stamps the alert and the order, so for each call you hold three numbers side by side: the price the caller posted, the time the post arrived, and the price the paper bot filled at. Over a few weeks that table is the track record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grade it on paper, not on a screenshot
&lt;/h2&gt;

&lt;p&gt;Run the service on a free paper bot for a few weeks and let the caller's real posts fire. Two ways in. If the caller publishes the calls as a Nyria strategy, subscribe and deploy a bot on Nyria Paper. If you run the server or the Telegram group, link it under Integrations, create a strategy with that channel as the alert source, and add an author filter so only the caller's posts count.&lt;/p&gt;

&lt;p&gt;Read the paper numbers for what they are. Nyria Paper fills a market order at the quoted bid or ask, so it pays the spread but never slips, and a limit exit inside the spread fills at once at its own price. Paper P&amp;amp;L reads better than live. A service that cannot hold up on paper will not hold up live; one that does has passed a test, not every test.&lt;/p&gt;

&lt;p&gt;Count the exits. A position closes only when a post reads as an exit, so calls the caller never closed in writing stay open in the paper account, and they would stay open at your broker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fill timing: a person versus a bot
&lt;/h2&gt;

&lt;p&gt;The caller fills first, then types. A reader then sees the post, opens the broker and types again. That is two gaps, and a bot removes only the second one. Discord and Telegram stamp every message, so compare the caller's posted price with where the contract was trading at the stamp. A gap of seconds is normal. A gap of minutes, on a contract expiring today, is the whole edge.&lt;/p&gt;

&lt;p&gt;A bot does not wait for a better price either. On Telegram, an entry Nyria handles more than a minute after it was posted is not placed at all; the strategy log says so, and a close in the same message still runs. Whatever the source, the paper fill tells you what the post was worth by the time anything could act on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Red flags
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A promise about returns&lt;/strong&gt; A percentage on a banner is a claim, not a record. The only record you can audit is the channel history: scroll it, count the entries, match each to its exit, and note the ones that never closed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No exits posted&lt;/strong&gt; A room that posts entries and leaves the exit to you is half a service, and the half you cannot grade. Partial exits and trims count if they are posted every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alerts that change after the fact&lt;/strong&gt; An edited or deleted post is a different record from the one that was traded. Nyria does not read an edit again, and editing or deleting a post after it trades does not undo the order, so what the bot logged is what was posted at the time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A track record that is a screenshot&lt;/strong&gt; A picture of a month's P&amp;amp;L shows one account on one day. It does not show which calls were skipped, which were closed late, or what a reader could have filled at.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Calls mixed into chat&lt;/strong&gt; Alerts in a channel of their own, from one author, in one notation, can be read at a glance and by a bot. A channel where calls, chat and screenshots share the stream is slower to read by eye and harder for anything else to read at all.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Going live later, with your own broker
&lt;/h2&gt;

&lt;p&gt;When the paper table holds up, deploy a bot on the live account, or edit the paper bot and pick the live account. The first live deploy asks you to accept the risk disclosure. Orders go to Charles Schwab, Tradier, tastytrade or Alpaca, in your own account, and the money never leaves your broker.&lt;/p&gt;

&lt;p&gt;Paper did not test slippage, partial fills, broker rejections or assignment, so start with a budget per entry you can afford to lose in full, and hold capital for both the entry and the exit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can Nyria read a paid Discord I only belong to?
&lt;/h3&gt;

&lt;p&gt;No. Nyria reads channels in servers you run, and groups or channels you administer on Telegram. If the caller publishes the calls as a Nyria strategy, you subscribe to it and deploy a bot in your own account.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long should the paper test run?
&lt;/h3&gt;

&lt;p&gt;Long enough to see the service in more than one kind of week. The docs ask for one to two weeks of paper before a first live bot; for grading a service you are about to pay for, a few weeks is a fairer sample.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does the test cost?
&lt;/h3&gt;

&lt;p&gt;Nothing. Paper is free. Live trading from $15 a month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grade an alert service on a paper bot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://app.nyria.io/login?mode=signup" rel="noopener noreferrer"&gt;Start a paper bot free&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>trading</category>
      <category>finance</category>
      <category>options</category>
      <category>beginners</category>
    </item>
    <item>
      <title>No-code trading bot: stock and option alerts in, orders out</title>
      <dc:creator>Nyria</dc:creator>
      <pubDate>Thu, 22 Oct 2026 20:18:15 +0000</pubDate>
      <link>https://dev.to/nyriaautomation/no-code-trading-bot-stock-and-option-alerts-in-orders-out-54ed</link>
      <guid>https://dev.to/nyriaautomation/no-code-trading-bot-stock-and-option-alerts-in-orders-out-54ed</guid>
      <description>&lt;p&gt;I write code for a living, and the trading setup I actually run has no code in it. The signal lives in a TradingView chart. The alert is one line of text. The thing on the other end turns that line into an order at my broker. I used to run a VPS with a Flask app in the middle, and I don't miss it.&lt;/p&gt;

&lt;p&gt;Disclosure: I build Nyria, which is the thing on the other end here. Everything below is how it actually behaves, including the parts that will annoy you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the alert comes from
&lt;/h2&gt;

&lt;p&gt;Three sources, one destination.&lt;/p&gt;

&lt;p&gt;TradingView: an alert on an indicator or a strategy, with the strategy's webhook URL pasted into the alert's webhook field. The message can be plain text.&lt;/p&gt;

&lt;p&gt;Discord or Telegram: a channel in a server or group you run. It reads the posts, with an author or keyword filter so chatter does not trade. The post can name the contract itself.&lt;/p&gt;

&lt;p&gt;Anything that can POST to a URL: TrendSpider, a Zapier step, your own scanner.&lt;/p&gt;

&lt;p&gt;The alert for a stock, or for an options strategy that picks its own contract, is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPY long entry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the exit is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPY long exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole template. Ticker, direction, action. JSON works too if your source speaks it, but for a chart alert I use text so I can read it in the TradingView alert log.&lt;/p&gt;

&lt;h2&gt;
  
  
  The strategy is a form, and the form decides four things
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;What each alert does. Four alert kinds: long entry, long exit, short entry, short exit. Under Long behavior and Short behavior you say whether an entry opens a position, closes the other side, or does nothing, and whether exits close. A long-only setup can treat short entries as its exits, which is handy when the indicator only knows "flip".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How the option contract is picked. "From my rules": one to four legs, each finding its strike by delta, by price, by strikes from the money, or by offset from another leg, with buy or sell, call or put, and a DTE range. "Provided in alert": the message names the strike and expiry (Discord, Telegram, your own bot).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Order type, duration and limit target. Market or limit, set separately for entries and exits on each side. A limit takes its price from Bid, Ask, Mid, a custom number, or the price in the alert. Duration is Day, GTC or FOK. Tradier has no FOK, so FOK goes there as Day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trading window and stacking. "Limit trading hours" drops entries outside your window and still lets exits through. "Allow multiple entries" is off by default, so a second entry on an open position is skipped and logged, not stacked.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The bot is the strategy on one account with one budget
&lt;/h2&gt;

&lt;p&gt;Deploying a bot is: pick a connected account, set a budget per entry. That is it.&lt;/p&gt;

&lt;p&gt;Stocks size in whole shares. $500 on a $50 stock buys 10. Options size per contract, and a contract is 100 shares, so a $2.00 premium costs $200: a $500 budget buys 2 and leaves $100 unspent. If one contract costs more than the budget, the deploy is refused up front when a live quote shows it. You cannot deploy a bot that can never trade.&lt;/p&gt;

&lt;p&gt;The same strategy on a second account is a second bot, with its own stats.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real one, start to finish
&lt;/h2&gt;

&lt;p&gt;Strategy: options, From my rules, one leg: Buy, Call, 0.40 delta, 14 to 31 DTE. Long behavior: Enter position, with Close on exit alerts on. Entries limit at Ask, exits limit at Bid, Day. Bot on the built-in paper broker, $500 per entry.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SPY long entry&lt;/code&gt; arrives at 10:14. The bot picks the listed SPY call closest to 0.40 delta inside 14 to 31 DTE with a live quote. The ask is $2.00, so it buys 2. &lt;code&gt;SPY long exit&lt;/code&gt; at 2:50 sells the 2 it holds.&lt;/p&gt;

&lt;p&gt;What breaks, from my own logs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No contract inside the DTE range within 0.25 of the target delta: entry skipped, nothing placed, the Logs tab says so.&lt;/li&gt;
&lt;li&gt;Options market closed: the options alert is refused. Stocks on the built-in paper broker fill at any hour, which makes an evening test look fine and a 9:25 AM live alert fail.&lt;/li&gt;
&lt;li&gt;An exit arrives and nothing is tracked as open, because the bot was paused or I closed by hand at the broker: logged as Skipped, not Failed. It tracks what it placed, not your whole account. Give it an account of its own.&lt;/li&gt;
&lt;li&gt;Two alerts 100 ms apart can arrive out of order. To flip, send one reversing alert, not an exit and then an entry.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Paper first, and what paper does not prove
&lt;/h2&gt;

&lt;p&gt;The built-in paper broker is free, up to 10 bots. A market order there fills at the quoted bid or ask: it pays the spread, never slips and never partially fills, so paper P&amp;amp;L reads better than live. What a paper run proves is that the alert fires, the text is read the way you meant, each exit closes its entry, and the quantity is what you expected. It cannot see a broker rejection such as an options approval level.&lt;/p&gt;

&lt;p&gt;For the broker's own rejections, connect the broker's paper account instead. Alpaca paper takes an API key generated with the dashboard toggled to Paper; keys start with PK, and a live AK key is refused on paper. Tradier sandbox takes the sandbox account number and token from developer.tradier.com, runs on 15-minute delayed data, and on weekends marks resting limit orders as filled at prices that never traded. Read Tradier sandbox results on weekday regular hours only.&lt;/p&gt;

&lt;p&gt;Run a week or two. Then deploy on the live account, or edit the bot to point at it. The first live deploy makes you accept the risk disclosure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it will not do
&lt;/h2&gt;

&lt;p&gt;Market and limit orders only. No stop or stop-limit, no bracket, no OCO, no trailing stop. A position closes when an exit alert arrives, or when an opposite entry is set to close it, and that is the entire exit model. No futures, no forex; an index alert trades as an option. No guessing at "thinking about SPY?", which places nothing. And no opinion on whether the strategy makes money.&lt;/p&gt;

&lt;p&gt;Live brokers: Charles Schwab, Tradier, tastytrade, Alpaca. Paper is free; live is from $15 a month. The strategy settings are documented &lt;a href="https://docs.nyria.io/strategies" rel="noopener noreferrer"&gt;here&lt;/a&gt;. To run the SPY example above on a paper bot, &lt;a href="https://app.nyria.io/login?mode=signup" rel="noopener noreferrer"&gt;start on Nyria&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nocode</category>
      <category>trading</category>
      <category>automation</category>
      <category>finance</category>
    </item>
    <item>
      <title>Send alerts from your Python script to your broker</title>
      <dc:creator>Nyria</dc:creator>
      <pubDate>Thu, 22 Oct 2026 19:47:43 +0000</pubDate>
      <link>https://dev.to/nyriaautomation/send-alerts-from-your-python-script-to-your-broker-1f8n</link>
      <guid>https://dev.to/nyriaautomation/send-alerts-from-your-python-script-to-your-broker-1f8n</guid>
      <description>&lt;p&gt;I run a couple of scanners in Python. For a long time their output was a Telegram message to myself, then me opening the broker app and typing the order by hand. The script knew the trade thirty seconds before I did, and I was still the slow part.&lt;/p&gt;

&lt;p&gt;Disclosure up front: I build Nyria, and the webhook in this post is its webhook. I also use it exactly this way, which is why the gotchas below are specific.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the script sends
&lt;/h2&gt;

&lt;p&gt;One HTTPS POST per alert, to a URL that belongs to one strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://trades.nyria.io/webhooks/{token}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token is 64 lowercase hex characters and it is the only authentication. No Authorization header, no HMAC signature, no IP allowlist. Treat the URL as a secret. If it ends up in a screenshot or a repo, rotate it from the strategy's Integration tab. The old URL dies instantly, so every sender you pointed at it needs the new one.&lt;/p&gt;

&lt;p&gt;The body is JSON. The smallest one that places a trade is two fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"buy"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't send that one from a script. &lt;code&gt;buy&lt;/code&gt; is read against the strategy's state: on a long-only strategy it opens a long, but on a strategy that trades both sides it covers an open short first and only otherwise opens a long. From code I want to say exactly what I mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LONG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"market"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;action&lt;/code&gt; is OPEN or CLOSE (MODIFY and CANCEL exist for working limit orders). &lt;code&gt;direction&lt;/code&gt; is LONG or SHORT. &lt;code&gt;order_type&lt;/code&gt; is market or limit, and it is only read on an alert that names action and direction like this one; on anything else the strategy's default applies. &lt;code&gt;price&lt;/code&gt; is the limit price when you send limit.&lt;/p&gt;

&lt;p&gt;There is no quantity field. I tried. &lt;code&gt;quantity&lt;/code&gt; and &lt;code&gt;notional&lt;/code&gt; are ignored on an entry; size always comes from the bot's budget per entry. Duration (Day, GTC, FOK) is a strategy setting too, not an alert field. The one exception is MODIFY, which can re-size a working order on Tradier and Alpaca.&lt;/p&gt;

&lt;p&gt;Options are a separate case. If the strategy is set to "From my rules", the alert is the same as above and the strategy picks the strike and expiry. If it is set to "Provided in alert", you send a &lt;code&gt;legs&lt;/code&gt; array with symbol, side, action, strike and expiration per leg, up to 4 legs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sender
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;WEBHOOK_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://trades.nyria.io/webhooks/REPLACE_WITH_YOUR_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;direction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LONG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WEBHOOK_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert accepted, handed to the bots&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not acted on:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good response looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"processed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"webhook"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"originalAlert"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"structuredAlert"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"instrument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LONG"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things in that script I got wrong the first time.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;raise_for_status()&lt;/code&gt; only catches 4xx and 5xx. A rejected alert and a duplicate both come back as 200 with a &lt;code&gt;status&lt;/code&gt; that is not &lt;code&gt;processed&lt;/code&gt;. Check &lt;code&gt;status&lt;/code&gt; too, or your script logs a green tick on a trade that never happened.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;timeout=10&lt;/code&gt;. The endpoint answers fast, but a hung socket on your side stalls the whole scanner loop and the next three alerts queue behind it.&lt;/p&gt;

&lt;p&gt;To close: same ticker, &lt;code&gt;"action": "CLOSE", "direction": "LONG"&lt;/code&gt;. It closes the whole tracked position. There is no partial close through the webhook.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the responses mean
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; with &lt;code&gt;status: processed&lt;/code&gt;. Read and handed to the bots. Not a fill. The bot's Logs tab shows what the broker did.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; with &lt;code&gt;rejected: true&lt;/code&gt;. The strategy is not Active (draft, paused, archived).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; with &lt;code&gt;status: rejected&lt;/code&gt;. Ticker not in the strategy's list, that direction turned off, or nothing open to close. The reason is in the body.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; with &lt;code&gt;status: duplicate&lt;/code&gt;. See below.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;400&lt;/code&gt; or &lt;code&gt;404&lt;/code&gt;. Bad or unknown token. Copy the URL again.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;413&lt;/code&gt;. Body over 256 KB.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;422&lt;/code&gt;. Unusable alert: missing field, unknown symbol, no strike in the DTE range, more than 4 legs. &lt;code&gt;userMessage&lt;/code&gt; says which.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;429&lt;/code&gt;. Burst limit is 10 at once, then 1 per second. Honor &lt;code&gt;Retry-After&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;503&lt;/code&gt;. Temporary, usually market data. Retrying is safe.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;500&lt;/code&gt;. Everything else. The strategy's Logs tab has the reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there is no row on the Logs tab at all, the request never arrived: wrong host, http instead of https, or a proxy ate it. One exception: a duplicate is only logged once every 5 minutes, so a missing row can also be a repeat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The duplicate rule, and the scale-in it ate
&lt;/h2&gt;

&lt;p&gt;An identical body within 30 seconds is a duplicate. So is an identical body within 5 minutes if nothing else came in between. Only the first one trades.&lt;/p&gt;

&lt;p&gt;That saved me when &lt;code&gt;requests&lt;/code&gt; retried after a flaky connection. It also silently dropped a second scale-in I sent 90 seconds after the first with the same payload. The response was 200 with &lt;code&gt;status: duplicate&lt;/code&gt;, which my script was not checking at the time.&lt;/p&gt;

&lt;p&gt;The fix is a unique &lt;code&gt;metadata.message_id&lt;/code&gt; per intended order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scalein-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dedup matches on that id when it is present, and on a hash of the whole body when it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broker sandbox gotcha
&lt;/h2&gt;

&lt;p&gt;I test against three paper accounts and they behave differently, which matters when a test "passes".&lt;/p&gt;

&lt;p&gt;The built-in paper broker fills or refuses every order instantly. A limit order never rests there, so MODIFY and CANCEL have nothing to act on. Test those against a real broker's paper account.&lt;/p&gt;

&lt;p&gt;Alpaca paper: generate the keys with the dashboard toggled to Paper. A paper key starts with &lt;code&gt;PK&lt;/code&gt;, a live key with &lt;code&gt;AK&lt;/code&gt;, and each only works on its own endpoint. Paste a live key into the paper slot and the connection check fails; Alpaca answers 401 to a key on the wrong host. Outside market hours an Alpaca paper market order sits as &lt;code&gt;accepted&lt;/code&gt; until the open, which is correct, and which confused me the first Sunday I tried this.&lt;/p&gt;

&lt;p&gt;Tradier sandbox: it connects with the sandbox account number and access token from developer.tradier.com, not a tradier.com login. Its data is 15 minutes delayed, and on weekends it marks resting limit orders as filled at prices that never printed. A Friday 3:59 PM limit can show as filled on Saturday morning. The API reports it as a fill, so the bot records a fill and waits for an exit. Judge Tradier sandbox runs on weekday regular hours only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before the first real post
&lt;/h2&gt;

&lt;p&gt;Paste the body into the &lt;a href="https://nyria.io/tools/alert-tester" rel="noopener noreferrer"&gt;alert tester&lt;/a&gt;. It shows the order it would build, or the exact refusal text. No account, nothing traded. Then paste the same body on the strategy's Integration tab as the example for that trade type and select Check format. The strategy only goes Active after that passes, and an inactive strategy answers every post with rejected.&lt;/p&gt;

&lt;p&gt;Then point the script at a paper bot, send one OPEN, open the bot, and look at the quantity on the order. That number is your budget per entry divided by the price, floored to whole shares. If it is what you expected, you are done.&lt;/p&gt;

&lt;p&gt;Every field and a curl example per broker is in the &lt;a href="https://docs.nyria.io/webhook-reference" rel="noopener noreferrer"&gt;webhook payload reference&lt;/a&gt;. Paper bots are free, up to 10, no card. &lt;a href="https://app.nyria.io/login?mode=signup" rel="noopener noreferrer"&gt;Start on Nyria&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webhooks</category>
      <category>trading</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
