VectorBT Pro - Custom Simulator 3: CandleStick Strategy + StopLoss + TakeProfit + Partial Profits
4 min read

VectorBT Pro - Custom Simulator 3: CandleStick Strategy + StopLoss + TakeProfit + Partial Profits

VectorBT Pro - Custom Simulator 3: CandleStick Strategy + StopLoss + TakeProfit + Partial Profits

Importing The Dependencies

import vectorbtpro as vbt
import numpy as np
import pandas as pd
from numba import njit
import talib
import datetime as dt
import time
from collections import namedtuple
import itertools
import math
from vectorbtpro.records.nb import col_map_nb
from vectorbtpro.portfolio import nb as pf_nb, enums as pf_enums
import plotly.io as pio
from numba import njit

Strategy Rules πŸ“

In addition to the previous rules , the only additional rule here is to close 50 % of the position when the price hits the first take profit tp1 (RRR of 1) moving the stop loss to breakeven (entry_price)


The tracking dictionary here does exactly what it says , it will keep track of the entry_price , stop_loss , tp1 (take profit 1) , tp2 (take profit 2) for both long 🟩 and short πŸŸ₯ positions.


🐌 Indexing into a dictionary to update / retrieve values that will subsequently be used in the trading logic will be undoubtly be computationally slow however it is very easy to understand.


⚑The dictionary variables can be unpacked and be used independently to increase the speed.


tracking_dict = {'long' : {'entry_price': None,'stop_loss': None,'tp1': None,'tp2': None},
                'short': {'entry_price': None,'stop_loss': None,'tp1': None,'tp2': None}}

When tp1 (first take profit) the tracking dictionary (tracking_dict) must be updated to reflect this change (set to None).


In conjunction to this when tp1 is hit the stop_loss should be moved to breakeven (stop_loss price is set to entry_price)


When the stop_loss or tp2 is hit every variable in the tracking_dict is set to None to reflect that we are no longer in that position.

Custom Simulator

def custom_simulator_candlestick_partial_profs(open_ , high_ , low_ , close_ , IsBullArray, init_cash = 10000):
    
    order_records = np.empty((2663,1), dtype = vbt.pf_enums.order_dt)
    order_counts = np.full(1,0, dtype = np.int_)
    
    
    tracking_dict = {'long' : {'entry_price': None,'stop_loss': None,'tp1': None,'tp2': None},
                     'short': {'entry_price': None,'stop_loss': None,'tp1': None,'tp2': None}}

    exec_state = vbt.pf_enums.ExecState(
                        cash = float(init_cash),
                        position = 0.0,
                        debt = 0.0,
                        locked_cash = 0.0,
                        free_cash = float(init_cash),
                        val_price = np.nan,
                        value = np.nan)
     
    for i in range(len(close_)):
        price_area = vbt.pf_enums.PriceArea(open = open_[i], 
                                            high = high_[i], 
                                            low = low_[i], 
                                            close = close_[i])
        
        value_price = price_area.close
        value = exec_state.cash + (exec_state.position * value_price)
        
        current_price = price_area.close
        
        exec_state = vbt.pf_enums.ExecState(
                                            cash = exec_state.cash,
                                            position = exec_state.position,
                                            debt = exec_state.debt,
                                            locked_cash = exec_state.locked_cash,
                                            free_cash = exec_state.free_cash,
                                            val_price = value_price,
                                            value = value)
        
        if IsBullArray[i] and exec_state.position == 0:
            long_stop_loss = price_area.low #Long Stop loss
            long_entry_price = price_area.close #Long Entry Price
            long_tp1 = price_area.close + ( 1 * (price_area.close - long_stop_loss) ) #1RRR
            long_tp2 = price_area.close + ( 2 * (price_area.close - long_stop_loss) ) #2RRR
            
            tracking_dict['long']['entry_price'] = long_entry_price
            tracking_dict['long']['stop_loss'] = long_stop_loss
            tracking_dict['long']['tp1'] = long_tp1
            tracking_dict['long']['tp2'] = long_tp2
            
            
            
            order_result , exec_state = enter_position(
                                                        execution_state_ = exec_state,
                                                        price_area_ = price_area,
                                                        percent_risk_ = 1,
                                                        group_ = 0, column_= 0, bar_ = i,
                                                        direction = 'Buy',
                                                        order_records_= order_records,
                                                        order_counts_ = order_counts)
            
        elif not IsBullArray[i] and exec_state.position == 0:
            short_stop_loss = price_area.high #Short Stop Loss
            short_entry_price = price_area.close #Short Entry Price
            short_tp1 = price_area.close - ( 1 * (price_area.high - short_stop_loss) ) #1RRR
            short_tp2 = price_area.close - ( 2 * (price_area.high - short_stop_loss) ) #2RRR
            
            tracking_dict['short']['entry_price'] = short_entry_price
            tracking_dict['short']['stop_loss'] = short_stop_loss
            tracking_dict['short']['tp1'] = short_tp1
            tracking_dict['short']['tp2'] = short_tp2
            
            order_result , exec_state = enter_position(
                                                        execution_state_ = exec_state,
                                                        price_area_ = price_area,
                                                        percent_risk_ = 1,
                                                        group_ = 0, column_= 0, bar_ = i,
                                                        direction = 'Sell',
                                                        order_records_= order_records,
                                                        order_counts_ = order_counts)
        
        
        
        elif not IsBullArray[i] and exec_state.position > 0:
            
            tracking_dict['long']['entry_price'] = None
            tracking_dict['long']['stop_loss'] = None
            tracking_dict['long']['tp1'] = None
            tracking_dict['long']['tp2'] = None
            
            order_result , exec_state = close_full_position(
                                                            execution_state_ = exec_state,
                                                            price_area_ = price_area,
                                                            group_ = 0, column_= 0, bar_ = i,
                                                            order_records_= order_records,
                                                            order_counts_ = order_counts)
            
            
        elif IsBullArray[i] and exec_state.position < 0:
            
            tracking_dict['short']['entry_price'] = None
            tracking_dict['short']['stop_loss'] = None
            tracking_dict['short']['tp1'] = None
            tracking_dict['short']['tp2'] = None
            
            order_result , exec_state = close_full_position(
                                                            execution_state_ = exec_state,
                                                            price_area_ = price_area,
                                                            group_ = 0, column_= 0, bar_ = i,
                                                            order_records_= order_records,
                                                            order_counts_ = order_counts)
  
        else:
            
            if exec_state.position > 0: #In Long 
                entry_price = tracking_dict['long']['entry_price']
                
                if (price_area.low <= long_stop_loss):
                    #Long Stop Loss Has Been Hit
                    tracking_dict['long']['entry_price'] = None
                    tracking_dict['long']['stop_loss'] = None
                    tracking_dict['long']['tp1'] = None
                    tracking_dict['long']['tp2'] = None
                    
                    
                    order_result , exec_state = close_full_position(
                                                                execution_state_ = exec_state,
                                                                price_area_ = price_area,
                                                                group_ = 0, column_= 0, bar_ = i,
                                                                order_records_= order_records,
                                                                order_counts_ = order_counts)
                    
                    
                    
                
                elif (price_area.high >= tracking_dict['long']['tp1']):
                    # First Long Take Profit Has Been Hit
                    # Move The Stop Loss To Break Even (Entry Price) & Close 50% of The Position
                    tracking_dict['long']['stop_loss'] = entry_price #SL Moved To Entry Price (BreakEven)
                    tracking_dict['tp1'] = None #take profit 1 has been hit
                    
                    order_result, exec_state = close_partial_pos(
                                                        execution_state_ = exec_state,
                                                        price_area_ = price_area,
                                                        closing_percent = 50, 
                                                        group_ = 0 , column_ = 0, bar_ = None,
                                                        update_value_ = False, order_records_ = None, 
                                                        order_counts_ = None, log_records_ = None,
                                                        log_counts_ = None)
                    
                
                
                elif (price_area.high >= tracking_dict['long']['tp2']):
                    # Second Take Profit Has Been Hit
                    tracking_dict['long']['stop_loss'] = None
                    tracking_dict['long']['tp2'] = None
                    
                    order_result , exec_state = close_full_position(
                                                                execution_state_ = exec_state,
                                                                price_area_ = price_area,
                                                                group_ = 0, column_= 0, bar_ = i,
                                                                order_records_= order_records,
                                                                order_counts_ = order_counts)
                
                else:
                    continue
            
            elif exec_state.position < 0:
                
                entry_price = tracking_dict['short']['entry_price']
                
                if (price_area.high >= tracking_dict['short']['stop_loss']):
                    #Short Stop Loss Has Been Hit
                    tracking_dict['short']['entry_price'] = None
                    tracking_dict['short']['stop_loss'] = None
                    tracking_dict['short']['tp1'] = None
                    tracking_dict['short']['tp2'] = None
                    
                    
                    order_result , exec_state = close_full_position(
                                                                execution_state_ = exec_state,
                                                                price_area_ = price_area,
                                                                group_ = 0, column_= 0, bar_ = i,
                                                                order_records_= order_records,
                                                                order_counts_ = order_counts)
                
                elif (price_area.low <= tracking_dict['short']['tp1']):
                    # Frist Take Profit Has Been Hit
                    
                    tracking_dict['stop_loss'] = entry_price #SL Moved To Entry Price (BreakEven)
                    tracking_dict['tp1'] = None
                    
                    order_result, exec_state = close_partial_pos(
                                                            execution_state_ = exec_state,
                                                            price_area_ = price_area,
                                                            closing_percent = 50, 
                                                            group_ = 0 , column_ = 0, bar_ = None,
                                                            update_value_ = False, order_records_ = None, 
                                                            order_counts_ = None, log_records_ = None,
                                                            log_counts_ = None)
                
                elif (price_area.low <= tracking_dict['short']['tp2']):
                    # Second Take Profit Has Been Hit
                    tracking_dict['short']['entry_price'] = None
                    tracking_dict['short']['stop_loss'] = None
                    tracking_dict['short']['tp1'] = None
                    tracking_dict['short']['tp2'] = None
                    
                    order_result , exec_state = close_full_position(
                                                                execution_state_ = exec_state,
                                                                price_area_ = price_area,
                                                                group_ = 0, column_= 0, bar_ = i,
                                                                order_records_= order_records,
                                                                order_counts_ = order_counts)
                
                else:
                    continue
        
        
        
                
    return vbt.nb.repartition_nb(order_records, order_counts)         
                

Key Points / Summary πŸ’‘


As you can see there is not a whole lot of differences when looking at partial profits and moving stop loss to breakeven.


These are just simple examples to demonstrate how one can do these things that are quite common amongst most strategies.


You can use this code as a point of reference and get creative 🎨 when you are coding your own strategies ⚑