Spotting scheduled insider accumulation programs

Texas Pacific Land Corp (TPL) has one of the most distinctive insider patterns visible in the SEC Form 4 feed right now: Horizon Kinetics is buying one share on the open market almost every trading day. Here is how to detect these scheduled-buy programs in real time.

Want this data via API instead of reading about it? Get a free API key →

The pattern

Pulled directly from the SEC EDGAR Form 4 feed via the EdgarKit API, last 30 business days of TPL:

  • 20 open-market purchases by Horizon Kinetics Asset Management LLC (a 10%+ owner)
  • 19 of them were exactly 1 share
  • 1 was 4 shares (that one was Director Peter Doyle)
  • Price range: $351.33 to $435.74
  • Total capital deployed: about $8,000

If you're wondering why anyone would deploy $400 at a time in a stock trading at $400+, that is the interesting question, and the pattern in the transaction sizes is the signal.

Why the standard tools miss it

Look at TPL on Yahoo Finance's insider page and you see a table of transactions with dollar totals. The one-share-a-day pattern gets flattened into "Horizon Kinetics bought $8k of stock." OpenInsider shows the trades but doesn't cluster or characterize them.

The pattern only surfaces if you:

  1. Ingest Form 4 as it files (within minutes, not the next day)
  2. Roll transactions up by reporter across a 30 to 90 day window
  3. Look at the *distribution* of transaction sizes, not just the sum

The detection code

const key = process.env.EDGARKIT_API_KEY;

async function detectScheduledAccumulation(ticker, days = 30) {
  const res = await fetch(
    `https://api.edgarkit.com/v1/insiders/transactions?ticker=${ticker}&days=${days}&code=P`,
    { headers: { 'x-api-key': key } }
  );
  const { data } = await res.json();

  // Group by reporter
  const byReporter = {};
  for (const tx of data) {
    if (!byReporter[tx.reporter_name]) byReporter[tx.reporter_name] = [];
    byReporter[tx.reporter_name].push(tx);
  }

  // Look for repeat, uniform-size buyers
  const programs = [];
  for (const [reporter, txs] of Object.entries(byReporter)) {
    if (txs.length < 8) continue;  // need at least 8 buys in the window

    const sizes = txs.map(t => Number(t.shares));
    const mode = sizes.sort((a,b) => sizes.filter(v => v === a).length - sizes.filter(v => v === b).length).pop();
    const sameSizeRatio = sizes.filter(s => s === mode).length / sizes.length;

    // Program signature: 80%+ of buys are the same size
    if (sameSizeRatio >= 0.8) {
      programs.push({
        reporter,
        transactions: txs.length,
        mode_size: mode,
        uniformity: sameSizeRatio,
        total_shares: sizes.reduce((a,b) => a+b, 0),
        price_range: [Math.min(...txs.map(t => Number(t.price_per_share))), Math.max(...txs.map(t => Number(t.price_per_share)))]
      });
    }
  }
  return programs;
}

await detectScheduledAccumulation('TPL', 30);
// [{ reporter: "HORIZON KINETICS ASSET MANAGEMENT LLC", transactions: 19, mode_size: 1, uniformity: 1.0, ... }]

What scheduled accumulation actually means

A Rule 10b5-1 plan lets an insider pre-commit to buying or selling on a schedule regardless of what they know at the time of execution. It's the standard mechanism insiders use to trade around blackout windows without breaking material-nonpublic-information rules.

A buy 1 share every business day 10b5-1 plan does four things:

  1. Occupies the top of every insider screener that sorts by date. SEC EDGAR and every third-party insider screener (OpenInsider, Yahoo Finance's insider tab, most retail broker views) defaults to newest-first. A once-a-day filer is always in the most visible slot. That's screener real estate, not just signaling. Credit to a commenter on r/investing who landed this angle cleaner than my original framing.
  2. Costs almost nothing. $400 a day is a rounding error for an entity holding millions of shares already.
  3. Cannot be gamed. Because the plan is pre-committed, the timing is not informational per se. But the *existence* of the plan is informational.
  4. Publishes a public conviction signal. Every Form 4 shows up on every screener. The insider is deliberately telling the market they still like the company, every business day.

The prior 90-day window at TPL shows the same shape: 62 purchases, mostly by Horizon, mostly small. This is a program that has been running for months.

Other tickers with similar signatures

Run the same detector across the top 30 tickers by Form 4 volume in the EdgarKit index and you find a handful of programs like this at any given time. The mode size varies (some are 1 share, some are 10, some are 100), and the interpretation is context-dependent, but the uniformity of the transaction size is the giveaway.

Turn this into a Discord alert bot

The most useful thing you can do with this signal is get pinged when new instances show up. Here is a Discord bot that polls edgarkit every 60 seconds and posts new open-market buys above your dollar threshold. 40 lines of Node, deploys anywhere.

import 'dotenv/config';

const KEY = process.env.EDGARKIT_API_KEY;
const WEBHOOK = process.env.DISCORD_WEBHOOK_URL;
const MIN_DOLLAR_VALUE = 100_000;
let lastSeenFilingId = null;

async function checkForNewBuys() {
  const res = await fetch(
    `https://api.edgarkit.com/v1/insiders/transactions?transaction_code=P&min_value=${MIN_DOLLAR_VALUE}&limit=20`,
    { headers: { 'x-api-key': KEY } }
  );
  const { data } = await res.json();

  const newBuys = lastSeenFilingId
    ? data.filter(tx => tx.filing_id > lastSeenFilingId)
    : data.slice(0, 1);

  for (const tx of newBuys.reverse()) {
    const dollars = (tx.shares * tx.price_per_share).toLocaleString();
    await fetch(WEBHOOK, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        content: `**${tx.reporter_name}** bought ${tx.shares.toLocaleString()} shares of **${tx.ticker}** at ${tx.price_per_share} (${dollars})`
      })
    });
  }
  if (data.length > 0) lastSeenFilingId = data[0].filing_id;
}

setInterval(checkForNewBuys, 60_000);

Full repo with .env.example, package.json, and a companion cluster-buy screener at github.com/mathewarena/edgarkit-examples.

Try it live

The full TPL insider page is here: edgarkit.com/company/tpl/insiders.

Signup for a free API key (10k requests/month, no card): edgarkit.com/#signup.

Real-time SEC Form 4 data, cleaned and typed, with the same latency the SEC publishes it.