<?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: Mike Frol</title>
    <description>The latest articles on DEV Community by Mike Frol (@mikefrol).</description>
    <link>https://dev.to/mikefrol</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%2F4080620%2Fbbda9239-8208-4aa8-8bfd-6ac4362391d5.png</url>
      <title>DEV Community: Mike Frol</title>
      <link>https://dev.to/mikefrol</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mikefrol"/>
    <language>en</language>
    <item>
      <title>The arbitrage that wasn't: when two markets are identical but their oracles aren't</title>
      <dc:creator>Mike Frol</dc:creator>
      <pubDate>Sun, 16 Aug 2026 21:57:29 +0000</pubDate>
      <link>https://dev.to/mikefrol/the-arbitrage-that-wasnt-when-two-markets-are-identical-but-their-oracles-arent-4pga</link>
      <guid>https://dev.to/mikefrol/the-arbitrage-that-wasnt-when-two-markets-are-identical-but-their-oracles-arent-4pga</guid>
      <description>&lt;h1&gt;
  
  
  The arbitrage that wasn't
&lt;/h1&gt;

&lt;p&gt;I build a scanner that looks for cross-venue arbitrage in prediction markets. The premise is simple enough to fit in one sentence: Polymarket, Kalshi, PredictIt and Limitless price the same real-world events independently, and when they disagree far enough you can buy YES on one venue and NO on the other for a combined cost under $1.00. Exactly one leg pays out $1.00 whichever way the world goes. It isn't a directional bet — you don't need to know who wins.&lt;/p&gt;

&lt;p&gt;That's the theory. This post is about the part of the theory that turned out to be wrong, and what it cost to find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that shouldn't exist
&lt;/h2&gt;

&lt;p&gt;A correctly matched hedge has a property you can check mechanically: &lt;strong&gt;exactly one leg pays&lt;/strong&gt;. If you hold YES on one venue and NO on another for the same question, the two outcomes are complementary. One resolves true, one resolves false. Always.&lt;/p&gt;

&lt;p&gt;So when I audited settled positions, this is what should have been impossible:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;127 of 426 settled positions (29.8%) had both legs settle the same way.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both won, or both lost. Nearly a third of them. That isn't a rounding error or a fee subtlety — it's a violation of the instrument's defining property. Something in my matching was pairing markets that were not actually complementary.&lt;/p&gt;

&lt;p&gt;My first assumption was the obvious one: the matcher is sloppy, tighten the similarity threshold. That assumption was wrong, and it took a classification pass to see why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitting the failures by cause
&lt;/h2&gt;

&lt;p&gt;Instead of tuning a threshold, I split the 127 by a single question: &lt;strong&gt;do the two legs name the same subject?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The split was stark, and it separated two completely different problems that a similarity score had been quietly blending together. (23 named different subjects, 97 named the same subject, and 7 were too ambiguous to classify either way.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Class A: different subjects — 23 positions
&lt;/h3&gt;

&lt;p&gt;Genuine false matches. Two markets that read alike and are not the same event. The two that hurt most:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-city weather.&lt;/strong&gt; "Highest temperature in Atlanta 92-93" and "highest temperature in Miami 92-93" differ by exactly one token out of about twelve. IDF-weighted similarity scored them &lt;strong&gt;0.65&lt;/strong&gt; — comfortably above threshold — and paired two different cities.&lt;/p&gt;

&lt;p&gt;The insight is that a city is not one signal among many in a weather market. It &lt;em&gt;is&lt;/em&gt; the market. So it can't be left to a bag-of-words score to weigh; it needs its own guard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;temperature|rainfall|snowfall|weather&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// "... in &amp;lt;City&amp;gt; on ..." / "... in &amp;lt;City&amp;gt; be ..." / "... in &amp;lt;City&amp;gt;?"&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;in&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;][&lt;/span&gt;&lt;span class="sr"&gt;a-z .'-&lt;/span&gt;&lt;span class="se"&gt;]{2,24}?)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(?:\b&lt;/span&gt;&lt;span class="sr"&gt;on&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;be&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;between&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;|,|&lt;/span&gt;&lt;span class="se"&gt;\?&lt;/span&gt;&lt;span class="sr"&gt;|$&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the scoring function, a disjoint pair of cities is killed rather than merely penalised:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locus&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the comparison is &lt;strong&gt;overlap, not equality&lt;/strong&gt;. Equality would reject "Portland" against "Portland Timbers" — a real pair. Overlap kills only genuinely disjoint names. That distinction shows up again and again in this codebase; matching on exact sets is almost always too brittle for titles written by different humans at different venues.&lt;/p&gt;

&lt;p&gt;A cheap detail that cost real time: my first version of that regex terminated the lazy capture on a bare &lt;code&gt;on&lt;/code&gt;, which quietly truncated &lt;strong&gt;"Boston" to "bost"&lt;/strong&gt;. The terminators have to be word-bounded — &lt;code&gt;\bon\b|\bbe\b|\bbetween\b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opponent inversion.&lt;/strong&gt; This one is my favourite, because both legs are correct in isolation and the pair is still nonsense. Venues name the same fixture two different ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polymarket: &lt;em&gt;"Will Las Vegas win?"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Kalshi: &lt;em&gt;"Miami vs Las Vegas — Miami"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Buy YES on the first and NO on the second and you have… one directional bet placed twice. Both pay when Las Vegas wins. Both lose when Miami does. Every token matches. Similarity is high. It settles as a double win or a double loss depending purely on luck.&lt;/p&gt;

&lt;p&gt;The fix parses which side each title actually backs — the tail after the dash in "A vs B — pick", otherwise the subject of "Will X win":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;v&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;s&lt;/span&gt;&lt;span class="se"&gt;\.?&lt;/span&gt;&lt;span class="sr"&gt;|ersus&lt;/span&gt;&lt;span class="se"&gt;)?\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;—–&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;(?:\s&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\s)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vs&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;dash&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dash&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[\s&lt;/span&gt;&lt;span class="sr"&gt;—–-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and kills pairs backing disjoint sides.&lt;/p&gt;

&lt;h3&gt;
  
  
  Class B: same subject — 97 positions
&lt;/h3&gt;

&lt;p&gt;Here's where it stopped being a matching problem.&lt;/p&gt;

&lt;p&gt;These pairs were &lt;em&gt;correctly matched&lt;/em&gt;. Same event, same wording, similarity around 0.80. The markets were genuinely identical. And both legs still settled the same way.&lt;/p&gt;

&lt;p&gt;The reason: &lt;strong&gt;each venue resolves from its own source.&lt;/strong&gt; Two venues can list "Will the high temperature in Denver exceed 90°F?" and read two different weather stations. One station reads 90.4°F, the other 89.8°F. Both legs settle YES, or both settle NO. The markets agree; the &lt;em&gt;oracles&lt;/em&gt; disagree.&lt;/p&gt;

&lt;p&gt;No amount of better matching fixes this. The similarity score is not wrong. The pair really is the same question. The hedge fails anyway, because the thing being hedged isn't a fact about the world — it's a fact about a measurement, and there are two measurements.&lt;/p&gt;

&lt;p&gt;I've come to think of this as a distinct category: &lt;strong&gt;oracle risk&lt;/strong&gt;. It is invisible to any text-similarity approach, because it lives entirely outside the text.&lt;/p&gt;

&lt;p&gt;Since it can't be matched away, it gets priced as a risk instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crossVenue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yesLeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noLeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oracleRisk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crossVenue&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;temperature|rainfall|snowfall|weather&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oracleText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oracleRisk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;riskFactors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weather market priced on two venues - each resolves from its own weather station, so both legs can settle the same way&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matchRisk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oracleRisk&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;sim&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;High match-risk positions never reach the recommended list. The pair still appears, correctly matched, with its warning attached — it just isn't presented as something to trade.&lt;/p&gt;

&lt;p&gt;This also explained a distribution I'd been staring at without understanding: weather-heavy &lt;strong&gt;Markets failed at 24%, versus Sports at 9% and Entertainment at 6%.&lt;/strong&gt; It wasn't that market questions were harder to match. It was that they were disproportionately resolved by instruments rather than by events.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable part
&lt;/h2&gt;

&lt;p&gt;Here's what makes this genuinely awkward to write about honestly.&lt;/p&gt;

&lt;p&gt;When both legs of a mispaired position settle YES, &lt;strong&gt;you get paid twice&lt;/strong&gt;. My two largest single gains on record — +$1,105 and +$1,083 — came from exactly the mechanism that produced my largest losses. Same bug. Different coin flip.&lt;/p&gt;

&lt;p&gt;So the temptation is obvious: publish the wins, call it arbitrage, quietly drop the rest. It would even be &lt;em&gt;technically&lt;/em&gt; true that those trades made money.&lt;/p&gt;

&lt;p&gt;It would also be cherry-picking the lucky half of a loss-making class. The correct headline is not the sum of the good outcomes; it's the scoped record after removing the class entirely. On the current live figures, that's &lt;strong&gt;87 recommended positions settled, 74 of which paid out as designed, averaging +2.24% each, or +$1,657 per $1,000 committed&lt;/strong&gt; — with the 13 that didn't behave as hedges named on the page rather than deleted from it.&lt;/p&gt;

&lt;p&gt;A hedge that is correctly matched cannot lose. So a track record led by losses would misdescribe the instrument — but a track record with the failures deleted would misdescribe the engineering. Both numbers have to be visible. &lt;a href="https://provisum.ai/results" rel="noopener noreferrer"&gt;The full settled record is public&lt;/a&gt;, failures included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two testing lessons that nearly cost me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A passing test can prove nothing.&lt;/strong&gt; My first tests for the opponent-inversion guard passed immediately. They were worthless. With the guard &lt;em&gt;disabled&lt;/em&gt;, those cases still scored 0 — existing period and ordinal scope guards were already rejecting them for unrelated reasons, and the scorer returns 0 for "no match", so &lt;code&gt;s &amp;lt; MIN_SIM&lt;/code&gt; passed trivially.&lt;/p&gt;

&lt;p&gt;The rewritten tests use two titles with &lt;strong&gt;identical token sets&lt;/strong&gt; — &lt;code&gt;"Arsenal vs Chelsea — Arsenal"&lt;/code&gt; versus &lt;code&gt;"— Chelsea"&lt;/code&gt; — so similarity, IDF and every other guard see identical input and the backed side is the only variable. Same side: 0.52. Opposite side: 0.00.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Always A/B a new guard by disabling it and confirming the case scores &lt;em&gt;above&lt;/em&gt; threshold without it. Otherwise you're testing your other guards.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2. Verify a regex by calling it, never by reading it.&lt;/strong&gt; I once wrote &lt;code&gt;\b&lt;/code&gt; into a JS regex through a bash-heredoc → python pipeline. It emitted a &lt;strong&gt;literal backspace byte (0x08)&lt;/strong&gt;, not the two-character escape. The regex silently matched nothing. &lt;code&gt;node --check&lt;/code&gt; passed. The file &lt;em&gt;looked&lt;/em&gt; correct in every editor.&lt;/p&gt;

&lt;p&gt;Detect with &lt;code&gt;grep -c $'\x08' file&lt;/code&gt;. Better: never trust a regex you have only read.&lt;/p&gt;

&lt;h2&gt;
  
  
  A performance note, because ordering beat filtering
&lt;/h2&gt;

&lt;p&gt;Unrelated to correctness but too good not to share. Matching took 61.5s per crawl, of which one venue pair — Polymarket × Kalshi, 10,528 × 41,733 markets — was &lt;strong&gt;51.6s, or 84% of all matching CPU&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The inner loop looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qtok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;poolTok&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;idf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;dateFactor&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;similarity()&lt;/code&gt; is the expensive token-set operation. &lt;code&gt;dateFactor&lt;/code&gt; is &lt;strong&gt;0&lt;/strong&gt; whenever two markets resolve more than 30 days apart. Since &lt;code&gt;similarity()&lt;/code&gt; is bounded by 1 and every guard only scales it &lt;em&gt;down&lt;/em&gt;, &lt;code&gt;s &amp;lt;= dateFactor&lt;/code&gt; always holds — so computing the cheap factor first and bailing out is provably behaviour-preserving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dateFactorTs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;poolEnd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;poolMarkets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qtok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;poolTok&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;idf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4.85× faster on that pair.&lt;/strong&gt; Zero output difference — verified by A/B on a frozen snapshot, which mattered more than I expected: comparing across two live fetches proves nothing, because venue prices and dates drift between runs. My first comparison showed different hashes at identical pair counts and looked like a regression that wasn't there.&lt;/p&gt;

&lt;p&gt;What &lt;em&gt;didn't&lt;/em&gt; work, measured and rejected: pre-filtering the Kalshi pool to priced, liquid markets cut it from 41.7k to 11.6k rows (72% smaller) for only &lt;strong&gt;13% speedup&lt;/strong&gt; — and lost 486 real pairs. The cost was on the query side, not the indexed pool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone building this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;When your data violates an invariant, don't tune a threshold — classify the violations.&lt;/strong&gt; "Tighten the similarity score" would have suppressed some of Class A and none of Class B, while hiding the fact that two unrelated problems existed at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some risks are not in the text.&lt;/strong&gt; Two identical questions can resolve differently because they're measured differently. No NLP improvement reaches that. Price it, disclose it, or refuse the trade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match on overlap, not equality&lt;/strong&gt;, whenever humans wrote the strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your biggest wins deserve the same audit as your biggest losses.&lt;/strong&gt; If you only investigate the losses, a broken mechanism that happens to be paying out will survive indefinitely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The scanner is at &lt;a href="https://provisum.ai" rel="noopener noreferrer"&gt;provisum.ai&lt;/a&gt; if you want to see the output. The settled record — including the positions where the matching failed — is &lt;a href="https://provisum.ai/results" rel="noopener noreferrer"&gt;public&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nothing here is financial advice. Past resolutions do not predict future ones.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>datascience</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
