Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open trade at fixed price #251

Closed
AlgoQ opened this issue Feb 15, 2021 · 12 comments
Closed

Open trade at fixed price #251

AlgoQ opened this issue Feb 15, 2021 · 12 comments
Labels
question Not a bug, but a FAQ entry

Comments

@AlgoQ
Copy link

AlgoQ commented Feb 15, 2021

Expected Behavior

I would expect that the trade would open the 'longLine' and close on the 'sma' line (in case of a long trade), but the trade opens/closes always on the close price. Is there a way to make sure that the the trade opens on the long/short line and closes on the sma line. These lines are based on the previous candle close value, so these are fixed values.

Code

def next(self):
    if not self.position:
        longLine = self.sma[-2] - (self.sma[-2] * (self.longLineParam / 100))
        shortLine = self.sma[-2] + (self.sma[-2] * (self.shortLineParam / 100))
        if self.data.Close[-1] <= longLine:
            self.buy()
        elif self.data.Close[-1] >= shortLine:
            self.sell()
    
    elif self.position:
        if cross(self.data.Close, self.sma[-2]):
            self.position.close()

Additional info

Example:
image

@kernc
Copy link
Owner

kernc commented Feb 15, 2021

The trade opens on next open price, unless trade_on_close=True, in which case it opens on the close already.

If you want the trade to open at a specific price point, issue a limit order:

self.buy(limit=longLine[-1])

If you want the trade to close at a specific price point, set its take-profit price:

self.buy(..., tp=shortLine[-1])

# or, in later next() calls:
trade = self.trades[-1]
trade.sl = shortLine[-1]

@AlgoQ
Copy link
Author

AlgoQ commented Feb 15, 2021

When I did these changes (adding the trade_on_close=True param, limit param to the buy/sell function and tp later in the 'next()' function). By the way I'm also a sl, the tp that I'm using should update every candle that's why I called the tp later in the next function. But after these changes the trades does look pretty simular.

Code:

    def next(self):
        if not self.position:
            longLine = self.sma[-2] - (self.sma[-2] * (self.longLineParam / 100))
            shortLine = self.sma[-2] + (self.sma[-2] * (self.shortLineParam / 100))
            
            if self.data.Close[-1] <= longLine:
                self.buy(limit=longLine, sl=self.data.Close[-1] - (self.atr * self.atrSl))
            elif self.data.Close[-1] >= shortLine:
                self.sell(limit=shortLine, sl=self.data.Close[-1] + (self.atr * self.atrSl))
        
        elif self.position:
            trade = self.trades[-1]
            trade.tp = self.sma[-2]

bt = Backtest(ohlcv, MAShiftSl, cash=10000, commission=0, trade_on_close=True)

Updated example:
image

@kernc
Copy link
Owner

kernc commented Feb 16, 2021

trade.tp = self.sma[-2]

but

shortLine = self.sma[-2] + (self.sma[-2] * (self.shortLineParam / 100))

?

I don't know what I'm looking at. Unless you can pack it all into a small, reproducible example (i.e. something worth of a bug report), it's all anyone's guess. 😬

@AlgoQ
Copy link
Author

AlgoQ commented Feb 16, 2021

The short (red) / long (green) line are a certain percentage away from the previous value of the sma (blue) line. When I open a trade at the long/short line I want to close it at the previous sma value. Than for the stop-loss I'm just using an atr and a multiplier (atrSl).

image

@kernc
Copy link
Owner

kernc commented Feb 16, 2021

Tried trade.tp = self.sma[-1]? That's the previous sma value on next next().

@kernc
Copy link
Owner

kernc commented Mar 17, 2021

@JanssensKobe Have you maybe got it working, or what's the real issue here?

@windowshopr
Copy link

Just saw this thread. I too would love to see this happen. In my case, I would like to define a custom purchase, and close price to make life so much easier.

As an example, let's say I want to buy when the price of the current candle crosses over the 9 period moving average. Even with trade_on_close=True, I don't want to wait until the bar's closed before my order gets filled, I want to designate the moving average price as my entry. You COULD set a stop order during the previous candle if peaking ahead, but this isn't the best practice I don't think. Then, maybe I want to close the position at the end of the current candle (so opened, and closed in the same candle). I can't be sure, but I think the close order only gets processed on the NEXT candle. Again, one could set a tp at the close price of the current candle, but this didn't work for me either.

I've been looking through the source code trying to jerry-rig my own solution, and here's what I've got so far, at least for the .buy() side:

In the main script (notice my new "specific_price" variable):

if self.data.High[-1] > self.ma[-1] and self.positions == 0:
    self.buy(specific_price = self.ma[-1] + 0.01)

Then, in the backtesting.py script, under the Strategy class (see the commented additions):

def buy(self, *,
        size: float = _FULL_EQUITY,
###########################################
        specific_price: float = None,
###########################################
        limit: float = None,
        stop: float = None,
        sl: float = None,
        tp: float = None):
    """
    Place a new long order. For explanation of parameters, see `Order` and its properties.
    See also `Strategy.sell()`.
    """
    assert 0 < size < 1 or round(size) == size, \
        "size must be a positive fraction of equity, or a positive whole number of units"
###########################################
    return self._broker.new_order(size, specific_price, limit, stop, sl, tp)
###########################################

Then, under the Broker class, here's the new_order function (I've commented surrounded my changes again):

def new_order(self,
              size: float,
              specific_price: float = None,
              limit: float = None,
              stop: float = None,
              sl: float = None,
              tp: float = None,
              *,
              trade: Trade = None):
    """
    Argument size indicates whether the order is long or short
    """
###########################################
    self.specific_price = specific_price
###########################################
    size = float(size)
    stop = stop and float(stop)
    limit = limit and float(limit)
    sl = sl and float(sl)
    tp = tp and float(tp)

    is_long = size > 0
###########################################
    adjusted_price = self._adjusted_price(size, self.specific_price)
###########################################

    if is_long:
        if not (sl or -np.inf) < (limit or stop or adjusted_price) < (tp or np.inf):
            raise ValueError(
                "Long orders require: "
                f"SL ({sl}) < LIMIT ({limit or stop or adjusted_price}) < TP ({tp})")
    else:
        if not (tp or -np.inf) < (limit or stop or adjusted_price) < (sl or np.inf):
            raise ValueError(
                "Short orders require: "
                f"TP ({tp}) < LIMIT ({limit or stop or adjusted_price}) < SL ({sl})")

    order = Order(self, size, limit, stop, sl, tp, trade)
    # Put the new order in the order queue,
    # inserting SL/TP/trade-closing orders in-front
    if trade:
        self.orders.insert(0, order)
    else:
        # If exclusive orders (each new order auto-closes previous orders/position),
        # cancel all non-contingent orders and close all open trades beforehand
        if self._exclusive_orders:
            for o in self.orders:
                if not o.is_contingent:
                    o.cancel()
            for t in self.trades:
                t.close()

        self.orders.append(order)

    return order

Finally, under the _process_orders function, I made this little addition in market/touched section:

else:
    # Market-if-touched / market order
    ##########################################
    if self.specific_price != None:
        try:
            price = self.specific_price[-1]
        except:
            price = self.specific_price
    ##########################################

Making these changes, and using my simple buying only example (no closing logic), you will see in the plots that your order gets processed at the price you specified. The trick now is, implementing the same logic into the .close(), where one could specify the exact price at which they'd like to close their order, like say at the close of the current candle like I mentioned before, without waiting until the next candle to have it processed. You'll find that using this example, setting the tp as the close of the current candle can sometimes result in error because the close price can sometimes of course be lower than your entry price, however attempting a try: by setting the tp, and an except: with setting the sl, you'll see some weird closing behaviour, likely due to my "editing".

Long story short, it would be cool to see the ability to define a specific entry price when using buy() / sell() and close() and have the orders processed as soon as they're sent if they're defined. Some traders don't like to trade close to close/open to open.

Anyway just my 2 cents. Thanks!

@kernc
Copy link
Owner

kernc commented May 10, 2021

You COULD set a stop order during the previous candle if peaking ahead, but this isn't the best practice I don't think.

Exactly. Precomputing where the next MA is going to be is one way to do it. The only other way I'm aware of is to use more fine-grained data (more candles).

@AlgoQ
Copy link
Author

AlgoQ commented May 11, 2021

Isn't it possible to just check if the limit price is been hit within the current candle?
For example the limit price is 5.55, than you check between the high and low of the candle if the limit price is been hit? Or is this way not very accurate?

@windowshopr
Copy link

I can let @kernc follow up if needed, but yes, that's what using limit prices does, but the issue is, this library trades close-to-close (or open-to-open if specified). So what that means is, the orders only get processed at the close of the current (or open of the next) candle. So if you set a limit price right now above the current candle's close price, and you're trading on close, the order will fulfill right away which is fine. The issue here is, we don't always know where to set the limit price prior to the current candle.

For example, if we're waiting for the current candle to cross over a moving average, we want to issue a trade AT the moving average price, not wait until close. So in order to accomplish what we want, we would have had to set a buy-stop order at the NEXT candle's moving average in order to have an order trigger at that price, but you can't really do that because A) moving averages are generally always in motion as the price moves in the current candle, and B) you're now forward peaking into your dataset, which isn't what you want to do. What we're chatting about here is just an elementary way to kind of make it work. I took a stab at trying to implement a "purchase at a specific price" logic into the library so that the order would process right away at the designated price (in the current candle), and not wait until the close to process.

Hope that (maybe) clears it up?

@AlgoQ
Copy link
Author

AlgoQ commented May 11, 2021

Ooh yeah I understand, I didn't know the library only uses close-to-close/open-to-open candles. Thanks for your explanation @windowshopr!

@windowshopr
Copy link

No problem @JanssensKobe , another option like kernc mentioned might be to use a smaller timeframe (with bigger period moving averages in our example) to be able to get a closer entry price, that would work too.

@kernc kernc closed this as completed Feb 5, 2022
@kernc kernc added the question Not a bug, but a FAQ entry label Feb 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Not a bug, but a FAQ entry
Projects
None yet
Development

No branches or pull requests

3 participants