-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathPosition.pine
283 lines (246 loc) · 9.92 KB
/
Position.pine
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Electrified
//@version=5
// @description Allows for simulating trades within an indicator.
library("Position", overlay = true)
//////////////////////////////////////////////////
// @type Represents a single trade.
// @field size Size of the trade in units.
// @field price Price of the trade in currency.
// @field value Total value of the trade in currency units.
// @field time Timestamp of the trade.
export type Trade
float size
float price
float value
int time
//////////////////////////////////////////////////
// @type Represents a single position.
// @field size The size of the position.
// @field price Average price of the position in currency.
// @field value Total value of the position in currency units.
// @field start Timestamp of the first trade that opened the position.
// @field net Realized gains and losses of the position in currency units.
// @field history Array of trades that make up the position.
export type Position
float size
float price
float value
int start
float net
Trade[] history
//////////////////////////////////////////////////
// @type The state of a position in one direction
// @field size The size of the position
// @field change The change in size
// @field holding Indicates whether a position is currently being held
// @field increased Indicates whether the position has increased
// @field decreased Indicates whether the position has decreased
export type DirectionState
float size
float change
bool holding
bool increased
bool decreased
//////////////////////////////////////////////////
// @type The bi-directional state of a position
// @field holding Indicates whether a position is currently being held
// @field long The state of the long position
// @field short The state of the short position
export type State
float size
float change
bool holding
DirectionState long
DirectionState short
float price
newDirState(series float size) =>
change = ta.change(math.abs(size))
DirectionState.new(size, change, size != 0, change > 0, change < 0)
validate(float size, float price, int time) =>
if na(size)
runtime.error("size cannot be NA")
if na(price)
runtime.error("price cannot be NA")
if na(time)
runtime.error("time cannot be NA")
//////////////////////////////////////////////////
// @function Creates a new trade object.
// @param size The size of the trade (number of shares or contracts).
// @param price The price at which the trade took place.
// @param timestamp The timestamp of the trade. Defaults to the current time.
// @returns A new trade object.
export newTrade(float size, float price = close, int timestamp = time) =>
validate(size, price, time)
Trade.new(size, price, size * price, timestamp)
//////////////////////////////////////////////////
// @function Starts a new position.
// @param size The size of the position (number of shares or contracts).
// @param price The price at which the position was started.
// @param timestamp The timestamp of the start of the position. Defaults to the current time.
// @returns A new position object.
export start(float size, float price = close, int timestamp = time) =>
validate(size, price, time)
history = array.new<Trade>(1, newTrade(size, price, timestamp))
Position.new(size, price, size * price, timestamp, 0, history)
//////////////////////////////////////////////////
// @function Starts a new (blank) position with no trades.
// @returns A new position object.
export start() =>
Position.new(0, na, 0, na, 0, array.new<Trade>())
//////////////////////////////////////////////////
// @function Returns the current state of the position.
// @param posSize the current size of the position.
export state(float posSize) =>
size = nz(posSize)
change = ta.change(size)
sizeLong = math.min(size, 0)
sizeShort = math.max(size, 0)
long = newDirState(sizeLong)
short = newDirState(sizeShort)
State.new(size, change, size != 0, long, short)
//////////////////////////////////////////////////
// @function Returns the current state of the position.
// @param pos The position to read the size from.
export method state(Position pos) =>
size = na(pos) ? na : pos.size
state(size)
//////////////////////////////////////////////////
// @function Modifies an existing position.
// @param pos The position to be modified.
// @param size The size of the trade (number of shares or contracts).
// @param price The price at which the trade took place.
// @param timestamp The timestamp of the trade. Defaults to the current time.
// @returns The result of the trade. (The state of the position.)
export method trade(Position pos, float size, float price = close, int timestamp = time) =>
if na(pos)
runtime.error("cannot modify an NA position")
validate(size, price, time)
trade = newTrade(size, price, timestamp)
array.push(pos.history, trade)
newSize = pos.size + size
// Determine if we are increasing or decresing our position as increasing affects the position price and decreasing affects the net.
[shrink, flip, longChange, shortChange] = if pos.size > 0 and size < 0
s = math.min(pos.size, -size)
f = math.min(newSize, 0)
[s, f, s, f]
else if pos.size < 0 and size > 0
s = math.max(pos.size, -size)
f = math.max(newSize, 0)
[s, f, f, s]
else // Expand only?
long = size > 0 ? size : 0
short = size < 0 ? size : 0
[0, 0, long, short]
if shrink != 0
pos.net += shrink * (price - pos.price)
// Full exit?
if newSize == 0
pos.size := 0
pos.price := na
pos.value := 0
// Typical increase or decrease?
else if flip == 0
pos.size += size
pos.value += trade.value
if shrink == 0
pos.price := pos.value / pos.size
// Flipped from long to short or vice versa?
else
pos.size := flip
pos.price := price
pos.value := flip * price
State.new(newSize, size, newSize != 0,
DirectionState.new(newSize > 0 ? newSize : 0, longChange, newSize > 0, longChange > 0, longChange < 0),
DirectionState.new(newSize < 0 ? newSize : 0, shortChange, newSize < 0, shortChange < 0, shortChange > 0),
trade.price)
//////////////////////////////////////////////////
// @function Closes a position by trading the entire position size at a given price and timestamp.
// @param pos The position being closed.
// @param price The price at which the position is being closed.
// @param timestamp The timestamp of the trade, defaults to the current time.
// @returns The updated position after the trade.
export method exit(Position pos, float price = close, int timestamp = time) =>
if na(pos)
runtime.error("cannot modify an NA position")
if na(price)
runtime.error("price cannot be NA")
if na(time)
runtime.error("time cannot be NA")
trade(pos, -pos.size, price, timestamp)
//////////////////////////////////////////////////
// @function Calculates the unrealized gain or loss for a given position and price.
// @param pos The position for which to calculate unrealized gain/loss.
// @param price The current market price.
// @returns The calculated unrealized gain or loss.
export method unrealized(Position pos, float price = close) =>
if na(pos)
0.0
else if na(price)
runtime.error("price cannot be NA")
0.0
else
pos.size == 0 ? 0.0 : pos.size * (price - pos.price)
//////////////////////////////////////////////////
// @function Returns the number of shares held by a position.
// @param pos The position for which to read the size. A value of NA will return zero.
export method size(Position pos) =>
na(pos) ? 0.0 : pos.size
//////////////////////////////////////////////////
// @function Returns true if the position has shares.
// @param pos The position for which to read the size. A value of NA will return false.
export method isActive(Position pos) =>
na(pos) ? false : nz(pos.size) != 0
//////////////////////////////////////////////////
// @function Creates a lablel if a trade was made.
// @param pos The position.
// @param ts The state of the position or result of the trade to generate a label from.
// @returns The label that was generated or na if none.
export addLabel(Position pos, State ts, string details = na) =>
label lbl = na
if na(pos) or na(ts)
lbl
else
if ts.change > 0
s = array.new_string()
s.push(str.format("+{0,number}", ts.change))
if ts.short.change != 0
s.push(ts.short.size==0 ? "S:0" : str.format("S:{0, number}", -ts.short.size))
if ts.long.change != 0
s.push(str.format("\:{0, number}", ts.long.size))
lbl := label.new(time, low, s.join("\n"), xloc.bar_time, yloc.price, #006600, label.style_label_up, color.white)
else if ts.change < 0
s = array.new_string()
s.push(str.format("{0,number}", ts.change))
if ts.long.change != 0
s.push(str.format("L:{0, number}", ts.long.size))
if ts.short.change != 0
s.push(ts.short.size==0 ? "S:0" : str.format("S:{0, number}", -ts.short.size))
lbl := label.new(time, high, s.join("\n"), xloc.bar_time, yloc.price, #660000, label.style_label_down, color.white)
tooltip = array.new_string()
tooltip.push(str.format(ts.change > 0 ? "+{0,number} @ {1,number,currency}" : "{0,number} @ {1,number,currency}", ts.change, ts.price))
if ts.price != pos.price
tooltip.push(str.format("Average: {0,number,currency}", pos.price))
if pos.net != 0
tooltip.push(str.format("Realized: {0,number,currency}", pos.net))
if not na(details)
tooltip.push(str.format("\n{0}", details))
label.set_tooltip(lbl, tooltip.join("\n"))
lbl
//////////////////////////////////////////////////
//// Demo ////////////////////////////////////////
//////////////////////////////////////////////////
var pos = start()
posState = state(pos)
tradeSize = posState.holding ? 2 : 1
fast = ta.wma(hlc3, 100)
slow = ta.wma(hlc3, 150)
longSignal = ta.crossover(fast, slow)
shortSignal = ta.crossunder(fast, slow)
plot(pos.size(), "Size", display = display.status_line)
State ts = na
if longSignal
ts := pos.trade(+tradeSize)
else if shortSignal
ts := pos.trade(-tradeSize)
addLabel(pos, ts)