-
Notifications
You must be signed in to change notification settings - Fork 62
/
Copy pathstop-loss.Rmd
243 lines (197 loc) · 8.96 KB
/
stop-loss.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# Stop-Loss Orders {#stop-loss}
We'll continue using a variation of the Luxor strategy. This time we're going to implement stop-loss orders.
We're also going to keep all of our settings in variables so as to make the code easier to work with from here forward.
```{r stop-loss-strategy-vars}
.fast <- 10
.slow <- 30
.threshold <- 0.0005
.orderqty <- 100
.txnfees <- -10
.stoploss <- 3e-3 # 0.003 or 0.3%
```
```{r stop-loss-create-objects}
portfolio.st <- "Port.Luxor.Stop.Loss"
account.st <- "Acct.Luxor.Stop.Loss"
strategy.st <- "Strat.Luxor.Stop.Loss"
rm.strat(portfolio.st)
rm.strat(account.st)
initPortf(name = portfolio.st,
symbols = symbols,
initDate = init_date)
initAcct(name = account.st,
portfolios = portfolio.st,
initDate = init_date,
initEq = init_equity)
initOrders(portfolio = portfolio.st,
symbols = symbols,
initDate = init_date)
strategy(strategy.st, store = TRUE)
```
## Add Indicators
```{r stop-loss-add-indicators}
add.indicator(strategy.st,
name = "SMA",
arguments = list(x = quote(Cl(mktdata)),
n = .fast),
label = "nFast")
add.indicator(strategy.st,
name = "SMA",
arguments = list(x = quote(Cl(mktdata)),
n = .slow),
label = "nSlow")
```
## Add Signals
```{r stop-loss-add-signals}
add.signal(strategy.st,
name = "sigCrossover",
arguments = list(columns = c("nFast", "nSlow"),
relationship = "gte"),
label = "long"
)
add.signal(strategy.st,
name = "sigCrossover",
arguments = list(columns = c("nFast", "nSlow"),
relationship = "lt"),
label = "short")
```
## Add Rules
Our rules are largely the same as they were in our original Luxor strategy. However, we have added some slight modifications.
Let's start off with `osFUN` which is abbreviated for order size function. It is defined as:
> function or text descriptor of function to use for order sizing.
The default value for this parameter is `osNoOp` which is an ordering function that performs no operation. In other words, if you pass 100 as `orderqty` that is what is purchased.
In the `EnterLong` rule below we pass a different function, `osMaxPos()`. `osMaxPos()` works with `addPosLimit()` (next section) to set a maximum position per symbol. This will keep us from executing the same orders repeatedly.
We've also included the `orderset` parameter with a value of "ocolong". This will help group our long and short orders together.
```{r stop-lossa-add-rules}
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "long" ,
sigval = TRUE,
replace = FALSE,
orderside = "long" ,
ordertype = "stoplimit",
prefer = "High",
threshold = .threshold,
TxnFees = .txnfees,
orderqty = +.orderqty,
osFUN = osMaxPos,
orderset = "ocolong"),
type = "enter",
label = "EnterLONG")
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "short",
sigval = TRUE,
replace = FALSE,
orderside = "short",
ordertype = "stoplimit",
prefer = "Low",
threshold = .threshold,
TxnFees = .txnfees,
orderqty = -.orderqty,
osFUN = osMaxPos,
orderset = "ocoshort"),
type = "enter",
label = "EnterSHORT")
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "short",
sigval = TRUE,
replace = TRUE,
orderside = "long" ,
ordertype = "market",
TxnFees = .txnfees,
orderqty = "all",
orderset = "ocolong"),
type = "exit",
label = "Exit2SHORT")
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "long",
sigval = TRUE,
replace = TRUE,
orderside = "short",
ordertype = "market",
TxnFees = .txnfees,
orderqty = "all",
orderset = "ocoshort"),
type = "exit",
label = "Exit2LONG")
```
Up to this point our `Luxor.Stop.Loss` strategy has been the same as our original `Luxor` strategy. When we take a long position we stay in it until we get a short signal, rinse and repeat.
However, now we're going to put stops in place. From the onset there isn't much different from the previous rules we have added. Many of the parameters are similar. We do have some new ones though.
First, we've created rule **StopLossLONG** as a child rule of the `parent` rule **EnterLONG**, part of the `orderset` **ocolong**. Currently it is not `enabled`.
The critical portion of **StopLossLONG** is the `tmult` and `threshold` parameter. When a long order is filled `threshold` and `tmult` work together to determine the stoplimit price (`ordertype`). `.stoploss` is multiplied (`tmult`) against the price of the filled long order. That price serves as the stop-loss price.
For example,
$$ \text{StopLossLONG} = \text{fill price } - \left( \text{.stoploss } * \text{fill price}\right) $$
$$ \text{StopLossLONG} = 134.39 - \left(0.003 * 134.39\right) $$
$$ \text{StopLossLONG} = $133.9868 $$
If market price moves below $ \$133.9868 $ the **StopLossLONG** order becomes a market order and the **Exit2SHORT** order is cancelled (OCO).
The same applies to **StopLossSHORT** which is a child of **EnterSHORT** except `.stoploss` is added to the fill price.
```{r stop-lossb-add-rules}
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "long" ,
sigval = TRUE,
replace = FALSE,
orderside = "long",
ordertype = "stoplimit",
tmult = TRUE,
threshold = quote(.stoploss),
TxnFees = .txnfees,
orderqty = "all",
orderset = "ocolong"),
type = "chain",
parent = "EnterLONG",
label = "StopLossLONG",
enabled = FALSE)
add.rule(strategy.st,
name = "ruleSignal",
arguments = list(sigcol = "short",
sigval = TRUE,
replace = FALSE,
orderside = "short",
ordertype = "stoplimit",
tmult = TRUE,
threshold = quote(.stoploss),
TxnFees = .txnfees,
orderqty = "all",
orderset = "ocoshort"),
type = "chain",
parent = "EnterSHORT",
label = "StopLossSHORT",
enabled = FALSE)
```
## Add Position Limit
As mentioned previously when using `osMaxPos()` we must supply a position limit to each symbol our strategy is working. We do this with `addPosLimit`. For now the only parameter we apply is `maxpos` which we set to `.orderqty`.
```{r stop-loss-add-pos-limit}
for(symbol in symbols){
addPosLimit(portfolio = portfolio.st,
symbol = symbol,
timestamp = init_date,
maxpos = .orderqty)
}
```
## Enable Rules
When we wrote **StopLossLONG** and **StopLossSHORT** we disabled them by assigning `enabled = FALSE`. Now we enable both rules set. This is very beneficial when you want to test a strategy versus different rulesets (rather than rewriting code).
`label` can apply to a specific rule or by matching the value to all rules with a similar value (grep). By supply "StopLoss" to `label` we are instructing `quantstrat` to enable all of our rules with the string "StopLoss" in the `label`, `StopLossLONG` and `StopLossSHORT`.
```{r stop-loss-enable-rules}
enable.rule(strategy.st,
type = "chain",
label = "StopLoss")
```
## Apply Strategy
```{r stop-loss-apply-strategy, results = "hide"}
cwd <- getwd()
setwd("./_data/")
results_file <- paste("results", strategy.st, "RData", sep = ".")
if( file.exists(results_file) ) {
load(results_file)
} else {
results <- applyStrategy(strategy.st, portfolios = portfolio.st)
if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) {
save(list = "results", file = results_file)
save.strategy(strategy.st)
}
}
setwd(cwd)
```