<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[SIGNUM HQ Engineering]]></title><description><![CDATA[SIGNUM HQ Engineering]]></description><link>https://signumhq.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ad48ccc2eac5731b5426737/c6ad5087-ed9d-4d9b-b5ff-468f8b866bd7.png</url><title>SIGNUM HQ Engineering</title><link>https://signumhq.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 24 Oct 2026 23:08:41 GMT</lastBuildDate><atom:link href="https://signumhq.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Before calculating an OTC percentage, validate the denominator and preserve missing values]]></title><description><![CDATA[A percentage without a coverage definition is easy to misread. In market data, ATS activity, non-ATS activity and consolidated market volume are different…]]></description><link>https://signumhq.hashnode.dev/before-calculating-an-otc-percentage-validate-the-denominator-and-preserve-missing-values</link><guid isPermaLink="true">https://signumhq.hashnode.dev/before-calculating-an-otc-percentage-validate-the-denominator-and-preserve-missing-values</guid><category><![CDATA[Python]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[finance]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[SIGNUM HQ]]></dc:creator><pubDate>Wed, 21 Oct 2026 14:52:20 GMT</pubDate><content:encoded><![CDATA[<p>A percentage without a coverage definition is easy to misread. In market data, ATS activity, non-ATS activity and consolidated market volume are different quantities. Calling all of them “dark-pool volume” hides the distinction before any calculation begins.</p>
<p>Disclosure: this is an AI-generated technical example from the SIGNUM HQ team. The share counts below are synthetic. They are not market observations, and the function is not presented as our production code.</p>
<p>Assume the inputs already use the same security, observation window, unit and revision. Under that assumption, an OTC share percentage can use ATS plus non-ATS shares over consolidated shares. A missing component remains unknown.</p>
<pre><code class="language-python">from decimal import Decimal

def otc_percent(ats, non_ats, consolidated):
    if any(x is None for x in (ats, non_ats, consolidated)):
        return None
    if not all(isinstance(x, int) and not isinstance(x, bool)
               for x in (ats, non_ats, consolidated)):
        raise ValueError("integer share counts required")
    if min(ats, non_ats) &lt; 0 or consolidated &lt;= 0:
        raise ValueError("invalid volume")
    if ats + non_ats &gt; consolidated:
        raise ValueError("coverage or window mismatch")
    return Decimal(100) * Decimal(ats + non_ats) / Decimal(consolidated)
</code></pre>
<p>In the synthetic example, 20 ATS shares plus 30 non-ATS shares against 100 consolidated shares produce 50%. Zero plus zero against a positive denominator produces 0%. Missing ATS volume produces no percentage. Those last two states should not collapse into one.</p>
<p>The local exercise checks eight cases: a valid ratio, observed zero, a missing component, a zero denominator, components exceeding the total, negative volume, a Boolean and a fractional share count. Reproduction: running the example file under Python 3.9.6 on my machine printed <code>otc_denominator: 8 cases passed</code>.</p>
<p>Rejecting a result above 100% is a diagnostic, not permission to clamp it to 100%. The periods, coverage, adjustment or revision may be inconsistent. The function also cannot detect a mismatch that happens to produce a plausible result; the assumptions must be validated upstream.</p>
<p>FINRA distinguishes ATS and non-ATS data and publishes observations with reporting delays. A weekly observation cannot become today's intraday signal merely because it was downloaded today. Nor does the execution venue alone identify buying intent or accumulation.</p>
<p>Reference: <a href="https://www.finra.org/sites/default/files/OTC-Transparency-Data-File-Download-API-v04.pdf">FINRA OTC data fields and publication dates</a>.</p>
<p>Product context: a free page that checks a reading of off-exchange share against observed FINRA data (no login, no AI calls): <a href="https://www.signumhq.com/en/tools/read-check?from=hashnode_readcheck">Does this reading hold up?</a></p>
]]></content:encoded></item><item><title><![CDATA[Translating economic-calendar event names without collisions (MoM vs level, ex-autos, U-6)]]></title><description><![CDATA[Our app has an economic calendar (CPI, jobless claims, retail sales and so on) that we translate into Korean and Japanese. One day I noticed that two…]]></description><link>https://signumhq.hashnode.dev/translating-economic-calendar-event-names-without-collisions-mom-vs-level-ex-autos-u-6</link><guid isPermaLink="true">https://signumhq.hashnode.dev/translating-economic-calendar-event-names-without-collisions-mom-vs-level-ex-autos-u-6</guid><category><![CDATA[webdev]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[i18n]]></category><category><![CDATA[Testing]]></category><category><![CDATA[webdevelopment]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[i18next]]></category><category><![CDATA[Testing Library]]></category><dc:creator><![CDATA[SIGNUM HQ]]></dc:creator><pubDate>Sun, 18 Oct 2026 09:36:15 GMT</pubDate><content:encoded><![CDATA[<p>Our app has an economic calendar (CPI, jobless claims, retail sales and so on) that we translate into Korean and Japanese. One day I noticed that two different releases on the same day had the same Korean name. This is how that happened and how I now prevent it.</p>
<h2>The symptom</h2>
<p>On a single day the calendar had these English events:</p>
<ul>
<li><code>Durable Goods Orders MoM</code></li>
<li><code>Durable Goods Orders Ex Transp MoM</code></li>
<li><code>Durable Goods Orders Ex Defense MoM</code></li>
</ul>
<p>All three came out as the same translated name. To a reader that looks like the same number printed three times, when they are three different series with three different values.</p>
<p>I then captured the whole public calendar (106 events at the time) and ran every name through the translator. <strong>12 events collided</strong> with another event on the same date. Others in the same family:</p>
<ul>
<li><code>Retail Sales</code> vs <code>Retail Sales Ex Autos</code> vs <code>Retail Sales Ex Gas/Autos</code></li>
<li><code>Retail Inventories</code> vs <code>Retail Inventories Ex Autos</code></li>
<li><code>Unemployment Rate</code> vs <code>U-6 Unemployment Rate</code></li>
<li><code>GDP</code> vs <code>GDP Price Index</code></li>
<li><code>Initial Jobless Claims</code> vs <code>Jobless Claims 4-Week Average</code></li>
</ul>
<h2>The cause</h2>
<p>The translator is an ordered list of rules. Each rule is a regex plus the localized name, and the first rule that matches wins. The variants above had no rule of their own, so the general rule caught all of them:</p>
<pre><code class="language-ts">// simplified
const RULES = [
  { match: /durable goods orders/i, ko: '내구재 주문' },   // swallows Ex Transp, Ex Defense, ...
];
</code></pre>
<p>Nothing crashes. The output is a plausible-looking string, just the wrong one, which is the worst kind of bug in a localization layer.</p>
<h2>Two other traps in the same area</h2>
<p><strong>Suffixes carry the meaning.</strong> <code>MoM</code>, <code>YoY</code>, <code>QoQ</code> (rate of change versus a level), <code>Ex Autos</code>, <code>Ex Defense</code>, and the reporting period <code>(Sep)</code> or <code>(Oct/16)</code> are not noise. If you strip them to "normalize" the name, you merge events that should stay separate. An earlier change had already started preserving the qualifiers in localized names; this one finished the job for the remaining families.</p>
<p><strong>Fix the locale you are fixing.</strong> After the change, English output went through a code path that re-spaced CamelCase names and one feed value (<code>FedWallerSpeech</code>, no spaces) leaked through raw. The English and "translation off" path must behave exactly as before; I restored it and added it to the tests.</p>
<h2>The fix</h2>
<ol>
<li>Add explicit rules for the specific variants (<code>ex transp</code>, <code>ex defense</code>, <code>ex gas/autos</code>, <code>ex autos</code>, <code>u-6</code>, <code>price index</code>, <code>4-week average</code>) and keep them before the general ones, because the first match wins.</li>
<li>Add a test that does not depend on me remembering which events exist: capture the real public calendar once, translate every name, and assert that <strong>no two events on the same date map to the same localized name</strong>, for each locale.</li>
</ol>
<pre><code class="language-ts">for (const locale of ['ko', 'ja']) {
  const seen = new Map();                       // key: date + localized name
  for (const ev of capturedCalendar.events) {
    const key = ev.date + '|' + localize(ev.event, locale);
    assert(!seen.has(key), `collision: ${seen.get(key)} / ${ev.event}`);
    seen.set(key, ev.event);
  }
}
</code></pre>
<p>The captured file is a snapshot of the real production names, so the test fails the day a new vendor event name collides, instead of the day a user screenshots it.</p>
<h2>What I would tell my past self</h2>
<ul>
<li>In an ordered-rule system, order is part of the logic. Write a test that checks uniqueness of outputs, not just that each input maps somewhere.</li>
<li>Test against captured production data, not just hand-written examples. My examples always looked fine.</li>
<li>A wrong translation is more damaging than an untranslated name. A user can read the English, but they cannot know the Korean is wrong.</li>
</ul>
<p>I build this in <a href="https://www.signumhq.com/app?from=hashnode_post1">SIGNUM HQ</a>, a solo-developer US-stock app (options flow, dark pool prints, earnings calendar).</p>
<p><em>Disclosure: I used an AI assistant to help draft and edit this post. The bug, the fix and the conclusions come from my own project.</em></p>
]]></content:encoded></item></channel></rss>