The hard part of an automated trading bot was never "detect a pattern." It was building a signal engine that scores confidence instead of firing on every match, and an execution layer that can lose its connection mid-trade and reconcile back to reality instead of quietly diverging from it. This post walks through the actual pipeline behind the Goldmine Trading Bot — detection, scoring, execution, failure recovery — with the code for each stage.
The part nobody warns you about when you automate a discretionary strategy
I'd been trading Smart Money Concepts manually for a while before I tried to automate it, and I assumed the hard part would be encoding the pattern logic — CHoCH, BOS, order blocks, fair value gaps. It wasn't. Pattern detection on well-defined structure is a solved problem; you can get a first working version in an afternoon.
The hard part was everything downstream of "pattern detected": deciding which detected patterns were actually worth trading, and building an execution layer that stays correct when the connection drops, the broker rejects an order, or two signals fire close enough together to conflict.
This post is the architecture of that full pipeline — detection, confidence scoring, execution, and recovery — as it actually runs in the Goldmine Trading Bot, not the toy version.
Stage 1: Structure detection (the easy 80%)
Detecting a Change of Character or Break of Structure algorithmically is mostly swing-point bookkeeping:
def detect_choch(swings, direction):
"""
swings: list of confirmed swing highs/lows, chronological
direction: 'bullish' or 'bearish' — the prior trend
"""
last_swing = swings[-1]
prior_structure_point = get_prior_structure_point(swings, direction)
if direction == 'bearish' and last_swing.price > prior_structure_point.price:
return ChoCH(type='bullish_reversal', level=prior_structure_point.price)
if direction == 'bullish' and last_swing.price < prior_structure_point.price:
return ChoCH(type='bearish_reversal', level=prior_structure_point.price)
return None
Order blocks and fair value gaps follow a similar pattern: well-defined geometric rules over swing/candle data. None of this is where a bot lives or dies. Where it gets interesting is what happens after detection.
Stage 2: Confidence scoring (the part that actually matters)
A raw pattern match is not a trade signal — it's a candidate. Trading every CHoCH the moment it's detected produces a bot that's technically "working" and financially unusable, because plenty of detected patterns occur in low-quality context (thin volume, no HTF alignment, no liquidity behind the move).
The Goldmine Trading Bot runs every candidate through a confidence scorer before it's allowed anywhere near execution:
def score_signal(candidate):
score = 0
score += 25 if candidate.htf_aligned else 0
score += 20 if candidate.swept_liquidity else 0
score += 20 if candidate.volume_confirmation else 0
score += 15 if candidate.fresh_zone else 0 # unmitigated OB/FVG
score += 20 if candidate.session_in_window else 0 # avoid dead sessions
return score
MIN_EXECUTION_THRESHOLD = 70
def should_execute(candidate):
return score_signal(candidate) >= MIN_EXECUTION_THRESHOLD
This single threshold is doing more work for real-world performance than the entire detection layer above it. Tune it too low and the bot trades noise; too high and it goes silent for days waiting for a "perfect" setup that costs you real opportunities. Getting this number right took far more forward-testing than the pattern-matching code did.
Stage 3: Execution — where "it works in backtest" goes to die
This is the stage most hobby bots underbuild, because it's the least interesting to write and the most load-bearing in production.
class ExecutionEngine:
def __init__(self, broker_client, max_retries=3):
self.broker = broker_client
self.max_retries = max_retries
def execute(self, signal):
for attempt in range(self.max_retries):
try:
order = self.broker.place_order(
symbol=signal.symbol,
direction=signal.direction,
size=signal.size,
sl=signal.invalidation_level,
tp=signal.target,
client_order_id=signal.idempotency_key # critical
)
return self._confirm_fill(order)
except BrokerTimeoutError:
# don't blindly retry — check if it actually filled first
existing = self.broker.get_order(signal.idempotency_key)
if existing:
return self._confirm_fill(existing)
continue # genuine timeout, safe to retry
raise ExecutionFailure(signal)
The client_order_id / idempotency key is the single most important line in this whole file. Without it, a timeout during retry can place the same order twice — which, on a leveraged instrument, is not a bug you find in a code review. You find it in your account balance.
Stage 4: Reconciliation — the stage that only matters after something breaks
Every automated trading system will eventually experience a dropped connection mid-position. What separates a bot you can trust from one you can't is what happens on reconnect:
def reconcile_on_startup(local_state, broker):
live_positions = broker.get_open_positions()
live_ids = {p.client_order_id for p in live_positions}
local_ids = {p.client_order_id for p in local_state.positions}
# broker has positions we don't know about — adopt them
for pos in live_positions:
if pos.client_order_id not in local_ids:
local_state.adopt(pos)
# we think we have positions the broker doesn't — drop them
for pos in local_state.positions:
if pos.client_order_id not in live_ids:
local_state.remove(pos)
return local_state
Local state is a cache. The broker is the source of truth, always. A bot that trusts its own local state over the broker's actual position list will eventually diverge from reality in exactly the moment — a dropped connection during a volatile session — where that divergence costs the most.
The Bot In Action
Get Instant Access to The Goldmine Trading Bot
Get Instant Access to The Goldmine Grid System
What actually broke in production (not in testing)
Duplicate signals within the same candle. Two slightly different detection windows can both flag a valid CHoCH on the same structural move, milliseconds apart. Without deduplication keyed to the structural level itself (not just a timestamp), this produces double entries on what should be one signal.
Session boundary edge cases. A signal scored and queued right at a session close can execute into a session with completely different liquidity characteristics than the one it was scored for. The scorer now checks execution-time session context, not just detection-time.
Silent broker-side partial fills. Some brokers fill part of a grid/ladder order and report it as "pending" rather than "partially filled" depending on order type. Trusting the reported status without polling actual position size directly led to phantom size mismatches that only reconciliation (Stage 4) catches.
Where the packaged bot fits in
Everything above — detection, scoring, execution, reconciliation — is the actual architecture running inside the Goldmine Trading Bot. If you're building your own version, the order of priority that mattered most in practice was: get reconciliation right before you optimize detection, because a bot with a perfect signal and a broken execution layer will still lose you money, just more elegantly.
Full disclosure: the bot is a product I built and sell — I'm sharing the real pipeline because I think the architecture is worth discussing on its own merits, not as a pitch. If you'd rather use the packaged version instead of building and maintaining your own reconciliation layer, that's what it's for.
FAQ
Why not just trade every detected pattern instead of scoring it?
Because detection and quality are different problems. A pattern can be geometrically valid and contextually worthless (thin volume, dead session, no HTF alignment). Scoring is what filters detection noise down to tradeable signal.
How do you avoid double-execution on reconnect?
Idempotency keys on every order (Stage 3) plus reconciliation against broker state on every reconnect (Stage 4) — never trust local state as the source of truth after any connection gap.
What's the actual latency from signal to order?
Depends on the detection timeframe and broker API, but the scoring and execution stages themselves add negligible latency (milliseconds) — the dominant factor is broker round-trip time, not the bot's internal logic.
Can this run on any broker/platform?
The architecture is broker-agnostic — the execution and reconciliation layers just need a broker client that exposes idempotent order placement and a queryable open-positions endpoint. The production version runs against MT5.
Is the confidence threshold static or does it adapt?
Static per instrument/session in the current version, tuned through forward-testing rather than online learning — an adaptive threshold is an interesting extension but introduces its own risk of overfitting to recent regime.
Get Instant Access to The Goldmine Trading Bot
Get Instant Access to The Goldmine Grid System
Let Talk
If you've built execution or reconciliation logic against any external, occasionally-unreliable API — payments, brokers, even just a flaky third-party service — what's the failure mode that took you longest to catch? Reconciliation bugs in particular tend to hide until exactly the worst moment to find them.
Top comments (0)