Deployed 5dccabe to develop in en with MkDocs 1.6.1 and mike 2.1.4

This commit is contained in:
github-actions[bot]
2026-03-30 04:44:32 +00:00
parent 80d3b2a925
commit 6e863baebc
51 changed files with 7045 additions and 13400 deletions
+170 -163
View File
@@ -23,7 +23,7 @@
<link rel="icon" href="../images/logo.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.5">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.6">
@@ -2106,65 +2106,66 @@ If you're just getting started, please familiarize yourself with the <a href="..
<p>Using a trade object, information can be stored using <code>trade.set_custom_data(key='my_key', value=my_value)</code> and retrieved using <code>trade.get_custom_data(key='my_key')</code>. Each data entry is associated with a trade and a user supplied key (of type <code>string</code>). This means that this can only be used in callbacks that also provide a trade object.</p>
<p>For the data to be able to be stored within the database, freqtrade must serialized the data. This is done by converting the data to a JSON formatted string.
Freqtrade will attempt to reverse this action on retrieval, so from a strategy perspective, this should not be relevant.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">freqtrade.persistence</span><span class="w"> </span><span class="kn">import</span> <span class="n">Trade</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">datetime</span><span class="w"> </span><span class="kn">import</span> <span class="n">timedelta</span>
<p>```python
from freqtrade.persistence import Trade
from datetime import timedelta</p>
<p>class AwesomeStrategy(IStrategy):</p>
<div class="codehilite"><pre><span></span><code>def bot_loop_start(self, **kwargs) -&gt; None:
for trade in Trade.get_open_order_trades():
fills = trade.select_filled_orders(trade.entry_side)
if trade.pair == &#39;ETH/USDT&#39;:
trade_entry_type = trade.get_custom_data(key=&#39;entry_type&#39;)
if trade_entry_type is None:
trade_entry_type = &#39;breakout&#39; if &#39;entry_1&#39; in trade.enter_tag else &#39;dip&#39;
elif len(fills) &gt; 1:
trade_entry_type = &#39;buy_up&#39;
trade.set_custom_data(key=&#39;entry_type&#39;, value=trade_entry_type)
return super().bot_loop_start(**kwargs)
<span class="k">class</span><span class="w"> </span><span class="nc">AwesomeStrategy</span><span class="p">(</span><span class="n">IStrategy</span><span class="p">):</span>
def adjust_entry_price(self, trade: Trade, order: Order | None, pair: str,
current_time: datetime, proposed_rate: float, current_order_rate: float,
entry_tag: str | None, side: str, **kwargs) -&gt; float:
# Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
if (
pair == &#39;BTC/USDT&#39;
and entry_tag == &#39;long_sma200&#39;
and side == &#39;long&#39;
and (current_time - timedelta(minutes=10)) &gt; trade.open_date_utc
and order.filled == 0.0
):
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze()
# store information about entry adjustment
existing_count = trade.get_custom_data(&#39;num_entry_adjustments&#39;, default=0)
if not existing_count:
existing_count = 1
else:
existing_count += 1
trade.set_custom_data(key=&#39;num_entry_adjustments&#39;, value=existing_count)
<span class="k">def</span><span class="w"> </span><span class="nf">bot_loop_start</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">for</span> <span class="n">trade</span> <span class="ow">in</span> <span class="n">Trade</span><span class="o">.</span><span class="n">get_open_order_trades</span><span class="p">():</span>
<span class="n">fills</span> <span class="o">=</span> <span class="n">trade</span><span class="o">.</span><span class="n">select_filled_orders</span><span class="p">(</span><span class="n">trade</span><span class="o">.</span><span class="n">entry_side</span><span class="p">)</span>
<span class="k">if</span> <span class="n">trade</span><span class="o">.</span><span class="n">pair</span> <span class="o">==</span> <span class="s1">&#39;ETH/USDT&#39;</span><span class="p">:</span>
<span class="n">trade_entry_type</span> <span class="o">=</span> <span class="n">trade</span><span class="o">.</span><span class="n">get_custom_data</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="s1">&#39;entry_type&#39;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">trade_entry_type</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">trade_entry_type</span> <span class="o">=</span> <span class="s1">&#39;breakout&#39;</span> <span class="k">if</span> <span class="s1">&#39;entry_1&#39;</span> <span class="ow">in</span> <span class="n">trade</span><span class="o">.</span><span class="n">enter_tag</span> <span class="k">else</span> <span class="s1">&#39;dip&#39;</span>
<span class="k">elif</span> <span class="nb">len</span><span class="p">(</span><span class="n">fills</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
<span class="n">trade_entry_type</span> <span class="o">=</span> <span class="s1">&#39;buy_up&#39;</span>
<span class="n">trade</span><span class="o">.</span><span class="n">set_custom_data</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="s1">&#39;entry_type&#39;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="n">trade_entry_type</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">bot_loop_start</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
# adjust order price
return current_candle[&#39;sma_200&#39;]
<span class="k">def</span><span class="w"> </span><span class="nf">adjust_entry_price</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">trade</span><span class="p">:</span> <span class="n">Trade</span><span class="p">,</span> <span class="n">order</span><span class="p">:</span> <span class="n">Order</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">pair</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">current_time</span><span class="p">:</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">proposed_rate</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">current_order_rate</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span>
<span class="n">entry_tag</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">side</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
<span class="c1"># Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.</span>
<span class="k">if</span> <span class="p">(</span>
<span class="n">pair</span> <span class="o">==</span> <span class="s1">&#39;BTC/USDT&#39;</span>
<span class="ow">and</span> <span class="n">entry_tag</span> <span class="o">==</span> <span class="s1">&#39;long_sma200&#39;</span>
<span class="ow">and</span> <span class="n">side</span> <span class="o">==</span> <span class="s1">&#39;long&#39;</span>
<span class="ow">and</span> <span class="p">(</span><span class="n">current_time</span> <span class="o">-</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">minutes</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span> <span class="o">&gt;</span> <span class="n">trade</span><span class="o">.</span><span class="n">open_date_utc</span>
<span class="ow">and</span> <span class="n">order</span><span class="o">.</span><span class="n">filled</span> <span class="o">==</span> <span class="mf">0.0</span>
<span class="p">):</span>
<span class="n">dataframe</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">dp</span><span class="o">.</span><span class="n">get_analyzed_dataframe</span><span class="p">(</span><span class="n">pair</span><span class="o">=</span><span class="n">pair</span><span class="p">,</span> <span class="n">timeframe</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">timeframe</span><span class="p">)</span>
<span class="n">current_candle</span> <span class="o">=</span> <span class="n">dataframe</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">squeeze</span><span class="p">()</span>
<span class="c1"># store information about entry adjustment</span>
<span class="n">existing_count</span> <span class="o">=</span> <span class="n">trade</span><span class="o">.</span><span class="n">get_custom_data</span><span class="p">(</span><span class="s1">&#39;num_entry_adjustments&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">existing_count</span><span class="p">:</span>
<span class="n">existing_count</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">existing_count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="n">trade</span><span class="o">.</span><span class="n">set_custom_data</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="s1">&#39;num_entry_adjustments&#39;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="n">existing_count</span><span class="p">)</span>
# default: maintain existing order
return current_order_rate
<span class="c1"># adjust order price</span>
<span class="k">return</span> <span class="n">current_candle</span><span class="p">[</span><span class="s1">&#39;sma_200&#39;</span><span class="p">]</span>
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs):
<span class="c1"># default: maintain existing order</span>
<span class="k">return</span> <span class="n">current_order_rate</span>
entry_adjustment_count = trade.get_custom_data(key=&#39;num_entry_adjustments&#39;)
trade_entry_type = trade.get_custom_data(key=&#39;entry_type&#39;)
if entry_adjustment_count is None:
if current_profit &gt; 0.01 and (current_time - timedelta(minutes=100) &gt; trade.open_date_utc):
return True, &#39;exit_1&#39;
else
if entry_adjustment_count &gt; 0 and if current_profit &gt; 0.05:
return True, &#39;exit_2&#39;
if trade_entry_type == &#39;breakout&#39; and current_profit &gt; 0.1:
return True, &#39;exit_3
<span class="k">def</span><span class="w"> </span><span class="nf">custom_exit</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pair</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">trade</span><span class="p">:</span> <span class="n">Trade</span><span class="p">,</span> <span class="n">current_time</span><span class="p">:</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">current_rate</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">current_profit</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">entry_adjustment_count</span> <span class="o">=</span> <span class="n">trade</span><span class="o">.</span><span class="n">get_custom_data</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="s1">&#39;num_entry_adjustments&#39;</span><span class="p">)</span>
<span class="n">trade_entry_type</span> <span class="o">=</span> <span class="n">trade</span><span class="o">.</span><span class="n">get_custom_data</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="s1">&#39;entry_type&#39;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">entry_adjustment_count</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">current_profit</span> <span class="o">&gt;</span> <span class="mf">0.01</span> <span class="ow">and</span> <span class="p">(</span><span class="n">current_time</span> <span class="o">-</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">minutes</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">trade</span><span class="o">.</span><span class="n">open_date_utc</span><span class="p">):</span>
<span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s1">&#39;exit_1&#39;</span>
<span class="k">else</span>
<span class="k">if</span> <span class="n">entry_adjustment_count</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="k">if</span> <span class="n">current_profit</span> <span class="o">&gt;</span> <span class="mf">0.05</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s1">&#39;exit_2&#39;</span>
<span class="k">if</span> <span class="n">trade_entry_type</span> <span class="o">==</span> <span class="s1">&#39;breakout&#39;</span> <span class="ow">and</span> <span class="n">current_profit</span> <span class="o">&gt;</span> <span class="mf">0.1</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s1">&#39;exit_3</span>
<span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="kc">None</span>
return False, None
</code></pre></div>
<p>```</p>
<p>The above is a simple example - there are simpler ways to retrieve trade data like entry-adjustments.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
@@ -2196,21 +2197,23 @@ Please use <a href="#storing-information-persistent">Persistent Storage</a> inst
<summary>Storing information</summary>
<p>Storing information can be accomplished by creating a new dictionary within the strategy class.</p>
<p>The name of the variable can be chosen at will, but should be prefixed with <code>custom_</code> to avoid naming collisions with predefined strategy variables.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">AwesomeStrategy</span><span class="p">(</span><span class="n">IStrategy</span><span class="p">):</span>
<span class="c1"># Create custom dictionary</span>
<span class="n">custom_info</span> <span class="o">=</span> <span class="p">{}</span>
<p>```python
class AwesomeStrategy(IStrategy):
# Create custom dictionary
custom_info = {}</p>
<div class="codehilite"><pre><span></span><code>def populate_indicators(self, dataframe: DataFrame, metadata: dict) -&gt; DataFrame:
# Check if the entry already exists
if not metadata[&quot;pair&quot;] in self.custom_info:
# Create empty entry for this pair
self.custom_info[metadata[&quot;pair&quot;]] = {}
<span class="k">def</span><span class="w"> </span><span class="nf">populate_indicators</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dataframe</span><span class="p">:</span> <span class="n">DataFrame</span><span class="p">,</span> <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">DataFrame</span><span class="p">:</span>
<span class="c1"># Check if the entry already exists</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">metadata</span><span class="p">[</span><span class="s2">&quot;pair&quot;</span><span class="p">]</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">custom_info</span><span class="p">:</span>
<span class="c1"># Create empty entry for this pair</span>
<span class="bp">self</span><span class="o">.</span><span class="n">custom_info</span><span class="p">[</span><span class="n">metadata</span><span class="p">[</span><span class="s2">&quot;pair&quot;</span><span class="p">]]</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">if</span> <span class="s2">&quot;crosstime&quot;</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">custom_info</span><span class="p">[</span><span class="n">metadata</span><span class="p">[</span><span class="s2">&quot;pair&quot;</span><span class="p">]]:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">custom_info</span><span class="p">[</span><span class="n">metadata</span><span class="p">[</span><span class="s2">&quot;pair&quot;</span><span class="p">]][</span><span class="s2">&quot;crosstime&quot;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">custom_info</span><span class="p">[</span><span class="n">metadata</span><span class="p">[</span><span class="s2">&quot;pair&quot;</span><span class="p">]][</span><span class="s2">&quot;crosstime&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
if &quot;crosstime&quot; in self.custom_info[metadata[&quot;pair&quot;]]:
self.custom_info[metadata[&quot;pair&quot;]][&quot;crosstime&quot;] += 1
else:
self.custom_info[metadata[&quot;pair&quot;]][&quot;crosstime&quot;] = 1
</code></pre></div>
<p>```</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash.</p>
@@ -2222,30 +2225,31 @@ Please use <a href="#storing-information-persistent">Persistent Storage</a> inst
</details>
<h2 id="dataframe-access">Dataframe access<a class="headerlink" href="#dataframe-access" title="Permanent link">&para;</a></h2>
<p>You may access dataframe in various strategy functions by querying it from dataprovider.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">freqtrade.exchange</span><span class="w"> </span><span class="kn">import</span> <span class="n">timeframe_to_prev_date</span>
<p>``` python
from freqtrade.exchange import timeframe_to_prev_date</p>
<p>class AwesomeStrategy(IStrategy):
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
rate: float, time_in_force: str, exit_reason: str,
current_time: 'datetime', **kwargs) -&gt; bool:
# Obtain pair dataframe.
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)</p>
<div class="codehilite"><pre><span></span><code> # Obtain last available candle. Do not use current_time to look up latest candle, because
# current_time points to current incomplete candle whose data is not available.
last_candle = dataframe.iloc[-1].squeeze()
# &lt;...&gt;
<span class="k">class</span><span class="w"> </span><span class="nc">AwesomeStrategy</span><span class="p">(</span><span class="n">IStrategy</span><span class="p">):</span>
<span class="k">def</span><span class="w"> </span><span class="nf">confirm_trade_exit</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pair</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">trade</span><span class="p">:</span> <span class="s1">&#39;Trade&#39;</span><span class="p">,</span> <span class="n">order_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">amount</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span>
<span class="n">rate</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">time_in_force</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">exit_reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">current_time</span><span class="p">:</span> <span class="s1">&#39;datetime&#39;</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
<span class="c1"># Obtain pair dataframe.</span>
<span class="n">dataframe</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">dp</span><span class="o">.</span><span class="n">get_analyzed_dataframe</span><span class="p">(</span><span class="n">pair</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">timeframe</span><span class="p">)</span>
<span class="c1"># Obtain last available candle. Do not use current_time to look up latest candle, because </span>
<span class="c1"># current_time points to current incomplete candle whose data is not available.</span>
<span class="n">last_candle</span> <span class="o">=</span> <span class="n">dataframe</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">squeeze</span><span class="p">()</span>
<span class="c1"># &lt;...&gt;</span>
<span class="c1"># In dry/live runs trade open date will not match candle open date therefore it must be </span>
<span class="c1"># rounded.</span>
<span class="n">trade_date</span> <span class="o">=</span> <span class="n">timeframe_to_prev_date</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">timeframe</span><span class="p">,</span> <span class="n">trade</span><span class="o">.</span><span class="n">open_date_utc</span><span class="p">)</span>
<span class="c1"># Look up trade candle.</span>
<span class="n">trade_candle</span> <span class="o">=</span> <span class="n">dataframe</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">dataframe</span><span class="p">[</span><span class="s1">&#39;date&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">trade_date</span><span class="p">]</span>
<span class="c1"># trade_candle may be empty for trades that just opened as it is still incomplete.</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">trade_candle</span><span class="o">.</span><span class="n">empty</span><span class="p">:</span>
<span class="n">trade_candle</span> <span class="o">=</span> <span class="n">trade_candle</span><span class="o">.</span><span class="n">squeeze</span><span class="p">()</span>
<span class="c1"># &lt;...&gt;</span>
# In dry/live runs trade open date will not match candle open date therefore it must be
# rounded.
trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
# Look up trade candle.
trade_candle = dataframe.loc[dataframe[&#39;date&#39;] == trade_date]
# trade_candle may be empty for trades that just opened as it is still incomplete.
if not trade_candle.empty:
trade_candle = trade_candle.squeeze()
# &lt;...&gt;
</code></pre></div>
<p>```</p>
<div class="admonition warning">
<p class="admonition-title">Using .iloc[-1]</p>
<p>You can use <code>.iloc[-1]</code> here because <code>get_analyzed_dataframe()</code> only returns candles that backtesting is allowed to see.
@@ -2256,37 +2260,38 @@ Also, this will only work starting with version 2021.5.</p>
<h2 id="enter-tag">Enter Tag<a class="headerlink" href="#enter-tag" title="Permanent link">&para;</a></h2>
<p>When your strategy has multiple entry signals, you can name the signal that triggered.
Then you can access your entry signal on <code>custom_exit</code></p>
<div class="highlight"><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">populate_entry_trend</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dataframe</span><span class="p">:</span> <span class="n">DataFrame</span><span class="p">,</span> <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">DataFrame</span><span class="p">:</span>
<span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;enter_tag&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span>
<span class="n">signal_rsi</span> <span class="o">=</span> <span class="p">(</span><span class="n">qtpylib</span><span class="o">.</span><span class="n">crossed_above</span><span class="p">(</span><span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;rsi&quot;</span><span class="p">],</span> <span class="mi">35</span><span class="p">))</span>
<span class="n">signal_bblower</span> <span class="o">=</span> <span class="p">(</span><span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;bb_lowerband&quot;</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;close&quot;</span><span class="p">])</span>
<span class="c1"># Additional conditions</span>
<span class="n">dataframe</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span>
<span class="p">(</span>
<span class="n">signal_rsi</span>
<span class="o">|</span> <span class="n">signal_bblower</span>
<span class="c1"># ... additional signals to enter a long position</span>
<span class="p">)</span>
<span class="o">&amp;</span> <span class="p">(</span><span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;volume&quot;</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">,</span> <span class="s2">&quot;enter_long&quot;</span>
<span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1"># Concatenate the tags so all signals are kept</span>
<span class="n">dataframe</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">signal_rsi</span><span class="p">,</span> <span class="s2">&quot;enter_tag&quot;</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">&quot;long_signal_rsi &quot;</span>
<span class="n">dataframe</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">signal_bblower</span><span class="p">,</span> <span class="s2">&quot;enter_tag&quot;</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">&quot;long_signal_bblower &quot;</span>
<span class="k">return</span> <span class="n">dataframe</span>
<span class="k">def</span><span class="w"> </span><span class="nf">custom_exit</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pair</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">trade</span><span class="p">:</span> <span class="n">Trade</span><span class="p">,</span> <span class="n">current_time</span><span class="p">:</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">current_rate</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span>
<span class="n">current_profit</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">dataframe</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">dp</span><span class="o">.</span><span class="n">get_analyzed_dataframe</span><span class="p">(</span><span class="n">pair</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">timeframe</span><span class="p">)</span>
<span class="n">last_candle</span> <span class="o">=</span> <span class="n">dataframe</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">squeeze</span><span class="p">()</span>
<span class="k">if</span> <span class="s2">&quot;long_signal_rsi&quot;</span> <span class="ow">in</span> <span class="n">trade</span><span class="o">.</span><span class="n">enter_tag</span> <span class="ow">and</span> <span class="n">last_candle</span><span class="p">[</span><span class="s2">&quot;rsi&quot;</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">80</span><span class="p">:</span>
<span class="k">return</span> <span class="s2">&quot;exit_signal_rsi&quot;</span>
<span class="k">if</span> <span class="s2">&quot;long_signal_bblower&quot;</span> <span class="ow">in</span> <span class="n">trade</span><span class="o">.</span><span class="n">enter_tag</span> <span class="ow">and</span> <span class="n">last_candle</span><span class="p">[</span><span class="s2">&quot;high&quot;</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">last_candle</span><span class="p">[</span><span class="s2">&quot;bb_upperband&quot;</span><span class="p">]:</span>
<span class="k">return</span> <span class="s2">&quot;exit_signal_bblower&quot;</span>
<span class="c1"># ...</span>
<span class="k">return</span> <span class="kc">None</span>
<p>```python
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -&gt; DataFrame:
dataframe["enter_tag"] = ""
signal_rsi = (qtpylib.crossed_above(dataframe["rsi"], 35))
signal_bblower = (dataframe["bb_lowerband"] &lt; dataframe["close"])
# Additional conditions
dataframe.loc[
(
signal_rsi
| signal_bblower
# ... additional signals to enter a long position
)
&amp; (dataframe["volume"] &gt; 0)
, "enter_long"
] = 1
# Concatenate the tags so all signals are kept
dataframe.loc[signal_rsi, "enter_tag"] += "long_signal_rsi "
dataframe.loc[signal_bblower, "enter_tag"] += "long_signal_bblower "</p>
<div class="codehilite"><pre><span></span><code>return dataframe
</code></pre></div>
<p>def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
if "long_signal_rsi" in trade.enter_tag and last_candle["rsi"] &gt; 80:
return "exit_signal_rsi"
if "long_signal_bblower" in trade.enter_tag and last_candle["high"] &gt; last_candle["bb_upperband"]:
return "exit_signal_bblower"
# ...
return None</p>
<p>```</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p><code>enter_tag</code> is limited to 255 characters, remaining data will be truncated.</p>
@@ -2300,26 +2305,28 @@ These results are a consequence of the strategy overwriting prior tags - where t
</div>
<h2 id="exit-tag">Exit tag<a class="headerlink" href="#exit-tag" title="Permanent link">&para;</a></h2>
<p>Similar to <a href="#enter-tag">Entry Tagging</a>, you can also specify an exit tag.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">populate_exit_trend</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dataframe</span><span class="p">:</span> <span class="n">DataFrame</span><span class="p">,</span> <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">DataFrame</span><span class="p">:</span>
<span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;exit_tag&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span>
<span class="n">rsi_exit_signal</span> <span class="o">=</span> <span class="p">(</span><span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;rsi&quot;</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">70</span><span class="p">)</span>
<span class="n">ema_exit_signal</span> <span class="o">=</span> <span class="p">(</span><span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;ema20&quot;</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;ema50&quot;</span><span class="p">])</span>
<span class="c1"># Additional conditions</span>
<span class="n">dataframe</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span>
<span class="p">(</span>
<span class="n">rsi_exit_signal</span>
<span class="o">|</span> <span class="n">ema_exit_signal</span>
<span class="c1"># ... additional signals to exit a long position</span>
<span class="p">)</span> <span class="o">&amp;</span>
<span class="p">(</span><span class="n">dataframe</span><span class="p">[</span><span class="s2">&quot;volume&quot;</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">,</span>
<span class="s2">&quot;exit_long&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1"># Concatenate the tags so all signals are kept</span>
<span class="n">dataframe</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">rsi_exit_signal</span><span class="p">,</span> <span class="s2">&quot;exit_tag&quot;</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">&quot;exit_signal_rsi &quot;</span>
<span class="n">dataframe</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">rsi_exit_signal2</span><span class="p">,</span> <span class="s2">&quot;exit_tag&quot;</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">&quot;exit_signal_rsi &quot;</span>
<span class="k">return</span> <span class="n">dataframe</span>
<p>``` python
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -&gt; DataFrame:
dataframe["exit_tag"] = ""
rsi_exit_signal = (dataframe["rsi"] &gt; 70)
ema_exit_signal = (dataframe["ema20"] &lt; dataframe["ema50"])
# Additional conditions
dataframe.loc[
(
rsi_exit_signal
| ema_exit_signal
# ... additional signals to exit a long position
) &amp;
(dataframe["volume"] &gt; 0)
,
"exit_long"] = 1
# Concatenate the tags so all signals are kept
dataframe.loc[rsi_exit_signal, "exit_tag"] += "exit_signal_rsi "
dataframe.loc[rsi_exit_signal2, "exit_tag"] += "exit_signal_rsi "</p>
<div class="codehilite"><pre><span></span><code>return dataframe
</code></pre></div>
<p>```</p>
<p>The provided exit-tag is then used as exit-reason - and shown as such in backtest results.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
@@ -2327,12 +2334,12 @@ These results are a consequence of the strategy overwriting prior tags - where t
</div>
<h2 id="strategy-version">Strategy version<a class="headerlink" href="#strategy-version" title="Permanent link">&para;</a></h2>
<p>You can implement custom strategy versioning by using the "version" method, and returning the version you would like this strategy to have.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">version</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Returns version of the strategy.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="s2">&quot;1.1&quot;</span>
</code></pre></div>
<p><code>python
def version(self) -&gt; str:
"""
Returns version of the strategy.
"""
return "1.1"</code></p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>You should make sure to implement proper version control (like a git repository) alongside this, as freqtrade will not keep historic versions of your strategy, so it's up to the user to be able to eventually roll back to a prior version of the strategy.</p>
@@ -2361,15 +2368,15 @@ This is done by utilizing BASE64 encoding and providing this string at the strat
in your chosen config file.</p>
<h3 id="encoding-a-string-as-base64">Encoding a string as BASE64<a class="headerlink" href="#encoding-a-string-as-base64" title="Permanent link">&para;</a></h3>
<p>This is a quick example, how to generate the BASE64 string in python</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">base64</span><span class="w"> </span><span class="kn">import</span> <span class="n">urlsafe_b64encode</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">content</span> <span class="o">=</span> <span class="n">urlsafe_b64encode</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">))</span>
</code></pre></div>
<p>```python
from base64 import urlsafe_b64encode</p>
<p>with open(file, 'r') as f:
content = f.read()
content = urlsafe_b64encode(content.encode('utf-8'))
```</p>
<p>The variable 'content', will contain the strategy file in a BASE64 encoded form. Which can now be set in your configurations file as following</p>
<div class="highlight"><pre><span></span><code><span class="nt">&quot;strategy&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;NameOfStrategy:BASE64String&quot;</span>
</code></pre></div>
<p><code>json
"strategy": "NameOfStrategy:BASE64String"</code></p>
<p>Please ensure that 'NameOfStrategy' is identical to the strategy name!</p>
<h2 id="performance-warning">Performance warning<a class="headerlink" href="#performance-warning" title="Permanent link">&para;</a></h2>
<p>When executing a strategy, one can sometimes be greeted by the following in the logs</p>
@@ -2380,19 +2387,19 @@ in your chosen config file.</p>
use <code>pd.concat(axis=1)</code>.
This can have slight performance implications, which are usually only visible during hyperopt (when optimizing an indicator).</p>
<p>For example:</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span> <span class="n">val</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">buy_ema_short</span><span class="o">.</span><span class="n">range</span><span class="p">:</span>
<span class="n">dataframe</span><span class="p">[</span><span class="sa">f</span><span class="s1">&#39;ema_short_</span><span class="si">{</span><span class="n">val</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">ta</span><span class="o">.</span><span class="n">EMA</span><span class="p">(</span><span class="n">dataframe</span><span class="p">,</span> <span class="n">timeperiod</span><span class="o">=</span><span class="n">val</span><span class="p">)</span>
</code></pre></div>
<p><code>python
for val in self.buy_ema_short.range:
dataframe[f'ema_short_{val}'] = ta.EMA(dataframe, timeperiod=val)</code></p>
<p>should be rewritten to</p>
<div class="highlight"><pre><span></span><code><span class="n">frames</span> <span class="o">=</span> <span class="p">[</span><span class="n">dataframe</span><span class="p">]</span>
<span class="k">for</span> <span class="n">val</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">buy_ema_short</span><span class="o">.</span><span class="n">range</span><span class="p">:</span>
<span class="n">frames</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DataFrame</span><span class="p">({</span>
<span class="sa">f</span><span class="s1">&#39;ema_short_</span><span class="si">{</span><span class="n">val</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">:</span> <span class="n">ta</span><span class="o">.</span><span class="n">EMA</span><span class="p">(</span><span class="n">dataframe</span><span class="p">,</span> <span class="n">timeperiod</span><span class="o">=</span><span class="n">val</span><span class="p">)</span>
<span class="p">}))</span>
<span class="c1"># Combine all dataframes, and reassign the original dataframe column</span>
<span class="n">dataframe</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">concat</span><span class="p">(</span><span class="n">frames</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div>
<p>```python
frames = [dataframe]
for val in self.buy_ema_short.range:
frames.append(DataFrame({
f'ema_short_{val}': ta.EMA(dataframe, timeperiod=val)
}))</p>
<h1 id="combine-all-dataframes-and-reassign-the-original-dataframe-column">Combine all dataframes, and reassign the original dataframe column<a class="headerlink" href="#combine-all-dataframes-and-reassign-the-original-dataframe-column" title="Permanent link">&para;</a></h1>
<p>dataframe = pd.concat(frames, axis=1)
```</p>
<p>Freqtrade does however also counter this by running <code>dataframe.copy()</code> on the dataframe right after the <code>populate_indicators()</code> method - so performance implications of this should be low to non-existent.</p>