Why doesn't it close the position? quantstrat package

163 views Asked by At

I have a simple signal strategy

0 = close all positions

1 = open buy (close sell if available)

-1= open a sell (close a buy if there is one)

Only one position can be opened during trading

Here is the code for generating fake data and calculating signals

require(quantstrat)

set.seed(1)
fake_data <- rnorm(100) |> cumsum() |> xts(order.by=as.POSIXct(x=60*1:100, origin='2021-07-01'))
colnames(fake_data) <-'Close'

make_seq <- function() sample(c(-1,0,1),size=1) |> rep(sample(3:20,1))
fake_data$signal <- lapply(1:50,\(x) make_seq())  |> unlist() |> tail(n = length(fake_data))

# signal 
chart_Series(fake_data)
abline(h=0)



fake_data$buy_open <- 0
fake_data$buy_close <- 0
fake_data$sell_open <- 0
fake_data$sell_close <- 0

S <- fake_data$signal |> coredata() |> as.vector()
for(i in 2:nrow(fake_data)){
  if(S[i-1]!=  1 & S[i]==  1) fake_data$buy_open[i]   <- 1
  if(S[i-1]==  1 & S[i]!=  1) fake_data$buy_close[i]  <- 1
  if(S[i-1]!= -1 & S[i]== -1) fake_data$sell_open[i]  <- 1
  if(S[i-1]== -1 & S[i]!= -1) fake_data$sell_close[i] <- 1
}

Next I create rules for trading

rm.strat(strat.st)

strat.st <- "FAKESTRAT"
currency("USD")
stock("fake_data",currency = "USD")
initPortf(strat.st, symbols="fake_data")
initEq<-1000
initAcct(strat.st, portfolios=strat.st, initEq=initEq)
initOrders(portfolio=strat.st)
strategy(name=strat.st,store=TRUE)

addPosLimit("FAKESTRAT", "fake_data", maxpos = 1, timestamp = start(fake_data)-1)


# enterLong  exitLong

add.rule(strat.st,"ruleSignal", 
         arguments=list(sigcol="buy_open",
                        sigval=TRUE,
                        orderqty=1,
                        ordertype='market', 
                        orderside='long',
                        osFUN = osMaxPos),
         type="enter",
         label="enterLong"
)

add.rule(strat.st,"ruleSignal", 
         arguments=list(sigcol="buy_close",
                        sigval=TRUE,
                        orderqty="all",
                        ordertype='market', 
                        orderside='long'),
         type="exit",
         label="exitLong"
)

# enterShort  exitShort

add.rule(strat.st,"ruleSignal", 
         arguments=list(sigcol="sell_open",
                        sigval=TRUE,
                        orderqty=-1,
                        ordertype='market', 
                        orderside='short',
                        osFUN = osMaxPos),
         type="enter",
         label="enterShort"
)

add.rule(strat.st,"ruleSignal", 
         arguments=list(sigcol="sell_close",
                        sigval=TRUE,
                        orderqty="all",
                        ordertype='market', 
                        orderside='short'),
         type="exit",
         label="exitShort"
)




out<-applyStrategy(strat.st , portfolios=strat.st, verbose=T)
updatePortf(strat.st)
book <- getOrderBook(portfolio=strat.st)

....

print(book)


book
$FAKESTRAT
$FAKESTRAT$fake_data
                    Order.Qty      Order.Price Order.Type Order.Side Order.Threshold
2021-07-01 03:24:00        -1 3.59680448297358     market      short            <NA>
2021-07-01 03:40:00       all 3.68104715087989     market      short            <NA>
2021-07-01 03:40:00         1 3.68104715087989     market       long            <NA>
2021-07-01 03:54:00        -1 4.02025008047271     market      short            <NA>
2021-07-01 04:12:00       all 10.7346453103169     market      short            <NA>
2021-07-01 04:35:00        -1 13.8781211399328     market      short            <NA>
                    Order.Status    Order.StatusTime Prefer Order.Set Txn.Fees       Rule
2021-07-01 03:24:00       closed 2021-07-01 03:25:00             <NA>        0 enterShort
2021-07-01 03:40:00     replaced 2021-07-01 03:40:00             <NA>        0  exitShort
2021-07-01 03:40:00       closed 2021-07-01 03:41:00             <NA>        0  enterLong
2021-07-01 03:54:00       closed 2021-07-01 03:55:00             <NA>        0 enterShort
2021-07-01 04:12:00       closed 2021-07-01 04:13:00             <NA>        0  exitShort
2021-07-01 04:35:00       closed 2021-07-01 04:36:00             <NA>        0 enterShort
                    Time.In.Force
2021-07-01 03:24:00              
2021-07-01 03:40:00              
2021-07-01 03:40:00              
2021-07-01 03:54:00              
2021-07-01 04:12:00              
2021-07-01 04:35:00     

And I don't understand why the open position is long

2021-07-01 03:40:00         1 3.68104715087989     market       long

was not closed when the sell signal arrived

 2021-07-01 03:54:00        -1 4.02025008047271     market      short
2

There are 2 answers

0
mr.T On BEST ANSWER

I don't understand how this works but the answer lies in the replace variable.

All you need to do is include it in the rule for opening and closing a position

replace=TRUE for close position

replace=FALSE for open position

# enterLong  exitLong

add.rule(strat.st,"ruleSignal", 
         arguments=list(sigcol="buy_open",
                        sigval=TRUE,
                        replace=FALSE,
                        orderqty=1,
                        ordertype='market', 
                        orderside='long',
                        osFUN = osMaxPos),
         type="enter",
         label="enterLong"
)

add.rule(strat.st,"ruleSignal", 
         arguments=list(sigcol="buy_close",
                        sigval=TRUE,
                        replace=TRUE,
                        orderqty="all",
                        ordertype='market', 
                        orderside='long'),
         type="exit",
         label="exitLong"
)

The same needs to be done for sales rules

book$FAKESTRAT$fake_data[,c(1,4,11)]
                    Order.Qty Order.Side       Rule
2021-07-01 03:24:00        -1      short enterShort
2021-07-01 03:40:00       all      short  exitShort
2021-07-01 03:40:00         1       long  enterLong
2021-07-01 03:54:00       all       long   exitLong
2021-07-01 03:54:00        -1      short enterShort
2021-07-01 04:12:00       all      short  exitShort
2021-07-01 04:35:00        -1      short enterShort
4
VonC On

Each of your functions for entering (enterLong, enterShort) and exiting (exitLong, exitShort) positions is only focused on its designated operation: entering or exiting a long or a short position based on the corresponding signal column (buy_open, buy_close, sell_open, sell_close). There is no mechanism in these rules to check if an opposite position exists when a new position is about to be opened.

For instance, when the enterShort rule is triggered, it does not first check if a long position is already open that needs to be closed. It simply proceeds to enter a short position. The same goes for the enterLong rule; it does not check for an existing short position before opening a long position.

So there does not seem to be any logic in your current setup to close a long position if a signal for entering a short position comes in, or vice versa.

If we consider this part of your order book as an example:

2021-07-01 03:40:00         1 3.68104715087989     market       long
2021-07-01 03:54:00        -1 4.02025008047271     market      short

You opened a long position at 2021-07-01 03:40:00, and a sell signal arrived at 2021-07-01 03:54:00. However, the existing long position was not closed when the sell signal arrived.

In your current setup, you used the osMaxPos function for order sizing to make sure you only have one position open at a time, but it does not include logic to close an existing opposite position. osMaxPos only looks at the current position type (either long or short) and limits it, but does not close the opposite type.

You may need to implement additional logic to close a long position when a signal for entering a short position is received, and vice versa. That could be a custom order sizing function (osFUN) that checks the current position before executing a trade. That function would need to close the existing position if it is opposite to the new signal.

For example, you could define an osCloseOpposite function:

osCloseOpposite <- function(timestamp, orderqty, portfolio, symbol, ...) {
  # Close opposite position if it exists
  currentPos <- getPosQty(portfolio, symbol, timestamp)
  if (orderqty > 0 && currentPos < 0) {
    # Close existing short position
    return(-currentPos)
  } else if (orderqty < 0 && currentPos > 0) {
    # Close existing long position
    return(-currentPos)
  }
  
  # If no opposite position, apply osMaxPos logic
  return(osMaxPos(timestamp, orderqty, portfolio, symbol, ...))
}

Then use this function as the osFUN in your existing rules:

add.rule(strat.st,"ruleSignal", 
         arguments=list(sigcol="sell_open",
                        sigval=TRUE,
                        orderqty=-1,
                        ordertype='market', 
                        orderside='short',
                        osFUN = osCloseOpposite),
         type="enter",
         label="enterShort"
)

Do the same for the enterLong rule. That way, your custom function will check for the existing position and close it if it is opposite to the new signal.