<?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: BoosterHand Lab</title>
    <description>The latest articles on DEV Community by BoosterHand Lab (@andrewcharov).</description>
    <link>https://dev.to/andrewcharov</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%2F4244218%2F069afc07-0bfd-4f1d-9c61-645bd9fe85bc.png</url>
      <title>DEV Community: BoosterHand Lab</title>
      <link>https://dev.to/andrewcharov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrewcharov"/>
    <language>en</language>
    <item>
      <title>I Simulated the Martingale Strategy on 10,000 Players With NumPy</title>
      <dc:creator>BoosterHand Lab</dc:creator>
      <pubDate>Sat, 14 Nov 2026 09:26:11 +0000</pubDate>
      <link>https://dev.to/andrewcharov/i-simulated-the-martingale-strategy-on-10000-players-with-numpy-bn3</link>
      <guid>https://dev.to/andrewcharov/i-simulated-the-martingale-strategy-on-10000-players-with-numpy-bn3</guid>
      <description>&lt;p&gt;The Martingale strategy is the oldest "can't lose" idea in gambling: bet $10 on red, double after every loss, and the first win recovers everything plus $10. Every completed sequence ends in profit.&lt;/p&gt;

&lt;p&gt;The catch is the word &lt;em&gt;completed&lt;/em&gt;. I wanted to see what that catch costs, so I gave the strategy to 10,000 simulated roulette players and let them play 1,000 spins each. The whole simulation is about 30 lines of NumPy and runs in a couple of seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;European roulette, betting on red: wins 18 of 37 spins (48.6%), so the house edge is 1/37 = 2.7%&lt;/li&gt;
&lt;li&gt;$1,000 bankroll, $10 base bet, $1,000 table limit&lt;/li&gt;
&lt;li&gt;Double after a loss, reset to $10 after a win&lt;/li&gt;
&lt;li&gt;A player is out when they can't cover the $10 base bet&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;The trick that makes it fast: don't loop over players. Every player is one slot in a NumPy array, and each spin updates all 10,000 at once with boolean masks.&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="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;777&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;P_WIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;37&lt;/span&gt;            &lt;span class="c1"&gt;# red on a European wheel&lt;/span&gt;
&lt;span class="no"&gt;PLAYERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SPINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1_000&lt;/span&gt;
&lt;span class="no"&gt;BANK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1_000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1_000.0&lt;/span&gt;

&lt;span class="n"&gt;bal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PLAYERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BANK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PLAYERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PLAYERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;wagered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PLAYERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SPINS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bet&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BASE&lt;/span&gt;           &lt;span class="c1"&gt;# table limit: start over&lt;/span&gt;
    &lt;span class="n"&gt;bet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;# can't bet more than you have&lt;/span&gt;
    &lt;span class="n"&gt;win&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PLAYERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;P_WIN&lt;/span&gt;
    &lt;span class="n"&gt;wagered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;bal&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bet&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;bet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bet&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# Martingale: double after a loss&lt;/span&gt;
    &lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;=&lt;/span&gt; &lt;span class="n"&gt;bal&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="no"&gt;BASE&lt;/span&gt;                        &lt;span class="c1"&gt;# broke = can't cover the base bet&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"broke: {(~alive).mean():.1%}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"ahead: {(bal &amp;gt; BANK).mean():.1%}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"average loss: ${BANK - bal.mean():.0f} on ${wagered.mean():,.0f} wagered "&lt;/span&gt;
      &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"({(BANK - bal.mean()) / wagered.mean():.2%} of every bet)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output with that seed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;broke: 79.0%
ahead: 18.8%
average loss: $379 on $14,726 wagered (2.58% of every bet)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What the numbers say
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;79% of players were broke after 1,000 spins.&lt;/strong&gt; Only 18.8% finished ahead.&lt;/p&gt;

&lt;p&gt;That surprises people, because Martingale feels safe while you play it. After 100 spins, 62% of the same players were ahead and the median player was up $390. The balance line climbs in small, steady steps, which is exactly why the strategy is so convincing.&lt;/p&gt;

&lt;p&gt;The reason it fails is the ladder. $10, $20, $40, $80, $160, $320, $640: after six losses in a row you have lost $630 and the next bet is $640, but you only have $370 left. A $1,000 bankroll survives &lt;strong&gt;six&lt;/strong&gt; losses in a row, not seven.&lt;/p&gt;

&lt;p&gt;Seven reds missed in a row is about 1 in 106 on any single spin. Sounds rare, but a Martingale player is not making one bet; they are rolling that dice every spin. Over a 1,000-spin session, a streak of at least seven showed up in &lt;strong&gt;99%&lt;/strong&gt; of sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that does not move
&lt;/h2&gt;

&lt;p&gt;Look at the last line of the output: the average player lost 2.58% of everything they wagered. The theoretical house edge is 2.70%. Martingale doesn't change that number at all. It only changes the &lt;em&gt;shape&lt;/em&gt; of the losses: many small wins, then one large loss that takes all of them.&lt;/p&gt;

&lt;p&gt;That is the general lesson for any betting system. The expected value of every bet is fixed by the game, and you can't add up negative-expectation bets into a positive one by changing their size. I later &lt;a href="https://boosterhand.com/lab/betting-systems-tested-70000-players/" rel="noopener noreferrer"&gt;tested Martingale against six other systems&lt;/a&gt; (Fibonacci, D'Alembert, Labouchere, Paroli, Oscar's Grind and flat betting) on 70,000 players, with the same result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Change &lt;code&gt;BANK&lt;/code&gt;, &lt;code&gt;BASE&lt;/code&gt; or &lt;code&gt;LIMIT&lt;/code&gt; and watch the bust rate. A bigger bankroll delays the bust; it does not prevent it.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;P_WIN = 18 / 38&lt;/code&gt; for an American wheel (two zeros) and the losses nearly double.&lt;/li&gt;
&lt;li&gt;Add a stop-win rule: stop each player once they are $100 up. In the full write-up that variant got 89% of players to +$100, yet the average player still lost $23.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full write-up with charts, the stop-win variant and the streak distribution is here: &lt;a href="https://boosterhand.com/lab/martingale-strategy-simulation-10000-players/" rel="noopener noreferrer"&gt;Martingale strategy tested on 10,000 players&lt;/a&gt;. All the simulation code is on GitHub: &lt;a href="https://github.com/AndrewCharov/casino-math-simulations" rel="noopener noreferrer"&gt;casino-math-simulations&lt;/a&gt;. If you want to plug in your own bankroll, there is also a &lt;a href="https://boosterhand.com/tools/martingale-calculator/" rel="noopener noreferrer"&gt;Martingale calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;18+. This is a math exercise, not a betting tip: no system removes the house edge.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>numpy</category>
      <category>simulation</category>
    </item>
  </channel>
</rss>
