backtest-kit-docs / documents /concept_01_monorepo_parallel_execution.html
tripolskypetr's picture
patch
d99335f
Raw
History Blame Contribute Delete
77.1 kB
<!DOCTYPE html><html class="default" lang="en" data-base=".."><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>concept/01_monorepo_parallel_execution | backtest-kit</title><meta name="description" content="Documentation for backtest-kit"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><div class="table-cell" id="tsd-search"><div class="field"><label for="tsd-search-field" class="tsd-widget tsd-toolbar-icon search no-caption"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></label><input type="text" id="tsd-search-field" aria-label="Search"/></div><div class="field"><div id="tsd-toolbar-links"></div></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">backtest-kit</a></div><div class="table-cell" id="tsd-widgets"><a href="#" class="tsd-widget tsd-toolbar-icon menu no-caption" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb"><li><a href="../modules.html">backtest-kit</a></li><li><a href="concept_01_monorepo_parallel_execution.html">concept/01_monorepo_parallel_execution</a></li></ul></div><div class="tsd-panel tsd-typography"><a id="⚑-monorepo-architecture-for-parallel-execution-of-trading-strategies" class="tsd-anchor"></a><h1 class="tsd-anchor-link">⚑ Monorepo architecture for parallel execution of trading strategies<a href="#⚑-monorepo-architecture-for-parallel-execution-of-trading-strategies" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h1><blockquote>
<p>The source code discussed in the article is published in <a href="https://github.com/backtest-kit/backtest-monorepo-parallel">this GitHub repository</a></p>
</blockquote>
<p><img src="../media/monorepo_parallel.png" alt="splashscreen"></p>
<p>The imperative approach does not work in the AI world. If you build a codebase on a God Object, out of three employees only one will be able to write code, the rest will be blocked by merge conflicts. If you split the repositories, no knowledge base will accumulate: the only information about how a trading strategy works lives in the code itself. The synergistic effect is lost: the Coding Agent will not be able to read code from prior iterations of development. Each next strategy is a random shot, not a continuation of the thought from the previous one</p>
<a id="the-problem" class="tsd-anchor"></a><h2 class="tsd-anchor-link">The problem<a href="#the-problem" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Open-source software for programming automated trading strategies is all in Python. In Python it is uncomfortable to write declarative code - all examples are imperative classes bloated to thousands of lines, the God Object antipattern</p>
<pre><code class="python">class AwesomeStrategy(IStrategy):
position_adjustment_enable = True # global switch, without it DCA does not work
# 1. Ladder parameters - class attributes
max_entry_position_adjustment = 9 # max 9 averaging steps + 1 initial entry = 10 steps
max_dca_multiplier = 5.5 # reserve 5.5x of the stake for future averaging
stoploss = -0.30 # a high hard stop is needed, otherwise DCA blows up
# 2. Initial entry - trim the stake so there is enough left for the ladder
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: float, max_stake: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
# Reserve the larger part for possible DCA averaging
return proposed_stake / self.max_dca_multiplier
# 3. The ladder itself - a separate callback that Freqtrade calls on every tick
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: float, max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs) -> Optional[float]:
# Current drawdown is not deep enough - do not average
if current_profit > -0.05 and trade.nr_of_successful_entries == 1:
return None
if current_profit > -0.10 and trade.nr_of_successful_entries == 2:
return None
# ... repeat this ladder of conditions for each subsequent step
# Pull the list of already filled entries from the trade state
filled_entries = trade.select_filled_orders(trade.entry_side)
count_of_entries = trade.nr_of_successful_entries
try:
# Stake of the first purchase x (count_of_entries - for ladder geometry)
stake_amount = filled_entries[0].stake_amount
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
return stake_amount
except Exception:
return None
# 4. populate_indicators / populate_entry_trend / populate_exit_trend are still needed,
# because without an entry signal the initial entry will never happen.
def populate_indicators(self, dataframe, metadata): ...
def populate_entry_trend(self, dataframe, metadata): ...
def populate_exit_trend(self, dataframe, metadata): ...
</code><button type="button">Copy</button></pre>
<p>This boilerplate only serves laddered averaging: price fell - we bought more. To figure out when to start buying in the first place, in order not to go bankrupt, you will have to add database integration methods into the same class. A direct violation of SOLID</p>
<pre><code class="python">class TelegramSignalStrategy(IStrategy):
custom_signals = {} # nested dict: pair -> list of signals
def bot_start(self) -> None:
# starts a cron task in a separate thread that goes to Telegram
# and writes the results into self.custom_signals
threading.Thread(target=self._poll_telegram_loop, daemon=True).start()
def _poll_telegram_loop(self):
while True:
try:
messages = telegram_client.iter_messages("crypto_yoda_channel")
for msg in messages:
parsed = self._parse_signal(msg.text) # regex inline
if parsed:
self.custom_signals.setdefault(parsed['pair'], []).append(parsed)
except Exception:
pass
time.sleep(60)
def _parse_signal(self, text: str) -> Optional[dict]:
m = re.search(r"#([A-Z0-9]+)/USDT.*?(LONG|SHORT).*?zone\s+\$?([\d.,]+)\s*[-–-]\s*\$?([\d.,]+)", text)
if not m:
return None
return {
"pair": m.group(1) + "/USDT",
"direction": "long" if m.group(2) == "LONG" else "short",
"entry_from": float(m.group(3).replace(",", ".")),
"entry_to": float(m.group(4).replace(",", ".")),
}
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
signals = self.custom_signals.get(metadata['pair'], [])
# ... somehow apply signals to the dataframe here
</code><button type="button">Copy</button></pre>
<p>The entry point deserves separate attention: in freqtrade it is a json file. API keys and magic constants like <code>dry_run</code> are stuffed in together. It is unclear whether to commit these changes or not: if you do commit them, you will always have a merge conflict when working in a team</p>
<pre><code class="json"><span class="hl-2">{</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;dry_run&quot;</span><span class="hl-2">: </span><span class="hl-4">true</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;timeframe&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;3m&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;stake_currency&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;USDT&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;stake_amount&quot;</span><span class="hl-2">: </span><span class="hl-8">200</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;max_open_trades&quot;</span><span class="hl-2">: </span><span class="hl-8">5</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;exchange&quot;</span><span class="hl-2">: {</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;name&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;binance&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;key&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;af8ddd35195e9dc500b9a6f799f6f5c93d89193b&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;secret&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;08a9dc6db3d7b53e1acebd9275677f4b0a04f1a5&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;pair_whitelist&quot;</span><span class="hl-2">: [</span><span class="hl-3">&quot;BTC/USDT&quot;</span><span class="hl-2">, </span><span class="hl-3">&quot;ETH/USDT&quot;</span><span class="hl-2">]</span><br/><span class="hl-2"> },</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;timerange&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;20260401-20260427&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-16">&quot;datadir&quot;</span><span class="hl-2">: </span><span class="hl-3">&quot;user_data/data/binance&quot;</span><br/><span class="hl-2">}</span>
</code><button type="button">Copy</button></pre>
<p>With this kind of architecture, look-ahead bias cannot be avoided: to build a frontend integration you need to write data into an intermediate database and delete it through crontab. This imperative code is mathematically unprovable, because it does not even come close to looking like a pure function</p>
<a id="solving-the-problem" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Solving the problem<a href="#solving-the-problem" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Before hiring employees, design a pipeline that will convert the cumulative effect of time spent into the next turn of the dialectical spiral. For that you need to build the following folder structure for the code</p>
<pre><code class="bash"><span class="hl-1">monorepo/</span><br/><span class="hl-1">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">content/</span><br/><span class="hl-1">β”‚</span><span class="hl-2"> </span><span class="hl-3">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">apr_2026.strategy/</span><span class="hl-2"> </span><br/><span class="hl-2">| </span><span class="hl-1">β”œβ”€β”€modules</span><br/><span class="hl-1">β”‚</span><span class="hl-2"> </span><span class="hl-3">β”‚</span><span class="hl-2"> </span><span class="hl-3">β”œβ”€β”€backtest.module.ts</span><span class="hl-2"> </span><span class="hl-0"># testnet paper</span><br/><span class="hl-1">β”‚</span><span class="hl-2"> </span><span class="hl-3">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">apr_2026.strategy.ts</span><span class="hl-2"> </span><span class="hl-0"># strategy production code</span><br/><span class="hl-1">β”‚</span><span class="hl-2"> </span><span class="hl-3">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">apr_2026.test.ts</span><span class="hl-2"> </span><span class="hl-0"># developer playground</span><br/><span class="hl-1">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">modules/</span><br/><span class="hl-1">β”‚</span><span class="hl-2"> </span><span class="hl-3">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">backtest.module.ts</span><span class="hl-2"> </span><span class="hl-0"># mainnet paper</span><br/><span class="hl-1">β”‚</span><span class="hl-2"> </span><span class="hl-3">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">live.module.ts</span><span class="hl-2"> </span><span class="hl-0"># mainnet live</span><br/><span class="hl-1">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">packages/</span><br/><span class="hl-1">β”‚</span><span class="hl-2"> </span><span class="hl-3">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">core/</span><span class="hl-2"> </span><span class="hl-0"># database layer</span><br/><span class="hl-1">β”‚</span><span class="hl-2"> </span><span class="hl-3">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">main/</span><span class="hl-2"> </span><span class="hl-0"># cli arguments parser</span>
</code><button type="button">Copy</button></pre>
<a id="n-entry-points-instead-of-one-gated-by-a-cli-flag" class="tsd-anchor"></a><h3 class="tsd-anchor-link">N entry points instead of one: gated by a CLI flag<a href="#n-entry-points-instead-of-one-gated-by-a-cli-flag" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>The root import in <code>packages/main/src/index.ts</code> pulls in four files</p>
<pre><code class="typescript"><span class="hl-6">import</span><span class="hl-2"> </span><span class="hl-3">&quot;./main/session&quot;</span><span class="hl-2">;</span><br/><span class="hl-6">import</span><span class="hl-2"> </span><span class="hl-3">&quot;./main/backtest&quot;</span><span class="hl-2">;</span><br/><span class="hl-6">import</span><span class="hl-2"> </span><span class="hl-3">&quot;./main/live&quot;</span><span class="hl-2">;</span><br/><span class="hl-6">import</span><span class="hl-2"> </span><span class="hl-3">&quot;./main/paper&quot;</span><span class="hl-2">;</span>
</code><button type="button">Copy</button></pre>
<p>Each of which is a standalone entry point</p>
<pre><code class="typescript"><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-1">main</span><span class="hl-2"> = </span><span class="hl-4">async</span><span class="hl-2"> () </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> { </span><span class="hl-7">values</span><span class="hl-2"> } = </span><span class="hl-1">getArgs</span><span class="hl-2">();</span><br/><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (!</span><span class="hl-5">values</span><span class="hl-2">.</span><span class="hl-5">entry</span><span class="hl-2">) </span><span class="hl-6">return</span><span class="hl-2">;</span><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (!</span><span class="hl-5">values</span><span class="hl-2">.</span><span class="hl-5">backtest</span><span class="hl-2">) </span><span class="hl-6">return</span><span class="hl-2">;</span><br/><br/><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">waitForReady</span><span class="hl-2">(</span><span class="hl-4">true</span><span class="hl-2">);</span><br/><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> [</span><span class="hl-7">strategySchema</span><span class="hl-2">] = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">listStrategySchema</span><span class="hl-2">();</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> [</span><span class="hl-7">exchangeSchema</span><span class="hl-2">] = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">listExchangeSchema</span><span class="hl-2">();</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> [</span><span class="hl-7">frameSchema</span><span class="hl-2">] = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">listFrameSchema</span><span class="hl-2">();</span><br/><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (</span><span class="hl-5">values</span><span class="hl-2">.</span><span class="hl-5">cache</span><span class="hl-2">) {</span><br/><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">CACHE_CANDLES_FN</span><span class="hl-2">();</span><br/><span class="hl-2"> }</span><br/><br/><span class="hl-2"> </span><span class="hl-6">for</span><span class="hl-2"> (</span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">symbol</span><span class="hl-2"> </span><span class="hl-4">of</span><span class="hl-2"> </span><span class="hl-7">CC_SYMBOL_LIST</span><span class="hl-2">) {</span><br/><span class="hl-2"> </span><span class="hl-5">Backtest</span><span class="hl-2">.</span><span class="hl-1">background</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, {</span><br/><span class="hl-2"> </span><span class="hl-5">exchangeName:</span><span class="hl-2"> </span><span class="hl-5">exchangeSchema</span><span class="hl-2">.</span><span class="hl-5">exchangeName</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">strategyName:</span><span class="hl-2"> </span><span class="hl-5">strategySchema</span><span class="hl-2">.</span><span class="hl-5">strategyName</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">frameName:</span><span class="hl-2"> </span><span class="hl-5">frameSchema</span><span class="hl-2">.</span><span class="hl-5">frameName</span><span class="hl-2">,</span><br/><span class="hl-2"> });</span><br/><span class="hl-2"> }</span><br/><span class="hl-2">};</span><br/><br/><span class="hl-1">main</span><span class="hl-2">();</span>
</code><button type="button">Copy</button></pre>
<a id="shared-event-driven-strategy-code" class="tsd-anchor"></a><h3 class="tsd-anchor-link">Shared event-driven strategy code<a href="#shared-event-driven-strategy-code" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>The entry, averaging, stop loss and early exit have to be written in a functionally pure way, in a declarative style, so that they can be split across different files and reused</p>
<pre><code class="typescript"><span class="hl-6">import</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-5">addStrategySchema</span><span class="hl-2">, </span><span class="hl-5">listenActivePing</span><span class="hl-2">, </span><span class="hl-5">listenError</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">Log</span><span class="hl-2">, </span><span class="hl-5">Position</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">commitClosePending</span><span class="hl-2">, </span><span class="hl-5">commitAverageBuy</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">getPositionPnlPercent</span><span class="hl-2">, </span><span class="hl-5">getPositionEntryOverlap</span><span class="hl-2">, </span><span class="hl-5">getPositionEntries</span><span class="hl-2">,</span><br/><span class="hl-2">} </span><span class="hl-6">from</span><span class="hl-2"> </span><span class="hl-3">&quot;backtest-kit&quot;</span><span class="hl-2">;</span><br/><br/><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">HARD_STOP</span><span class="hl-2"> = </span><span class="hl-8">10.0</span><span class="hl-2">;</span><br/><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">TARGET_PROFIT</span><span class="hl-2"> = </span><span class="hl-8">3</span><span class="hl-2">;</span><br/><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">LADDER_STEP_COST</span><span class="hl-2"> = </span><span class="hl-8">100</span><span class="hl-2">;</span><br/><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">LADDER_UPPER_STEP</span><span class="hl-2"> = </span><span class="hl-8">5</span><span class="hl-2">;</span><br/><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">LADDER_LOWER_STEP</span><span class="hl-2"> = </span><span class="hl-8">1</span><span class="hl-2">;</span><br/><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">LADDER_MAX_STEPS</span><span class="hl-2"> = </span><span class="hl-8">10</span><span class="hl-2">;</span><br/><br/><span class="hl-1">addStrategySchema</span><span class="hl-2">({</span><br/><span class="hl-2"> </span><span class="hl-5">strategyName:</span><span class="hl-2"> </span><span class="hl-3">&quot;apr_2026_strategy&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-1">getSignal</span><span class="hl-5">:</span><span class="hl-2"> </span><span class="hl-4">async</span><span class="hl-2"> (</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">when</span><span class="hl-2">, </span><span class="hl-5">currentPrice</span><span class="hl-2">) </span><span class="hl-4">=&gt;</span><span class="hl-2"> ({</span><br/><span class="hl-2"> ...</span><span class="hl-5">Position</span><span class="hl-2">.</span><span class="hl-1">moonbag</span><span class="hl-2">({ </span><br/><span class="hl-2"> </span><span class="hl-5">position:</span><span class="hl-2"> </span><span class="hl-3">&quot;long&quot;</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">currentPrice</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">percentStopLoss:</span><span class="hl-2"> </span><span class="hl-7">HARD_STOP</span><br/><span class="hl-2"> }),</span><br/><span class="hl-2"> </span><span class="hl-5">minuteEstimatedTime:</span><span class="hl-2"> </span><span class="hl-4">Infinity</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">cost:</span><span class="hl-2"> </span><span class="hl-7">LADDER_STEP_COST</span><span class="hl-2">,</span><br/><span class="hl-2"> }),</span><br/><span class="hl-2">});</span><br/><br/><span class="hl-1">listenActivePing</span><span class="hl-2">(</span><span class="hl-4">async</span><span class="hl-2"> ({ </span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">currentPrice</span><span class="hl-2"> }) </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> { </span><span class="hl-5">length</span><span class="hl-2">: </span><span class="hl-7">steps</span><span class="hl-2"> } = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">getPositionEntries</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (</span><span class="hl-5">steps</span><span class="hl-2"> &gt;= </span><span class="hl-7">LADDER_MAX_STEPS</span><span class="hl-2">) </span><span class="hl-6">return</span><span class="hl-2">;</span><br/><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">hasOverlap</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">getPositionEntryOverlap</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">currentPrice</span><span class="hl-2">, {</span><br/><span class="hl-2"> </span><span class="hl-5">upperPercent:</span><span class="hl-2"> </span><span class="hl-7">LADDER_UPPER_STEP</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">lowerPercent:</span><span class="hl-2"> </span><span class="hl-7">LADDER_LOWER_STEP</span><span class="hl-2">,</span><br/><span class="hl-2"> });</span><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (</span><span class="hl-5">hasOverlap</span><span class="hl-2">) </span><span class="hl-6">return</span><span class="hl-2">;</span><br/><br/><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">commitAverageBuy</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-7">LADDER_STEP_COST</span><span class="hl-2">);</span><br/><span class="hl-2">});</span><br/><br/><span class="hl-1">listenActivePing</span><span class="hl-2">(</span><span class="hl-4">async</span><span class="hl-2"> ({ </span><span class="hl-5">symbol</span><span class="hl-2"> }) </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">currentProfit</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">getPositionPnlPercent</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (</span><span class="hl-5">currentProfit</span><span class="hl-2"> &lt; </span><span class="hl-7">TARGET_PROFIT</span><span class="hl-2">) {</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2">;</span><br/><span class="hl-2"> }</span><br/><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">commitClosePending</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, { </span><span class="hl-5">id:</span><span class="hl-2"> </span><span class="hl-3">&quot;unknown&quot;</span><span class="hl-2">, </span><span class="hl-5">note:</span><span class="hl-2"> </span><span class="hl-3">&quot;# closed by target pnl&quot;</span><span class="hl-2"> });</span><br/><span class="hl-2">});</span>
</code><button type="button">Copy</button></pre>
<p>This way you can incrementally bolt a trailing take onto any strategy without changing the code. Any code change is a side effect, because it grows the table of possible inputs and outputs of the function, even if the nature of the mutation comes from a shit-coding neural net and not from runtime circumstances.</p>
<pre><code class="typescript"><span class="hl-6">if</span><span class="hl-2"> (</span><span class="hl-7">GLOBAL_CONFIG</span><span class="hl-2">.</span><span class="hl-7">ATTACH_TRAILING_TAKE</span><span class="hl-2">) {</span><br/><span class="hl-2"> </span><span class="hl-1">listenActivePing</span><span class="hl-2">(</span><span class="hl-4">async</span><span class="hl-2"> ({ </span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">data</span><span class="hl-2"> }) </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">peakProfitDistance</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">getPositionHighestProfitDistancePnlPercentage</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">currentProfit</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">getPositionPnlPercent</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (</span><span class="hl-5">currentProfit</span><span class="hl-2"> &lt; </span><span class="hl-8">0</span><span class="hl-2">) {</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2">;</span><br/><span class="hl-2"> }</span><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (</span><span class="hl-5">peakProfitDistance</span><span class="hl-2"> &lt; </span><span class="hl-7">TRAILING_TAKE</span><span class="hl-2">) {</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2">;</span><br/><span class="hl-2"> }</span><br/><span class="hl-2"> </span><span class="hl-5">Log</span><span class="hl-2">.</span><span class="hl-1">info</span><span class="hl-2">(</span><span class="hl-3">&quot;position closed due to the trailing take&quot;</span><span class="hl-2">, {</span><br/><span class="hl-2"> </span><span class="hl-5">symbol</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">data</span><span class="hl-2">,</span><br/><span class="hl-2"> });</span><br/><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">commitClosePending</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, {</span><br/><span class="hl-2"> </span><span class="hl-5">id:</span><span class="hl-2"> </span><span class="hl-3">&quot;unknown&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">note:</span><span class="hl-2"> </span><span class="hl-5">str</span><span class="hl-2">.</span><span class="hl-1">newline</span><span class="hl-2">(</span><br/><span class="hl-2"> </span><span class="hl-3">&quot;# closed by trailing take&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> ),</span><br/><span class="hl-2"> });</span><br/><span class="hl-2"> });</span><br/><span class="hl-2">}</span>
</code><button type="button">Copy</button></pre>
<p>In Freqtrade, deleting <code>custom_stoploss</code> means &quot;you have to remember what was important in there&quot;. We do not lose information, we only accumulate it</p>
<a id="modular-plug-and-play-configuration-per-mode" class="tsd-anchor"></a><h3 class="tsd-anchor-link">Modular plug-and-play configuration per mode<a href="#modular-plug-and-play-configuration-per-mode" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Next to the strategy lies <code>modules/backtest.module.ts</code>. This is a neighbour file that the CLI loads together with the strategy and that describes &quot;everything that is not the strategy itself&quot; - the exchange, the historical frame, the global parameters</p>
<pre><code class="typescript"><span class="hl-1">addExchangeSchema</span><span class="hl-2">({</span><br/><span class="hl-2"> </span><span class="hl-5">exchangeName:</span><span class="hl-2"> </span><span class="hl-3">&quot;ccxt-exchange&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-1">getCandles</span><span class="hl-5">:</span><span class="hl-2"> </span><span class="hl-4">async</span><span class="hl-2"> (</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">, </span><span class="hl-5">since</span><span class="hl-2">, </span><span class="hl-5">limit</span><span class="hl-2">) </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">exchange</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">getExchange</span><span class="hl-2">();</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">candles</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-5">exchange</span><span class="hl-2">.</span><span class="hl-1">fetchOHLCV</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">, </span><span class="hl-5">since</span><span class="hl-2">.</span><span class="hl-1">getTime</span><span class="hl-2">(), </span><span class="hl-5">limit</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> </span><span class="hl-5">candles</span><span class="hl-2">.</span><span class="hl-1">map</span><span class="hl-2">(([</span><span class="hl-5">timestamp</span><span class="hl-2">, </span><span class="hl-5">open</span><span class="hl-2">, </span><span class="hl-5">high</span><span class="hl-2">, </span><span class="hl-5">low</span><span class="hl-2">, </span><span class="hl-5">close</span><span class="hl-2">, </span><span class="hl-5">volume</span><span class="hl-2">]) </span><span class="hl-4">=&gt;</span><span class="hl-2"> ({</span><br/><span class="hl-2"> </span><span class="hl-5">timestamp</span><span class="hl-2">, </span><span class="hl-5">open</span><span class="hl-2">, </span><span class="hl-5">high</span><span class="hl-2">, </span><span class="hl-5">low</span><span class="hl-2">, </span><span class="hl-5">close</span><span class="hl-2">, </span><span class="hl-5">volume</span><span class="hl-2">,</span><br/><span class="hl-2"> }));</span><br/><span class="hl-2"> },</span><br/><span class="hl-2"> </span><span class="hl-1">getOrderBook</span><span class="hl-5">:</span><span class="hl-2"> </span><span class="hl-4">async</span><span class="hl-2"> (</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">depth</span><span class="hl-2">, </span><span class="hl-5">_from</span><span class="hl-2">, </span><span class="hl-5">_to</span><span class="hl-2">, </span><span class="hl-5">backtest</span><span class="hl-2">) </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (</span><span class="hl-5">backtest</span><span class="hl-2">) </span><span class="hl-6">throw</span><span class="hl-2"> </span><span class="hl-4">new</span><span class="hl-2"> </span><span class="hl-1">Error</span><span class="hl-2">(</span><span class="hl-3">&quot;Order book not supported in backtest&quot;</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">exchange</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">getExchange</span><span class="hl-2">();</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">bookData</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-5">exchange</span><span class="hl-2">.</span><span class="hl-1">fetchOrderBook</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">depth</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-5">symbol</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">asks:</span><span class="hl-2"> </span><span class="hl-5">bookData</span><span class="hl-2">.</span><span class="hl-5">asks</span><span class="hl-2">.</span><span class="hl-1">map</span><span class="hl-2">(([</span><span class="hl-5">price</span><span class="hl-2">, </span><span class="hl-5">quantity</span><span class="hl-2">]) </span><span class="hl-4">=&gt;</span><span class="hl-2"> ({ </span><span class="hl-5">price:</span><span class="hl-2"> </span><span class="hl-1">String</span><span class="hl-2">(</span><span class="hl-5">price</span><span class="hl-2">), </span><span class="hl-5">quantity:</span><span class="hl-2"> </span><span class="hl-1">String</span><span class="hl-2">(</span><span class="hl-5">quantity</span><span class="hl-2">) })),</span><br/><span class="hl-2"> </span><span class="hl-5">bids:</span><span class="hl-2"> </span><span class="hl-5">bookData</span><span class="hl-2">.</span><span class="hl-5">bids</span><span class="hl-2">.</span><span class="hl-1">map</span><span class="hl-2">(([</span><span class="hl-5">price</span><span class="hl-2">, </span><span class="hl-5">quantity</span><span class="hl-2">]) </span><span class="hl-4">=&gt;</span><span class="hl-2"> ({ </span><span class="hl-5">price:</span><span class="hl-2"> </span><span class="hl-1">String</span><span class="hl-2">(</span><span class="hl-5">price</span><span class="hl-2">), </span><span class="hl-5">quantity:</span><span class="hl-2"> </span><span class="hl-1">String</span><span class="hl-2">(</span><span class="hl-5">quantity</span><span class="hl-2">) })),</span><br/><span class="hl-2"> };</span><br/><span class="hl-2"> },</span><br/><span class="hl-2"> </span><span class="hl-0">// ...formatPrice, formatQuantity, getAggregatedTrades</span><br/><span class="hl-2">});</span><br/><br/><span class="hl-1">addFrameSchema</span><span class="hl-2">({</span><br/><span class="hl-2"> </span><span class="hl-5">frameName:</span><span class="hl-2"> </span><span class="hl-3">&quot;apr_2026_frame&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">interval:</span><span class="hl-2"> </span><span class="hl-3">&quot;1m&quot;</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">startDate:</span><span class="hl-2"> </span><span class="hl-4">new</span><span class="hl-2"> </span><span class="hl-1">Date</span><span class="hl-2">(</span><span class="hl-3">&quot;2026-04-01T00:00:00Z&quot;</span><span class="hl-2">),</span><br/><span class="hl-2"> </span><span class="hl-5">endDate:</span><span class="hl-2"> </span><span class="hl-4">new</span><span class="hl-2"> </span><span class="hl-1">Date</span><span class="hl-2">(</span><span class="hl-3">&quot;2026-04-27T00:00:00Z&quot;</span><span class="hl-2">),</span><br/><span class="hl-2">});</span>
</code><button type="button">Copy</button></pre>
<p>A new strategy is a folder under the content directory. This way several strategies can be running in different terminal tabs without changing the root json</p>
<pre><code class="bash"><span class="hl-1">content/apr_2026.strategy/</span><br/><span class="hl-1">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">apr_2026.strategy.ts</span><span class="hl-2"> </span><span class="hl-0"># strategy entry file</span><br/><span class="hl-1">β”œβ”€β”€</span><span class="hl-2"> </span><span class="hl-3">apr_2026.test.ts</span><span class="hl-2"> </span><span class="hl-0"># developer playground</span><br/><span class="hl-1">└──</span><span class="hl-2"> </span><span class="hl-3">modules/</span><br/><span class="hl-2"> </span><span class="hl-1">└──</span><span class="hl-2"> </span><span class="hl-3">backtest.module.ts</span><span class="hl-2"> </span><span class="hl-0"># exchange + timeframe</span>
</code><button type="button">Copy</button></pre>
<p>It is notable that the timeframe the strategy was run against is not lost and gets committed as code. You can figure out why something stopped working, relative to a known point in time when it definitely did work</p>
<a id="mvc-for-the-data-layer" class="tsd-anchor"></a><h3 class="tsd-anchor-link">MVC for the data layer<a href="#mvc-for-the-data-layer" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>The trigger of any significant market move is the news background. As of 2026 indicators do not work, because a price increase is not news. If the news parser code is kept in the same class as the strategy, the system cannot be debugged. The parser code has to be kept separately so that it can be developed via TDD</p>
<pre><code class="typescript"><span class="hl-6">export</span><span class="hl-2"> </span><span class="hl-4">class</span><span class="hl-2"> </span><span class="hl-10">ScraperService</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">private</span><span class="hl-2"> </span><span class="hl-4">readonly</span><span class="hl-2"> </span><span class="hl-5">loggerService</span><span class="hl-2"> = </span><span class="hl-1">inject</span><span class="hl-2">&lt;</span><span class="hl-10">LoggerService</span><span class="hl-2">&gt;(</span><span class="hl-7">TYPES</span><span class="hl-2">.</span><span class="hl-5">loggerService</span><span class="hl-2">);</span><br/><br/><span class="hl-2"> </span><span class="hl-4">public</span><span class="hl-2"> </span><span class="hl-1">scrapeDay</span><span class="hl-2"> = </span><span class="hl-4">async</span><span class="hl-2"> (</span><span class="hl-5">channel</span><span class="hl-2">: </span><span class="hl-10">string</span><span class="hl-2">, </span><span class="hl-5">date</span><span class="hl-2">: </span><span class="hl-10">Date</span><span class="hl-2">): </span><span class="hl-10">Promise</span><span class="hl-2">&lt;</span><span class="hl-10">ScraperMessage</span><span class="hl-2">[]&gt; </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">client</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-1">getTelegram</span><span class="hl-2">();</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">dayStart</span><span class="hl-2"> = </span><span class="hl-4">new</span><span class="hl-2"> </span><span class="hl-1">Date</span><span class="hl-2">(</span><span class="hl-5">date</span><span class="hl-2">); </span><span class="hl-5">dayStart</span><span class="hl-2">.</span><span class="hl-1">setUTCHours</span><span class="hl-2">(</span><span class="hl-8">0</span><span class="hl-2">, </span><span class="hl-8">0</span><span class="hl-2">, </span><span class="hl-8">0</span><span class="hl-2">, </span><span class="hl-8">0</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">dayEnd</span><span class="hl-2"> = </span><span class="hl-4">new</span><span class="hl-2"> </span><span class="hl-1">Date</span><span class="hl-2">(</span><span class="hl-5">date</span><span class="hl-2">); </span><span class="hl-5">dayEnd</span><span class="hl-2">.</span><span class="hl-1">setUTCHours</span><span class="hl-2">(</span><span class="hl-8">23</span><span class="hl-2">, </span><span class="hl-8">59</span><span class="hl-2">, </span><span class="hl-8">59</span><span class="hl-2">, </span><span class="hl-8">999</span><span class="hl-2">);</span><br/><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">rows</span><span class="hl-2">: </span><span class="hl-10">ScraperMessage</span><span class="hl-2">[] = [];</span><br/><span class="hl-2"> </span><span class="hl-6">for</span><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> (</span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">message</span><span class="hl-2"> </span><span class="hl-4">of</span><span class="hl-2"> </span><span class="hl-5">client</span><span class="hl-2">.</span><span class="hl-1">iterMessages</span><span class="hl-2">(</span><span class="hl-5">channel</span><span class="hl-2">, {</span><br/><span class="hl-2"> </span><span class="hl-5">offsetDate:</span><span class="hl-2"> </span><span class="hl-5">Math</span><span class="hl-2">.</span><span class="hl-1">floor</span><span class="hl-2">(</span><span class="hl-5">dayEnd</span><span class="hl-2">.</span><span class="hl-1">getTime</span><span class="hl-2">() / </span><span class="hl-8">1000</span><span class="hl-2">) + </span><span class="hl-8">1</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">reverse:</span><span class="hl-2"> </span><span class="hl-4">false</span><span class="hl-2">,</span><br/><span class="hl-2"> })) {</span><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (!</span><span class="hl-5">message</span><span class="hl-2">.</span><span class="hl-5">message</span><span class="hl-2">) </span><span class="hl-6">continue</span><span class="hl-2">;</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">ts</span><span class="hl-2"> = </span><span class="hl-5">message</span><span class="hl-2">.</span><span class="hl-5">date</span><span class="hl-2"> * </span><span class="hl-8">1000</span><span class="hl-2">;</span><br/><span class="hl-2"> </span><span class="hl-6">if</span><span class="hl-2"> (</span><span class="hl-5">ts</span><span class="hl-2"> &lt; </span><span class="hl-5">dayStart</span><span class="hl-2">.</span><span class="hl-1">getTime</span><span class="hl-2">()) </span><span class="hl-6">break</span><span class="hl-2">;</span><br/><span class="hl-2"> </span><span class="hl-5">rows</span><span class="hl-2">.</span><span class="hl-1">push</span><span class="hl-2">({ </span><span class="hl-5">id:</span><span class="hl-2"> </span><span class="hl-5">message</span><span class="hl-2">.</span><span class="hl-5">id</span><span class="hl-2">, </span><span class="hl-5">content:</span><span class="hl-2"> </span><span class="hl-5">message</span><span class="hl-2">.</span><span class="hl-5">message</span><span class="hl-2">, </span><span class="hl-5">channel</span><span class="hl-2">, </span><span class="hl-5">date:</span><span class="hl-2"> </span><span class="hl-4">new</span><span class="hl-2"> </span><span class="hl-1">Date</span><span class="hl-2">(</span><span class="hl-5">ts</span><span class="hl-2">) });</span><br/><span class="hl-2"> }</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> </span><span class="hl-5">rows</span><span class="hl-2">;</span><br/><span class="hl-2"> }</span><br/><span class="hl-2">}</span>
</code><button type="button">Copy</button></pre>
<p>The same applies to the database integration. It is not enough to just write an SQL query - before executing it in backtest you need to check that there was no time-travel into the future, in order to avoid look-ahead bias. To implement this you need a class with methods</p>
<pre><code class="typescript"><span class="hl-4">class</span><span class="hl-2"> </span><span class="hl-10">CandleDbService</span><span class="hl-2"> </span><span class="hl-4">extends</span><span class="hl-2"> </span><span class="hl-1">BaseCRUD</span><span class="hl-2">(</span><span class="hl-5">CandleModel</span><span class="hl-2">) {</span><br/><span class="hl-2"> </span><span class="hl-4">readonly</span><span class="hl-2"> </span><span class="hl-5">loggerService</span><span class="hl-2"> = </span><span class="hl-1">inject</span><span class="hl-2">&lt;</span><span class="hl-10">LoggerService</span><span class="hl-2">&gt;(</span><span class="hl-7">TYPES</span><span class="hl-2">.</span><span class="hl-5">loggerService</span><span class="hl-2">);</span><br/><br/><span class="hl-2"> </span><span class="hl-4">public</span><span class="hl-2"> </span><span class="hl-1">create</span><span class="hl-2"> = </span><span class="hl-4">async</span><span class="hl-2"> (</span><span class="hl-5">dto</span><span class="hl-2">: </span><span class="hl-10">ICandleDto</span><span class="hl-2">): </span><span class="hl-10">Promise</span><span class="hl-2">&lt;</span><span class="hl-10">ICandleRow</span><span class="hl-2">&gt; </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-5">loggerService</span><span class="hl-2">.</span><span class="hl-1">log</span><span class="hl-2">(</span><span class="hl-3">&quot;candleDbService create&quot;</span><span class="hl-2">, { </span><span class="hl-5">dto</span><span class="hl-2"> });</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">filter</span><span class="hl-2"> = {</span><br/><span class="hl-2"> </span><span class="hl-5">symbol:</span><span class="hl-2"> </span><span class="hl-5">dto</span><span class="hl-2">.</span><span class="hl-5">symbol</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">interval:</span><span class="hl-2"> </span><span class="hl-5">dto</span><span class="hl-2">.</span><span class="hl-5">interval</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">timestamp:</span><span class="hl-2"> </span><span class="hl-5">dto</span><span class="hl-2">.</span><span class="hl-5">timestamp</span><span class="hl-2">,</span><br/><span class="hl-2"> };</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">insertOnly</span><span class="hl-2"> = {</span><br/><span class="hl-2"> </span><span class="hl-5">exchangeName:</span><span class="hl-2"> </span><span class="hl-7">EXCHANGE_NAME</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">open:</span><span class="hl-2"> </span><span class="hl-5">dto</span><span class="hl-2">.</span><span class="hl-5">open</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">high:</span><span class="hl-2"> </span><span class="hl-5">dto</span><span class="hl-2">.</span><span class="hl-5">high</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">low:</span><span class="hl-2"> </span><span class="hl-5">dto</span><span class="hl-2">.</span><span class="hl-5">low</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">close:</span><span class="hl-2"> </span><span class="hl-5">dto</span><span class="hl-2">.</span><span class="hl-5">close</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">volume:</span><span class="hl-2"> </span><span class="hl-5">dto</span><span class="hl-2">.</span><span class="hl-5">volume</span><span class="hl-2">,</span><br/><span class="hl-2"> };</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">document</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-5">CandleModel</span><span class="hl-2">.</span><span class="hl-1">findOneAndUpdate</span><span class="hl-2">(</span><br/><span class="hl-2"> </span><span class="hl-5">filter</span><span class="hl-2">,</span><br/><span class="hl-2"> { </span><span class="hl-5">$setOnInsert:</span><span class="hl-2"> </span><span class="hl-5">insertOnly</span><span class="hl-2"> },</span><br/><span class="hl-2"> { </span><span class="hl-5">upsert:</span><span class="hl-2"> </span><span class="hl-4">true</span><span class="hl-2">, </span><span class="hl-5">new:</span><span class="hl-2"> </span><span class="hl-4">true</span><span class="hl-2">, </span><span class="hl-5">setDefaultsOnInsert:</span><span class="hl-2"> </span><span class="hl-4">true</span><span class="hl-2"> },</span><br/><span class="hl-2"> );</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">result</span><span class="hl-2"> = </span><span class="hl-1">readTransform</span><span class="hl-2">(</span><span class="hl-5">document</span><span class="hl-2">.</span><span class="hl-1">toJSON</span><span class="hl-2">()) </span><span class="hl-6">as</span><span class="hl-2"> </span><span class="hl-10">unknown</span><span class="hl-2"> </span><span class="hl-6">as</span><span class="hl-2"> </span><span class="hl-10">ICandleRow</span><span class="hl-2">;</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> </span><span class="hl-5">result</span><span class="hl-2">;</span><br/><span class="hl-2"> };</span><br/><br/><span class="hl-2"> </span><span class="hl-4">public</span><span class="hl-2"> </span><span class="hl-1">hasCandle</span><span class="hl-2"> = </span><span class="hl-4">async</span><span class="hl-2"> (</span><span class="hl-5">symbol</span><span class="hl-2">: </span><span class="hl-10">string</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">: </span><span class="hl-10">CandleInterval</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2">: </span><span class="hl-10">number</span><span class="hl-2">): </span><span class="hl-10">Promise</span><span class="hl-2">&lt;</span><span class="hl-10">boolean</span><span class="hl-2">&gt; </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-5">loggerService</span><span class="hl-2">.</span><span class="hl-1">log</span><span class="hl-2">(</span><span class="hl-3">&quot;candleDbService hasCandle&quot;</span><span class="hl-2">, { </span><br/><span class="hl-2"> </span><span class="hl-5">symbol</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">interval</span><span class="hl-2">,</span><br/><span class="hl-2"> </span><span class="hl-5">timestamp</span><span class="hl-2">,</span><br/><span class="hl-2"> });</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">candle</span><span class="hl-2"> = </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-1">findBySymbolIntervalTimestamp</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> !!</span><span class="hl-5">candle</span><span class="hl-2">;</span><br/><span class="hl-2"> };</span><br/><br/><span class="hl-2"> </span><span class="hl-4">public</span><span class="hl-2"> </span><span class="hl-5">findBySymbolIntervalTimestamp</span><span class="hl-2"> = </span><br/><span class="hl-2"> </span><span class="hl-4">async</span><span class="hl-2"> (</span><span class="hl-5">symbol</span><span class="hl-2">: </span><span class="hl-10">string</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">: </span><span class="hl-10">CandleInterval</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2">: </span><span class="hl-10">number</span><span class="hl-2">): </span><span class="hl-10">Promise</span><span class="hl-2">&lt;</span><span class="hl-10">ICandleRow</span><span class="hl-2"> | </span><span class="hl-10">null</span><span class="hl-2">&gt; </span><span class="hl-4">=&gt;</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-5">loggerService</span><span class="hl-2">.</span><span class="hl-1">log</span><span class="hl-2">(</span><span class="hl-3">&quot;candleDbService findBySymbolIntervalTimestamp&quot;</span><span class="hl-2">, { </span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2"> });</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-4">super</span><span class="hl-2">.</span><span class="hl-1">findByFilter</span><span class="hl-2">({ </span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">, </span><span class="hl-5">exchangeName:</span><span class="hl-2"> </span><span class="hl-7">EXCHANGE_NAME</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2"> });</span><br/><span class="hl-2"> };</span><br/><br/><span class="hl-2">}</span>
</code><button type="button">Copy</button></pre>
<a id="performance" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Performance<a href="#performance" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>After rolling out the software, it turned out that the modular approach not only creates jobs and speeds up development, but also delivers significantly higher performance.</p>
<pre><code class="typescript"><span class="hl-4">class</span><span class="hl-2"> </span><span class="hl-10">CandleCacheService</span><span class="hl-2"> </span><span class="hl-4">extends</span><span class="hl-2"> </span><span class="hl-1">BaseMap</span><span class="hl-2">(</span><span class="hl-7">REDIS_KEY</span><span class="hl-2">, -</span><span class="hl-8">1</span><span class="hl-2">) {</span><br/><span class="hl-2"> </span><span class="hl-4">readonly</span><span class="hl-2"> </span><span class="hl-5">loggerService</span><span class="hl-2"> = </span><span class="hl-1">inject</span><span class="hl-2">&lt;</span><span class="hl-10">LoggerService</span><span class="hl-2">&gt;(</span><span class="hl-7">TYPES</span><span class="hl-2">.</span><span class="hl-5">loggerService</span><span class="hl-2">);</span><br/><br/><span class="hl-2"> </span><span class="hl-4">private</span><span class="hl-2"> </span><span class="hl-1">_cacheKey</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">: </span><span class="hl-10">string</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">: </span><span class="hl-10">CandleInterval</span><span class="hl-2">, </span><span class="hl-5">exchangeName</span><span class="hl-2">: </span><span class="hl-10">string</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2">: </span><span class="hl-10">number</span><span class="hl-2">): </span><span class="hl-10">string</span><span class="hl-2"> {</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> </span><span class="hl-3">`</span><span class="hl-4">${</span><span class="hl-5">exchangeName</span><span class="hl-4">}</span><span class="hl-3">:</span><span class="hl-4">${</span><span class="hl-5">symbol</span><span class="hl-4">}</span><span class="hl-3">:</span><span class="hl-4">${</span><span class="hl-5">interval</span><span class="hl-4">}</span><span class="hl-3">:</span><span class="hl-4">${</span><span class="hl-5">timestamp</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-2">;</span><br/><span class="hl-2"> }</span><br/><br/><span class="hl-2"> </span><span class="hl-4">public</span><span class="hl-2"> </span><span class="hl-4">async</span><span class="hl-2"> </span><span class="hl-1">hasCandleId</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">: </span><span class="hl-10">string</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">: </span><span class="hl-10">CandleInterval</span><span class="hl-2">, </span><span class="hl-5">exchangeName</span><span class="hl-2">: </span><span class="hl-10">string</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2">: </span><span class="hl-10">number</span><span class="hl-2">) {</span><br/><span class="hl-2"> </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-5">loggerService</span><span class="hl-2">.</span><span class="hl-1">log</span><span class="hl-2">(</span><span class="hl-3">&quot;candleCacheService getCandleId&quot;</span><span class="hl-2">, { </span><br/><span class="hl-2"> </span><span class="hl-5">symbol</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">interval</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">exchangeName</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">timestamp</span><span class="hl-2">,</span><br/><span class="hl-2"> });</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">key</span><span class="hl-2"> = </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-1">_cacheKey</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">, </span><span class="hl-5">exchangeName</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-1">has</span><span class="hl-2">(</span><span class="hl-5">key</span><span class="hl-2">);</span><br/><span class="hl-2"> }</span><br/><br/><span class="hl-2"> </span><span class="hl-4">public</span><span class="hl-2"> </span><span class="hl-4">async</span><span class="hl-2"> </span><span class="hl-1">getCandleId</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">: </span><span class="hl-10">string</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">: </span><span class="hl-10">CandleInterval</span><span class="hl-2">, </span><span class="hl-5">exchangeName</span><span class="hl-2">: </span><span class="hl-10">string</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2">: </span><span class="hl-10">number</span><span class="hl-2">): </span><span class="hl-10">Promise</span><span class="hl-2">&lt;</span><span class="hl-10">string</span><span class="hl-2"> | </span><span class="hl-10">null</span><span class="hl-2">&gt; {</span><br/><span class="hl-2"> </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-5">loggerService</span><span class="hl-2">.</span><span class="hl-1">log</span><span class="hl-2">(</span><span class="hl-3">&quot;candleCacheService getCandleId&quot;</span><span class="hl-2">, { </span><br/><span class="hl-2"> </span><span class="hl-5">symbol</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">interval</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">exchangeName</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">timestamp</span><span class="hl-2">,</span><br/><span class="hl-2"> });</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">key</span><span class="hl-2"> = </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-1">_cacheKey</span><span class="hl-2">(</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">interval</span><span class="hl-2">, </span><span class="hl-5">exchangeName</span><span class="hl-2">, </span><span class="hl-5">timestamp</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">id</span><span class="hl-2"> = &lt;</span><span class="hl-10">string</span><span class="hl-2">&gt;</span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-4">super</span><span class="hl-2">.</span><span class="hl-1">get</span><span class="hl-2">(</span><span class="hl-5">key</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> </span><span class="hl-5">id</span><span class="hl-2"> ?? </span><span class="hl-4">null</span><span class="hl-2">;</span><br/><span class="hl-2"> }</span><br/><br/><span class="hl-2"> </span><span class="hl-4">public</span><span class="hl-2"> </span><span class="hl-4">async</span><span class="hl-2"> </span><span class="hl-1">setCandleId</span><span class="hl-2">(</span><span class="hl-5">row</span><span class="hl-2">: </span><span class="hl-10">ICandleRow</span><span class="hl-2">): </span><span class="hl-10">Promise</span><span class="hl-2">&lt;</span><span class="hl-10">string</span><span class="hl-2">&gt; {</span><br/><span class="hl-2"> </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-5">loggerService</span><span class="hl-2">.</span><span class="hl-1">log</span><span class="hl-2">(</span><span class="hl-3">`candleCacheService setCandleId`</span><span class="hl-2">, { </span><br/><span class="hl-2"> </span><span class="hl-5">symbol:</span><span class="hl-2"> </span><span class="hl-5">row</span><span class="hl-2">.</span><span class="hl-5">symbol</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">interval:</span><span class="hl-2"> </span><span class="hl-5">row</span><span class="hl-2">.</span><span class="hl-5">interval</span><span class="hl-2">, </span><br/><span class="hl-2"> </span><span class="hl-5">timestamp:</span><span class="hl-2"> </span><span class="hl-5">row</span><span class="hl-2">.</span><span class="hl-5">timestamp</span><br/><span class="hl-2"> });</span><br/><span class="hl-2"> </span><span class="hl-4">const</span><span class="hl-2"> </span><span class="hl-7">key</span><span class="hl-2"> = </span><span class="hl-4">this</span><span class="hl-2">.</span><span class="hl-1">_cacheKey</span><span class="hl-2">(</span><span class="hl-5">row</span><span class="hl-2">.</span><span class="hl-5">symbol</span><span class="hl-2">, </span><span class="hl-5">row</span><span class="hl-2">.</span><span class="hl-5">interval</span><span class="hl-2">, </span><span class="hl-5">row</span><span class="hl-2">.</span><span class="hl-5">exchangeName</span><span class="hl-2">, </span><span class="hl-5">row</span><span class="hl-2">.</span><span class="hl-5">timestamp</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">await</span><span class="hl-2"> </span><span class="hl-4">super</span><span class="hl-2">.</span><span class="hl-1">set</span><span class="hl-2">(</span><span class="hl-5">key</span><span class="hl-2">, </span><span class="hl-5">row</span><span class="hl-2">.</span><span class="hl-5">id</span><span class="hl-2">);</span><br/><span class="hl-2"> </span><span class="hl-6">return</span><span class="hl-2"> </span><span class="hl-5">row</span><span class="hl-2">.</span><span class="hl-5">id</span><span class="hl-2">;</span><br/><span class="hl-2"> }</span><br/><span class="hl-2">}</span>
</code><button type="button">Copy</button></pre>
<p>All of the most commonly used databases - <code>Postgres</code>, <code>MongoDB</code>, <code>MariaDB</code>, <code>MySQL</code> - use a <code>B-Tree</code> for row lookup, which is <code>O(log n)</code> complexity, where <code>n</code> is the number of rows in the table. A Redis cache lets you reduce the complexity to <code>O(1)</code> by looking up the row id by a composite key, however with the imperative syntax of freqtrade there is simply nowhere to apply it. If you store in redis not just a key-id dictionary but the data itself, performance will be lost on json serialization/deserialization. Below is the table with the measured performance metrics</p>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Wall-clock span (first β†’ last event)</td>
<td><code>1779292952202 βˆ’ 1779292949309</code> = <strong>2 893 ms</strong> (~2.9 s)</td>
</tr>
<tr>
<td>Total events captured</td>
<td><strong>297</strong></td>
</tr>
<tr>
<td>Symbols running in parallel</td>
<td><strong>9</strong> (BTC, POL, ZEC, HYPE, XAUT, DOGE, SOL, PENGU, HBAR)</td>
</tr>
<tr>
<td>Historical time advanced per symbol</td>
<td><code>1775003640000 βˆ’ 1775001600000</code> = <strong>2 040 000 ms</strong> = <strong>34 minutes</strong></td>
</tr>
<tr>
<td><strong>Per-symbol replay speed</strong></td>
<td>34 min historical Γ· 2.9 s wall = <strong>β‰ˆ 703Γ—</strong> real-time</td>
</tr>
<tr>
<td><strong>Aggregate replay speed (9 symbols)</strong></td>
<td>9 Γ— 703 = <strong>β‰ˆ 6 326Γ—</strong> real-time</td>
</tr>
<tr>
<td>Event throughput</td>
<td>297 ev / 2.893 s = <strong>β‰ˆ 103 events/sec</strong> (one Node process)</td>
</tr>
<tr>
<td>Frame coverage</td>
<td><code>2026-04-01 β†’ 2026-04-27</code> = 27 days Γ— 1m candles = <strong>38 880 candles/symbol Γ— 9</strong> = <strong>~350 000 candle ticks</strong></td>
</tr>
</tbody>
</table>
<p><strong>Speed: ~700x acceleration of historical time relative to real time</strong>, with 9 parallel contexts - an effective <strong>~6300x</strong> relative to a check that waits for candles in real time</p>
<a id="thank-you-for-your-attention" class="tsd-anchor"></a><h2 class="tsd-anchor-link">Thank you for your attention<a href="#thank-you-for-your-attention" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2></div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><h3><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><h3><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#⚑-monorepo-architecture-for-parallel-execution-of-trading-strategies"><span>⚑ <wbr/>Monorepo architecture for parallel execution of trading strategies</span></a><ul><li><a href="#the-problem"><span>The problem</span></a></li><li><a href="#solving-the-problem"><span>Solving the problem</span></a></li><li><ul><li><a href="#n-entry-points-instead-of-one-gated-by-a-cli-flag"><span>N entry points instead of one: gated by a CLI flag</span></a></li><li><a href="#shared-event-driven-strategy-code"><span>Shared event-<wbr/>driven strategy code</span></a></li><li><a href="#modular-plug-and-play-configuration-per-mode"><span>Modular plug-<wbr/>and-<wbr/>play configuration per mode</span></a></li><li><a href="#mvc-for-the-data-layer"><span>MVC for the data layer</span></a></li></ul></li><li><a href="#performance"><span>Performance</span></a></li><li><a href="#thank-you-for-your-attention"><span>Thank you for your attention</span></a></li></ul></div></details></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">backtest-kit</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
<!-- Yandex.Metrika counter -->
<script type="text/javascript">
(function(m,e,t,r,i,k,a){
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=105455585', 'ym');
ym(105455585, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", accurateTrackBounce:true, trackLinks:true});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/105455585" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-3MQZEBBDDR"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-3MQZEBBDDR');
</script>