<?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: Arnold Holm</title>
    <description>The latest articles on DEV Community by Arnold Holm (@stratcorealpha).</description>
    <link>https://dev.to/stratcorealpha</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%2F4078355%2Fc2ecba14-1ac4-43ef-b437-2a38e810c2ca.png</url>
      <title>DEV Community: Arnold Holm</title>
      <link>https://dev.to/stratcorealpha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stratcorealpha"/>
    <language>en</language>
    <item>
      <title>A Paired Buy and Sell EA Needs One Explicit Cycle State</title>
      <dc:creator>Arnold Holm</dc:creator>
      <pubDate>Wed, 02 Sep 2026 12:34:42 +0000</pubDate>
      <link>https://dev.to/stratcorealpha/a-paired-buy-and-sell-ea-needs-one-explicit-cycle-state-1l37</link>
      <guid>https://dev.to/stratcorealpha/a-paired-buy-and-sell-ea-needs-one-explicit-cycle-state-1l37</guid>
      <description>&lt;p&gt;A paired buy and sell EA can open both legs correctly and still fail after the first exit. The reason is usually not the entry signal. The lifecycle was never defined.&lt;/p&gt;

&lt;p&gt;That distinction matters to a buyer because a clean compile only proves that the code is syntactically valid. It does not prove that the Expert Advisor knows which cycle is active, which leg survived, or whether a restart permits another entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The buyer question
&lt;/h2&gt;

&lt;p&gt;Before development starts, the specification should answer one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What state is the EA in after one leg closes, and what exact event completes the cycle?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If that answer is vague, the implementation can place a replacement order too early, leave an orphaned position unmanaged, or restart into the wrong state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use one explicit lifecycle
&lt;/h2&gt;

&lt;p&gt;A practical model has four states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;IDLE&lt;/strong&gt;: no active pair exists and a new cycle may start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PAIR_OPEN&lt;/strong&gt;: both legs belong to the active cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ONE_LEG_CLOSED&lt;/strong&gt;: one leg has exited and the remaining leg still belongs to the same cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CYCLE_COMPLETE&lt;/strong&gt;: both legs are closed, cleanup is complete, and the EA may return to IDLE.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The names can change. The important part is that every transition has one deterministic trigger.&lt;/p&gt;

&lt;p&gt;For example, the transition from &lt;code&gt;PAIR_OPEN&lt;/code&gt; to &lt;code&gt;ONE_LEG_CLOSED&lt;/code&gt; should be caused by one verified close event for a ticket that belongs to the active cycle. It should not be inferred only from a temporary order count. The transition from &lt;code&gt;ONE_LEG_CLOSED&lt;/code&gt; to &lt;code&gt;CYCLE_COMPLETE&lt;/code&gt; should require proof that the survivor has closed and that no cycle-owned pending order remains.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal transition table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Current state&lt;/th&gt;
&lt;th&gt;Verified event&lt;/th&gt;
&lt;th&gt;Next state&lt;/th&gt;
&lt;th&gt;Forbidden side effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IDLE&lt;/td&gt;
&lt;td&gt;entry condition and no active cycle&lt;/td&gt;
&lt;td&gt;PAIR_OPEN&lt;/td&gt;
&lt;td&gt;opening two cycles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PAIR_OPEN&lt;/td&gt;
&lt;td&gt;one cycle-owned leg closes&lt;/td&gt;
&lt;td&gt;ONE_LEG_CLOSED&lt;/td&gt;
&lt;td&gt;replacing the closed leg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONE_LEG_CLOSED&lt;/td&gt;
&lt;td&gt;survivor closes&lt;/td&gt;
&lt;td&gt;CYCLE_COMPLETE&lt;/td&gt;
&lt;td&gt;opening a new pair twice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CYCLE_COMPLETE&lt;/td&gt;
&lt;td&gt;cleanup persisted&lt;/td&gt;
&lt;td&gt;IDLE&lt;/td&gt;
&lt;td&gt;losing the completed-cycle record&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table gives a buyer something more useful than “the bot works.” It defines what can be checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze three acceptance tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Normal closure
&lt;/h3&gt;

&lt;p&gt;Both entries fill. Both legs close through their intended exit rules. The EA records the cycle as complete and does not open a replacement until the next valid cycle trigger.&lt;/p&gt;

&lt;p&gt;The evidence should include the two entry tickets, both close events, the single cycle identifier, and the final state.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. One leg closes first
&lt;/h3&gt;

&lt;p&gt;One position hits its stop, target, or included manual-close condition. The surviving leg remains managed by the current cycle. The EA must not treat the account as flat and must not create a duplicate pair.&lt;/p&gt;

&lt;p&gt;This is where many apparently simple paired-order requests become ambiguous. “Keep the other trade running” is not enough. The specification still needs to say which management rules remain active and whether another signal is ignored until the survivor closes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Terminal restart
&lt;/h3&gt;

&lt;p&gt;MetaTrader restarts while the pair is open or while one leg remains. The EA reconstructs the same state from durable identifiers and current positions. It must not depend only on variables that disappeared with the terminal session.&lt;/p&gt;

&lt;p&gt;A useful restart test records the state before shutdown, starts the terminal again, and compares the reconstructed cycle identifier, leg ownership and permission to open the next pair. Any difference is a failed acceptance case.&lt;/p&gt;

&lt;h2&gt;
  
  
  The identifiers matter
&lt;/h2&gt;

&lt;p&gt;A stable implementation should be able to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which positions belong to the current cycle?&lt;/li&gt;
&lt;li&gt;Which leg is the buy and which is the sell?&lt;/li&gt;
&lt;li&gt;Has either leg already closed?&lt;/li&gt;
&lt;li&gt;Was the close expected, manual, or caused by a broker event?&lt;/li&gt;
&lt;li&gt;Is a new cycle allowed now?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Magic numbers, symbols, comments, ticket mappings and persisted cycle identifiers can all help. The correct combination depends on the platform and scope. What matters is that the reconstruction rule is explicit and testable.&lt;/p&gt;

&lt;p&gt;In MT4, order-history scans also need a fixed selection rule. “Take the last closed order” can be wrong when another EA or a manual trade uses the same account. In MT5 hedging mode, position and deal history introduce a different mapping problem. Platform choice is part of the acceptance contract, not a detail to leave until coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warning signs in a specification
&lt;/h2&gt;

&lt;p&gt;The following phrases usually need clarification before coding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Open a new pair when one trade closes.”&lt;/li&gt;
&lt;li&gt;“Keep the other trade running as normal.”&lt;/li&gt;
&lt;li&gt;“Restart the bot after both trades finish.”&lt;/li&gt;
&lt;li&gt;“Avoid duplicate orders.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each phrase hides multiple state transitions. Does a partial close count? What happens after a manual close? What if the broker rejects one entry? What if the terminal restarts between the close event and the state update?&lt;/p&gt;

&lt;p&gt;These are not reasons to delay the project. They are decisions that can be frozen as safe defaults. For a first version, a reasonable boundary might reject partial closes, keep the survivor under its original stop and target, and permit the next cycle only after both legs and all cycle-owned pending orders are gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope boundaries prevent revision loops
&lt;/h2&gt;

&lt;p&gt;A clean first version should state which close paths are included. It should also list exclusions such as partial closes, cross-symbol baskets, manual intervention, broker-side order changes, or recovery from edited comments if those cases are not part of the initial scope.&lt;/p&gt;

&lt;p&gt;That is not bureaucracy. It gives the buyer and developer the same definition of “done.” A revision is then about a failed agreed case, not a new interpretation of an undefined lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What proof should be delivered?
&lt;/h2&gt;

&lt;p&gt;For this kind of EA, a useful handover contains more than the &lt;code&gt;.mq4&lt;/code&gt; or &lt;code&gt;.mq5&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;editable source code;&lt;/li&gt;
&lt;li&gt;a clean compile result;&lt;/li&gt;
&lt;li&gt;the transition table used by the implementation;&lt;/li&gt;
&lt;li&gt;logs or screenshots for the three acceptance cases;&lt;/li&gt;
&lt;li&gt;a short change note;&lt;/li&gt;
&lt;li&gt;setup notes covering platform mode, symbol and identifiers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source lets the buyer retain control. The matrix makes the behavior reviewable. The evidence separates “compiled” from “accepted.”&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple pre-coding checklist
&lt;/h2&gt;

&lt;p&gt;Before funding implementation, freeze:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the four lifecycle states;&lt;/li&gt;
&lt;li&gt;every allowed transition;&lt;/li&gt;
&lt;li&gt;the identifier used to rebuild state;&lt;/li&gt;
&lt;li&gt;the three acceptance tests above;&lt;/li&gt;
&lt;li&gt;the included close paths;&lt;/li&gt;
&lt;li&gt;the excluded edge cases;&lt;/li&gt;
&lt;li&gt;the exact event that allows the next cycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those points are deterministic, the entry and exit rules become much easier to implement and test.&lt;/p&gt;

&lt;p&gt;The full buyer checklist is available here: &lt;a href="https://stratcorealpha.com/engineering-notes/mt4-paired-order-lifecycle" rel="noopener noreferrer"&gt;https://stratcorealpha.com/engineering-notes/mt4-paired-order-lifecycle&lt;/a&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Eight Session-Boundary Tests I Run Before Trusting a Trading Bot</title>
      <dc:creator>Arnold Holm</dc:creator>
      <pubDate>Sat, 15 Aug 2026 01:56:19 +0000</pubDate>
      <link>https://dev.to/stratcorealpha/eight-session-boundary-tests-i-run-before-trusting-a-trading-bot-ofj</link>
      <guid>https://dev.to/stratcorealpha/eight-session-boundary-tests-i-run-before-trusting-a-trading-bot-ofj</guid>
      <description>&lt;p&gt;A session reset looks simple until a platform reloads data, reconnects or sends the same timestamp twice.&lt;/p&gt;

&lt;p&gt;If a bot resets state from the calendar date alone, a restart can unlock a risk control early. Or the same session can reset twice. I keep two facts separate before I touch entry logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a new bar&lt;/li&gt;
&lt;li&gt;a new trading session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most new bars are not session starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small classifier first
&lt;/h2&gt;

&lt;p&gt;The first pass can be read-only. It receives the current timestamp and the platform's native session marker. It returns one observation class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Observation&lt;/span&gt; &lt;span class="nf"&gt;Classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;nativeSessionStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;hasPrevious&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;hasPrevious&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&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;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;previous&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;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reordered&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;previous&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;nativeSessionStart&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DuplicateSessionMarker&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duplicate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&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;nativeSessionStart&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBarAndSession&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBar&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 does not decide when trading is allowed. It only makes platform events observable. The business reset stays separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The eight tests
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First observation.&lt;/strong&gt; Initialize without inventing a previous bar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forward bar.&lt;/strong&gt; Advance once without a session event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate update.&lt;/strong&gt; Keep state when the timestamp has not changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate session marker.&lt;/strong&gt; Suppress the same marker at the same timestamp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reordered data.&lt;/strong&gt; Report a backward timestamp instead of treating it as forward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reordered session marker.&lt;/strong&gt; Reject the marker when its timestamp moves backward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native session start.&lt;/strong&gt; Report it exactly once on the platform signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit reset.&lt;/strong&gt; Clear observation state without claiming that the market began a new session.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Put restart behavior in the contract
&lt;/h2&gt;

&lt;p&gt;A pure classifier is only the start. If the bot carries a daily loss lock, trade counter or opening range across bars, choose the recovery model before implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;restore the last trusted state from persistent storage;&lt;/li&gt;
&lt;li&gt;rebuild it from authorized account and chart history;&lt;/li&gt;
&lt;li&gt;stay locked until the next verified native session boundary;&lt;/li&gt;
&lt;li&gt;require an explicit operator decision in a demo or test environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Leaving this undefined is what creates restart-only bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the evidence packet small
&lt;/h2&gt;

&lt;p&gt;For each run I record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;source hash and platform version;&lt;/li&gt;
&lt;li&gt;instrument, bar type and trading-hours template;&lt;/li&gt;
&lt;li&gt;ordered timestamps and native session markers;&lt;/li&gt;
&lt;li&gt;expected and observed class for each test;&lt;/li&gt;
&lt;li&gt;reload and restart result;&lt;/li&gt;
&lt;li&gt;what was not tested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is enough to separate a code defect from a chart-template mismatch. It also keeps a later test from quietly using different source or settings.&lt;/p&gt;

&lt;p&gt;The business rule should still fit in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reset &lt;code&gt;named state&lt;/code&gt; when &lt;code&gt;observable event&lt;/code&gt; occurs, provided &lt;code&gt;state condition&lt;/code&gt;; otherwise keep the prior state until &lt;code&gt;fallback event&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then add one case where the reset must happen and one where it must not.&lt;/p&gt;

&lt;p&gt;For the longer reasoning behind calendar midnight and native session events, see &lt;a href="https://medium.com/@stratcorealpha/a-trading-session-is-an-event-not-just-a-date-c3ffd3b2668f" rel="noopener noreferrer"&gt;A Trading Session Is an Event, Not Just a Date&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is about software behavior and testing. It is not investment advice and does not promise profitability or trading performance.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>csharp</category>
      <category>automation</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
