<?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: Hidenari Fujiwara</title>
    <description>The latest articles on DEV Community by Hidenari Fujiwara (@hidenari).</description>
    <link>https://dev.to/hidenari</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%2F4077248%2F594b9fc2-5c51-4a9b-a400-929fa7376e88.png</url>
      <title>DEV Community: Hidenari Fujiwara</title>
      <link>https://dev.to/hidenari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hidenari"/>
    <language>en</language>
    <item>
      <title>I Asked Claude Code to Find Me a Trading Edge. It Killed Three Strategies First.</title>
      <dc:creator>Hidenari Fujiwara</dc:creator>
      <pubDate>Fri, 14 Aug 2026 07:26:03 +0000</pubDate>
      <link>https://dev.to/hidenari/i-asked-claude-code-to-find-me-a-trading-edge-it-killed-three-strategies-first-22ai</link>
      <guid>https://dev.to/hidenari/i-asked-claude-code-to-find-me-a-trading-edge-it-killed-three-strategies-first-22ai</guid>
      <description>&lt;p&gt;&lt;em&gt;How I built a fully automated, rule-based Japanese stock research pipeline with Claude Code, J-Quants, and a macOS cron job — and why "the AI is not allowed to predict anything" turned out to be the most useful constraint in the whole project.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Everyone's first idea for "AI + stocks" is the same: ask the model whether a stock will go up. That idea has been tested, and it fails — LLMs guessing post-event price direction land around coin-flip accuracy. I'm a freelance web developer in Japan, not a quant, and I knew that if I let an LLM "predict" anything I would just be laundering my own wishful thinking through a chatbot.&lt;/p&gt;

&lt;p&gt;So I gave Claude Code a different job description. In my project's &lt;code&gt;CLAUDE.md&lt;/code&gt; — the standing instructions file the agent reads every session — there's a hard rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;No predictions. Data only.&lt;/strong&gt; The AI's role is limited to four things: (1) structuring data, (2) computing factual metrics, (3) checking facts against pre-defined rules, (4) explaining results with sources. Trading decisions are rule-based. Overfitting to past data is a hidden prediction — prefer economically sensible rules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude Code's job was to be the engineer: build the data pipeline, implement backtests I specified, and then — this is the important part — &lt;strong&gt;kill my ideas with evidence&lt;/strong&gt;. Over a few weeks it killed three of them. Here's the honest record.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Nothing exotic. The point was cheap and reproducible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;J-Quants API&lt;/strong&gt; — the official Japan Exchange data service. The free personal tier has a 12-week delay on prices, which is fine for backtesting; I fill in current prices with &lt;code&gt;yfinance&lt;/code&gt; for live tracking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python + pandas&lt;/strong&gt; for backtests, with a local file cache so repeated runs don't hammer the API. (One lesson learned: cache &lt;em&gt;empty&lt;/em&gt; responses too. Half my early runtime was re-requesting data that legitimately didn't exist.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; as the pair engineer. I describe the rule, it writes the backtest, I interrogate the output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;launchd&lt;/strong&gt; (macOS's cron) for the daily tracking job. No cloud, no server bill.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Round 1: technical strategies — dead
&lt;/h2&gt;

&lt;p&gt;First I had Claude Code implement the classics on Nikkei-universe data: breakout entries, RSI mean-reversion, with realistic costs and taxes included.&lt;/p&gt;

&lt;p&gt;Ten-year portfolio result: &lt;strong&gt;+15.4%&lt;/strong&gt; total. Sounds okay until you put it next to the benchmark: buy-and-holding the Nikkei over the same period returned &lt;strong&gt;+138.8%&lt;/strong&gt; (with a −26% max drawdown). My "strategy" wasn't a strategy; it was an expensive way to sit out a bull market.&lt;/p&gt;

&lt;p&gt;Verdict: dead. Next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 2: earnings drift (PEAD) — killed by a single filter
&lt;/h2&gt;

&lt;p&gt;Post-earnings announcement drift is one of the best-documented anomalies: stocks that beat guidance keep drifting up for weeks. On large caps, my backtest found nothing — no monotonicity across surprise sizes, slightly negative drift everywhere. Institutional money has eaten that edge.&lt;/p&gt;

&lt;p&gt;Small caps looked genuinely exciting: 276 stocks, 1,385 earnings events, 423 trades. The threshold sweep was beautifully monotonic — bigger guidance beats, bigger drift: +1.6% → +2.1% → +2.7% → +3.4% per trade as the threshold rose.&lt;/p&gt;

&lt;p&gt;Then I asked Claude Code to add one boring, adult filter: &lt;strong&gt;only keep stocks with at least ¥100M in daily trading value&lt;/strong&gt; — i.e., stocks I could actually buy without moving the price.&lt;/p&gt;

&lt;p&gt;The edge evaporated: &lt;strong&gt;−0.34%&lt;/strong&gt; per trade on the 33 surviving trades. At ¥300M/day it got worse (−2.48%).&lt;/p&gt;

&lt;p&gt;The anomaly was real, but it lives exclusively in stocks too illiquid to trade at size. This was the single most valuable chart the pipeline ever produced, and it's a &lt;em&gt;negative&lt;/em&gt; result. If your backtest doesn't include an executability filter, it isn't a backtest — it's fan fiction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 3: small-cap value × quality — the survivor
&lt;/h2&gt;

&lt;p&gt;Third idea: a quarterly-rebalanced screen. Rank small caps by valuation (with quality guards: equity ratio ≥ 25%, positive forecast EPS, and a filter against one-off earnings spikes), split into quintiles, hold the cheapest.&lt;/p&gt;

&lt;p&gt;This one behaved differently. The cheapest quintile (Q1) returned &lt;strong&gt;+8.89% per quarter&lt;/strong&gt;, returns were fully monotonic down the ranks, the cheap-vs-expensive spread was +8.96% per quarter, and Q1 beat the universe average in &lt;strong&gt;all six&lt;/strong&gt; backtest quarters.&lt;/p&gt;

&lt;p&gt;And the crucial difference from PEAD: &lt;strong&gt;the edge survived the liquidity filter.&lt;/strong&gt; Restricted to ≥¥100M/day stocks, Q1 still returned +10.06% per quarter (+78.7% cumulative vs +42.2% for the universe). The sign didn't flip.&lt;/p&gt;

&lt;p&gt;One strategy out of three survived its own audit. That's the pipeline working as intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the paper trade
&lt;/h2&gt;

&lt;p&gt;A backtest that survives in-sample is still just a hypothesis, so the surviving strategy went into "Phase 0": a paper portfolio tracked automatically every trading day, with a pre-committed bar to clear — beat the benchmark over 2–3 months &lt;em&gt;without touching the parameters&lt;/em&gt; — before real money scales beyond pocket change.&lt;/p&gt;

&lt;p&gt;The automation is deliberately low-tech. A launchd job fires at 15:45 JST on weekdays (after the Tokyo close) and runs a shell script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/zsh&lt;/span&gt;
&lt;span class="c"&gt;# Runs from launchd on weekdays at 15:45 JST.&lt;/span&gt;
&lt;span class="c"&gt;# 1) Record any not-yet-entered positions at today's opening price&lt;/span&gt;
&lt;span class="c"&gt;# 2) Append the daily report to the log, push a summary to macOS notifications&lt;/span&gt;
&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/work/makemoney"&lt;/span&gt;
&lt;span class="nv"&gt;PY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;/.venv/bin/python"&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"===== &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%F&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; ====="&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; phase0_track.py open &lt;span class="nt"&gt;--date&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%F&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;report&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; phase0_track.py report&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$report&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;/data/phase0_daily.log"&lt;/span&gt;

&lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$report&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Portfolio"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"display notification &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$summary&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; with title &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Phase 0&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Python side is a small CLI with two subcommands. &lt;code&gt;open&lt;/code&gt; records entries at the day's opening price (same conditions as the fractional-share "opening auction" orders I'd use with real money) with an idempotency guard, so re-running never double-records:&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="c1"&gt;# double-entry guard
&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open_positions&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;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strategy&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="n"&gt;STRATEGY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;lst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;lst&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="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&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;report&lt;/code&gt; marks the portfolio to market and compares it against two benchmarks (Nikkei 225 and a TOPIX ETF) &lt;em&gt;measured from the same entry date&lt;/em&gt; — the comparison that Round 1 taught me never to skip. The result lands in a log file and a macOS notification. Total infrastructure cost: ¥0.&lt;/p&gt;

&lt;h2&gt;
  
  
  One month in: the honest numbers
&lt;/h2&gt;

&lt;p&gt;As of 2026-08-14, one month after entry (18 positions, a small paper portfolio):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Portfolio: +4.32%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Nikkei 225: +2.55%&lt;/li&gt;
&lt;li&gt;TOPIX ETF: &lt;strong&gt;+4.66%&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beating the Nikkei, narrowly losing to TOPIX. Inside the portfolio the dispersion is exactly what a small-cap quintile bet looks like: the best position is +46%, the worst is −33%. The daily report ends with a line I wrote for my own discipline: &lt;em&gt;"The pass bar is 2–3 months against the benchmark. Do not react to daily noise."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Maybe it clears the bar in October. Maybe it doesn't and the strategy joins the graveyard with the other three. Either outcome is the system working.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude Code was actually good at
&lt;/h2&gt;

&lt;p&gt;A few workflow notes for anyone trying something similar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Put your constraints in &lt;code&gt;CLAUDE.md&lt;/code&gt;, not in your willpower.&lt;/strong&gt; "No predictions", "never commit to main", "every research claim needs a source URL" — encoded as standing project rules, the agent enforces them even when I'm tempted not to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the agent a strategy killer, not a strategy generator.&lt;/strong&gt; LLMs are dangerously good at producing plausible-sounding trading ideas. They are &lt;em&gt;actually&lt;/em&gt; useful at implementing the boring falsification machinery — cost models, liquidity filters, benchmark alignment — fast enough that you'll really run it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit each negative result.&lt;/strong&gt; My git log reads like a lab notebook: "large-cap PEAD: no edge", "PEAD edge concentrates in illiquid names", "value×quality survives liquidity filter". Claude Code picks up this context every session and stops me from re-testing yesterday's dead idea with today's optimism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent writes the pipeline; the human owns the rules.&lt;/strong&gt; Every threshold in the system (quality guards, liquidity floor, rebalance cadence) was decided by me and frozen &lt;em&gt;before&lt;/em&gt; the evaluation window. The fastest way to fool yourself with an eager coding agent is to let it "just try a few more parameter values."&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Nothing here is investment advice — it's a build log of a personal research tool, running on pocket-money stakes precisely because the evidence isn't in yet. J-Quants data is used under its personal-use license.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>finance</category>
    </item>
  </channel>
</rss>
