Deployed 5dccabe to develop in en with MkDocs 1.6.1 and mike 2.1.4
This commit is contained in:
@@ -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) -> None:
|
||||
for trade in Trade.get_open_order_trades():
|
||||
fills = trade.select_filled_orders(trade.entry_side)
|
||||
if trade.pair == 'ETH/USDT':
|
||||
trade_entry_type = trade.get_custom_data(key='entry_type')
|
||||
if trade_entry_type is None:
|
||||
trade_entry_type = 'breakout' if 'entry_1' in trade.enter_tag else 'dip'
|
||||
elif len(fills) > 1:
|
||||
trade_entry_type = 'buy_up'
|
||||
trade.set_custom_data(key='entry_type', 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) -> 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 == 'BTC/USDT'
|
||||
and entry_tag == 'long_sma200'
|
||||
and side == 'long'
|
||||
and (current_time - timedelta(minutes=10)) > 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('num_entry_adjustments', default=0)
|
||||
if not existing_count:
|
||||
existing_count = 1
|
||||
else:
|
||||
existing_count += 1
|
||||
trade.set_custom_data(key='num_entry_adjustments', 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">-></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">'ETH/USDT'</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">'entry_type'</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">'breakout'</span> <span class="k">if</span> <span class="s1">'entry_1'</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">'dip'</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">></span> <span class="mi">1</span><span class="p">:</span>
|
||||
<span class="n">trade_entry_type</span> <span class="o">=</span> <span class="s1">'buy_up'</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">'entry_type'</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['sma_200']
|
||||
|
||||
<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">-></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">'BTC/USDT'</span>
|
||||
<span class="ow">and</span> <span class="n">entry_tag</span> <span class="o">==</span> <span class="s1">'long_sma200'</span>
|
||||
<span class="ow">and</span> <span class="n">side</span> <span class="o">==</span> <span class="s1">'long'</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">></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">'num_entry_adjustments'</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">'num_entry_adjustments'</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">'sma_200'</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='num_entry_adjustments')
|
||||
trade_entry_type = trade.get_custom_data(key='entry_type')
|
||||
if entry_adjustment_count is None:
|
||||
if current_profit > 0.01 and (current_time - timedelta(minutes=100) > trade.open_date_utc):
|
||||
return True, 'exit_1'
|
||||
else
|
||||
if entry_adjustment_count > 0 and if current_profit > 0.05:
|
||||
return True, 'exit_2'
|
||||
if trade_entry_type == 'breakout' and current_profit > 0.1:
|
||||
return True, '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">'num_entry_adjustments'</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">'entry_type'</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">></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">></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">'exit_1'</span>
|
||||
<span class="k">else</span>
|
||||
<span class="k">if</span> <span class="n">entry_adjustment_count</span> <span class="o">></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">></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">'exit_2'</span>
|
||||
<span class="k">if</span> <span class="n">trade_entry_type</span> <span class="o">==</span> <span class="s1">'breakout'</span> <span class="ow">and</span> <span class="n">current_profit</span> <span class="o">></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">'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) -> DataFrame:
|
||||
# Check if the entry already exists
|
||||
if not metadata["pair"] in self.custom_info:
|
||||
# Create empty entry for this pair
|
||||
self.custom_info[metadata["pair"]] = {}
|
||||
|
||||
<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">-></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">"pair"</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">"pair"</span><span class="p">]]</span> <span class="o">=</span> <span class="p">{}</span>
|
||||
|
||||
<span class="k">if</span> <span class="s2">"crosstime"</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">"pair"</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">"pair"</span><span class="p">]][</span><span class="s2">"crosstime"</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">"pair"</span><span class="p">]][</span><span class="s2">"crosstime"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
|
||||
if "crosstime" in self.custom_info[metadata["pair"]]:
|
||||
self.custom_info[metadata["pair"]]["crosstime"] += 1
|
||||
else:
|
||||
self.custom_info[metadata["pair"]]["crosstime"] = 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">¶</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) -> 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()
|
||||
# <...>
|
||||
|
||||
<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">'Trade'</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">'datetime'</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-></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"># <...></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">'date'</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"># <...></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['date'] == 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()
|
||||
# <...>
|
||||
</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">¶</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">-></span> <span class="n">DataFrame</span><span class="p">:</span>
|
||||
<span class="n">dataframe</span><span class="p">[</span><span class="s2">"enter_tag"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">""</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">"rsi"</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">"bb_lowerband"</span><span class="p">]</span> <span class="o"><</span> <span class="n">dataframe</span><span class="p">[</span><span class="s2">"close"</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">&</span> <span class="p">(</span><span class="n">dataframe</span><span class="p">[</span><span class="s2">"volume"</span><span class="p">]</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span>
|
||||
<span class="p">,</span> <span class="s2">"enter_long"</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">"enter_tag"</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">"long_signal_rsi "</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">"enter_tag"</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">"long_signal_bblower "</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">"long_signal_rsi"</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">"rsi"</span><span class="p">]</span> <span class="o">></span> <span class="mi">80</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="s2">"exit_signal_rsi"</span>
|
||||
<span class="k">if</span> <span class="s2">"long_signal_bblower"</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">"high"</span><span class="p">]</span> <span class="o">></span> <span class="n">last_candle</span><span class="p">[</span><span class="s2">"bb_upperband"</span><span class="p">]:</span>
|
||||
<span class="k">return</span> <span class="s2">"exit_signal_bblower"</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) -> DataFrame:
|
||||
dataframe["enter_tag"] = ""
|
||||
signal_rsi = (qtpylib.crossed_above(dataframe["rsi"], 35))
|
||||
signal_bblower = (dataframe["bb_lowerband"] < dataframe["close"])
|
||||
# Additional conditions
|
||||
dataframe.loc[
|
||||
(
|
||||
signal_rsi
|
||||
| signal_bblower
|
||||
# ... additional signals to enter a long position
|
||||
)
|
||||
& (dataframe["volume"] > 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"] > 80:
|
||||
return "exit_signal_rsi"
|
||||
if "long_signal_bblower" in trade.enter_tag and last_candle["high"] > 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">¶</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">-></span> <span class="n">DataFrame</span><span class="p">:</span>
|
||||
<span class="n">dataframe</span><span class="p">[</span><span class="s2">"exit_tag"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">""</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">"rsi"</span><span class="p">]</span> <span class="o">></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">"ema20"</span><span class="p">]</span> <span class="o"><</span> <span class="n">dataframe</span><span class="p">[</span><span class="s2">"ema50"</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">&</span>
|
||||
<span class="p">(</span><span class="n">dataframe</span><span class="p">[</span><span class="s2">"volume"</span><span class="p">]</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span>
|
||||
<span class="p">,</span>
|
||||
<span class="s2">"exit_long"</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">"exit_tag"</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">"exit_signal_rsi "</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">"exit_tag"</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">"exit_signal_rsi "</span>
|
||||
|
||||
<span class="k">return</span> <span class="n">dataframe</span>
|
||||
<p>``` python
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe["exit_tag"] = ""
|
||||
rsi_exit_signal = (dataframe["rsi"] > 70)
|
||||
ema_exit_signal = (dataframe["ema20"] < dataframe["ema50"])
|
||||
# Additional conditions
|
||||
dataframe.loc[
|
||||
(
|
||||
rsi_exit_signal
|
||||
| ema_exit_signal
|
||||
# ... additional signals to exit a long position
|
||||
) &
|
||||
(dataframe["volume"] > 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">¶</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">-></span> <span class="nb">str</span><span class="p">:</span>
|
||||
<span class="w"> </span><span class="sd">"""</span>
|
||||
<span class="sd"> Returns version of the strategy.</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">return</span> <span class="s2">"1.1"</span>
|
||||
</code></pre></div>
|
||||
<p><code>python
|
||||
def version(self) -> 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">¶</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">'r'</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">'utf-8'</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">"strategy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"NameOfStrategy:BASE64String"</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">¶</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">'ema_short_</span><span class="si">{</span><span class="n">val</span><span class="si">}</span><span class="s1">'</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">'ema_short_</span><span class="si">{</span><span class="n">val</span><span class="si">}</span><span class="s1">'</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">¶</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>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user