Build StocksBrew

KINDAreplaces $9/mosaves $108/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A personal research cockpit whose hard problems are producing a stance that is nothing more than a visible sum of stated rules, keeping every displayed number tied to a source and a retrieval time, and fitting a daily refresh inside a data quota that is far smaller than the watchlist wants to be.

estimated effort multi-daythe files for this build are in the project pack

RuntimeNode with a server-rendered interface on the loopback address, plus a scheduled refreshStorageSQLite, one file, with every fetched document retainedPricesOne provider whose free quota you have read, with an explicit request budgetFilingsThe regulator's own full text search and submission feeds, with a declared user agentRulesOne plain file of named rules, each producing a hit with its inputs

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the local interface and the scheduled refresh.

    Get it nodejs.org, or your version manager. open ↗

    Verify node --version prints v22 or higher

  2. API keyfree tiers exist and are small; paid tiers start where the free allowance stops

    Why The daily allowance decides how many tickers the watchlist can hold, which decides the refresh design.

    Get it Register with a provider, read the current free allowance and the redistribution clause in their terms, and note both with the date.

    Verify One refresh consumes the number of requests you predicted

  3. decidefree

    Why Automated access is expected to identify itself, and using a fake address is the kind of shortcut that gets an address range blocked.

    Get it Decide which address you are willing to publish in a user agent string.

    Verify The user agent contains a real address and appears in the request log

  4. decidefree

    Why Rules chosen after seeing the data are fitted to the data, and a stance built from them means nothing.

    Get it Write each rule as a sentence with its threshold and why that threshold, and date the document.

    Verify Every rule in the code matches a sentence in that document

  5. API keyfree tiers are ample at this volume

    Why Alerts that only appear in a page you have to open are not alerts.

    Get it Any transactional sender, or a local mail relay you already run.

    Verify A threshold crossing reaches your inbox with the retrieval time in it

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- tickers(symbol, name, cik, added_at, active) · the cik is what joins a ticker to its filings
- price_snapshots(symbol, as_of_date, close, volume, source, retrieved_at, delayed) · delayed records what the provider said it was giving you
- filings(id, cik, form_type, filed_at, accession, url, title, retrieved_at, body_sha256)
- news_items(id, symbol, source_url, title, published_at, retrieved_at, dedup_key)
- rule_hits(id, symbol, rule_name, as_of_date, inputs_json, fired, explanation) · every rule writes a row whether it fired or not
- stances(symbol, as_of_date, stance, hit_ids_json, computed_at) · the stance is a function of hit_ids and nothing else
- alerts(id, symbol, kind, threshold_json, last_fired_at, cooldown_minutes, muted)
- requests(id, at, provider, endpoint, symbol, status, quota_remaining) · the request budget only works if every call is counted
- There is no column called target_price and no column called recommendation. The stance is a label over rule hits, and removing the hits removes the label.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_PATHrequired./data/research.dbPrices, filings, news, hits and stances. One file to copy.
PRICE_API_KEYsecretrequiredredactedMarket data provider. Its allowance is the constraint the refresh is built around.
DAILY_REQUEST_ALLOWANCErequired25The provider's published free allowance. Recheck it and update this.
EDGAR_USER_AGENTrequiredYour Name your.address@example.comDeclared on every filing request, as the service asks.
STALE_AFTER_HOURSrequired30Beyond this an input is stale and its symbol shows no stance.
SMTP_URLsecretrequiredsmtps://user:redacted@mail.example.com:465Where alerts go. Nothing else leaves the machine.
BIND_ADDRESSrequired127.0.0.1Loopback only. The server refuses to start on anything else.

The build, in order

  1. A refresh that fits inside the quota

    A daily update of the whole watchlist stays inside the provider's published free allowance, with every call counted.

    1. The refresh design follows from this number, not the other way round.

      Files config/providers.jsondocs/quota.md

    2. A budget you cannot audit is not a budget.

      Files src/data/budget.jstests/budget.test.js

    3. Silent degradation is worse than a clear refusal.

      Files src/watchlist/add.js

    done when · tick each as it passes
    watch out
    • Adding a retry loop to a provider with a daily allowance burns the day's budget on one failing symbol and leaves the rest of the watchlist with yesterday's prices, which then feed rules that report as though they ran. Count requests before making them, retry at most once, and mark a symbol as not refreshed rather than letting stale data flow into a rule.
  2. Filings and news that can be reopened

    Every document the tool mentions is stored with its URL, its retrieval time and a hash of what was retrieved.

    1. The regulator publishes both and expects both.

      Files src/data/edgar.js

    2. A citation you cannot reopen is a claim.

      Files src/data/filings.jstests/filings.test.js

    3. The same story from three feeds is one event.

      Files src/data/news.js

    done when · tick each as it passes
    watch out
    • Fetching a filing, extracting a sentence and keeping only the sentence produces a citation that cannot be checked three months later when the page has moved, and by then you no longer remember whether the tool or the source was wrong. Store the URL, the retrieval time and a hash of the body you actually read, and make the interface link to your stored copy alongside the original.
  3. Rules that show their work

    A stance is a visible function of named rule hits, and no hits means no stance.

    1. You will read this file more than any other.

      Files src/rules/rules.jstests/rules.test.js

    2. What did not fire is half the explanation.

      Files src/rules/evaluate.js

    3. A stance computed from Tuesday's price is fiction.

      Files src/rules/staleness.js

    done when · tick each as it passes
    watch out
    • Blending rule outputs into a weighted score produces a number that looks like a measurement and cannot be argued with, and within a fortnight you are acting on a figure whose weights you chose arbitrarily on a Tuesday. Keep the stance as a named combination of named hits, show every hit including the ones that did not fire, and never display a composite number without the hits beside it.
  4. Alerts that fire once and say how old the data is

    A threshold crossing notifies you exactly once, with the retrieval time in the message.

    1. A standing condition is not news after the first time.

      Files src/alerts/crossing.jstests/crossing.test.js

    2. Acting on a figure means knowing how old it is.

      Files src/alerts/message.js

    3. You will want the history back later.

      Files src/alerts/mute.js

    done when · tick each as it passes
    watch out
    • Alerting whenever the current value is past the threshold sends an identical message on every refresh for as long as the condition holds, which trains you to ignore the channel within two days. Compare the previous stored value with the current one and fire only on the transition, with a cooldown and a stored last-fired time.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more personal finance.