<?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: Christian Pichichero</title>
    <description>The latest articles on DEV Community by Christian Pichichero (@tradevodata).</description>
    <link>https://dev.to/tradevodata</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%2F4017923%2Fe278d800-4f1b-4a9a-8659-027387e2544f.png</url>
      <title>DEV Community: Christian Pichichero</title>
      <link>https://dev.to/tradevodata</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tradevodata"/>
    <language>en</language>
    <item>
      <title>Why a Lease Is Not Enough to Stop Duplicate Scheduled Jobs</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Thu, 05 Nov 2026 21:14:47 +0000</pubDate>
      <link>https://dev.to/tradevodata/why-a-lease-is-not-enough-to-stop-duplicate-scheduled-jobs-275h</link>
      <guid>https://dev.to/tradevodata/why-a-lease-is-not-enough-to-stop-duplicate-scheduled-jobs-275h</guid>
      <description>&lt;p&gt;A cron job that matters enough to run on two machines has changed category. It is no longer a timer with a function attached. It is a distributed system.&lt;/p&gt;

&lt;p&gt;On one machine, the scheduler starts a job and assumes it owns that execution. Add a second machine for availability, and both schedulers may observe that the same job is due. If duplicate work means sending the same cleanup query twice, that may be acceptable. If the job submits a settlement instruction or broadcasts a blockchain transaction, the second execution may be irreversible.&lt;/p&gt;

&lt;p&gt;This is where “at most once” stops being free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic lease
&lt;/h2&gt;

&lt;p&gt;A common coordination mechanism is a lease stored in a shared database. The row usually contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A job identifier&lt;/li&gt;
&lt;li&gt;The current owner&lt;/li&gt;
&lt;li&gt;An expiry timestamp&lt;/li&gt;
&lt;li&gt;A monotonically increasing fencing token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Acquisition must be atomic. Two workers cannot read an expired row and then both decide they own it. A transaction, row lock, or compare-and-swap update must select one winner.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;with transaction:
    lease = lock(job_id)

    if lease.expires_at &amp;lt;= database_time():
        lease.owner = this_worker
        lease.expires_at = database_time() + lease_duration
        lease.fence = lease.fence + 1
        acquired_fence = lease.fence
    else:
        acquisition_failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the lease store’s clock avoids making acquisition depend on the wall clocks of separate workers. The owner can renew the lease while running, provided the update checks both its identity and fencing token.&lt;/p&gt;

&lt;p&gt;This prevents normal overlap. It does not stop a stale process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The paused-worker failure
&lt;/h2&gt;

&lt;p&gt;Suppose scheduler A acquires a lease with fencing token 41. It reads the input and then pauses for longer than the lease duration. Causes include a long garbage-collection pause, CPU starvation, a frozen virtual machine, or a network partition.&lt;/p&gt;

&lt;p&gt;The lease expires. Scheduler B acquires it with token 42 and performs the job.&lt;/p&gt;

&lt;p&gt;Scheduler A then resumes. Its local process still has the state saying it acquired the lease. Checking its own wall clock does not repair the situation. Even if it checks the database before acting, another pause can occur between that check and the external side effect.&lt;/p&gt;

&lt;p&gt;A lease answers, “Who may work during this time window?” It cannot, by itself, make an old worker forget what it was doing.&lt;/p&gt;

&lt;p&gt;Longer lease durations reduce how often this race appears, but also delay recovery when a worker actually dies. Frequent renewal narrows the window but does not remove process pauses or network uncertainty. A timeout is a failure detector, not proof that the old worker has stopped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fencing the side effect
&lt;/h2&gt;

&lt;p&gt;A fencing token turns each successful acquisition into a newer generation. Every operation sent to the protected resource includes that token.&lt;/p&gt;

&lt;p&gt;The resource records the highest token it has accepted. Once it has seen token 42, it rejects an operation carrying token 41. Scheduler A can wake up, but it can no longer act as the current owner.&lt;/p&gt;

&lt;p&gt;For a database-owned resource, the check can be part of the same transaction as the write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if request.fence &amp;lt; resource.highest_fence:
    reject_as_stale
else:
    resource.highest_fence = request.fence
    apply_operation()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Equal tokens may need separate operation identifiers so retries from the current owner are deduplicated. Fencing distinguishes lease generations; it does not replace request-level idempotency.&lt;/p&gt;

&lt;p&gt;The important limitation is that the resource performing the side effect must participate. Putting a token in application logs does nothing. If an external API cannot inspect a fencing token, the scheduler cannot force it to reject a stale caller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Irreversible execution exposes the gap
&lt;/h2&gt;

&lt;p&gt;Consider a scheduled job that constructs and submits a blockchain transaction. Scheduler A prepares the action and pauses. Scheduler B takes the newer lease and submits its transaction. Scheduler A resumes and submits a separately constructed valid transaction.&lt;/p&gt;

&lt;p&gt;The old lease cannot unsend either transaction. Both may be accepted if they are distinct and valid under the relevant account rules.&lt;/p&gt;

&lt;p&gt;Rebroadcasting identical signed bytes is a different case because the transaction identity can provide deduplication. That protection disappears when separate executions create distinct transactions, use different nonces, or represent the same business action through different requests.&lt;/p&gt;

&lt;p&gt;A duplicated email is usually an annoyance that can be observed and handled. A duplicated asset transfer or swap changes durable state. Detection after the fact is not coordination.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to use when the destination cannot fence
&lt;/h2&gt;

&lt;p&gt;If the destination has no fencing mechanism, move deduplication to the narrowest durable boundary you control:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assign a stable execution ID to each logical schedule slot, such as &lt;code&gt;job-name/2026-09-02T10:00Z&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Store that ID under a uniqueness constraint before creating the external action.&lt;/li&gt;
&lt;li&gt;Use an outbox so job-state changes and dispatch intent are committed together.&lt;/li&gt;
&lt;li&gt;Pass the execution ID as an idempotency key when the destination supports one.&lt;/li&gt;
&lt;li&gt;Separate retries of one logical execution from creation of a new execution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An outbox alone still has an ambiguity window: a dispatcher can send a request and crash before recording the acknowledgement. Endpoint idempotency or a fenced single writer is needed to close that gap.&lt;/p&gt;

&lt;p&gt;If no layer can reject duplicate requests, honest semantics are limited. Marking a job complete before the side effect can avoid duplicate attempts but may lose work after a crash. Recording completion afterward supports retries but permits duplicate execution. That is a product decision, not a database trick.&lt;/p&gt;

&lt;p&gt;Disclosure: I build &lt;a href="https://app.tradevo.co/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=leases-and-the-two-schedulers-problem" rel="noopener noreferrer"&gt;Tradevo&lt;/a&gt;, software where creators describe an automated crypto strategy in plain English, historically test and paper-trade its deterministic rules, submit it for human review, set a free or monthly creator price, and publish approved strategies, while other users can discover, subscribe to, allocate USDC to, and run published strategies from a wallet they control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical rule
&lt;/h2&gt;

&lt;p&gt;Use leases to select an active worker. Use fencing tokens to reject workers from expired lease generations. Use stable operation IDs to deduplicate retries. For irreversible side effects, verify that the final boundary enforces one of those mechanisms.&lt;/p&gt;

&lt;p&gt;Without that final enforcement point, “we have a distributed lock” describes an intention, not at-most-once execution.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>architecture</category>
      <category>distributedsystems</category>
      <category>postgres</category>
    </item>
    <item>
      <title>What Each Test Layer Proves When Code Moves Funds</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Thu, 29 Oct 2026 19:52:49 +0000</pubDate>
      <link>https://dev.to/tradevodata/what-each-test-layer-proves-when-code-moves-funds-16p4</link>
      <guid>https://dev.to/tradevodata/what-each-test-layer-proves-when-code-moves-funds-16p4</guid>
      <description>&lt;p&gt;Your onchain executor can pass its unit tests, produce valid calldata, and still send funds through an unintended path. The difficult part is not writing more tests. It is knowing what each test environment proves and where its evidence stops.&lt;/p&gt;

&lt;p&gt;A useful testing ladder has distinct stages: pure simulation, forked-chain execution, a production canary using a trivial amount, and staged production use. None is a substitute for the others.&lt;/p&gt;

&lt;p&gt;The most dangerous design is often a &lt;code&gt;dryRun&lt;/code&gt; boolean scattered across the codebase. If some transaction paths respect it while another path broadcasts, the dry run creates false confidence. A system with no dry-run mode is visibly dangerous. A partially enforced one looks safe until the missed branch moves funds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a deterministic execution plan
&lt;/h2&gt;

&lt;p&gt;Separate deciding what to do from doing it. The planner should consume explicit inputs and produce an execution plan without holding signing or broadcast capabilities.&lt;/p&gt;

&lt;p&gt;A plan might contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;chain identifier&lt;/li&gt;
&lt;li&gt;contract and token addresses&lt;/li&gt;
&lt;li&gt;calldata&lt;/li&gt;
&lt;li&gt;value to send&lt;/li&gt;
&lt;li&gt;approval requirements&lt;/li&gt;
&lt;li&gt;slippage bounds&lt;/li&gt;
&lt;li&gt;deadline&lt;/li&gt;
&lt;li&gt;expected state transitions&lt;/li&gt;
&lt;li&gt;reasons the action should be rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same planner should run in simulation, fork tests, canaries, and production. Environment-specific code should sit behind the transport boundary.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;market snapshot + strategy state
              |
              v
       deterministic planner
              |
              v
       validated transaction plan
              |
      +-------+--------+
      |                |
 simulation transport  live transport
 no signing material   signing + broadcast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is stronger than checking a flag immediately before an RPC call. In simulation mode, the process should not load a signer capable of broadcasting. The restriction is structural rather than conventional.&lt;/p&gt;

&lt;p&gt;Pure planning tests are good at checking arithmetic, rounding, token decimals, state transitions, allocation rules, and invariants such as “a rejected plan cannot contain an executable transaction.” Property-based tests are useful here because many failures live at boundaries: zero balances, minimum trade sizes, stale timestamps, or values just inside a slippage limit.&lt;/p&gt;

&lt;p&gt;What this layer does badly is represent external state. A mock contract behaves as its author expects, not necessarily as the deployed contract behaves. A simulated quote does not expose an RPC outage, a changed pool state, or a transaction competing in the public mempool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a fork to test contract interactions
&lt;/h2&gt;

&lt;p&gt;A forked chain lets the executor interact with deployed bytecode and a captured state snapshot. This catches errors that pure simulation tends to miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;incorrect ABI encoding&lt;/li&gt;
&lt;li&gt;approval and allowance assumptions&lt;/li&gt;
&lt;li&gt;token decimal mistakes&lt;/li&gt;
&lt;li&gt;revert conditions in deployed contracts&lt;/li&gt;
&lt;li&gt;multi-call ordering problems&lt;/li&gt;
&lt;li&gt;unexpected behavior from non-standard tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fork tests should execute the complete sequence, not isolated calls. If production performs an approval followed by a swap, the test should run that sequence through the same transaction-building code.&lt;/p&gt;

&lt;p&gt;Pin the block used by the test. Without a pinned state, a passing test can become a failing test because liquidity, balances, or contract state changed rather than because the code changed. It is also useful to maintain fixtures for awkward historical states instead of testing only a convenient recent snapshot.&lt;/p&gt;

&lt;p&gt;A fork still does not reproduce production. It usually omits real RPC latency, provider rate limits, signer failures, relayer behavior, gas sponsorship, mempool ordering, and reorgs. Local mining also tends to be more predictable than public block production. A fork proves that a transaction can execute under the captured state. It does not prove that the transaction will execute under the state present when production broadcasts it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make dry run a capability boundary
&lt;/h2&gt;

&lt;p&gt;A safe dry run should be incapable of broadcasting, not merely instructed not to.&lt;/p&gt;

&lt;p&gt;Keep the live signer and broadcast client out of the dry-run process. Give that process a simulation transport that supports calls and gas estimation but has no method or credentials for sending transactions. If a code path unexpectedly requests a broadcast, it should fail closed and emit an observable error.&lt;/p&gt;

&lt;p&gt;Then test the boundary itself. Exercise every action type through dry-run mode and assert that no live transport was constructed. Include cancellation, retries, approval refreshes, cleanup jobs, and administrative paths. Background workers are easy to overlook because the visible strategy path may respect dry run while a retry queue still holds an executable payload.&lt;/p&gt;

&lt;p&gt;Audit logs should record the plan, validation decisions, simulation result, selected transport, and whether signing material was loaded. Logs are not an enforcement mechanism, but they make boundary violations easier to diagnose.&lt;/p&gt;

&lt;p&gt;The trade-off is duplication pressure. Simulation and live transports can drift. Reduce that risk by sharing the planner, serializer, and validators while keeping the final send capability separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a canary to test the production path
&lt;/h2&gt;

&lt;p&gt;A canary sends a real transaction using the production RPC, signer, policy checks, monitoring, and confirmation logic, but with the smallest practical exposure permitted by the venue and token precision.&lt;/p&gt;

&lt;p&gt;This stage can reveal configuration errors that earlier layers cannot: wrong chain selection, unavailable signing credentials, broken gas handling, provider restrictions, stale configuration, and monitoring that watches the wrong address.&lt;/p&gt;

&lt;p&gt;A canary is still weak evidence. It covers a particular route under a particular market state. It may not exercise retries, cancellation, partial completion, or adverse ordering. Success should not cause the system to bypass limits or broaden access automatically.&lt;/p&gt;

&lt;p&gt;Canaries also need containment. Use a dedicated wallet, strict token permissions, bounded transaction value, and alerts tied to expected transaction hashes and state changes. Stop after unexpected behavior rather than repeatedly retrying an uncertain action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production needs continuing controls
&lt;/h2&gt;

&lt;p&gt;After a canary, staged activation is safer than treating deployment as the end of testing. Keep explicit limits on value, action frequency, allowed contracts, tokens, and slippage. Monitor both submitted transactions and intended plans so that “nothing happened” is detectable alongside incorrect execution.&lt;/p&gt;

&lt;p&gt;Plan for ambiguous outcomes. An RPC timeout does not establish that a transaction was rejected. Before retrying, inspect the transaction hash, sender nonce, and chain state. Blind retries can duplicate an action when the initial request reached the network but its response did not reach the executor.&lt;/p&gt;

&lt;p&gt;Pausing should block new plans at the scheduler and block broadcasts at the transport boundary. Relying on one check leaves another path available.&lt;/p&gt;

&lt;p&gt;Disclosure: I build &lt;a href="https://app.tradevo.co/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=testing-code-that-spends-real-money" rel="noopener noreferrer"&gt;Tradevo&lt;/a&gt;, software where creators describe crypto strategies in plain English, historically test and paper-trade the compiled deterministic rules, submit them for human review, set a free or monthly price, and publish approved strategies, while other users can discover, subscribe to, allocate USDC to, and automatically run those rules from their own wallets.&lt;/p&gt;

&lt;p&gt;The testing ladder works because each stage answers a narrower question. Simulation checks planning logic. Forks check interaction with captured contract state. Canaries check the real operational path with contained exposure. Production controls limit what happens when assumptions fail. Treating any stage as proof of the next is where expensive mistakes begin.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>web3</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>Free Financial Data APIs for Backtesting: An Honest Comparison</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Sat, 24 Oct 2026 17:32:08 +0000</pubDate>
      <link>https://dev.to/tradevodata/free-financial-data-apis-for-backtesting-an-honest-comparison-17di</link>
      <guid>https://dev.to/tradevodata/free-financial-data-apis-for-backtesting-an-honest-comparison-17di</guid>
      <description>&lt;p&gt;Free financial data can support serious backtesting—but only if the source matches the question.&lt;/p&gt;

&lt;p&gt;A price-momentum test needs historical prices and corporate-action handling. A macro strategy needs revision-aware economic series. A fundamental strategy needs values as investors actually knew them on each historical date. Those are different data problems.&lt;/p&gt;

&lt;p&gt;This guide compares SEC EDGAR, yfinance, Stooq, FRED, Alpha Vantage’s free tier, and Tradevo Data’s public proof pack. The goal is not to declare one winner. It is to show where each source may be useful, where it can fail, and whether it is point-in-time safe for the intended test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SEC EDGAR:&lt;/strong&gt; a free primary source for US filings, but substantial engineering is required to produce clean point-in-time panels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;yfinance:&lt;/strong&gt; convenient for exploration and price-data prototypes, but do not assume every historical or fundamental field is point-in-time safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stooq:&lt;/strong&gt; potentially useful for accessible market-history research; validate adjustments, symbols, and coverage before relying on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FRED:&lt;/strong&gt; useful for macroeconomic research, with ALFRED often the better choice when historical revisions matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpha Vantage free tier:&lt;/strong&gt; convenient packaged APIs for prototypes; check current limits, history, adjustment rules, and PIT behavior in its documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tradevo Data:&lt;/strong&gt; an honest budget-tier option for research-grade point-in-time US equity fundamentals, with a free public proof pack and a paid API. It is not a broad market-data terminal and does not cover prices, non-US companies, or delisted companies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Free access&lt;/th&gt;
&lt;th&gt;Point-in-time-safe?&lt;/th&gt;
&lt;th&gt;Main caveat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.sec.gov/edgar" rel="noopener noreferrer"&gt;SEC EDGAR&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Primary US filing data&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Potentially, if modeled by public filing date&lt;/td&gt;
&lt;td&gt;Raw filings, amendments, tags, units, fiscal periods, and issuer history require careful processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/ranaroussi/yfinance" rel="noopener noreferrer"&gt;yfinance&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Fast price-data exploration and notebooks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not automatically&lt;/td&gt;
&lt;td&gt;A convenient interface does not itself prove that historical fundamentals were available on the simulated date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://stooq.com/" rel="noopener noreferrer"&gt;Stooq&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Downloadable market history and prototyping&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Depends on field and methodology&lt;/td&gt;
&lt;td&gt;Verify corporate-action adjustments, survivorship, identifiers, and coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://fred.stlouisfed.org/" rel="noopener noreferrer"&gt;FRED&lt;/a&gt; / &lt;a href="https://alfred.stlouisfed.org/" rel="noopener noreferrer"&gt;ALFRED&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Macroeconomic and rate series&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;FRED latest values: not necessarily; ALFRED vintages: potentially suitable&lt;/td&gt;
&lt;td&gt;Release lags and revisions must be modeled explicitly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.alphavantage.co/" rel="noopener noreferrer"&gt;Alpha Vantage&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Packaged market and fundamental APIs&lt;/td&gt;
&lt;td&gt;Free tier available&lt;/td&gt;
&lt;td&gt;Do not assume&lt;/td&gt;
&lt;td&gt;Check current limits, output history, adjustment methods, and filing-time semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;Tradevo Data proof pack&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Inspecting PIT US fundamentals and methodology&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Designed for PIT research&lt;/td&gt;
&lt;td&gt;Small proof pack: 5 companies, latest 3 fiscal years, and 225 rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://tradevodata.com/?utm_content=blog-free-financial-data-apis-for-backtesting&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;Tradevo Data API&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Budget-tier PIT US equity fundamentals&lt;/td&gt;
&lt;td&gt;Card-backed 7-day trial; renews at $29/mo unless canceled&lt;/td&gt;
&lt;td&gt;Uses &lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No prices, TTM, non-US, delisted, or Parquet coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;“Point-in-time-safe” is not a permanent badge attached to a vendor. It depends on the field, timestamp, revision policy, universe construction, and how your backtest queries the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. SEC EDGAR: a free primary source
&lt;/h2&gt;

&lt;p&gt;SEC EDGAR is a primary source when you need US company disclosures. The underlying filing data is public domain, and publication information can be used to determine when facts became public.&lt;/p&gt;

&lt;p&gt;That does not make an EDGAR download immediately backtest-ready.&lt;/p&gt;

&lt;p&gt;You still need to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;original filings and amendments;&lt;/li&gt;
&lt;li&gt;company and ticker identity changes;&lt;/li&gt;
&lt;li&gt;XBRL concepts that vary across issuers;&lt;/li&gt;
&lt;li&gt;duplicate facts, units, dimensions, and contexts;&lt;/li&gt;
&lt;li&gt;annual, quarterly, year-to-date, and instant-period distinctions;&lt;/li&gt;
&lt;li&gt;fiscal calendars and restatements;&lt;/li&gt;
&lt;li&gt;publication-time cutoffs;&lt;/li&gt;
&lt;li&gt;QA and reproducible snapshots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EDGAR can support point-in-time research because publication events are available. Your resulting database is PIT-safe only if your transformation preserves and models them correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  When EDGAR wins
&lt;/h3&gt;

&lt;p&gt;Build directly from EDGAR when you need custom tags, filing text, footnotes, dimensions, forms outside a standardized dataset, or complete control over methodology. It may also win when cash cost matters more than engineering time.&lt;/p&gt;

&lt;p&gt;For background, see &lt;a href="https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=free-financial-data-apis-for-backtesting" rel="noopener noreferrer"&gt;point-in-time fundamentals data&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. yfinance: convenient, with limited guarantees
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;yfinance&lt;/code&gt; is popular because it makes exploratory work easy. It can be useful for pulling historical prices, testing notebook logic, checking symbols, or building a first-pass prototype.&lt;/p&gt;

&lt;p&gt;The risk is treating interface convenience as evidence of point-in-time correctness.&lt;/p&gt;

&lt;p&gt;For price backtests, inspect whether you use adjusted or unadjusted prices and how dividends and splits are represented. For fundamental backtests, ask whether a field is today’s reconstructed history or the value actually available on the simulated date. If the source does not expose publication timestamps and revision vintages, do not infer PIT behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  When yfinance wins
&lt;/h3&gt;

&lt;p&gt;It may win when speed and accessibility matter more than contractual or PIT guarantees: teaching, exploratory analysis, rough price-based prototypes, and sanity checks. If your strategy does not depend on historical accounting vintages, a specialized PIT fundamentals product may be unnecessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Stooq: practical market history with validation required
&lt;/h2&gt;

&lt;p&gt;Stooq offers accessible historical market data and may be useful for research prototypes. Its downloads can be attractive when you want to get a price-based test running without building an ingestion system.&lt;/p&gt;

&lt;p&gt;As with any market-history source, validate the exact dataset you consume. Questions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are prices adjusted, and for which corporate actions?&lt;/li&gt;
&lt;li&gt;Are delisted securities represented?&lt;/li&gt;
&lt;li&gt;How stable are symbols and identifiers?&lt;/li&gt;
&lt;li&gt;What are the timezone and session conventions?&lt;/li&gt;
&lt;li&gt;Does your universe introduce survivorship bias?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Historical prices avoid some accounting-publication issues, but they do not automatically solve corporate-action, delisting, or universe bias.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Stooq wins
&lt;/h3&gt;

&lt;p&gt;It may win for low-cost price research, cross-checking, and prototypes where its available markets and conventions fit your design. Review its documentation and test known events before committing to a production pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. FRED and ALFRED: consider vintages for macro backtests
&lt;/h2&gt;

&lt;p&gt;FRED is a widely used free source for economic and financial series. It can support research involving rates, inflation, labor, production, and other macro variables.&lt;/p&gt;

&lt;p&gt;The central backtesting trap is revision history. Today’s value for a historical month may not equal the value investors saw at the time. Economic releases can be revised repeatedly.&lt;/p&gt;

&lt;p&gt;ALFRED preserves vintages for many series, making it potentially more appropriate when a strategy responds to what was known on a historical date. You must still model release timing. A value labeled for one month may have been published later.&lt;/p&gt;

&lt;h3&gt;
  
  
  When FRED or ALFRED wins
&lt;/h3&gt;

&lt;p&gt;They may win for macro signals rather than equity-fundamentals research. Consider ALFRED vintages where revisions could affect the result; standard FRED data may be sufficient when latest-revised history is explicitly what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Alpha Vantage: packaged APIs for quick prototypes
&lt;/h2&gt;

&lt;p&gt;Alpha Vantage packages multiple categories of financial data behind documented endpoints and offers a free tier. That may reduce setup work compared with normalizing multiple sources.&lt;/p&gt;

&lt;p&gt;Before using it in a backtest, see &lt;a href="https://www.alphavantage.co/premium/" rel="noopener noreferrer"&gt;its pricing page&lt;/a&gt; and documentation. Confirm current request limits, historical depth, corporate-action treatment, field definitions, and whether fundamental values are tied to filing-availability dates.&lt;/p&gt;

&lt;p&gt;A reported fiscal-period end is not an availability date. Joining a December year-end value to December prices can leak information from a filing published later.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Alpha Vantage wins
&lt;/h3&gt;

&lt;p&gt;It may win when you want one packaged interface, modest prototype volume, or data categories outside a narrow PIT fundamentals product. Evaluate each endpoint separately rather than assuming one methodology applies across the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Tradevo Data: inspect the free PIT proof pack first
&lt;/h2&gt;

&lt;p&gt;Tradevo Data provides point-in-time US equity fundamentals from SEC EDGAR public-domain data. The public &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;GitHub proof pack&lt;/a&gt; contains 5 companies, their latest 3 fiscal years, 225 rows, and the full methodology, with no signup.&lt;/p&gt;

&lt;p&gt;Each row includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;first_filed&lt;/code&gt;: the date the value became public;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;original_value&lt;/code&gt;: the first-reported PIT-safe value;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;latest_value&lt;/code&gt;: the current revision;&lt;/li&gt;
&lt;li&gt;a restatement flag for changes greater than 0.5% on the same tag, including amendments;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;qa_status&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The annual dataset contains 632,466 point-in-time rows across 5,168 US companies, 16 annual concepts, up to 12 fiscal years, and 37,804 labeled restatements. The concepts are Revenue, NetIncome, Assets, StockholdersEquity, OperatingCashFlow, EPSDiluted, DilutedShares, GrossProfit, OperatingIncome, PretaxIncome, IncomeTaxExpense, CapitalExpenditures, CashAndCashEquivalents, CurrentAssets, CurrentLiabilities, and NetPPE.&lt;/p&gt;

&lt;p&gt;Quarterly coverage includes 1,089,635 rows across 5,382 companies and 7 quarterly concepts. Q4 is reported where tagged or derived and labeled where supported. Derived Q4 EPS and shares are not provided.&lt;/p&gt;

&lt;p&gt;The JSON endpoint is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/v1/fundamentals?ticker&amp;amp;as_of[&amp;amp;concept][&amp;amp;period=annual|quarterly]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Annual is the default period. The server applies &lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt;, inclusive on the date. This addresses one common form of &lt;a href="https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=free-financial-data-apis-for-backtesting" rel="noopener noreferrer"&gt;lookahead bias in fundamental backtests&lt;/a&gt;, but users must still handle matters such as trading-session timing and universe construction.&lt;/p&gt;

&lt;p&gt;The card-backed 7-day free trial covers 10 companies, a rolling 3-year history, and 100 requests per day, with no bulk. It renews at $29/mo unless canceled. Pro provides complete available history, 5,000 requests per day, and bulk access through &lt;code&gt;/v1/download&lt;/code&gt; and &lt;code&gt;/v1/snapshot?as_of&lt;/code&gt;, each with &lt;code&gt;period=annual|quarterly&lt;/code&gt;. Parquet is not included.&lt;/p&gt;

&lt;p&gt;This is intentionally narrower than many alternatives: no price data, TTM, non-US companies, or delisted-company coverage.&lt;/p&gt;

&lt;p&gt;Review the free proof pack before considering the API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;https://github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tradevodata.com/?utm_content=blog-free-financial-data-apis-for-backtesting&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/?utm_content=blog-free-financial-data-apis-for-backtesting&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Documentation: &lt;a href="https://tradevodata.com/docs?utm_content=blog-free-financial-data-apis-for-backtesting&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/docs?utm_content=blog-free-financial-data-apis-for-backtesting&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  When Tradevo Data wins
&lt;/h3&gt;

&lt;p&gt;It may win when you want standardized point-in-time US equity fundamentals, server-side historical-date filtering, restatement fields, and JSON bulk access without building an EDGAR normalization pipeline. It does not win when you need prices, filing text, custom XBRL tags, TTM metrics, non-US or delisted coverage, Parquet, or a broader multi-asset platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Point-in-time safety checklist
&lt;/h2&gt;

&lt;p&gt;Whatever source you choose, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What was known?&lt;/strong&gt; Is the value original, latest-restated, or unspecified?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When was it known?&lt;/strong&gt; Use a publication or filing date, not merely a fiscal-period end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Was the date available intraday?&lt;/strong&gt; Daily backtests may need a next-session rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Was the security in the universe then?&lt;/strong&gt; A current constituent list creates survivorship bias.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How were prices adjusted?&lt;/strong&gt; Splits, dividends, and symbol changes can alter results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are revisions preserved?&lt;/strong&gt; This matters for both company fundamentals and macro data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can you reproduce a snapshot?&lt;/strong&gt; Save raw responses, query dates, documentation versions, and code.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When to build it yourself
&lt;/h2&gt;

&lt;p&gt;Build your own pipeline when your research requires data no standardized product exposes, when you need auditable transformation control, or when ongoing engineering is part of your process.&lt;/p&gt;

&lt;p&gt;For EDGAR fundamentals, expect work involving taxonomy mapping, amendment handling, fiscal-period inference, duplicate resolution, QA, identity history, and refresh monitoring. “Free data” means no license fee; it does not mean zero cost.&lt;/p&gt;

&lt;p&gt;Do not assume that easy ingestion makes a dataset PIT-safe. The difficult work is establishing that every historical row was available when your simulation says it was.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical selection rule
&lt;/h2&gt;

&lt;p&gt;Choose the narrowest source that answers your research question honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;price prototype: yfinance or Stooq may be enough;&lt;/li&gt;
&lt;li&gt;macro strategy: consider FRED, or ALFRED when revisions matter;&lt;/li&gt;
&lt;li&gt;custom US filing research: build from SEC EDGAR;&lt;/li&gt;
&lt;li&gt;packaged multi-category prototype: evaluate Alpha Vantage endpoint by endpoint;&lt;/li&gt;
&lt;li&gt;standardized PIT US fundamentals: inspect Tradevo Data’s free proof pack, then compare its API with credible alternatives such as &lt;a href="https://data.nasdaq.com/publishers/SHARADAR" rel="noopener noreferrer"&gt;Sharadar&lt;/a&gt;, &lt;a href="https://www.tiingo.com/pricing" rel="noopener noreferrer"&gt;Tiingo&lt;/a&gt;, and &lt;a href="https://www.quantconnect.com/pricing" rel="noopener noreferrer"&gt;QuantConnect&lt;/a&gt;. See each provider’s pricing page and documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No vendor removes the need for validation. A defensible backtest starts with explicit assumptions about availability, revisions, adjustments, and universe membership—not with the prettiest API response.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice. Verify current competitor features, documentation, and pricing on each provider’s website.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>“Non-Custodial” Is a Spectrum, Not a Boolean</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Thu, 22 Oct 2026 18:51:10 +0000</pubDate>
      <link>https://dev.to/tradevodata/non-custodial-is-a-spectrum-not-a-boolean-475k</link>
      <guid>https://dev.to/tradevodata/non-custodial-is-a-spectrum-not-a-boolean-475k</guid>
      <description>&lt;p&gt;When a wallet integration is described as “non-custodial,” the useful engineering question is not whether the label applies. It is: &lt;strong&gt;who can move the assets, under what conditions, and who must cooperate to end that access?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That framing exposes distinctions hidden by a binary label. An app may never possess a user’s root key while still operating a delegated signer that the user cannot directly disable. Another system may split key material across several parties, none of which can act alone. Both may call themselves non-custodial, but their failure modes are different.&lt;/p&gt;

&lt;p&gt;Here is a practical spectrum, ordered from stronger to weaker unilateral user control. Lower positions are weaker specifically in the user’s ability to exclude other parties without their cooperation. They may still offer better recovery, automation, or support.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A plain externally owned account
&lt;/h2&gt;

&lt;p&gt;With a plain EOA, one private key controls the account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who can move assets?&lt;/strong&gt; Anyone with that private key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who must cooperate for third-party access to end?&lt;/strong&gt; No third party has access unless the key was shared or compromised. If it was compromised, access cannot be selectively revoked. The user must move assets to a new account.&lt;/p&gt;

&lt;p&gt;This model is simple to reason about, but unforgiving. There is no native permission scoping, spending limit, signer expiry, or recovery process. Automation usually means exposing the key to software that must remain available, which undermines the reason for choosing the model.&lt;/p&gt;

&lt;p&gt;An EOA provides strong independence from operators, not necessarily strong operational security.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A smart account with a user-revocable permission
&lt;/h2&gt;

&lt;p&gt;A smart account can let an owner authorize another key to perform a restricted set of actions. The restriction might cover certain contracts, tokens, methods, time periods, or spending amounts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who can move assets?&lt;/strong&gt; The owner and any signer accepted by the account’s current contract logic, each within whatever rules that logic enforces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who must cooperate for delegated access to end?&lt;/strong&gt; If the owner can submit an on-chain transaction that disables the permission, no operator needs to cooperate. The owner does need the chain to include that transaction and must retain whatever authority the account recognizes as its owner.&lt;/p&gt;

&lt;p&gt;This is a meaningful improvement for automation because revocation is independently verifiable and user-initiated. It also adds contract risk. A bug in permission checks, upgrade logic, or account recovery can be more consequential than a broken user interface.&lt;/p&gt;

&lt;p&gt;The important evidence is not a settings toggle. It is the contract path that removes the permission and proof that the user can call it without an operator signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A delegated session signer removed by the operator
&lt;/h2&gt;

&lt;p&gt;Some embedded wallets support a secondary signer controlled by an application or its execution service. A policy may restrict what that signer can request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who can move assets?&lt;/strong&gt; The user’s root authority can move assets, while the delegated signer can perform actions accepted by the wallet or signing provider’s policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who must cooperate for delegated access to end?&lt;/strong&gt; If no user-callable revocation path exists, the operator must remove its signer. A user who controls or can export the underlying wallet can instead move to a fresh wallet to eliminate residual access.&lt;/p&gt;

&lt;p&gt;This model is useful for unattended execution because the user does not need to approve every action. It is weaker than unilateral smart-account revocation because cancellation and signer removal are separate events. A successful cancellation request may stop the application’s executor immediately while signer removal remains an operator-side, best-effort operation.&lt;/p&gt;

&lt;p&gt;Policy placement matters too. An API policy enforced by a signing provider is not an on-chain permission. An executor check is not a wallet-level restriction. These controls may be useful, but documentation should identify the enforcing component and explain what happens if that component is unavailable or faulty.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Threshold MPC custody
&lt;/h2&gt;

&lt;p&gt;“MPC wallet” describes a family of designs, not one custody model. In a threshold setup, several key shares participate in signing. The threshold and share holders determine the actual control structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who can move assets?&lt;/strong&gt; Any coalition that satisfies the signing threshold. Depending on the design, that might be the user plus a provider, several provider systems, or the user acting through multiple devices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who must cooperate for provider access to end?&lt;/strong&gt; That depends on whether the user can rotate or replace shares without the provider. If resharing needs provider participation, ending provider access requires cooperation. If the user independently controls enough shares to authorize rotation, the answer is different.&lt;/p&gt;

&lt;p&gt;MPC can support recovery and hide key-management complexity. It can also make authority difficult to inspect because the relevant policy lives in protocols, infrastructure, and contractual procedures rather than a public account contract.&lt;/p&gt;

&lt;p&gt;Ask for the threshold, the share holders, the recovery path, and the share-rotation procedure. “We never reconstruct the key” does not answer who can authorize a signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. An exchange account
&lt;/h2&gt;

&lt;p&gt;In a conventional exchange account, the exchange controls the blockchain keys and maintains an internal ledger for customers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who can move assets?&lt;/strong&gt; The exchange, through its signing infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who must cooperate for the user to withdraw or terminate access?&lt;/strong&gt; The exchange must process the withdrawal. The user cannot independently move blockchain assets represented by an account balance.&lt;/p&gt;

&lt;p&gt;This model can offer account recovery, customer support, transaction screening, and familiar authentication. Its trade-off is direct: the operator controls asset movement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to ask instead of accepting the label
&lt;/h2&gt;

&lt;p&gt;For any automated wallet product, request concrete answers to these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which keys or shares can authorize movement?&lt;/li&gt;
&lt;li&gt;Where is each restriction enforced: account contract, signing provider, executor, or user interface?&lt;/li&gt;
&lt;li&gt;Can the user disable delegated access without operator cooperation?&lt;/li&gt;
&lt;li&gt;Does authorization expire automatically?&lt;/li&gt;
&lt;li&gt;What happens if cancellation succeeds but signer removal fails?&lt;/li&gt;
&lt;li&gt;Can the user export the wallet or migrate assets without permission?&lt;/li&gt;
&lt;li&gt;Are policy changes auditable?&lt;/li&gt;
&lt;li&gt;Which controls remain effective if the application server is compromised?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disclosure: I build &lt;a href="https://app.tradevo.co/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=custody-is-a-spectrum-not-a-boolean" rel="noopener noreferrer"&gt;Tradevo&lt;/a&gt;, software for describing a crypto strategy in plain English, compiling it into deterministic rules, testing and paper-trading it, submitting it for human review, setting a creator price, publishing approved strategies, and subscribing with a chosen allocation; execution uses a scoped and revocable delegated session signer attached to the user’s embedded wallet, while the token allowlist, per-subscription allocation cap, and slippage limits are enforced by the signing provider’s policy and the executor, the authorization does not expire by itself, cancellation stops execution and triggers best-effort server-side signer removal, and users can export their wallet for zero residual access.&lt;/p&gt;

&lt;p&gt;“Non-custodial” is useful only as the start of an architecture review. The real boundary is who can sign, who can change the signing rules, and whether the user can exclude every other actor without asking for help.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>security</category>
      <category>ethereum</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How to Backtest Value Investing Without Lookahead Bias</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:47:11 +0000</pubDate>
      <link>https://dev.to/tradevodata/how-to-backtest-value-investing-without-lookahead-bias-3b0a</link>
      <guid>https://dev.to/tradevodata/how-to-backtest-value-investing-without-lookahead-bias-3b0a</guid>
      <description>&lt;p&gt;A value backtest can be mathematically correct and still use information that investors did not have at the time.&lt;/p&gt;

&lt;p&gt;The common failure is simple: join historical prices to today’s cleaned fundamentals by fiscal period. That can place an annual report into the simulation before it was filed, overwrite original figures with later amendments, or include companies selected from today’s surviving universe.&lt;/p&gt;

&lt;p&gt;To backtest value investing without lookahead bias, you need three controls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;As-of filing dates:&lt;/strong&gt; a fundamental becomes eligible only when it was public.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A point-in-time universe:&lt;/strong&gt; membership must reflect what was investable on each rebalance date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As-reported values:&lt;/strong&gt; use the first-reported figure when modeling what an investor originally saw.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These controls apply whether your signal is P/E, P/B, gross profitability, operating margin, or a composite quality score.&lt;/p&gt;

&lt;p&gt;For more background, see &lt;a href="https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=how-to-backtest-value-investing-without-lookahead-bias" rel="noopener noreferrer"&gt;/blog/lookahead-bias-fundamental-backtests&lt;/a&gt; and &lt;a href="https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=how-to-backtest-value-investing-without-lookahead-bias" rel="noopener noreferrer"&gt;/blog/point-in-time-fundamentals-data&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ordinary fiscal-period joins fail
&lt;/h2&gt;

&lt;p&gt;Suppose a company’s fiscal year ends in December. Its annual revenue, earnings, equity, and cash-flow figures are not necessarily public on December 31. They become usable when the filing reaches SEC EDGAR.&lt;/p&gt;

&lt;p&gt;A join like this is therefore unsafe:&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="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&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;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fiscal_period_end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;
&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&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;prices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;
&lt;span class="n"&gt;panel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It matches by reporting period, not availability. A January rebalance could receive figures filed weeks later.&lt;/p&gt;

&lt;p&gt;The correct question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which fiscal period belongs to this calendar year?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What was the latest eligible filing publicly available by this rebalance date?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That requires an availability field such as &lt;code&gt;first_filed&lt;/code&gt; and an as-of join where &lt;code&gt;first_filed &amp;lt;= rebalance_date&lt;/code&gt;. Same-day inclusion should follow a documented convention. Tradevo Data’s server-side &lt;code&gt;as_of&lt;/code&gt; filter is same-day inclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: define the strategy before touching the data
&lt;/h2&gt;

&lt;p&gt;Write down the rules first. At minimum, specify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rebalance frequency and exact dates;&lt;/li&gt;
&lt;li&gt;eligible securities on each date;&lt;/li&gt;
&lt;li&gt;price used for signal formation and execution;&lt;/li&gt;
&lt;li&gt;accounting concepts and denominator definitions;&lt;/li&gt;
&lt;li&gt;treatment of negative earnings or equity;&lt;/li&gt;
&lt;li&gt;filing availability rule;&lt;/li&gt;
&lt;li&gt;restatement policy;&lt;/li&gt;
&lt;li&gt;transaction costs, liquidity constraints, and missing-data policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a simple value-and-quality screen might rank eligible companies on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Earnings yield:&lt;/strong&gt; net income divided by market capitalization;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Book-to-market:&lt;/strong&gt; stockholders’ equity divided by market capitalization;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gross profitability:&lt;/strong&gt; gross profit divided by assets;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operating quality:&lt;/strong&gt; operating income or operating cash flow relative to assets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using yields rather than raw P/E or P/B often makes ranking rules easier to define around negative denominators. That is a design choice, not a guarantee of better results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: build a point-in-time universe
&lt;/h2&gt;

&lt;p&gt;Correct fundamentals do not fix survivorship bias.&lt;/p&gt;

&lt;p&gt;If you start with companies that exist today and project them backward, failed, acquired, or delisted firms may disappear from the historical sample. The backtest then answers a different question: how today’s survivors would have performed historically.&lt;/p&gt;

&lt;p&gt;A proper universe table should contain effective membership intervals or date-stamped snapshots, including delisted securities where the research question requires them. It should also preserve historical identifiers because tickers can change or be reused.&lt;/p&gt;

&lt;p&gt;Tradevo Data covers US companies but does &lt;strong&gt;not&lt;/strong&gt; provide delisted coverage. It should not be treated as a complete point-in-time security master. For research requiring a survivorship-free universe, pair fundamentals with an appropriate historical universe and price source—or choose a provider that integrates those components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: select fundamentals by &lt;code&gt;first_filed&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For every rebalance date and company:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exclude rows where &lt;code&gt;first_filed&lt;/code&gt; is after the rebalance date.&lt;/li&gt;
&lt;li&gt;Choose the latest eligible fiscal observation under your strategy’s period rules.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;original_value&lt;/code&gt; for the information first reported to the market.&lt;/li&gt;
&lt;li&gt;Retain the restatement indicator for diagnostics.&lt;/li&gt;
&lt;li&gt;Apply a lag only if it represents an explicit operational assumption, not as a substitute for filing dates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A blanket rule such as “wait three months after fiscal year-end” is only an approximation. Some filings arrive earlier or later. Exact filing dates are cleaner because they model actual public availability.&lt;/p&gt;

&lt;p&gt;In Tradevo Data, each row includes &lt;code&gt;first_filed&lt;/code&gt;, &lt;code&gt;original_value&lt;/code&gt;, &lt;code&gt;latest_value&lt;/code&gt;, a restatement flag, and &lt;code&gt;qa_status&lt;/code&gt;. Restatements are labeled when a same-tag change, including amendments, exceeds 0.5%. The dataset contains 37,804 labeled restatements.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small worked example
&lt;/h2&gt;

&lt;p&gt;Assume a strategy rebalances on two dates and the sample contains these conceptual rows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ticker&lt;/th&gt;
&lt;th&gt;concept&lt;/th&gt;
&lt;th&gt;fiscal period&lt;/th&gt;
&lt;th&gt;first_filed&lt;/th&gt;
&lt;th&gt;original_value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ABC&lt;/td&gt;
&lt;td&gt;NetIncome&lt;/td&gt;
&lt;td&gt;prior annual period&lt;/td&gt;
&lt;td&gt;before first rebalance&lt;/td&gt;
&lt;td&gt;available then&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ABC&lt;/td&gt;
&lt;td&gt;NetIncome&lt;/td&gt;
&lt;td&gt;latest annual period&lt;/td&gt;
&lt;td&gt;after first rebalance&lt;/td&gt;
&lt;td&gt;not yet available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ABC&lt;/td&gt;
&lt;td&gt;StockholdersEquity&lt;/td&gt;
&lt;td&gt;latest annual period&lt;/td&gt;
&lt;td&gt;before second rebalance&lt;/td&gt;
&lt;td&gt;available then&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At the first rebalance, the latest-period net income must be excluded because its &lt;code&gt;first_filed&lt;/code&gt; date is still in the future. At the second rebalance, it becomes eligible if the filing date is on or before that date.&lt;/p&gt;

&lt;p&gt;The same principle applies to every ratio. For a point-in-time P/E calculation, both price and EPS must be known at signal time. If you calculate market capitalization from shares, the share figure also needs a valid availability rule. Never silently combine a historical price with a later-revised denominator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pandas example using the free sample
&lt;/h2&gt;

&lt;p&gt;The public proof pack contains 225 rows for five companies, covering their latest three fiscal years, with full methodology. Download it without signup at:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;https://github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core pandas operation is a backward as-of join:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;fund&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fundamentals.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parse_dates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;rebalances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rebalance_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2023-02-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2023-11-15&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Example concept; repeat or pivot for equity, assets, and quality inputs.
&lt;/span&gt;&lt;span class="n"&gt;eps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fund&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fund&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;concept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EPSDiluted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;eps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# merge_asof requires sorted keys.
&lt;/span&gt;&lt;span class="n"&gt;eps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;rebalances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rebalances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rebalance_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;pit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge_asof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;rebalances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;left_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rebalance_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;right_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="o"&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="n"&gt;direction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backward&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_exact_matches&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Attach a price observable at the chosen signal timestamp.
&lt;/span&gt;&lt;span class="n"&gt;pit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pe&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;pit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_at_signal&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;pit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;original_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, add safeguards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filter annual versus quarterly periods explicitly.&lt;/li&gt;
&lt;li&gt;Resolve duplicate tags using documented taxonomy and QA rules.&lt;/li&gt;
&lt;li&gt;Ensure the selected row represents the intended fiscal horizon.&lt;/li&gt;
&lt;li&gt;Exclude or separately classify negative and zero denominators.&lt;/li&gt;
&lt;li&gt;Preserve rows with missing data rather than filling them from the future.&lt;/li&gt;
&lt;li&gt;Test that every selected &lt;code&gt;first_filed&lt;/code&gt; is on or before the rebalance date.&lt;/li&gt;
&lt;li&gt;Keep signal time separate from execution time to avoid same-close assumptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful invariant is:&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="nf"&gt;assert &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;pit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rebalance_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: choose an explicit restatement policy
&lt;/h2&gt;

&lt;p&gt;There are two legitimate research questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What could an investor have calculated then?&lt;/strong&gt; Use &lt;code&gt;original_value&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What does the company currently report for that historical period?&lt;/strong&gt; Use &lt;code&gt;latest_value&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a historical trading simulation, the first question is usually the relevant one. Using revised values can leak later amendments into earlier portfolio decisions.&lt;/p&gt;

&lt;p&gt;Still, keep both fields. Comparing original and latest values can reveal whether a signal is sensitive to revisions. A &lt;code&gt;restated&lt;/code&gt; flag is also useful for audit trails and robustness checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: test the pipeline for leakage
&lt;/h2&gt;

&lt;p&gt;Add automated tests rather than relying on visual inspection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No fundamental has &lt;code&gt;first_filed &amp;gt; rebalance_date&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Universe membership is valid on the rebalance date.&lt;/li&gt;
&lt;li&gt;Prices are timestamped no later than signal formation.&lt;/li&gt;
&lt;li&gt;Execution occurs after the signal is observable.&lt;/li&gt;
&lt;li&gt;Revisions do not overwrite original values.&lt;/li&gt;
&lt;li&gt;Missing historical values remain missing.&lt;/li&gt;
&lt;li&gt;Fiscal periods do not accidentally mix annual and quarterly data.&lt;/li&gt;
&lt;li&gt;Split adjustments and share counts use compatible conventions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also rerun the backtest with conservative execution delays. A result that disappears under modestly stricter timing assumptions may be driven by timestamp choices rather than the intended factor.&lt;/p&gt;

&lt;p&gt;In the current five-company, 225-row public proof pack, the 224 rows with reliable filing dates showed a measured mean lookahead of 35.2 days and a maximum of 48 days. These sample statistics apply only to those 224 reliable-filing-date rows and must not be generalized to the full Tradevo Data dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data options: a fair comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Limitations&lt;/th&gt;
&lt;th&gt;Better fit when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build directly from SEC EDGAR&lt;/td&gt;
&lt;td&gt;Maximum methodological control; public-domain source&lt;/td&gt;
&lt;td&gt;Taxonomy normalization, amendments, filing timestamps, and QA require substantial work&lt;/td&gt;
&lt;td&gt;You need custom concepts or can maintain an ingestion pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tradevo Data&lt;/td&gt;
&lt;td&gt;Point-in-time US equity fundamentals, original and latest values, filing dates, restatement labels, API and bulk JSON&lt;/td&gt;
&lt;td&gt;No delisted, non-US, TTM, or Parquet coverage; not a full market-data stack&lt;/td&gt;
&lt;td&gt;You want a budget-tier PIT fundamentals layer and can supply universe and prices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sharadar&lt;/td&gt;
&lt;td&gt;Credible established alternative; available products may support broader research workflows&lt;/td&gt;
&lt;td&gt;Coverage and licensing details vary by product&lt;/td&gt;
&lt;td&gt;You want its specific ecosystem; see their pricing page: &lt;a href="https://www.nasdaq.com/solutions/data-link" rel="noopener noreferrer"&gt;https://www.nasdaq.com/solutions/data-link&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiingo&lt;/td&gt;
&lt;td&gt;Credible API-oriented market-data alternative&lt;/td&gt;
&lt;td&gt;Check its current products against your exact PIT and restatement requirements&lt;/td&gt;
&lt;td&gt;You want its API workflow; see their pricing page: &lt;a href="https://www.tiingo.com/" rel="noopener noreferrer"&gt;https://www.tiingo.com/&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QuantConnect&lt;/td&gt;
&lt;td&gt;Integrated research and execution environment with supported datasets&lt;/td&gt;
&lt;td&gt;May be less suitable if you want to own every storage and pipeline component&lt;/td&gt;
&lt;td&gt;You prefer a managed algorithmic-research platform; see their pricing page: &lt;a href="https://www.quantconnect.com/" rel="noopener noreferrer"&gt;https://www.quantconnect.com/&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Verify current documentation, coverage, licensing, and pricing directly with each provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  When they win: another option may be better
&lt;/h2&gt;

&lt;p&gt;Choose another provider or platform if you need delisted-company coverage, a ready-made survivorship-free universe, international equities, TTM fields, Parquet delivery, integrated prices and corporate actions, or an execution environment. Tradevo Data does not provide those capabilities.&lt;/p&gt;

&lt;p&gt;Sharadar, Tiingo, or QuantConnect may win when their current coverage, ecosystem, licensing, or integrated workflows better match your requirements. Building directly from SEC EDGAR may win when you need maximum methodological control or custom concepts. Verify each option’s current documentation and pricing before choosing.&lt;/p&gt;

&lt;p&gt;An integrated platform may also be the better choice when research speed matters more than controlling each transformation. Paying for a broader stack can be rational if it reduces engineering and audit costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to build it yourself
&lt;/h2&gt;

&lt;p&gt;Build from SEC EDGAR when your research depends on custom taxonomy mappings, filing-document lineage, unusual accounting concepts, or transformation rules that a standardized API cannot expose.&lt;/p&gt;

&lt;p&gt;Be realistic about maintenance. You will need to parse filings and amendments, normalize company identifiers and units, resolve duplicate facts, distinguish duration from instant concepts, track revisions, derive only defensible periods, and retain reproducible snapshots. You also need tests for silent upstream changes.&lt;/p&gt;

&lt;p&gt;A hybrid approach is often practical: use a normalized source for standard concepts, then independently validate critical rows against EDGAR.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical starting point
&lt;/h2&gt;

&lt;p&gt;Tradevo Data provides 632,466 annual point-in-time rows across 5,168 US companies, 16 annual concepts, and up to 12 fiscal years where available. It also provides 1,089,635 quarterly rows across 5,382 companies and seven quarterly concepts. Q4 is reported where tagged or derived and labeled where supported; derived Q4 EPS and shares are not provided.&lt;/p&gt;

&lt;p&gt;The JSON fundamentals endpoint, &lt;code&gt;/v1/fundamentals&lt;/code&gt;, accepts &lt;code&gt;ticker&lt;/code&gt;, &lt;code&gt;as_of&lt;/code&gt;, optional &lt;code&gt;concept&lt;/code&gt;, and &lt;code&gt;period=annual|quarterly&lt;/code&gt;; annual is the default. Bulk JSON is included with Pro through &lt;code&gt;/v1/download&lt;/code&gt; and &lt;code&gt;/v1/snapshot?as_of&lt;/code&gt;, each supporting &lt;code&gt;period=annual|quarterly&lt;/code&gt;. Parquet is not included. Documentation is available at &lt;a href="https://tradevodata.com/docs?utm_content=blog-how-to-backtest-value-investing-without-lookahead-bias&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/docs?utm_content=blog-how-to-backtest-value-investing-without-lookahead-bias&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start by auditing the public proof pack and reproducing the as-of join yourself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;https://github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the schema fits your process, the card-backed seven-day free trial covers 10 companies, a rolling three-year history, and 100 requests per day without bulk. It renews at $29 per month unless canceled. Pro provides complete available history, 5,000 requests per day, and bulk access:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tradevodata.com/?utm_content=blog-how-to-backtest-value-investing-without-lookahead-bias&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/?utm_content=blog-how-to-backtest-value-investing-without-lookahead-bias&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal is not to make a backtest look better. It is to ensure every simulated decision uses only the universe, prices, and accounting information that were actually available at that moment.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice. Verify competitor coverage, licensing, and pricing directly.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>SEC EDGAR Companyfacts API Tutorial</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Sat, 17 Oct 2026 17:22:41 +0000</pubDate>
      <link>https://dev.to/tradevodata/sec-edgar-companyfacts-api-tutorial-307h</link>
      <guid>https://dev.to/tradevodata/sec-edgar-companyfacts-api-tutorial-307h</guid>
      <description>&lt;p&gt;The SEC EDGAR APIs provide public filing data. For developers building US equity research pipelines, two resources are especially useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Companyfacts:&lt;/strong&gt; XBRL facts reported by one company.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Submissions:&lt;/strong&gt; filing history and filing metadata for one company.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The data is valuable, but it is not a ready-made point-in-time fundamentals panel. You still need to resolve tickers to CIKs, select tags and units, distinguish annual from quarterly facts, track filing dates, handle amendments and restatements, and prevent future information from leaking into historical tests.&lt;/p&gt;

&lt;p&gt;This tutorial covers that plumbing directly. If you would rather inspect a finished example first, Tradevo Data publishes a no-signup proof pack at &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;https://github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two SEC endpoints you need
&lt;/h2&gt;

&lt;p&gt;SEC requests use a zero-padded, 10-digit Central Index Key rather than a ticker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://data.sec.gov/api/xbrl/companyfacts/CIK0000320193.json
https://data.sec.gov/submissions/CIK0000320193.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first URL returns company facts grouped broadly as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;facts -&amp;gt; taxonomy -&amp;gt; concept -&amp;gt; units -&amp;gt; observations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical observation may contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fy&lt;/code&gt;: fiscal year&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fp&lt;/code&gt;: fiscal period, such as &lt;code&gt;FY&lt;/code&gt; or &lt;code&gt;Q1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;form&lt;/code&gt;: filing form, such as &lt;code&gt;10-K&lt;/code&gt; or &lt;code&gt;10-Q&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;filed&lt;/code&gt;: filing date&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt;: fact period boundaries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;val&lt;/code&gt;: reported value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;accn&lt;/code&gt;: filing accession number&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;frame&lt;/code&gt;: an SEC calendar frame when available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The submissions response contains filing metadata, including accession numbers, forms, filing dates, report dates, and primary documents. It is useful for joining facts back to filings and inspecting amendments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Respect SEC access rules
&lt;/h2&gt;

&lt;p&gt;Consult the SEC's current developer and fair-access guidance before operating an automated client. Identify requests through a descriptive &lt;code&gt;User-Agent&lt;/code&gt;, use a real application name and contact address, and apply conservative rate limiting.&lt;/p&gt;

&lt;p&gt;A conservative serial Python session looks like this:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FundamentalsResearch/1.0 research@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept-Encoding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gzip, deflate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sec_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production ingestion, add caching, exponential backoff, retry limits, logging, and a shared rate limiter. Do not let multiple workers independently assume they each have the full request allowance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolve a ticker to a CIK
&lt;/h2&gt;

&lt;p&gt;The SEC publishes a company-ticker mapping at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.sec.gov/files/company_tickers.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it to build a case-insensitive lookup and pad the numeric CIK:&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="n"&gt;TICKERS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.sec.gov/files/company_tickers.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ticker_to_cik&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sec_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TICKERS_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&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;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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;record&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;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cik_str&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;zfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ticker not found: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cache this mapping. Tickers are convenient identifiers, but they can change or be reused; keep the CIK in your internal security model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Download companyfacts and submissions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_companyfacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cik&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://data.sec.gov/api/xbrl/companyfacts/CIK&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cik&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sec_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_submissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cik&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://data.sec.gov/submissions/CIK&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cik&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sec_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;cik&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ticker_to_cik&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;companyfacts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_companyfacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cik&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;submissions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_submissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cik&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Companyfacts may include standard &lt;code&gt;us-gaap&lt;/code&gt; concepts and company-specific extension concepts. Do not assume every issuer reports the same economic item under the same tag throughout its history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extract a concept without losing provenance
&lt;/h2&gt;

&lt;p&gt;Here is a minimal extractor for observations associated with a concept and preferred unit:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_concept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;concept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taxonomy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-gaap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;concept_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;facts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;taxonomy&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;concept&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;observations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;concept_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;units&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;observations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;entity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;entityName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cik&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cik&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taxonomy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;taxonomy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;concept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;concept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;concept_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;val&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;form&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;form&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fiscal_year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fiscal_period&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accession&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frame&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frame&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;revenue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_concept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;companyfacts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RevenueFromContractWithCustomerExcludingAssessedTax&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;revenue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;form&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-K&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-K/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-Q/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the accession number, filing date, form, period boundaries, taxonomy, concept, and unit. Collapsing immediately to &lt;code&gt;fiscal_year -&amp;gt; value&lt;/code&gt; destroys information needed to audit duplicates and revisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 1: XBRL tag drift
&lt;/h2&gt;

&lt;p&gt;Revenue is not always represented by one universal tag. An issuer may use a legacy standard tag, migrate to a newer tag, or report an extension concept. Similar problems occur with cash flow and balance-sheet items.&lt;/p&gt;

&lt;p&gt;A durable concept mapping therefore needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Candidate tags for each economic concept.&lt;/li&gt;
&lt;li&gt;Valid units and expected period type.&lt;/li&gt;
&lt;li&gt;Issuer- and date-aware precedence rules.&lt;/li&gt;
&lt;li&gt;Overlap checks when tags coexist.&lt;/li&gt;
&lt;li&gt;QA flags rather than silent guesses.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not add every revenue-like tag together. Two tags may be alternative representations of the same total, or one may be a component of another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 2: annual, quarterly, and Q4 values
&lt;/h2&gt;

&lt;p&gt;A fact from a &lt;code&gt;10-Q&lt;/code&gt; may cover a quarter or a year-to-date interval. A &lt;code&gt;10-K&lt;/code&gt; annual fact does not automatically provide a separately tagged fourth quarter.&lt;/p&gt;

&lt;p&gt;You can sometimes derive Q4 as annual minus the first three quarters, but only when periods, units, concepts, and accounting scope align. Label derived values explicitly. Per-share values and diluted share counts are especially unsafe to derive through simple subtraction because averaging and weighting matter.&lt;/p&gt;

&lt;p&gt;Treat &lt;code&gt;fp&lt;/code&gt;, form type, &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;end&lt;/code&gt;, and duration together. The &lt;code&gt;frame&lt;/code&gt; field can help, but it is not present or sufficient for every observation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 3: restatements and duplicate periods
&lt;/h2&gt;

&lt;p&gt;The same fiscal period may appear in an original filing, a later comparative filing, and an amendment. Those values can differ.&lt;/p&gt;

&lt;p&gt;For each logical fact, preserve at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first date it became public;&lt;/li&gt;
&lt;li&gt;the first-reported value;&lt;/li&gt;
&lt;li&gt;later reported values;&lt;/li&gt;
&lt;li&gt;accession and form provenance;&lt;/li&gt;
&lt;li&gt;a revision or restatement indicator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A latest-only table answers, “What does the record say now?” A point-in-time table answers, “What could have been known on this date?” Those are different datasets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 4: filing lag and lookahead bias
&lt;/h2&gt;

&lt;p&gt;Fiscal period end is not the information-availability date. If a company's year ends in December and its filing arrives later, a backtest must not expose that filing in December.&lt;/p&gt;

&lt;p&gt;For an as-of date, filter facts using filing availability:&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;def&lt;/span&gt; &lt;span class="nf"&gt;as_of_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;work&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filed&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;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;work&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cik&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;concept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accession&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is only a starting point. Same-day treatment, amendments, tag equivalence, duplicate contexts, and filing acceptance timestamps may require an explicit policy. For the broader research issue, see &lt;a href="https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_content=blog-sec-edgar-companyfacts-api-tutorial&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_content=blog-sec-edgar-companyfacts-api-tutorial&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt; and &lt;a href="https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_content=blog-sec-edgar-companyfacts-api-tutorial&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_content=blog-sec-edgar-companyfacts-api-tutorial&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Raw EDGAR versus a normalized API
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Build directly on SEC EDGAR&lt;/th&gt;
&lt;th&gt;Use Tradevo Data&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;Public SEC filing data&lt;/td&gt;
&lt;td&gt;Point-in-time US equity fundamentals from SEC EDGAR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access model&lt;/td&gt;
&lt;td&gt;Raw companyfacts and submissions JSON&lt;/td&gt;
&lt;td&gt;One normalized JSON endpoint plus Pro bulk downloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concept mapping&lt;/td&gt;
&lt;td&gt;You design and maintain it&lt;/td&gt;
&lt;td&gt;16 annual concepts and 7 quarterly concepts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Point-in-time logic&lt;/td&gt;
&lt;td&gt;You implement filing-date and revision rules&lt;/td&gt;
&lt;td&gt;Server-side &lt;code&gt;as_of&lt;/code&gt;, inclusive when &lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restatements&lt;/td&gt;
&lt;td&gt;You identify and retain revisions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;original_value&lt;/code&gt;, &lt;code&gt;latest_value&lt;/code&gt;, restatement flag, and &lt;code&gt;qa_status&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coverage&lt;/td&gt;
&lt;td&gt;Depends on the pipeline you build&lt;/td&gt;
&lt;td&gt;Annual: 5,168 US companies and 632,466 rows; quarterly: 5,382 companies and 1,089,635 rows; no non-US or delisted coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formats&lt;/td&gt;
&lt;td&gt;SEC JSON and your own storage&lt;/td&gt;
&lt;td&gt;JSON API and bulk downloads; Parquet is not included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Public source; engineering and operations remain&lt;/td&gt;
&lt;td&gt;Card-backed seven-day trial that renews at $29/month unless canceled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tradevo Data is an honest budget tier of research-grade point-in-time data, not the only credible option. Sharadar, Tiingo, and QuantConnect may fit different workflows. See their pricing pages and documentation directly: &lt;a href="https://data.nasdaq.com/databases/SF1" rel="noopener noreferrer"&gt;https://data.nasdaq.com/databases/SF1&lt;/a&gt;, &lt;a href="https://www.tiingo.com/pricing" rel="noopener noreferrer"&gt;https://www.tiingo.com/pricing&lt;/a&gt;, and &lt;a href="https://www.quantconnect.com/pricing" rel="noopener noreferrer"&gt;https://www.quantconnect.com/pricing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When alternatives win
&lt;/h2&gt;

&lt;p&gt;Raw EDGAR wins when you need custom XBRL concepts, filing-level text, bespoke amendment policies, extension-tag research, broader filing coverage, or complete control over lineage. It can also win when maintaining ingestion and normalization is part of your research advantage.&lt;/p&gt;

&lt;p&gt;Sharadar, Tiingo, or QuantConnect may win when their coverage, delivery model, integrations, research environment, or supported workflows better match your needs. Verify current features and terms in each provider's documentation and see their pricing pages directly: &lt;a href="https://data.nasdaq.com/databases/SF1" rel="noopener noreferrer"&gt;https://data.nasdaq.com/databases/SF1&lt;/a&gt;, &lt;a href="https://www.tiingo.com/pricing" rel="noopener noreferrer"&gt;https://www.tiingo.com/pricing&lt;/a&gt;, and &lt;a href="https://www.quantconnect.com/pricing" rel="noopener noreferrer"&gt;https://www.quantconnect.com/pricing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tradevo Data is not a fit if you need non-US or delisted coverage, Parquet delivery, derived TTM values, or concepts outside its supported annual and quarterly sets.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to build it yourself
&lt;/h2&gt;

&lt;p&gt;Build directly on EDGAR if you can commit to monitoring schema changes, updating mappings, rerunning QA, storing historical revisions, and documenting every point-in-time decision. The source is public; the practical cost is sustained engineering attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a maintained layer is more practical
&lt;/h2&gt;

&lt;p&gt;A normalized service can be useful when your objective is testing research ideas rather than operating XBRL infrastructure. Tradevo Data exposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/v1/fundamentals?ticker&amp;amp;as_of[&amp;amp;concept][&amp;amp;period=annual|quarterly]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Annual is the default. Pro provides complete available history, 5,000 requests per day, and bulk access through &lt;code&gt;/v1/download&lt;/code&gt; and &lt;code&gt;/v1/snapshot?as_of&lt;/code&gt;, each supporting &lt;code&gt;period=annual|quarterly&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its annual dataset contains 632,466 point-in-time rows across 5,168 US companies, with up to 12 fiscal years and 16 concepts: Revenue, NetIncome, Assets, StockholdersEquity, OperatingCashFlow, EPSDiluted, DilutedShares, GrossProfit, OperatingIncome, PretaxIncome, IncomeTaxExpense, CapitalExpenditures, CashAndCashEquivalents, CurrentAssets, CurrentLiabilities, and NetPPE. Its quarterly dataset contains 1,089,635 rows across 5,382 companies and 7 concepts. Q4 is reported where tagged or derived and labelled where supported; EPS and share counts are not derived for Q4. TTM values are not provided.&lt;/p&gt;

&lt;p&gt;Each row includes &lt;code&gt;first_filed&lt;/code&gt;, &lt;code&gt;original_value&lt;/code&gt;, &lt;code&gt;latest_value&lt;/code&gt;, a restatement flag for same-tag changes above 0.5%, including amendments, and &lt;code&gt;qa_status&lt;/code&gt;. The dataset labels 37,804 restatements.&lt;/p&gt;

&lt;p&gt;Start with the public proof pack: &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;https://github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;. It contains 225 rows covering five companies and their latest three fiscal years, plus the methodology, without signup.&lt;/p&gt;

&lt;p&gt;If the schema fits your work, the card-backed seven-day free trial at &lt;a href="https://tradevodata.com/?utm_content=blog-sec-edgar-companyfacts-api-tutorial&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/?utm_content=blog-sec-edgar-companyfacts-api-tutorial&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt; covers 10 companies, a rolling three-year history, and 100 requests per day without bulk. It renews at $29/month unless canceled. Documentation is at &lt;a href="https://tradevodata.com/docs?utm_content=blog-sec-edgar-companyfacts-api-tutorial&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/docs?utm_content=blog-sec-edgar-companyfacts-api-tutorial&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Why a Scheduler Cannot Trust Its Own Database</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Thu, 15 Oct 2026 18:40:43 +0000</pubDate>
      <link>https://dev.to/tradevodata/why-a-scheduler-cannot-trust-its-own-database-4mh9</link>
      <guid>https://dev.to/tradevodata/why-a-scheduler-cannot-trust-its-own-database-4mh9</guid>
      <description>&lt;p&gt;If your scheduler submits transactions, its database is not a record of current chain state. It is a local projection assembled from RPC reads, transaction submissions, receipts, logs, and assumptions about what happened between those events.&lt;/p&gt;

&lt;p&gt;That projection is useful. Scheduling would be impractical if every decision required rebuilding state from chain history. But the projection is always slightly wrong. The important engineering question is not whether it becomes stale; it is how the system behaves when it does.&lt;/p&gt;

&lt;p&gt;There are two failure directions: stale-permissive state and stale-restrictive state. They have different costs and need different controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The database is authoritative about intent, not chain facts
&lt;/h2&gt;

&lt;p&gt;A scheduler can authoritatively say, “I created this job,” “I attempted this transaction,” or “I assigned this idempotency key.” Those are facts owned by the application.&lt;/p&gt;

&lt;p&gt;It cannot authoritatively say, “This wallet still has enough balance,” “This nonce is unused,” “This contract remains unpaused,” or “That transaction is final.” Those are chain facts. The database can cache observations of them, but an observation has a block context and an age.&lt;/p&gt;

&lt;p&gt;Even an RPC read is not an timeless answer. A read against a recent block can be displaced by a reorganization. Different RPC nodes can briefly disagree. A transaction visible in the pending pool may disappear, be replaced, or execute with different surrounding state than the scheduler expected.&lt;/p&gt;

&lt;p&gt;Treating cached chain data as ordinary application state hides all of this uncertainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stale-permissive: the database says yes
&lt;/h2&gt;

&lt;p&gt;Stale-permissive state means the local model allows an action that current chain state rejects or makes unsafe.&lt;/p&gt;

&lt;p&gt;Common examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cached balance still includes funds already spent elsewhere.&lt;/li&gt;
&lt;li&gt;A nonce has been consumed by another transaction.&lt;/li&gt;
&lt;li&gt;An authorization was removed after the last sync.&lt;/li&gt;
&lt;li&gt;A contract parameter changed, invalidating the scheduler’s assumptions.&lt;/li&gt;
&lt;li&gt;A previous transaction succeeded, but the local worker timed out and scheduled the same intent again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The obvious cost is a reverted transaction and wasted gas. The less obvious cost is incorrect execution. A transaction can succeed while doing something different from what the scheduler intended because price, liquidity, contract configuration, or account state changed before inclusion.&lt;/p&gt;

&lt;p&gt;Preflight simulation helps, but it is not a lock on future state. Other transactions can land between simulation and execution. The right interpretation is “this action was valid against this observed state,” not “this action will remain valid.”&lt;/p&gt;

&lt;p&gt;Safety-critical predicates therefore need checks as close to submission as practical. The scheduler should also encode constraints in the transaction itself when the contract interface supports them. A database check can reduce bad attempts; transaction-level constraints can stop execution when state moves beyond accepted bounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stale-restrictive: the database says no
&lt;/h2&gt;

&lt;p&gt;Stale-restrictive state is the mirror case. The database blocks an action that chain state would allow.&lt;/p&gt;

&lt;p&gt;This often happens when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A worker marks an intent as pending and crashes before clearing its lease.&lt;/li&gt;
&lt;li&gt;A transaction is replaced, but the database keeps watching the old hash.&lt;/li&gt;
&lt;li&gt;A receipt or log is missed because an RPC request failed.&lt;/li&gt;
&lt;li&gt;A transaction succeeds while the local record remains in a submitted state.&lt;/li&gt;
&lt;li&gt;A temporary chain condition clears, but the scheduler never revisits the job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This direction usually preserves funds but harms liveness. Jobs remain stuck, valid execution windows pass, and operators have to repair records manually. Over time, conservative patches can turn transient uncertainty into permanent blockage.&lt;/p&gt;

&lt;p&gt;A common mistake is to treat “unknown” as “failed” or “still pending.” Unknown should be its own state. It tells the reconciliation process to gather evidence rather than blindly submit again or wait forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple failure sequence
&lt;/h2&gt;

&lt;p&gt;Consider a scheduler that records an intended transaction and then broadcasts it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;intent: created
transaction: submitted
local state: pending
worker: crashes
chain: transaction succeeds
receipt update: never written
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On restart, resubmitting may duplicate the action. Refusing to resubmit may leave the job stuck indefinitely.&lt;/p&gt;

&lt;p&gt;The scheduler needs a third path: inspect the account nonce, transaction receipt, relevant logs, and resulting contract state. It can then classify the intent using chain evidence. That process is reconciliation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconciliation is a control loop
&lt;/h2&gt;

&lt;p&gt;Reconciliation should run continuously, not as an incident script. A practical design separates command handling from observation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persist an intent before submission, with an idempotency key tied to the logical action.&lt;/li&gt;
&lt;li&gt;Record every submission attempt and transaction hash.&lt;/li&gt;
&lt;li&gt;Advance state through explicit transitions rather than overwriting a generic status field.&lt;/li&gt;
&lt;li&gt;Scan receipts and logs from a durable checkpoint.&lt;/li&gt;
&lt;li&gt;Re-read critical contract state for records that remain uncertain.&lt;/li&gt;
&lt;li&gt;Expire worker leases independently from transaction status.&lt;/li&gt;
&lt;li&gt;Revisit recently confirmed records when the chain can still reorganize.&lt;/li&gt;
&lt;li&gt;Rewind derived state when a previously observed block leaves the canonical chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Logs are efficient for broad discovery, while direct reads are useful for checking current state. Neither is sufficient alone. A log scanner can miss data because of checkpoint bugs or provider failures. A direct read can show the current value without explaining which local intents produced it.&lt;/p&gt;

&lt;p&gt;Reconciliation also needs bounded concurrency and rate controls. An outage can create a large backlog, and an eager recovery worker can overwhelm the same RPC provider it depends on. The recovery path should be designed as normal production traffic, not exceptional traffic with unlimited resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide where freshness is worth its cost
&lt;/h2&gt;

&lt;p&gt;Reading chain state before every action reduces some stale-permissive failures, but adds latency and consumes RPC budget. It can also create a false sense of certainty because the state can change after the read.&lt;/p&gt;

&lt;p&gt;Skipping fresh reads improves throughput, but shifts more responsibility to transaction constraints, idempotency, and later reconciliation. For low-risk observations, cached state may be acceptable. For actions involving balances, permissions, nonces, or irreversible contract calls, fresher evidence is usually worth more.&lt;/p&gt;

&lt;p&gt;There is no universal freshness policy. The honest trade-off is latency and RPC usage versus correctness risk. Make that decision per predicate rather than applying one cache duration to the whole scheduler.&lt;/p&gt;

&lt;p&gt;Disclosure: I build &lt;a href="https://app.tradevo.co/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=why-a-scheduler-cannot-trust-its-own-database" rel="noopener noreferrer"&gt;Tradevo&lt;/a&gt;, software that compiles plain-English crypto strategies into deterministic rules, tests and paper-trades them, supports review and marketplace publishing, and executes subscribed rules on Base from wallets users control.&lt;/p&gt;

&lt;p&gt;A reliable scheduler does not eliminate stale state. It makes staleness visible, limits what stale assumptions can authorize, and continuously compares its local model with chain evidence. Reconciliation is not cleanup after the system fails. It is part of the system’s normal operation.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>architecture</category>
      <category>backend</category>
      <category>ethereum</category>
    </item>
    <item>
      <title>How to Find Financial Restatements in SEC Filings</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Wed, 14 Oct 2026 18:44:48 +0000</pubDate>
      <link>https://dev.to/tradevodata/how-to-find-financial-restatements-in-sec-filings-1349</link>
      <guid>https://dev.to/tradevodata/how-to-find-financial-restatements-in-sec-filings-1349</guid>
      <description>&lt;p&gt;Financial restatements can be useful research signals. They show that a previously reported figure changed after becoming public—sometimes because of an error, sometimes because of an amendment, and sometimes because the company changed how a fact was tagged or presented.&lt;/p&gt;

&lt;p&gt;For researchers, the central question is not merely: &lt;strong&gt;What does the latest filing say?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is: &lt;strong&gt;What was first reported, when did it become public, and what changed afterward?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Answering that question requires point-in-time data. A current-only financial database may replace an original value with a revision, obscuring how the historical record changed.&lt;/p&gt;

&lt;p&gt;This guide explains how to find financial restatements in SEC filings, compare original and revised values, and avoid false positives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What counts as a financial restatement?
&lt;/h2&gt;

&lt;p&gt;In a broad research workflow, a restatement flag can mean a meaningful change to a previously reported financial fact for the same company, fiscal period, and accounting concept.&lt;/p&gt;

&lt;p&gt;That is broader than searching only for filings explicitly labeled as restatements. A changed value may appear in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An amended filing such as a 10-K/A or 10-Q/A&lt;/li&gt;
&lt;li&gt;A later filing that presents comparative prior-period figures&lt;/li&gt;
&lt;li&gt;An XBRL fact using the same concept and period but a revised value&lt;/li&gt;
&lt;li&gt;A filing that corrects or updates an earlier disclosure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every change means fraud, distress, or even an accounting error. Units, decimals, contexts, fiscal periods, taxonomy choices, and presentation changes can all create apparent differences. Treat a restatement flag as a research lead, not a verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reliable method: preserve original and revised values
&lt;/h2&gt;

&lt;p&gt;To detect revisions, retain filing-time observations rather than storing only the latest value.&lt;/p&gt;

&lt;p&gt;For each fact, capture relevant fields such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Company identifier and ticker&lt;/li&gt;
&lt;li&gt;SEC filing accession or source filing&lt;/li&gt;
&lt;li&gt;Filing or acceptance date&lt;/li&gt;
&lt;li&gt;Form type&lt;/li&gt;
&lt;li&gt;Fiscal period and fiscal year&lt;/li&gt;
&lt;li&gt;XBRL concept or tag&lt;/li&gt;
&lt;li&gt;Units&lt;/li&gt;
&lt;li&gt;Context dimensions&lt;/li&gt;
&lt;li&gt;Reported value&lt;/li&gt;
&lt;li&gt;Whether the filing is an amendment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then group comparable observations by company, period, concept, units, and compatible context.&lt;/p&gt;

&lt;p&gt;Within each group, sort observations by the date they became public. The earliest valid observation becomes the &lt;strong&gt;original value&lt;/strong&gt;. The most recent comparable observation becomes the &lt;strong&gt;latest value&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A simple relative-change test is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;relative_change = abs(latest_value - original_value) / abs(original_value)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tradevo Data labels a row as restated when the same-tag value changes by more than 0.5%, including amendments. This is its documented classification rule, not a determination that a change is legally or economically material.&lt;/p&gt;

&lt;p&gt;The edge case is an original value of zero. Relative change is undefined there, so a detection system needs a separately documented rule rather than an arbitrary percentage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the same-tag requirement matters
&lt;/h2&gt;

&lt;p&gt;Comparing values across different XBRL concepts can produce misleading alerts.&lt;/p&gt;

&lt;p&gt;For example, a company may move from a standard taxonomy concept to a custom extension or disclose a related but not identical measure. Even when labels look similar, the accounting meaning may differ.&lt;/p&gt;

&lt;p&gt;A conservative detector can begin by comparing values reported under the same tag. This may not capture every economically equivalent revision, but it reduces semantic mismatches and makes each flag easier to audit.&lt;/p&gt;

&lt;p&gt;A more advanced pipeline can map extensions to standard concepts, but that requires validation and should preserve the original tags for inspection.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical SEC EDGAR workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Identify the original filing
&lt;/h3&gt;

&lt;p&gt;Start with the relevant 10-K or 10-Q in SEC EDGAR. Record the filing date, accession, fiscal period, units, context, and reported value.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Search for amendments
&lt;/h3&gt;

&lt;p&gt;Check for 10-K/A or 10-Q/A filings. Amendments are an obvious place to look, but do not stop there. A later annual or quarterly filing may present a changed comparative prior-period value.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Match the accounting fact
&lt;/h3&gt;

&lt;p&gt;Compare the same XBRL concept for the same fiscal period, units, and compatible context. Watch for consolidated versus segment facts and continuing operations versus total-company measures.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Compare chronologically
&lt;/h3&gt;

&lt;p&gt;Sort matched observations by when they became public. Preserve the first-reported value even if a later filing changes it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Apply a documented rule
&lt;/h3&gt;

&lt;p&gt;Use a documented classification rule, such as Tradevo Data's greater-than-0.5% same-tag rule. Keep the underlying observations so researchers can investigate or override the classification.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Review the filing narrative
&lt;/h3&gt;

&lt;p&gt;Read amendment explanations, footnotes, auditor language, accounting-policy disclosures, and internal-control sections. A numeric change identifies where to look; the filing may explain why it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why current-only databases can hide the signal
&lt;/h2&gt;

&lt;p&gt;Suppose a company first reports an accounting value and later revises it. If a database keeps only the revised figure, a historical backtest may behave as though that figure was known on the original date.&lt;/p&gt;

&lt;p&gt;That creates lookahead bias.&lt;/p&gt;

&lt;p&gt;A point-in-time record can instead separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;first_filed&lt;/code&gt;: the date the value became public&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;original_value&lt;/code&gt;: the first-reported, point-in-time-safe value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;latest_value&lt;/code&gt;: the current revision&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;restated&lt;/code&gt;: whether the same-tag change exceeds 0.5%&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;qa_status&lt;/code&gt;: a quality-control field&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an &lt;code&gt;as_of&lt;/code&gt; query, Tradevo Data includes rows where &lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt;, with the selected date included. For a deeper treatment, see &lt;a href="https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_content=blog-how-to-find-restatements-in-sec-filings&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_content=blog-how-to-find-restatements-in-sec-filings&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt; and &lt;a href="https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_content=blog-how-to-find-restatements-in-sec-filings&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_content=blog-how-to-find-restatements-in-sec-filings&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Tradevo Data to surface revisions
&lt;/h2&gt;

&lt;p&gt;Tradevo Data provides point-in-time US equity fundamentals sourced from public-domain SEC EDGAR data. Its annual dataset contains 632,466 point-in-time rows across 5,168 US companies, covers 16 annual concepts, and labels 37,804 restatements. It also contains 1,089,635 quarterly rows across 5,382 companies and seven quarterly concepts.&lt;/p&gt;

&lt;p&gt;The API exposes one fundamentals endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/v1/fundamentals?ticker&amp;amp;as_of[&amp;amp;concept][&amp;amp;period=annual|quarterly]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Annual is the default period. Server-side &lt;code&gt;as_of&lt;/code&gt; filtering uses &lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt;, including the selected date.&lt;/p&gt;

&lt;p&gt;Each row includes &lt;code&gt;first_filed&lt;/code&gt;, &lt;code&gt;original_value&lt;/code&gt;, &lt;code&gt;latest_value&lt;/code&gt;, a &lt;code&gt;restated&lt;/code&gt; flag, and &lt;code&gt;qa_status&lt;/code&gt;. Researchers can use these fields to screen for changed facts before reviewing the underlying SEC filings.&lt;/p&gt;

&lt;p&gt;Tradevo Data covers up to 12 fiscal years. It does not cover non-US or delisted companies and does not provide TTM figures. Quarterly Q4 is reported where tagged or derived and labeled where supported; Q4 EPS and share values are not derived. Parquet format is not included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Options compared fairly
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Limitations to evaluate&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SEC EDGAR&lt;/td&gt;
&lt;td&gt;Primary public source with complete filing context and no vendor normalization&lt;/td&gt;
&lt;td&gt;Building structured point-in-time data requires ingestion, XBRL normalization, chronology, deduplication, and QA&lt;/td&gt;
&lt;td&gt;Researchers who need maximum control and can maintain infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tradevo Data&lt;/td&gt;
&lt;td&gt;Original and latest values, filing chronology, restatement flags, server-side &lt;code&gt;as_of&lt;/code&gt;, and bulk access on Pro&lt;/td&gt;
&lt;td&gt;US scope; no non-US, delisted, TTM, or Parquet coverage&lt;/td&gt;
&lt;td&gt;Budget-conscious researchers who want structured point-in-time fundamentals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sharadar&lt;/td&gt;
&lt;td&gt;Credible established alternative&lt;/td&gt;
&lt;td&gt;Check its current coverage, methodology, and point-in-time semantics against your requirements&lt;/td&gt;
&lt;td&gt;Teams whose needs match its ecosystem; see their pricing page: &lt;a href="https://data.nasdaq.com/databases/SF1" rel="noopener noreferrer"&gt;https://data.nasdaq.com/databases/SF1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiingo&lt;/td&gt;
&lt;td&gt;Credible API-oriented market-data provider&lt;/td&gt;
&lt;td&gt;Verify current restatement fields, historical semantics, and coverage for your use case&lt;/td&gt;
&lt;td&gt;Developers seeking a broader API stack; see their pricing page: &lt;a href="https://www.tiingo.com/pricing/overview" rel="noopener noreferrer"&gt;https://www.tiingo.com/pricing/overview&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QuantConnect&lt;/td&gt;
&lt;td&gt;Integrated research and execution environment with supported datasets&lt;/td&gt;
&lt;td&gt;Evaluate whether its platform model fits a need for standalone filing data&lt;/td&gt;
&lt;td&gt;Researchers building inside its environment; see their pricing page: &lt;a href="https://www.quantconnect.com/pricing/" rel="noopener noreferrer"&gt;https://www.quantconnect.com/pricing/&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Competitor capabilities and pricing can change. Review each provider's current documentation, methodology, and pricing page rather than comparing labels alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  When another option wins
&lt;/h2&gt;

&lt;p&gt;Use SEC EDGAR directly when you need complete filing narratives, exhibits, custom tags, or provenance beyond normalized numeric rows.&lt;/p&gt;

&lt;p&gt;A broader vendor may win when you require international issuers, delisted-company coverage, additional asset classes, or a larger integrated data catalog. Sharadar or Tiingo may be a better fit when their broader products, coverage, or existing integration matches your workflow. QuantConnect may win when research, data, backtesting, and execution need to live in one environment.&lt;/p&gt;

&lt;p&gt;Tradevo Data is positioned as an honest budget tier of research-grade point-in-time fundamentals—not as the only credible or affordable option.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to build it yourself
&lt;/h2&gt;

&lt;p&gt;Build your own pipeline when restatement detection is core intellectual property, when you need custom materiality rules, or when vendor normalization would conceal details essential to your research.&lt;/p&gt;

&lt;p&gt;Be prepared to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEC submissions and accession histories&lt;/li&gt;
&lt;li&gt;Filing dates and acceptance timestamps&lt;/li&gt;
&lt;li&gt;XBRL contexts, units, dimensions, and custom extensions&lt;/li&gt;
&lt;li&gt;Fiscal-calendar irregularities&lt;/li&gt;
&lt;li&gt;Duplicate and conflicting facts&lt;/li&gt;
&lt;li&gt;Amendments and later comparative disclosures&lt;/li&gt;
&lt;li&gt;Taxonomy migrations&lt;/li&gt;
&lt;li&gt;Reproducible quality checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The advantage is control. The trade-off is continuing maintenance and validation, not just the initial parser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the method before paying
&lt;/h2&gt;

&lt;p&gt;The public proof pack contains 225 rows for five companies, covering their latest three fiscal years, with full methodology and no signup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;https://github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inspect the chronology, source logic, original values, and revisions before deciding whether the normalization fits your research.&lt;/p&gt;

&lt;p&gt;If it does, the card-backed 7-day free trial at &lt;a href="https://tradevodata.com/?utm_content=blog-how-to-find-restatements-in-sec-filings&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/?utm_content=blog-how-to-find-restatements-in-sec-filings&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt; covers 10 companies, a rolling 3-year history, and 100 requests per day without bulk. It renews at $29 per month unless canceled. Pro provides complete available history, 5,000 requests per day, and bulk annual or quarterly downloads through &lt;code&gt;/v1/download&lt;/code&gt; and &lt;code&gt;/v1/snapshot?as_of&lt;/code&gt;. Documentation is at &lt;a href="https://tradevodata.com/docs?utm_content=blog-how-to-find-restatements-in-sec-filings&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/docs?utm_content=blog-how-to-find-restatements-in-sec-filings&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Restatement flags are best used as triage: identify changed facts, inspect the filing sequence, read the disclosures, and decide whether the revision is relevant to your research.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Fundamentals Data for Finance LLM AI Agents: A Practical Grounding Layer</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Sat, 10 Oct 2026 18:44:17 +0000</pubDate>
      <link>https://dev.to/tradevodata/fundamentals-data-for-finance-llm-ai-agents-a-practical-grounding-layer-534j</link>
      <guid>https://dev.to/tradevodata/fundamentals-data-for-finance-llm-ai-agents-a-practical-grounding-layer-534j</guid>
      <description>&lt;p&gt;Finance AI agents have a basic reliability problem: language models can produce plausible answers, including plausible numbers. Plausible is not enough when a user asks what a company reported, when the information became public, or whether the figure was later restated.&lt;/p&gt;

&lt;p&gt;One practical approach is a grounding layer built from dated financial facts.&lt;/p&gt;

&lt;p&gt;For developers building retrieval-augmented generation systems, filing copilots, screening agents, research assistants, or automated company briefs, point-in-time fundamentals can provide a structured bridge between SEC filings and model-generated explanations. The model can reason over the data, but it should not invent the data.&lt;/p&gt;

&lt;p&gt;Tradevo Data provides point-in-time US equity fundamentals sourced from SEC EDGAR. Its annual dataset covers 5,168 US companies, 632,466 point-in-time rows, 16 annual concepts, and up to 12 fiscal years. Its quarterly dataset contains 1,089,635 rows across 5,382 companies and seven quarterly concepts. Coverage varies by filer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ordinary fundamentals can mislead an AI agent
&lt;/h2&gt;

&lt;p&gt;A conventional fundamentals database may emphasize the latest known value. That can be useful for current analysis, but it may be inappropriate for a historical question.&lt;/p&gt;

&lt;p&gt;Suppose an agent is asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What revenue and net income were publicly available for a company on June 15 of a past year?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If retrieval returns a figure restated months later, the answer contains information that was unavailable on June 15. The number may now be technically correct while still being historically invalid for that date.&lt;/p&gt;

&lt;p&gt;That distinction matters for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Historical research and event reconstruction&lt;/li&gt;
&lt;li&gt;Backtests using fundamental signals&lt;/li&gt;
&lt;li&gt;Filing-aware RAG systems&lt;/li&gt;
&lt;li&gt;Agents answering “as of” questions&lt;/li&gt;
&lt;li&gt;Audit trails for generated financial summaries&lt;/li&gt;
&lt;li&gt;Comparing first-reported figures with subsequent revisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is lookahead bias at the data layer. A longer explanation is available at &lt;a href="https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_content=blog-grounding-a-finance-llm-in-as-reported-fundamentals&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_content=blog-grounding-a-finance-llm-in-as-reported-fundamentals&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fields an agent needs for defensible answers
&lt;/h2&gt;

&lt;p&gt;A useful finance grounding record should do more than return a value. It should establish provenance.&lt;/p&gt;

&lt;p&gt;Each Tradevo Data row includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;first_filed&lt;/code&gt;: the date the value became public&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;original_value&lt;/code&gt;: the first-reported, point-in-time-safe value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;latest_value&lt;/code&gt;: the current revision&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;restated&lt;/code&gt;: a flag for a greater-than-0.5% change under the same XBRL tag, including amendments&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;qa_status&lt;/code&gt;: a quality-assurance status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dataset labels 37,804 restatements.&lt;/p&gt;

&lt;p&gt;For an AI agent, those fields support a stronger response pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieve facts available by the requested date.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;original_value&lt;/code&gt; for an as-reported historical answer.&lt;/li&gt;
&lt;li&gt;Compare it with &lt;code&gt;latest_value&lt;/code&gt; if revisions matter.&lt;/li&gt;
&lt;li&gt;Include &lt;code&gt;first_filed&lt;/code&gt; and the requested concept in the response.&lt;/li&gt;
&lt;li&gt;State uncertainty or coverage gaps instead of filling them with generated numbers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An XBRL tag is not a complete semantic guarantee: filers can use extensions, presentation varies, and accounting context still matters. But a dated record containing the original value, revised value, restatement flag, and QA status is more auditable than an unsupported number in model output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example query for a finance AI agent
&lt;/h2&gt;

&lt;p&gt;Tradevo Data exposes one JSON fundamentals endpoint with server-side point-in-time filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://tradevodata.com/v1/fundamentals?ticker=AAPL&amp;amp;as_of=2020-06-15&amp;amp;concept=Revenue&amp;amp;period=annual&amp;amp;utm_content=blog-grounding-a-finance-llm-in-as-reported-fundamentals&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server applies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;first_filed &amp;lt;= as_of
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The date is inclusive. Annual data is the default, while &lt;code&gt;period=quarterly&lt;/code&gt; requests quarterly rows.&lt;/p&gt;

&lt;p&gt;A basic agent tool contract might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_fundamentals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"as_of"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-06-15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"concept"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Revenue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"annual"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model should then compose an answer from returned records rather than relying on memorized parameters. A system instruction can require it to include the fiscal period, &lt;code&gt;first_filed&lt;/code&gt;, &lt;code&gt;original_value&lt;/code&gt;, and concept in every numerical citation.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use only retrieved values. If no qualifying row exists by the requested
as-of date, say that the dataset returned no supported value. Do not infer
or interpolate a financial figure.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That final rule is important. Retrieval does not prevent hallucination unless the agent is explicitly required to abstain when evidence is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical RAG architecture
&lt;/h2&gt;

&lt;p&gt;A filing agent can separate structured facts from unstructured context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use point-in-time fundamentals for supported numerical claims.&lt;/li&gt;
&lt;li&gt;Use filing text retrieval for management commentary, accounting policies, and risk disclosures.&lt;/li&gt;
&lt;li&gt;Store source metadata alongside every chunk or fact.&lt;/li&gt;
&lt;li&gt;Require citations in the final answer.&lt;/li&gt;
&lt;li&gt;Validate numerical statements against retrieved structured records before returning them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Structured fundamentals are not a replacement for the filing. They can serve as a compact numerical layer alongside it.&lt;/p&gt;

&lt;p&gt;For bulk workflows, the $29-per-month Pro plan includes &lt;code&gt;/v1/download&lt;/code&gt; and &lt;code&gt;/v1/snapshot?as_of&lt;/code&gt;, each supporting &lt;code&gt;period=annual|quarterly&lt;/code&gt;. These endpoints can support a local retrieval store or reproducible historical snapshot. Parquet format is not included.&lt;/p&gt;

&lt;p&gt;More background on the data model is available at &lt;a href="https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_content=blog-grounding-a-finance-llm-in-as-reported-fundamentals&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_content=blog-grounding-a-finance-llm-in-as-reported-fundamentals&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Tradevo Data covers—and what it does not
&lt;/h2&gt;

&lt;p&gt;The annual dataset contains these 16 concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Revenue&lt;/li&gt;
&lt;li&gt;NetIncome&lt;/li&gt;
&lt;li&gt;Assets&lt;/li&gt;
&lt;li&gt;StockholdersEquity&lt;/li&gt;
&lt;li&gt;OperatingCashFlow&lt;/li&gt;
&lt;li&gt;EPSDiluted&lt;/li&gt;
&lt;li&gt;DilutedShares&lt;/li&gt;
&lt;li&gt;GrossProfit&lt;/li&gt;
&lt;li&gt;OperatingIncome&lt;/li&gt;
&lt;li&gt;PretaxIncome&lt;/li&gt;
&lt;li&gt;IncomeTaxExpense&lt;/li&gt;
&lt;li&gt;CapitalExpenditures&lt;/li&gt;
&lt;li&gt;CashAndCashEquivalents&lt;/li&gt;
&lt;li&gt;CurrentAssets&lt;/li&gt;
&lt;li&gt;CurrentLiabilities&lt;/li&gt;
&lt;li&gt;NetPPE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quarterly coverage includes seven concepts. Q4 is reported where tagged or derived and labelled where supported. Derived Q4 EPS and share figures are not provided.&lt;/p&gt;

&lt;p&gt;The service does not provide TTM calculations, non-US coverage, delisted-company coverage, or Parquet output. It is not presented as a full filing-text corpus, market-data feed, estimates database, or accounting ontology.&lt;/p&gt;

&lt;p&gt;These limits matter. An agent should not present absence as zero, assume every filer reports every concept, or silently substitute one accounting concept for another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fair comparison for finance-agent developers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;Potential strengths&lt;/th&gt;
&lt;th&gt;Important considerations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tradevo Data&lt;/td&gt;
&lt;td&gt;Developers wanting a budget-tier API and bulk access for core US fundamentals&lt;/td&gt;
&lt;td&gt;Server-side &lt;code&gt;as_of&lt;/code&gt;, original and latest values, filing dates, restatement labels, and QA status&lt;/td&gt;
&lt;td&gt;Limited concept set; no delisted, TTM, non-US, or Parquet coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw SEC EDGAR/XBRL&lt;/td&gt;
&lt;td&gt;Teams needing maximum control and direct primary-source processing&lt;/td&gt;
&lt;td&gt;Public-domain source material and direct control over interpretation&lt;/td&gt;
&lt;td&gt;Requires ingestion, taxonomy mapping, duplicate handling, amendment processing, QA, and point-in-time logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sharadar&lt;/td&gt;
&lt;td&gt;Researchers evaluating a broader established data product&lt;/td&gt;
&lt;td&gt;A credible alternative with its own coverage and data model&lt;/td&gt;
&lt;td&gt;Confirm current scope and licensing; see their pricing page: &lt;a href="https://data.nasdaq.com/databases/SF1" rel="noopener noreferrer"&gt;https://data.nasdaq.com/databases/SF1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiingo&lt;/td&gt;
&lt;td&gt;Developers evaluating fundamentals alongside other financial APIs&lt;/td&gt;
&lt;td&gt;A credible provider with a broader product context&lt;/td&gt;
&lt;td&gt;Verify point-in-time semantics and coverage; see their pricing page: &lt;a href="https://www.tiingo.com/pricing/overview" rel="noopener noreferrer"&gt;https://www.tiingo.com/pricing/overview&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QuantConnect&lt;/td&gt;
&lt;td&gt;Teams building inside an integrated research and execution platform&lt;/td&gt;
&lt;td&gt;Data access within a broader algorithmic environment&lt;/td&gt;
&lt;td&gt;Platform fit may matter more than a standalone fundamentals API; see their pricing page: &lt;a href="https://www.quantconnect.com/pricing/" rel="noopener noreferrer"&gt;https://www.quantconnect.com/pricing/&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No provider should be chosen from a feature label alone. Test amendment handling, filing-date semantics, survivorship assumptions, missing values, concept normalization, redistribution rights, and historical coverage against your actual agent workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  When another option wins
&lt;/h2&gt;

&lt;p&gt;Another provider can be the better choice when you need a broader security universe, delisted companies, non-US issuers, more accounting concepts, analyst estimates, TTM figures, market data, or a managed research platform. A specialist vendor may also win when your institution needs enterprise support, contractual service levels, or licensing terms beyond a developer-oriented product.&lt;/p&gt;

&lt;p&gt;If your workflow already runs inside QuantConnect, its integrated environment may reduce engineering work. If you need a broader commercial dataset, Sharadar or Tiingo may fit better after you verify current coverage and point-in-time behavior.&lt;/p&gt;

&lt;p&gt;Tradevo Data is positioned as an honest budget tier of research-grade point-in-time data, not as the only affordable option and not as a universal financial-data layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to build it yourself
&lt;/h2&gt;

&lt;p&gt;Build directly from SEC EDGAR when provenance control is more important than implementation speed, your required concepts fall outside the supported set, or your team has specialized accounting and data-engineering expertise.&lt;/p&gt;

&lt;p&gt;Be prepared to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filing and amendment chronology&lt;/li&gt;
&lt;li&gt;XBRL standard tags and company extensions&lt;/li&gt;
&lt;li&gt;Units, periods, contexts, and duplicate facts&lt;/li&gt;
&lt;li&gt;Fiscal-year differences&lt;/li&gt;
&lt;li&gt;Restatements and comparable-tag logic&lt;/li&gt;
&lt;li&gt;Quarterly versus year-to-date facts&lt;/li&gt;
&lt;li&gt;Q4 derivation rules&lt;/li&gt;
&lt;li&gt;Missing and malformed filings&lt;/li&gt;
&lt;li&gt;Reprocessing when extraction rules change&lt;/li&gt;
&lt;li&gt;Reproducible historical snapshots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EDGAR is public domain, so self-building can be rational. The practical burden is maintaining consistent interpretation and QA over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect the evidence before connecting an agent
&lt;/h2&gt;

&lt;p&gt;A public proof pack is available at &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;https://github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;. It contains five companies, their latest three fiscal years, 225 rows, and the full methodology with no signup.&lt;/p&gt;

&lt;p&gt;On reliable-filing rows in that five-company proof pack only, measured lookahead averaged 35.2 days and reached a maximum of 48 days. Those measurements describe the proof pack, not the full dataset.&lt;/p&gt;

&lt;p&gt;For API evaluation, &lt;a href="https://tradevodata.com/?utm_content=blog-grounding-a-finance-llm-in-as-reported-fundamentals&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/?utm_content=blog-grounding-a-finance-llm-in-as-reported-fundamentals&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt; offers a card-backed seven-day free trial covering 10 companies, a rolling three-year history, and 100 requests per day without bulk. Unless canceled, it renews at $29 per month. Pro provides complete available history, 5,000 requests per day, and bulk endpoints. Documentation is at &lt;a href="https://tradevodata.com/docs?utm_content=blog-grounding-a-finance-llm-in-as-reported-fundamentals&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/docs?utm_content=blog-grounding-a-finance-llm-in-as-reported-fundamentals&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start with the public sample, test the dates and concepts against source filings, and make abstention part of the agent design. The goal is not to make a model sound certain. It is to make supported financial numbers traceable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>A Transaction Timeout Is Not a Transaction Failure</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Thu, 08 Oct 2026 20:09:56 +0000</pubDate>
      <link>https://dev.to/tradevodata/a-transaction-timeout-is-not-a-transaction-failure-mkn</link>
      <guid>https://dev.to/tradevodata/a-transaction-timeout-is-not-a-transaction-failure-mkn</guid>
      <description>&lt;p&gt;You broadcast a transaction, wait for the RPC response, and get a timeout. What happened?&lt;/p&gt;

&lt;p&gt;The honest answer is that you do not know. The node may never have received the request. It may have accepted the transaction into its mempool but lost the response. Another node may already have propagated it. The transaction may even be mined while your executor records a network error.&lt;/p&gt;

&lt;p&gt;Treating that timeout as a failure and sending a fresh transaction can execute the same business action twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the request from the transaction
&lt;/h2&gt;

&lt;p&gt;An RPC request and a blockchain transaction have different lifecycles. A request can fail at the HTTP layer while the transaction continues through the network.&lt;/p&gt;

&lt;p&gt;This distinction produces three outcomes, not two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Succeeded:&lt;/strong&gt; chain state shows the intended transaction was included.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failed:&lt;/strong&gt; chain state shows a terminal failure, such as a reverted transaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uncertain:&lt;/strong&gt; the executor cannot yet establish either outcome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;uncertain&lt;/code&gt; is not a softer spelling of &lt;code&gt;failed&lt;/code&gt;. It is a durable state that needs reconciliation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persist the intent before broadcasting
&lt;/h2&gt;

&lt;p&gt;The first durable record should describe the business action, not the RPC attempt. Write it before signing or sending anything.&lt;/p&gt;

&lt;p&gt;A minimal record might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;intent_id:      rebalance:account-42:2026-09-03T10
account:        0xabc...
chain_id:       8453
operation:      swap token A for token B
status:         created
nonce:          null
signed_tx:      null
tx_hashes:      []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give &lt;code&gt;intent_id&lt;/code&gt; a uniqueness constraint based on the business operation. If two workers receive the same job, they should load the same intent rather than create independent transactions.&lt;/p&gt;

&lt;p&gt;Next, reserve a nonce, construct the transaction, sign it, and persist the signed bytes and hash before broadcast. This ordering lets a restarted worker send the exact same payload.&lt;/p&gt;

&lt;p&gt;A database entry marked &lt;code&gt;broadcasting&lt;/code&gt; does not prove that any node saw the transaction. Likewise, a missing &lt;code&gt;sent&lt;/code&gt; log does not prove that no node saw it. Your database records what the executor attempted; the chain records what took effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use nonces deliberately
&lt;/h2&gt;

&lt;p&gt;For an EVM externally owned account, the nonce is the main duplicate-execution boundary. Two transactions from the same account with the same nonce cannot both become canonical.&lt;/p&gt;

&lt;p&gt;That does not mean an executor can derive any nonce from an intent ID. Account nonces are sequential. A practical nonce manager must serialize reservations per account and persist them so concurrent workers do not choose the same nonce for unrelated intents.&lt;/p&gt;

&lt;p&gt;After a timeout, rebroadcasting the exact signed bytes is the simplest safe retry. The transaction hash stays the same, and nodes that already know it can treat the broadcast as redundant.&lt;/p&gt;

&lt;p&gt;Fee replacement is more complicated. A replacement uses the same nonce but changes fee fields, producing a new hash. Persist every candidate hash under the same intent and monitor all of them. Never respond to a timeout by allocating a fresh nonce to the same action.&lt;/p&gt;

&lt;p&gt;If the original transaction has already been mined, a replacement may be rejected because the nonce is too low. That error is a reason to inspect chain state, not to create another transaction.&lt;/p&gt;

&lt;p&gt;Smart-contract accounts can provide a stronger application-level boundary by storing an operation ID and rejecting duplicate execution. That handles cases an account nonce cannot express, but it adds contract complexity and execution cost. Sequential EOA nonces are simpler, though they can become a throughput bottleneck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconcile with chain state
&lt;/h2&gt;

&lt;p&gt;A reconciliation worker should examine evidence in roughly this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check receipts for every transaction hash attached to the intent.&lt;/li&gt;
&lt;li&gt;Check whether the sending account's nonce has been consumed.&lt;/li&gt;
&lt;li&gt;Inspect the destination contract's state or events for the intended operation.&lt;/li&gt;
&lt;li&gt;Wait for the confirmation depth required by the application before marking the intent final.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A single RPC node saying “not found” is weak evidence. Mempools differ, nodes can lag, and old pending transactions may disappear from local views. Querying another provider can improve visibility, but it still does not turn absence into proof.&lt;/p&gt;

&lt;p&gt;If the account nonce has advanced, determine which transaction consumed it. Your intended transaction may have been included, replaced, or displaced by another process using the same account.&lt;/p&gt;

&lt;p&gt;Reorganizations also matter. &lt;code&gt;included&lt;/code&gt; and &lt;code&gt;finalized&lt;/code&gt; should be separate states. An included transaction can temporarily disappear and need further reconciliation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ugly middle state
&lt;/h2&gt;

&lt;p&gt;Suppose the broadcast timed out, no receipt is visible, the transaction is absent from the RPC node, and the account nonce has not moved. The transaction might still be propagating, or every node might have dropped it.&lt;/p&gt;

&lt;p&gt;There is no honest terminal answer yet.&lt;/p&gt;

&lt;p&gt;Keep the intent &lt;code&gt;uncertain&lt;/code&gt;, rebroadcast the same signed payload, and schedule another reconciliation pass. Time alone should not silently convert uncertainty into failure. If the application eventually stops retrying, preserve the unresolved status for operator inspection.&lt;/p&gt;

&lt;p&gt;A “cancel” transaction is also only a competing transaction with the same nonce. It can win the race, or the original can be mined first. Model the outcome from chain evidence rather than from the fact that a cancellation request was sent.&lt;/p&gt;

&lt;p&gt;Disclosure: I build &lt;a href="https://app.tradevo.co/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=idempotency-when-a-transaction-may-already-be-mined" rel="noopener noreferrer"&gt;Tradevo&lt;/a&gt;, which compiles plain-English crypto strategies into deterministic rules, runs historical tests and paper trading, lets creators submit strategies for human review and publish approved strategies to a marketplace, and lets subscribers run published rules from wallets they control.&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful executor invariant
&lt;/h2&gt;

&lt;p&gt;The core invariant is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One business intent may have several broadcast attempts and transaction hashes, but it must not silently become several independent nonce-bearing actions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That requires coordination across the database, signer, nonce manager, broadcaster, and reconciliation worker. Idempotency is not a retry flag attached to an RPC call. It is a state machine built around the possibility that the network acted while your process learned nothing.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>architecture</category>
      <category>ethereum</category>
      <category>backend</category>
    </item>
    <item>
      <title>Point-in-Time Data for Academic Finance Research: A Reproducibility Guide</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Wed, 07 Oct 2026 20:02:49 +0000</pubDate>
      <link>https://dev.to/tradevodata/point-in-time-data-for-academic-finance-research-a-reproducibility-guide-1mkj</link>
      <guid>https://dev.to/tradevodata/point-in-time-data-for-academic-finance-research-a-reproducibility-guide-1mkj</guid>
      <description>&lt;p&gt;In empirical finance, a backtest should use only information available on the simulated decision date. If it uses financial statements based on fiscal period-end dates, current database values, or later restatements, the model may incorporate facts that had not yet been published.&lt;/p&gt;

&lt;p&gt;That is lookahead bias. It can affect estimated predictability, reported coefficients, and reproducibility.&lt;/p&gt;

&lt;p&gt;Point-in-time, or PIT, fundamentals record when each value became public and allow researchers to reconstruct the information set available as of a historical date. For accounting-based asset-pricing signals, PIT treatment can be an important part of research design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ordinary fundamentals may not be enough
&lt;/h2&gt;

&lt;p&gt;Suppose a company has a fiscal year ending December 31. Its annual revenue does not necessarily become public on December 31. The filing may arrive weeks later, and an amendment or restatement may subsequently change the value.&lt;/p&gt;

&lt;p&gt;A conventional panel might contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the fiscal period end;&lt;/li&gt;
&lt;li&gt;the latest known value;&lt;/li&gt;
&lt;li&gt;a standardized concept;&lt;/li&gt;
&lt;li&gt;the company identifier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That panel may be useful for descriptive tasks, but it does not necessarily answer the historical question: &lt;strong&gt;What value could a researcher have observed on the portfolio formation date?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A PIT row needs an availability date or timestamp. Tradevo Data exposes &lt;code&gt;first_filed&lt;/code&gt;, defined as the date the value became public. Its API applies &lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt;, including same-day filings. Each row also contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;original_value&lt;/code&gt;: the first-reported, PIT-safe value;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;latest_value&lt;/code&gt;: the current revision;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;restated&lt;/code&gt;: a flag for changes above 0.5% on the same tag, including amendments;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;qa_status&lt;/code&gt;: a data-quality field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The distinction between original and latest values matters. Replacing the original observation with a later revision can introduce future information even if the fiscal period itself is correctly lagged.&lt;/p&gt;

&lt;p&gt;For a deeper treatment, see &lt;a href="https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/blog/lookahead-bias-fundamental-backtests?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  PIT design belongs in the methods section
&lt;/h2&gt;

&lt;p&gt;A reproducible paper should state more than “fundamentals were lagged.” A fixed lag is a convention, not proof that the information was public.&lt;/p&gt;

&lt;p&gt;A stronger methodology documents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Availability rule:&lt;/strong&gt; Which filing date or timestamp determines eligibility?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundary rule:&lt;/strong&gt; Are filings published on the formation date included?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revision policy:&lt;/strong&gt; Does the study use first-reported or latest values?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fiscal-period policy:&lt;/strong&gt; How are annual observations aligned across firms?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concept mapping:&lt;/strong&gt; Which source tags map to each standardized variable?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missingness policy:&lt;/strong&gt; Are missing values dropped, imputed, or interpreted economically?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universe policy:&lt;/strong&gt; Does the source include delisted companies and non-US issuers?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot procedure:&lt;/strong&gt; Can another researcher reconstruct the same extract later?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tradevo Data's server-side endpoint is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/v1/fundamentals?ticker&amp;amp;as_of[&amp;amp;concept][&amp;amp;period=annual|quarterly]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Annual is the default. Bulk access is available through &lt;code&gt;/v1/download&lt;/code&gt; and &lt;code&gt;/v1/snapshot?as_of&lt;/code&gt;, each accepting &lt;code&gt;period=annual|quarterly&lt;/code&gt;. The snapshot route makes the historical cutoff explicit. Parquet format is not included.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Tradevo Data covers—and what it does not
&lt;/h2&gt;

&lt;p&gt;Tradevo Data provides point-in-time US equity fundamentals derived from public-domain SEC EDGAR filings. Its annual dataset contains 632,466 point-in-time rows across 5,168 US companies, with 16 annual concepts and up to 12 fiscal years. Its quarterly dataset contains 1,089,635 rows across 5,382 companies and seven quarterly concepts. Across the data, 37,804 restatements are labeled.&lt;/p&gt;

&lt;p&gt;The annual concepts are Revenue, NetIncome, Assets, StockholdersEquity, OperatingCashFlow, EPSDiluted, DilutedShares, GrossProfit, OperatingIncome, PretaxIncome, IncomeTaxExpense, CapitalExpenditures, CashAndCashEquivalents, CurrentAssets, CurrentLiabilities, and NetPPE.&lt;/p&gt;

&lt;p&gt;Quarterly data is also available with seven concepts. Q4 is reported where tagged or derived and labeled where supported. Derived Q4 EPS and shares are excluded.&lt;/p&gt;

&lt;p&gt;The limitations are material:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;US companies only;&lt;/li&gt;
&lt;li&gt;no non-US coverage;&lt;/li&gt;
&lt;li&gt;no delisted-company coverage;&lt;/li&gt;
&lt;li&gt;no TTM fields;&lt;/li&gt;
&lt;li&gt;a focused concept set rather than the breadth of a large institutional platform;&lt;/li&gt;
&lt;li&gt;no included Parquet format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These constraints should appear in a paper's data section. In particular, the absence of delisted companies may create survivorship concerns depending on sample construction and the research question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tradevo Data versus WRDS and Compustat
&lt;/h2&gt;

&lt;p&gt;WRDS is an institutional research platform through which universities and other organizations may access datasets such as Compustat, subject to their subscriptions and licenses. It may be the stronger choice when a study requires broader historical coverage, standardized identifiers across databases, delisted securities, international accounting data, or established institutional workflows.&lt;/p&gt;

&lt;p&gt;Tradevo Data is a narrower, budget-tier PIT source for researchers who need US filing fundamentals without requiring university-gated access.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Tradevo Data&lt;/th&gt;
&lt;th&gt;WRDS/Compustat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Access model&lt;/td&gt;
&lt;td&gt;Public API and bulk files; card-backed trial&lt;/td&gt;
&lt;td&gt;Commonly accessed through subscribing institutions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source orientation&lt;/td&gt;
&lt;td&gt;Public-domain SEC EDGAR filings&lt;/td&gt;
&lt;td&gt;Commercial research databases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PIT mechanism&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;first_filed&lt;/code&gt;, original and latest values, and server-side &lt;code&gt;as_of&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Capabilities depend on the subscribed dataset and fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coverage&lt;/td&gt;
&lt;td&gt;US companies; focused annual and quarterly concepts&lt;/td&gt;
&lt;td&gt;Potentially broader options, depending on institutional subscriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delisted companies&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;May be the better route when delisted coverage is required; verify the specific product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;International research&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;May be better suited when subscribed products cover the required markets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reproducibility aids&lt;/td&gt;
&lt;td&gt;Public methodology, no-signup sample, and dated snapshots&lt;/td&gt;
&lt;td&gt;Institutional workflows subject to licensing and local access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;$29/month after the trial unless canceled&lt;/td&gt;
&lt;td&gt;See their pricing or institutional-access page: &lt;a href="https://wrds-www.wharton.upenn.edu/" rel="noopener noreferrer"&gt;https://wrds-www.wharton.upenn.edu/&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not an argument that one source universally replaces the other. The appropriate source depends on the identification strategy, required universe, licensing constraints, and replication plan. Sharadar, Tiingo, and QuantConnect are also credible alternatives; compare their current documentation, coverage, PIT methodology, and pricing pages directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public proof before signup
&lt;/h2&gt;

&lt;p&gt;Tradevo Data publishes a no-signup proof pack at:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;https://github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It contains 225 rows for five companies over their latest three fiscal years, plus the full methodology. Researchers can inspect the schema, test availability logic, and evaluate whether the concept mapping is suitable before entering payment details.&lt;/p&gt;

&lt;p&gt;On reliable-filing rows in that five-company proof pack only, measured lookahead averaged 35.2 days and reached a maximum of 48 days. These figures are not evidence about the full dataset. They demonstrate within the sample how filing availability can differ from fiscal period-end dating.&lt;/p&gt;

&lt;p&gt;More background on the data model is available at &lt;a href="https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/blog/point-in-time-fundamentals-data?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical research workflow
&lt;/h2&gt;

&lt;p&gt;A defensible workflow can be simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pre-register or document the formation-date rule.&lt;/li&gt;
&lt;li&gt;Query or snapshot the data with the required &lt;code&gt;as_of&lt;/code&gt; date.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;original_value&lt;/code&gt; for the historical information set unless the research question explicitly concerns revised accounts.&lt;/li&gt;
&lt;li&gt;Retain &lt;code&gt;first_filed&lt;/code&gt;, &lt;code&gt;restated&lt;/code&gt;, and &lt;code&gt;qa_status&lt;/code&gt; in intermediate files.&lt;/li&gt;
&lt;li&gt;Archive query parameters, code, and extraction dates.&lt;/li&gt;
&lt;li&gt;Report coverage exclusions, especially delisted and non-US companies.&lt;/li&gt;
&lt;li&gt;Run sensitivity checks using alternative availability rules where justified.&lt;/li&gt;
&lt;li&gt;Make a small, license-compliant replication extract available when possible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The card-backed seven-day free trial covers 10 companies, a rolling three-year history, and 100 requests per day, without bulk access. It renews at $29/month unless canceled. Pro provides the complete available history, 5,000 requests per day, and bulk access through &lt;code&gt;/v1/download&lt;/code&gt; and &lt;code&gt;/v1/snapshot?as_of&lt;/code&gt;. Documentation is at &lt;a href="https://tradevodata.com/docs?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/docs?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;, and the trial is available at &lt;a href="https://tradevodata.com/?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When WRDS or another institutional source wins
&lt;/h2&gt;

&lt;p&gt;Use WRDS, Compustat, or another appropriate institutional dataset when the paper requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delisted-company coverage to address survivorship bias;&lt;/li&gt;
&lt;li&gt;non-US or cross-country samples;&lt;/li&gt;
&lt;li&gt;history beyond Tradevo Data's available horizon;&lt;/li&gt;
&lt;li&gt;accounting fields outside Tradevo Data's focused concept set;&lt;/li&gt;
&lt;li&gt;linked returns, analyst estimates, ownership, executive, or security-master data;&lt;/li&gt;
&lt;li&gt;identifiers and conventions already standard in a target literature;&lt;/li&gt;
&lt;li&gt;a licensed source mandated by coauthors, reviewers, or the institution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Researchers should inspect the exact subscribed product, documentation, PIT fields, and licensing terms rather than assume every dataset exposed through a platform has identical historical properties. For current access and cost information, see each provider's pricing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to build it yourself
&lt;/h2&gt;

&lt;p&gt;Building directly from SEC EDGAR can be the better choice when the extraction pipeline is itself a research contribution, when unusual XBRL tags are central to the hypothesis, or when complete control over transformation rules matters more than implementation time.&lt;/p&gt;

&lt;p&gt;A credible build may need to handle filing chronology, amendments, duplicate facts, taxonomy changes, units, dimensions, fiscal calendars, concept mapping, accession-level provenance, and historical snapshots. It may also require tests for revisions and malformed filings.&lt;/p&gt;

&lt;p&gt;The advantage is transparency and customization. The disadvantage is the required data-engineering work. One approach is to validate a managed dataset against raw filings for a stratified sample and publish the audit code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The standard is reconstructability
&lt;/h2&gt;

&lt;p&gt;Using a familiar database does not by itself make academic finance reproducible. The core question is whether another researcher can reconstruct the information set available on each simulated date and understand the transformations applied.&lt;/p&gt;

&lt;p&gt;Tradevo Data offers a focused option for annual and quarterly US filing fundamentals, with explicit availability dates, original and revised values, restatement labels, snapshots, and a public methodology. It is not a substitute for broad institutional databases in every research design. It is an inspectable budget-tier option when its coverage matches the paper.&lt;/p&gt;

&lt;p&gt;Start by auditing the free sample at &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;https://github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;. If the schema and methodology fit the study, the card-backed seven-day free trial is available at &lt;a href="https://tradevodata.com/?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate" rel="noopener noreferrer"&gt;https://tradevodata.com/?utm_content=blog-point-in-time-data-for-academic-finance-research&amp;amp;utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&lt;/a&gt; and renews at $29/month unless canceled.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Sponsoring Gas Without Holding User Funds: Paymasters and the Trust Model</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Thu, 01 Oct 2026 19:50:45 +0000</pubDate>
      <link>https://dev.to/tradevodata/sponsoring-gas-without-holding-user-funds-paymasters-and-the-trust-model-5a0l</link>
      <guid>https://dev.to/tradevodata/sponsoring-gas-without-holding-user-funds-paymasters-and-the-trust-model-5a0l</guid>
      <description>&lt;p&gt;The awkward edge case in a consumer wallet flow is the first transaction: the user has the asset your application needs, but none of the chain’s native token. Asking them to visit an exchange or bridge before they can use the app is technically honest and often terrible UX.&lt;/p&gt;

&lt;p&gt;Account abstraction offers another option. A paymaster can cover the network fee while the user’s assets remain in their own smart account. That removes one prerequisite, but it also introduces a sponsor with policy control, infrastructure costs, and several ways to be abused.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic ERC-4337 flow
&lt;/h2&gt;

&lt;p&gt;In ERC-4337, a wallet submits a &lt;code&gt;UserOperation&lt;/code&gt; rather than sending a conventional transaction directly. Conceptually, the flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wallet builds and signs UserOperation
        |
        v
sponsor checks operation against its policy
        |
        v
sponsor adds signed paymaster data
        |
        v
bundler simulates and submits the operation
        |
        v
EntryPoint validates wallet and paymaster
        |
        v
call executes; fee is charged to paymaster deposit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The paymaster keeps native tokens deposited with the EntryPoint contract. Its validation logic decides whether it accepts responsibility for a particular operation. The EntryPoint then charges the actual execution cost to that deposit, subject to the operation’s gas fields and protocol rules.&lt;/p&gt;

&lt;p&gt;Most consumer applications use a verifying paymaster. An off-chain service inspects the operation and signs sponsorship data if the request satisfies its policy. The on-chain paymaster verifies that signature during validation.&lt;/p&gt;

&lt;p&gt;This split matters. The on-chain component checks that the sponsor approved the exact operation, while the off-chain service handles changing concerns such as rate limits, account status, application plans, and allowed contract methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the sponsor can do
&lt;/h2&gt;

&lt;p&gt;A sponsor can decide which operations it is willing to fund. Its policy might inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The destination contract and function selector.&lt;/li&gt;
&lt;li&gt;The chain and EntryPoint version.&lt;/li&gt;
&lt;li&gt;Maximum gas and fee fields.&lt;/li&gt;
&lt;li&gt;Calldata size.&lt;/li&gt;
&lt;li&gt;Wallet, user, device, or subscription quotas.&lt;/li&gt;
&lt;li&gt;Whether the operation was produced through an expected application flow.&lt;/li&gt;
&lt;li&gt;Estimated cost under current network conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sponsor can also stop issuing approvals, lower limits, or make sponsorship conditional on its backend being available. That is a real dependency. A wallet may remain usable, but the sponsored path can disappear.&lt;/p&gt;

&lt;p&gt;The sponsor also learns about an operation before inclusion. Even though the eventual transaction is public, advance visibility and request metadata can matter. Applications should avoid pretending that sponsorship is a privacy mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the sponsor cannot do
&lt;/h2&gt;

&lt;p&gt;Paying gas is not the same as authorizing the wallet’s call.&lt;/p&gt;

&lt;p&gt;The smart account still validates the user’s signature or whatever authorization scheme the account implements. A paymaster cannot modify the signed destination, calldata, or value without invalidating that authorization. It also cannot spend wallet assets merely because it paid the network fee.&lt;/p&gt;

&lt;p&gt;This distinction becomes blurred when one company operates several components. An application might run the sponsor, bundler access, and a delegated wallet signer. Those roles should be analyzed separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The paymaster decides whether to pay.&lt;/li&gt;
&lt;li&gt;The bundler decides whether to submit a valid operation.&lt;/li&gt;
&lt;li&gt;The wallet’s authorization logic decides which calls may execute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A restricted paymaster policy does not make a broad wallet key safe. Likewise, a narrow wallet authorization does not prevent somebody from wasting an overly permissive sponsorship budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open sponsorship is a griefing surface
&lt;/h2&gt;

&lt;p&gt;A policy that effectively says “pay for any operation from our app” is easy to consume at scale. An attacker does not need to steal the sponsor’s deposit. They only need to create operations that qualify and cost money.&lt;/p&gt;

&lt;p&gt;Simple per-wallet limits are weak if wallets are cheap to create. IP limits can block legitimate shared networks and are easy to distribute around. Requiring an application login raises the cost of abuse but moves the problem into account creation and session security.&lt;/p&gt;

&lt;p&gt;Failed calls also deserve attention. An operation can pass validation and then revert during execution, consuming gas anyway. Simulation catches many failures, but state can change between simulation and inclusion. Deliberately expensive valid calls may still satisfy a policy that checks only destinations and selectors.&lt;/p&gt;

&lt;p&gt;A practical policy therefore combines several controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow specific contracts and methods rather than arbitrary calls.&lt;/li&gt;
&lt;li&gt;Cap gas fields and calldata size.&lt;/li&gt;
&lt;li&gt;Check call arguments when their meaning is stable and unambiguous.&lt;/li&gt;
&lt;li&gt;Set budgets across wallets, users, and time windows.&lt;/li&gt;
&lt;li&gt;Reject stale sponsorship signatures.&lt;/li&gt;
&lt;li&gt;Monitor cost per successful operation and cost per failed operation.&lt;/li&gt;
&lt;li&gt;Add a global circuit breaker for unexpected spending.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these controls is free. More inspection creates more backend coupling and more ways to reject legitimate traffic after a contract upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sponsorship is an operating decision
&lt;/h2&gt;

&lt;p&gt;The contract is only part of the system. Someone must keep the paymaster deposit funded, monitor native-token exposure, handle fee spikes, and decide what happens when a daily budget is exhausted.&lt;/p&gt;

&lt;p&gt;You also need an outage mode. Can users pay their own gas if the sponsor is unavailable? Does the interface explain that option before asking them to sign? Can another bundler submit the same operation, or have you coupled sponsorship to one provider’s API?&lt;/p&gt;

&lt;p&gt;Cost attribution matters too. “Gasless” usually means somebody else receives the bill. A product team needs to know which user action created each charge, whether retries duplicate work, and how much abuse the support team is willing to tolerate. Sponsorship may be cheap during normal traffic but difficult to bound during a fee spike or automated attack.&lt;/p&gt;

&lt;p&gt;Alternatives have valid trade-offs. Letting users pay is simpler and makes costs explicit. A faucet is straightforward but attracts automated abuse. Reimbursing users after execution still requires them to hold native tokens first. Application-specific relayers can work well when contracts already support meta-transactions, though they introduce their own trust and compatibility constraints.&lt;/p&gt;

&lt;p&gt;Disclosure: I build &lt;a href="https://app.tradevo.co/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=syndicate&amp;amp;utm_content=sponsoring-gas-without-holding-user-funds" rel="noopener noreferrer"&gt;Tradevo&lt;/a&gt;, which lets people build, test, paper-trade, publish, subscribe to, and automatically run crypto strategies from wallets they control; its free tier sponsors gas.&lt;/p&gt;

&lt;p&gt;A paymaster improves onboarding by separating transaction authorization from fee payment. It does not remove trust; it moves part of the trust model into sponsorship policy and operations. Treat that policy like a security boundary, and treat its budget like production infrastructure rather than a promotional checkbox.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>ethereum</category>
      <category>architecture</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
