<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-05-11T07:48:21+00:00</updated><id>/feed.xml</id><title type="html">Nick’s Misc Blog</title><subtitle>Random musings related to my hobbies + projects</subtitle><entry><title type="html">Thoughts on ProgramBench Part 1</title><link href="/2026/05/10/thoughts-on-programbench-part1.html" rel="alternate" type="text/html" title="Thoughts on ProgramBench Part 1" /><published>2026-05-10T07:00:00+00:00</published><updated>2026-05-10T07:00:00+00:00</updated><id>/2026/05/10/thoughts-on-programbench-part1</id><content type="html" xml:base="/2026/05/10/thoughts-on-programbench-part1.html"><![CDATA[<p>Meta (in conjunction w/ Stanford + Harvard) released a <a href="https://arxiv.org/pdf/2605.03546v1">paper last week called <strong>ProgramBench</strong></a> detailing the state of language models for writing code repos. It consists of the paper, the <a href="https://github.com/facebookresearch/programbench">github</a> to run the eval, and the <a href="https://huggingface.co/datasets/programbench/ProgramBench-Tests">huggingface link</a> to download the test blobs.</p>

<p>They defined the task by scraping 200 github repos based off some heuristics, generate tests using a synthetic pipeline, then feed the golden compiled program and the repo’s documentation to <a href="https://github.com/SWE-agent/mini-swe-agent">mini-swe-agent</a> and have it generate the repo + a build script. The build script + code is then ran against the synthetic TB.</p>

<p>Notably, they found that no runs resulted in a 100% TB pass although a couple runs came close. I had some thoughts on how this experiment was carried out and wanted to dive into them in this post.</p>

<p>A full recreation of the repo w/ the necessary modifications to run my experiments can be found at <a href="https://github.com/NickCheng0921/ProgramBench/tree/main/modifications">my fork</a>. I’ve added support to make single runs w/ Openrouter as a provider.</p>
<ul>
  <li>main repo is eval only, I’ve added the steps + extra code to pull the TBs + run the task</li>
</ul>

<h1 id="fast-run-through-of-paper">Fast run through of paper</h1>

<p>Following images are from the paper.</p>

<p><a href="https://arxiv.org/pdf/2605.03546v1#page=3">Tasks were created</a> by scraping Github w/ some heuristics to create testbenches. TBs were made in Python using <code class="language-plaintext highlighter-rouge">mini-swe-agent</code> w/ <code class="language-plaintext highlighter-rouge">Sonnet 4.5</code> (interesting choice given they had access to better models).</p>

<p>The hand icon is <code class="language-plaintext highlighter-rouge">mini-swe-agent</code>’s logo and refers to an agentic step. <code class="language-plaintext highlighter-rouge">mini-swe-agent</code> is a lightweight single agent pipeline/flow, comparative to claude-agent-sdk.</p>

<p><img src="/imgs/programbench/programbench_task_creation.png" alt="alt text" /></p>

<p>The authors have provided docker images per task, and run the agent in there w/ network access disabled along w/ some additional checks to prevent cheating.</p>

<p><img src="/imgs/programbench/pbench_task_run.png" alt="alt text" /></p>

<h1 id="thoughts-on-methodology">Thoughts on Methodology</h1>

<p>None of these ideas were created by AI, they occured to me as I read the paper.</p>

<h3 id="1---task-formulation">1 - task formulation</h3>

<p>The goal of the paper is “to measure the ability of software engineering agents to develop software holisitically”. Having the golden executable when creating software seems like a bit of a stretch.</p>

<p>I see the reasoning for it, as we lack a clean specification, but this doesn’t seem to match any software development task I’ve seen outside of reverse engineering and the paper targets “holistic” software development.</p>

<h3 id="2---questionable-test-quality-from-synthetic-pipeline">2 - questionable test quality from synthetic pipeline</h3>

<p>Due to the scale of the benchmark in comparison to the size of the research team, I see the use of a synthetic pipeline.</p>

<p>However, agents are <strong>spectacularly awful</strong> at writing good integration tests w/o human oversight (my claim from experience) and we can see this in the repo. Cmatrix (one of the tested libs) has very few tests checking the actual animation functionality of the library and a majority of the tests are unit tests checking CLI arg behavior. What tests do exist for animation just check that something happened.</p>

<p>An executable that produces an unsatisfactory animation, but handles the CLI args properly would score extremely high on the benchmark (and could even pass it).</p>

<p>This issue is fundamental to synthetic pipelines being used to create TBs. Without human oversight, it’s very hard to describe the <strong>use</strong> of software. It’s trivially easy for the model to enumerate the flags and create combinations and write hundreds of garbage tests.</p>

<h3 id="3---lack-of-replication">3 - lack of replication</h3>

<p>Each of the 200 tasks was ran w/ each of the 9 LMs once. LMs are non-deterministic and I believe that output quality varies more as turns increase (variance of a random walk).</p>

<p>It’s standard practice to use pass@k or make multiple runs to capture output variability and I don’t see a real reason that wasn’t done here.</p>

<p>I could see an argument for cost?, but less LMs or tasks could have been selected and I’m sure that the lab (<strong>Meta “Superintelligence”</strong>) has the budget to perform this run.</p>

<h3 id="4---lack-of-open-source-models">4 - lack of open source models</h3>

<p>self-explanatory</p>

<h3 id="5---model-memorization">5 - model memorization</h3>

<p>Training on github repos is standard practice for LMs now and it’s <a href="https://arxiv.org/html/2512.10218v2">made model/agent evaluation</a> very difficult. There’s a real possibility of contamination here, especially w/ the newer models.</p>

<p>I’ll come back to this point in my experiment.</p>

<h1 id="my-experiment">My experiment</h1>

<p>So where does this leave us?</p>

<p>I have two goals I want to pursue from here:</p>
<ol>
  <li>Recreate the experiment and note any learnings</li>
  <li>Improve on the methodology (multiple ideas I’m saving for a future post)</li>
</ol>

<p>To do this, I made a <a href="https://github.com/NickCheng0921/ProgramBench/tree/main/modifications">fork</a> that runs tasks w/ OpenRouter as the provider. I used <code class="language-plaintext highlighter-rouge">deepseek-v4-flash</code> because I don’t have a superintelligence sized budget (the 14 runs I made cost me ~4 buckaroos), but I’m planning to test more down the line.</p>

<p>I selected 3 C repos (<a href="https://github.com/abishekvashok/cmatrix">cmatrix</a>, <a href="https://github.com/arq5x/bedtools2">bedtools2</a>, <a href="https://github.com/doxygen/doxygen">doxygen</a>) that most models did well on to recreate the runs for, because I’m the most competent w/ C out of the compilable languages the authors selected.</p>

<p>I used <code class="language-plaintext highlighter-rouge">mini-swe-agent</code> w/ the same sys prompt from the repo, and used the following task msg since I couldn’t find the original.</p>

<p><code class="language-plaintext highlighter-rouge">Write some tests for the existing executable, then write your own code and check if it passes. Complete once done by issung COMPLETE_TASK. If a binary errors with Error opening terminal or similar, it's a curses app — focus on --help, version, and error-path testing rather than trying to run it interactively. Use script -qc only if a specific test requires the binary to start successfully under a pty.</code></p>

<h3 id="experiment-results--findings">Experiment Results + Findings</h3>

<p>Deepseek values are from my runs, other values are from the paper/website.</p>

<p>Note that <strong>pass rate</strong> is <strong>test pass %</strong>. No runs resulted in a 100% TB test, they all get partial scores.</p>

<h3 id="cmatrix"><strong>Cmatrix</strong></h3>

<p>I noticed that deepseek outputted the original authors in the headers of the code for 1 of 4 runs. Compared to the next 2 tasks, all the models did well leading me to believe that the models strongly memorized the original code, or the TB is trivial because the application is GUI focused and hard to test.</p>

<p>Runs 2-4 gave 99% TB pass rate (the code is different). I wonder if this is related to the provider (Novita through OpenRouter) potentially caching some of my calls.</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>Pass rate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>deepseek-v4-flash</td>
      <td>81%, 99%, 99%, 99%</td>
    </tr>
    <tr>
      <td>opus-4.7</td>
      <td>95%</td>
    </tr>
    <tr>
      <td>gemini-3.1-pro</td>
      <td>94%</td>
    </tr>
    <tr>
      <td>gpt-5.4</td>
      <td>92%</td>
    </tr>
  </tbody>
</table>

<p><img src="/imgs/programbench/cmatrix.png" alt="alt text" /></p>

<h3 id="bedtools2--doxygen"><strong>Bedtools2 + Doxygen</strong></h3>

<p>0% deepseek runs are compile failures due to buggy code.</p>

<p><img src="/imgs/programbench/bedtools2.png" alt="alt text" /></p>

<p><img src="/imgs/programbench/doxygen.png" alt="alt text" /></p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>bedtools2 pass rate</th>
      <th>doxygen pass rate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>deepseek-v4-flash</td>
      <td>24%, 16%, 19%, 0%, 23%</td>
      <td>0%, 34%, 18%, 24%, 23%</td>
    </tr>
    <tr>
      <td>opus-4.7</td>
      <td>2%</td>
      <td>35%</td>
    </tr>
    <tr>
      <td>gemini-3.1-pro</td>
      <td>20%</td>
      <td>16%</td>
    </tr>
    <tr>
      <td>gpt-5.4</td>
      <td>17%</td>
      <td>30%</td>
    </tr>
  </tbody>
</table>

<p>We can see that multiple runs of deepseek gave a big spread. I’m curious to see what the spread of the closed source models looks like.</p>

<h3 id="next-steps">Next Steps</h3>

<ol>
  <li>
    <p>Improve on methodology</p>
  </li>
  <li>
    <p>Does the model creating the TB matter? Maybe Anthropic models performed a bit better here because Sonnet 4.5 created the TB</p>
  </li>
  <li>
    <p>Investigate sensitivity of model performance to task prompt</p>
  </li>
</ol>]]></content><author><name></name></author><summary type="html"><![CDATA[Meta (in conjunction w/ Stanford + Harvard) released a paper last week called ProgramBench detailing the state of language models for writing code repos. It consists of the paper, the github to run the eval, and the huggingface link to download the test blobs.]]></summary></entry><entry><title type="html">Conferences Attended</title><link href="/conferences/2026/05/05/conferences.html" rel="alternate" type="text/html" title="Conferences Attended" /><published>2026-05-05T07:00:00+00:00</published><updated>2026-05-05T07:00:00+00:00</updated><id>/conferences/2026/05/05/conferences</id><content type="html" xml:base="/conferences/2026/05/05/conferences.html"><![CDATA[<p>List will be updated as I attend more conferences :)</p>

<h2 id="2026">2026</h2>
<ul>
  <li><strong>SNUG</strong> - <a href="https://www.synopsys.com/community/snug/snug-silicon-valley.html">Synopsys User Group</a></li>
</ul>

<h2 id="2025">2025</h2>
<ul>
  <li><strong>ICLAD</strong> - <a href="https://iclad.ai/lad25-home-page">International Conference on LLM-Aided Design</a>
    <ul>
      <li>attended Hackathon event as well</li>
    </ul>
  </li>
  <li><strong>DAC</strong> - <a href="https://dac.com/2025/62nd-dac">Design Automation Conference</a></li>
</ul>]]></content><author><name></name></author><category term="Conferences" /><summary type="html"><![CDATA[List will be updated as I attend more conferences :)]]></summary></entry><entry><title type="html">Monte Carlo State-Value Estimation for BlackJack</title><link href="/monte/carlo/python/blackjack/reinforcement/learning/2026/04/28/mc-blackjack.html" rel="alternate" type="text/html" title="Monte Carlo State-Value Estimation for BlackJack" /><published>2026-04-28T07:07:45+00:00</published><updated>2026-04-28T07:07:45+00:00</updated><id>/monte/carlo/python/blackjack/reinforcement/learning/2026/04/28/mc-blackjack</id><content type="html" xml:base="/monte/carlo/python/blackjack/reinforcement/learning/2026/04/28/mc-blackjack.html"><![CDATA[<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>

<script id="MathJax-script" async="" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>

<p>I’ve been looking into reinforcement learning lately to create a <a href="https://github.com/NickCheng0921/GodotRLRacer">racing agent</a>.</p>

<p>As part of this process, I’m reading <strong>Reinforcement Learning: An Intro 2nd Edition</strong> by Sutton &amp; Barto as my background in ML is supervised/unsupervised learning (<a href="https://github.com/NickCheng0921/BandSplitRNN-Pytorch">blind source separation</a>, <a href="https://oaktrust.library.tamu.edu/server/api/core/bitstreams/1e4c3130-48ee-4356-955d-b82f1ed548ed/content">variance pretraining</a>). It’s not too useful for my project implementation as I’m experimenting with newer methods like PPO and reward design, but it helps me build a stronger foundation and intuition.</p>

<p>Chapter 5 (Monte Carlo Methods) shows an interesting looking plot of the state-value estimation of an arbitrary player strategy in BlackJack. The plot tells us what the expected return is for a given state under the following strategy: player hits until &gt;= 20 and dealer hits til &gt;= 17.</p>

<p><img src="/imgs/sutton_barto_rl_mc_blackjack.png" alt="img" /></p>

<p>I wanted to try and recreate this from scratch in Python out of curiosity. Pdfs of the book float around online, such as <a href="https://web.stanford.edu/class/psych209/Readings/SuttonBartoIPRLBook2ndEd.pdf">here</a> (above fig from pdf pg 130).</p>

<h3 id="more-info-on-the-environment">More Info on the Environment</h3>

<p>Agent env is standard BJ, with rewards {1, -1, 0} for winning, losing and drawing. We assume an infinite deck, and the agent’s observation space is their current hand value, whether they have a usable ace, and the visible dealer card. The environment auto hits the player to &gt; 11 so we can’t have &gt; 1 usable ace (if you were wondering why usable ace is binary).</p>

<p>This gives us an observation space of 200 discrete states (Player hand value [12, 21] = 10 states, Dealer Card [A, 10] = 10 states, Usable Ace {0, 1} = 2 states). The player can hit or stand which is a discrete action of 2 states.</p>

<p>The value estimation formula can be found in the core code in the next section, we estimate the value using V(state) = V(s) + (G - V(s))/N where V is the value, G is return from this state to episode end, and N is the total # of times we reached the state.</p>

<h3 id="code">Code</h3>

<p>Core logic to run the MC sim is at <a href="/projects/monte_carlo_blackjack/core.txt">core.txt</a></p>

<p>To get the plots, run <a href="/projects/monte_carlo_blackjack/sweep.txt">sweep.txt</a> (you’ll need core as well, rename both to .py files)</p>

<h3 id="results">Results</h3>

<p>The following plot shows the value estimation improving from 1K to 50K episodes. The usable ace cases are rarer so it takes a bit longer for the values to converge.</p>

<p><img src="/imgs/mc_bj_1K_50K_sweep.gif" alt="img" /></p>

<p>Here’s what it looks like after 500K episodes (much closer to the book image than the 50K episode one from above).</p>

<p><img src="/imgs/mc_bj_500K.png" alt="img" /></p>

<p>Notice how most of the plot is dark blue/purple? That means we’re expected to lose at that point since our strategy of hitting til 20 is suboptimal compared to the dealer one. Let’s try playing the same strategy as the dealer instead.</p>

<p><img src="/imgs/mc_bj_player17_sweep.gif" alt="img" /></p>

<p><img src="/imgs/mc_bj_player17_500K_sweep.png" alt="img" /></p>

<p>Much better, the brighter colors (higher values) show that our new strategy doesn’t lose as hard as before.</p>]]></content><author><name></name></author><category term="Monte" /><category term="Carlo" /><category term="Python" /><category term="Blackjack" /><category term="Reinforcement" /><category term="Learning" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Fast Monte Carlo Ticker Permutation w/ Pandas</title><link href="/pandas/monte/carlo/permutation/backtest/2025/10/08/faster-pandas-permutation.html" rel="alternate" type="text/html" title="Fast Monte Carlo Ticker Permutation w/ Pandas" /><published>2025-10-08T07:03:00+00:00</published><updated>2025-10-08T07:03:00+00:00</updated><id>/pandas/monte/carlo/permutation/backtest/2025/10/08/faster-pandas-permutation</id><content type="html" xml:base="/pandas/monte/carlo/permutation/backtest/2025/10/08/faster-pandas-permutation.html"><![CDATA[<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>

<script id="MathJax-script" async="" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>

<p>This post details how using native Pandas operations gives significant speedup over a naive implementation in the context of permuting price paths.</p>

<h3 id="motivation">Motivation</h3>

<p>Lately, I’ve been looking into how to perform more rigorous backtests for my trading indicators.</p>

<p>The main source of my study is <a href="https://books.google.com/books/about/Permutation_and_Randomization_Tests_for.html?id=SiJczQEACAAJ">Permutation and Randomization Tests for Trading System Development</a> by Timothy Masters. The book details methodologies for estimating strategy performance such as permuting existing price data to form multiple alternate paths, allowing us to run our strategy on multiple possible realities.</p>

<p>Suppose we want to generate one of these walks for our asset \(S\) from time \(t_0\) to \(t_n\) which moves as \(S_0, S_1, ..., S_n\). \(S_i\) can be Microsoft’s closing price at day \(i\) as an example.</p>

<p>The movement of the asset can be modelled as a small multiplicative modifier \(\sigma\) where \(S_0\cdot\sigma_0=S_1\) and \(S_0\cdot\sigma_0\cdot\sigma_1=S_1\cdot\sigma_1=S_2\) (assuming \(\sigma\) comes from a normal dist. gives us a geometric brownian motion, handy for basic pricing + modelling).</p>

<p>The rest of this post assumes that we work w/ log pricing as this makes the movement additive and gives the benefit of normalizing price differences over long horizons.</p>

<p>Under log pricing, our new equation for asset movement is then \(S_n = S_0 + \sigma_0 + \sigma_1 + ... + \sigma_{n-1}\). Notice how <strong>reordering</strong> the \(\sigma\)’s doesn’t change the final price as the movements are commutative.</p>

<p>We can obtain \(S_3\) from (\(S_0 + \sigma_0 + \sigma_2 + \sigma_1\)) or (\(S_0 + \sigma_2 + \sigma_1 + \sigma_0\)) despite them being separate paths.</p>

<p>As such, we can generate nearly any number of alternate paths that start at \(S_0\) and end at \(S_n\) by reordering \(\sigma\). The book details why we want to do this, but as a TL:DR, path permutation destroys patterns and preserves trends.</p>

<h3 id="naive-implementation">Naive Implementation</h3>

<p>Here’s a basic implementation of the path permutation algorithm in Python.</p>

<p>Note 3 key differences in the implementation vs the explanation:</p>
<ul>
  <li>the <code class="language-plaintext highlighter-rouge">Close</code>, <code class="language-plaintext highlighter-rouge">High</code> and <code class="language-plaintext highlighter-rouge">Low</code> are moved relative to the daily <code class="language-plaintext highlighter-rouge">Open</code> to preserve intraday trends</li>
  <li>we permute <code class="language-plaintext highlighter-rouge">Open</code> -&gt; <code class="language-plaintext highlighter-rouge">Close</code> -&gt; <code class="language-plaintext highlighter-rouge">Open</code> to preserve interday trends rather than <code class="language-plaintext highlighter-rouge">Close</code> -&gt; <code class="language-plaintext highlighter-rouge">Close</code></li>
  <li>we permute interday and intraday movements separately to break up predictable patterns</li>
</ul>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_example_data</span><span class="p">():</span>
    <span class="kn">import</span> <span class="nn">yfinance</span> <span class="k">as</span> <span class="n">yf</span>
    <span class="n">df</span> <span class="o">=</span> <span class="n">yf</span><span class="p">.</span><span class="n">download</span><span class="p">([</span><span class="s">'MSFT'</span><span class="p">],</span> <span class="n">period</span><span class="o">=</span><span class="s">'10y'</span><span class="p">,</span> <span class="n">auto_adjust</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
    <span class="n">df</span><span class="p">.</span><span class="n">columns</span> <span class="o">=</span> <span class="p">[</span><span class="s">'Close'</span><span class="p">,</span> <span class="s">'High'</span><span class="p">,</span> <span class="s">'Low'</span><span class="p">,</span> <span class="s">'Open'</span><span class="p">,</span> <span class="s">'Volume'</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">df</span>

<span class="k">def</span> <span class="nf">get_perm_slow</span><span class="p">(</span><span class="n">log_df</span><span class="p">,</span> <span class="n">seed</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
    <span class="n">log_perm_df</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="n">copy</span><span class="p">.</span><span class="n">copy</span><span class="p">(</span><span class="n">log_df</span><span class="p">))</span>
    <span class="c1"># intraday relative movements of high, low, close relative to open
</span>    <span class="c1"># keep first bar constant or else first and last bar won't be identical
</span>    <span class="n">intra_h_o</span> <span class="o">=</span> <span class="p">(</span><span class="n">log_df</span><span class="p">[</span><span class="s">'High'</span><span class="p">]</span> <span class="o">-</span> <span class="n">log_df</span><span class="p">[</span><span class="s">'Open'</span><span class="p">])[</span><span class="mi">1</span><span class="p">:]</span>
    <span class="n">intra_l_o</span> <span class="o">=</span> <span class="p">(</span><span class="n">log_df</span><span class="p">[</span><span class="s">'Low'</span><span class="p">]</span> <span class="o">-</span> <span class="n">log_df</span><span class="p">[</span><span class="s">'Open'</span><span class="p">])[</span><span class="mi">1</span><span class="p">:]</span>
    <span class="n">intra_c_o</span> <span class="o">=</span> <span class="p">(</span><span class="n">log_df</span><span class="p">[</span><span class="s">'Close'</span><span class="p">]</span> <span class="o">-</span> <span class="n">log_df</span><span class="p">[</span><span class="s">'Open'</span><span class="p">])[</span><span class="mi">1</span><span class="p">:]</span>
    <span class="c1"># shift(-1) is next row val, shift(1) is prev
</span>    <span class="n">inter_c_o</span> <span class="o">=</span> <span class="p">(</span><span class="n">log_df</span><span class="p">[</span><span class="s">'Open'</span><span class="p">].</span><span class="n">shift</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">-</span> <span class="n">log_df</span><span class="p">[</span><span class="s">'Close'</span><span class="p">])[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>

    <span class="c1"># keep price of first bar stable
</span>    <span class="n">np</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">seed</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
    <span class="n">perm_ids</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">permutation</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">log_df</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">)))</span>
    <span class="n">perm_ids2</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">permutation</span><span class="p">(</span><span class="n">perm_ids</span><span class="p">)</span>
    
    <span class="n">log_perm_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'Close'</span><span class="p">,</span> <span class="s">'High'</span><span class="p">,</span> <span class="s">'Low'</span><span class="p">,</span> <span class="s">'Open'</span><span class="p">])</span>
    <span class="n">log_perm_df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">copy</span><span class="p">.</span><span class="n">copy</span><span class="p">(</span><span class="n">log_df</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
    
    <span class="c1"># starting from first bar
</span>    <span class="k">for</span> <span class="n">idx</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">log_df</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">):</span>
        <span class="n">pidx</span> <span class="o">=</span> <span class="n">perm_ids</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span>
        <span class="n">pidx2</span> <span class="o">=</span> <span class="n">perm_ids2</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span>
        <span class="c1"># new open = last Close + permuted inter-bar movement
</span>        <span class="n">o</span> <span class="o">=</span> <span class="n">log_perm_df</span><span class="p">[</span><span class="s">'Close'</span><span class="p">].</span><span class="n">iloc</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">inter_c_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">pidx</span><span class="p">]</span>
    
        <span class="c1"># h,l,c = new open + permuted log inter diff
</span>        <span class="n">h</span> <span class="o">=</span> <span class="n">o</span> <span class="o">+</span> <span class="n">intra_h_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">pidx2</span><span class="p">]</span>
        <span class="n">l</span> <span class="o">=</span> <span class="n">o</span> <span class="o">+</span> <span class="n">intra_l_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">pidx2</span><span class="p">]</span>
        <span class="n">c</span> <span class="o">=</span> <span class="n">o</span> <span class="o">+</span> <span class="n">intra_c_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">pidx2</span><span class="p">]</span>
    
        <span class="n">log_perm_df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">log_perm_df</span><span class="p">)]</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">o</span><span class="p">]</span>
    
    <span class="n">log_perm_df</span><span class="p">.</span><span class="n">index</span> <span class="o">=</span> <span class="n">log_df</span><span class="p">.</span><span class="n">index</span>
    <span class="k">return</span> <span class="n">log_perm_df</span></code></pre></figure>

<p>It gets the job done, but runs fairly slowly averaging .51 seconds per call on a 10 year chunk of OHLC data.</p>

<h3 id="back-to-the-drawing-board">Back to the drawing board</h3>

<p>We want this code to run thousands of times per strategy, meaning we want to squeeze more performance. The bottleneck is the <code class="language-plaintext highlighter-rouge">for</code> loop recreating the price path by going through each movement in a random order.</p>

<p>We can greatly expedite this process if we can find a way to write that loop using Pandas operations, and letting the library handle the optimization.</p>

<p>The <code class="language-plaintext highlighter-rouge">for</code> loop can be broken down into two operations:</p>
<ul>
  <li>get a new <code class="language-plaintext highlighter-rouge">Open</code> by adding interday movement from the last <code class="language-plaintext highlighter-rouge">Close</code></li>
  <li>get <code class="language-plaintext highlighter-rouge">High</code>, <code class="language-plaintext highlighter-rouge">Low</code> and <code class="language-plaintext highlighter-rouge">Close</code> by adding intraday movement relative to <code class="language-plaintext highlighter-rouge">Open</code></li>
</ul>

<p>Careful reading of the code + algorithm shows that we can perform these updates independently w/ vectorized calls as long as we take care to propagate the <code class="language-plaintext highlighter-rouge">Close</code> -&gt; <code class="language-plaintext highlighter-rouge">Open</code> shift properly.</p>

<p><code class="language-plaintext highlighter-rouge">High</code> and <code class="language-plaintext highlighter-rouge">Low</code> are intraday movement relative to <code class="language-plaintext highlighter-rouge">Open</code> and rely on <code class="language-plaintext highlighter-rouge">Close</code>, so we can trivially vectorize their movement relative to the <code class="language-plaintext highlighter-rouge">Open</code>.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="c1"># Old
</span><span class="k">for</span> <span class="n">idx</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">log_df</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">):</span>
    <span class="n">pidx2</span> <span class="o">=</span> <span class="n">perm_ids2</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span>
    <span class="n">h</span> <span class="o">=</span> <span class="n">o</span> <span class="o">+</span> <span class="n">intra_h_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">pidx2</span><span class="p">]</span>

<span class="c1"># New
</span><span class="n">x</span><span class="p">[</span><span class="s">'High'</span><span class="p">]</span> <span class="o">+=</span> <span class="n">intra_h_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">perm_ids2</span><span class="p">].</span><span class="n">to_numpy</span><span class="p">()</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">Open</code> only depends on the intraday movement from the last <code class="language-plaintext highlighter-rouge">Close</code> so we don’t need to modify it.</p>

<p>Intraday <code class="language-plaintext highlighter-rouge">Close</code> movement is trickier and requires 2 vectorized operations:</p>
<ul>
  <li>Add the permuted movement of <code class="language-plaintext highlighter-rouge">Close</code> relative to <code class="language-plaintext highlighter-rouge">Open</code> as a cumulative sum (each <code class="language-plaintext highlighter-rouge">Close</code> movement permanently affects every subsequent <code class="language-plaintext highlighter-rouge">Close</code>)</li>
  <li>propagate this movement to every subsequent bar except <code class="language-plaintext highlighter-rouge">Close</code> (intraday <code class="language-plaintext highlighter-rouge">Close</code> update lags by 1 timestep to every other bar)</li>
</ul>

<p>Lastly, we add the interday movement from <code class="language-plaintext highlighter-rouge">Close</code> to <code class="language-plaintext highlighter-rouge">Open</code> using the same trivial vectorization as the <code class="language-plaintext highlighter-rouge">High</code> and <code class="language-plaintext highlighter-rouge">Low</code> bar, except we use a cumulative sum to propagate the movement to subsequent rows.</p>

<h3 id="better-implementation">Better Implementation</h3>

<p>Putting all of this together gives us the following code.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">get_perm</span><span class="p">(</span><span class="n">log_df</span><span class="p">,</span> <span class="n">seed</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
    <span class="n">np</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">seed</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
    <span class="c1"># decouple intraday bar from interday shift
</span>    <span class="n">perm_ids</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">permutation</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">log_df</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">)))</span>
    <span class="n">perm_ids2</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">permutation</span><span class="p">(</span><span class="n">perm_ids</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    
    <span class="n">x</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">index</span><span class="o">=</span><span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">log_df</span><span class="p">)),</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'Close'</span><span class="p">,</span> <span class="s">'High'</span><span class="p">,</span> <span class="s">'Low'</span><span class="p">,</span> <span class="s">'Open'</span><span class="p">])</span>
    <span class="n">x</span><span class="p">.</span><span class="n">index</span> <span class="o">=</span> <span class="n">log_df</span><span class="p">.</span><span class="n">index</span>

    <span class="n">intra_h_o</span> <span class="o">=</span> <span class="p">(</span><span class="n">log_df</span><span class="p">[</span><span class="s">'High'</span><span class="p">]</span> <span class="o">-</span> <span class="n">log_df</span><span class="p">[</span><span class="s">'Open'</span><span class="p">])</span>
    <span class="n">intra_l_o</span> <span class="o">=</span> <span class="p">(</span><span class="n">log_df</span><span class="p">[</span><span class="s">'Low'</span><span class="p">]</span> <span class="o">-</span> <span class="n">log_df</span><span class="p">[</span><span class="s">'Open'</span><span class="p">])</span>
    <span class="n">intra_c_o</span> <span class="o">=</span> <span class="p">(</span><span class="n">log_df</span><span class="p">[</span><span class="s">'Close'</span><span class="p">]</span> <span class="o">-</span> <span class="n">log_df</span><span class="p">[</span><span class="s">'Open'</span><span class="p">])</span>
    <span class="n">inter_c_o</span> <span class="o">=</span> <span class="p">(</span><span class="n">log_df</span><span class="p">[</span><span class="s">'Open'</span><span class="p">].</span><span class="n">shift</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">-</span> <span class="n">log_df</span><span class="p">[</span><span class="s">'Close'</span><span class="p">])[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>

    <span class="c1">#  Don't move first bar
</span>    <span class="n">intra_h_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">intra_l_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">intra_c_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="n">start</span> <span class="o">=</span> <span class="n">log_df</span><span class="p">[</span><span class="s">'Close'</span><span class="p">].</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
    <span class="n">x</span> <span class="o">+=</span> <span class="n">start</span>
    
    <span class="n">x</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="n">x</span><span class="p">.</span><span class="n">index</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="p">:]</span> <span class="o">=</span> <span class="n">x</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="n">x</span><span class="p">.</span><span class="n">index</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="p">:].</span><span class="n">combine_first</span><span class="p">(</span><span class="n">log_df</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
    
    <span class="c1"># Intraday permute
</span>    <span class="n">x</span><span class="p">[</span><span class="s">'High'</span><span class="p">]</span> <span class="o">+=</span> <span class="n">intra_h_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">perm_ids2</span><span class="p">].</span><span class="n">to_numpy</span><span class="p">()</span>
    <span class="n">x</span><span class="p">[</span><span class="s">'Low'</span><span class="p">]</span>  <span class="o">+=</span> <span class="n">intra_l_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">perm_ids2</span><span class="p">].</span><span class="n">to_numpy</span><span class="p">()</span>
    <span class="n">x</span><span class="p">[</span><span class="s">'Close'</span><span class="p">]</span> <span class="o">+=</span> <span class="n">intra_c_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">perm_ids2</span><span class="p">].</span><span class="n">to_numpy</span><span class="p">().</span><span class="n">cumsum</span><span class="p">()</span>

    <span class="c1"># Propagate close movement to future bars
</span>    <span class="n">propagate_close</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">concatenate</span><span class="p">([[</span><span class="mi">0</span><span class="p">],</span> <span class="n">intra_c_o</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">perm_ids2</span><span class="p">].</span><span class="n">cumsum</span><span class="p">()[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]])</span>

    <span class="c1"># Already updated Close, other bars lag update by 1
</span>    <span class="n">cols_to_update</span> <span class="o">=</span> <span class="n">x</span><span class="p">.</span><span class="n">columns</span><span class="p">.</span><span class="n">difference</span><span class="p">([</span><span class="s">'Close'</span><span class="p">])</span>
    <span class="n">x</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="mi">1</span><span class="p">:,</span> <span class="n">x</span><span class="p">.</span><span class="n">columns</span><span class="p">.</span><span class="n">get_indexer</span><span class="p">(</span><span class="n">cols_to_update</span><span class="p">)]</span> <span class="o">+=</span> <span class="n">propagate_close</span><span class="p">[</span><span class="mi">1</span><span class="p">:,</span> <span class="bp">None</span><span class="p">]</span>

    <span class="c1"># Interday permute
</span>    <span class="n">x</span> <span class="o">=</span> <span class="n">x</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">concatenate</span><span class="p">([[</span><span class="mi">0</span><span class="p">],</span> <span class="n">np</span><span class="p">.</span><span class="n">cumsum</span><span class="p">(</span><span class="n">inter_c_o</span><span class="p">)]),</span> <span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">x</span></code></pre></figure>

<p>We’ve replaced the <code class="language-plaintext highlighter-rouge">for</code> loop with vectorized operations allowing Pandas to optimize the code using its backend framework (NumPy).</p>

<p>In my testing, the faster implementation gives &gt; 200% speedup, and is able to run the example in ~.002 seconds rather than 0.51.</p>

<p>An example of some permuted <code class="language-plaintext highlighter-rouge">Close</code> paths for the last 10 years of MSFT is shown below.</p>

<p><img src="/imgs/mcpt_walks.png" alt="image of walks" /></p>]]></content><author><name></name></author><category term="Pandas" /><category term="monte" /><category term="carlo" /><category term="permutation" /><category term="backtest" /><summary type="html"><![CDATA[]]></summary></entry></feed>