-
Notifications
You must be signed in to change notification settings - Fork 1
/
ordersWithFillsSet.ipl
209 lines (169 loc) · 5 KB
/
ordersWithFillsSet.ipl
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
//
// Imandra Inc.
// Copyright (c) 2024
//
// Minimal FIX 4.4 model with fills
//
//
// For further info see https://docs.imandra.ai
//
//
import FIX_4_4
// This makes no sense in a real circumstance
// but demonstrates how multiple values can be overridden and defined
@encoding: MultipleValueChar
extend enum OrdType {
}
// Import inbound NewOrderSingle message
message NewOrderSingle {
req ClOrdID valid when it in ["a","n"]
req Side
req TransactTime
req OrdType
valid when it in [{| OrdType.Limit, OrdType.Market |},{|OrdType.Limit|},{|OrdType.Market|}]
valid when subset(it,{| OrdType.Limit, OrdType.Market |})
req OrderQtyData.OrderQty valid when it > 0.0
opt Price
validate { state.live_order == false }
validate { state.OrdStatus in [ OrdStatus.New ]}
// For Limit orders, Price must be positive.
validate {
OrdType.Limit in this.OrdType ==>
(case this.Price
{Some price: price > 0.0}
{None: false}
)
}
// For Market orders, Price must not be present.
validate {
OrdType.Market in this.OrdType ==> !present(this.Price)
}
}
// Import an outbound message ExecutionReport
outbound message ExecutionReport {
req OrderID
req ExecID
req ExecType
req OrdStatus
req Side
req LeavesQty
req CumQty
req AvgPx
}
// Define the interface state
internal state {
// The following fields would be assigned to
assignable {
OrdStatus : OrdStatus = OrdStatus.New;
Side : Side = Side.Buy;
OrderID : string = "";
LeavesQty : Qty = 0.0;
CumQty : Qty = 0.0;
AvgPx : Price = 0.0;
Price :? Price = None;
OrdType : OrdType
OrderQtyData.OrderQty : Qty
}
live_order : bool = false;
// These are not required within the orders, etc...
filledQty : Qty = 0.0;
current_time : UTCTimeOnly = UTCTimeOnly (8, 0, 0, None);
// Define the opening and closing times
opening_time : UTCTimeOnly = UTCTimeOnly (8, 0, 0, None);
closing_time : UTCTimeOnly = UTCTimeOnly (16, 0, 0, None);
}
// Define how the model should process a validated
// incoming NewOrderSingle message
receive ( msg : NewOrderSingle ) {
// Extract all relevant fields from within the message
assignFrom (msg, state)
state.LeavesQty = msg.OrderQtyData.OrderQty
state.live_order = true
send ExecutionReport {
state with
ExecID = "";
ExecType = ExecType.New;
}
}
// Handle rejections for 'NewOrderSingle'
reject ( msg : NewOrderSingle, rejectReason : string ) {
// A field is missing
missingfield : {
send ExecutionReport {
state with
ExecID = "";
ExecType = ExecType.Rejected;
}
}
invalidfield : {
send ExecutionReport {
state with
ExecID = "";
ExecType = ExecType.Rejected;
}
}
// One or more 'validate' statements is invalid
invalid : {
send ExecutionReport {
state with
ExecID = "";
ExecType = ExecType.Rejected;
}
}
}
// Define action 'time_change'
action time_change {
new_time : UTCTimeOnly
validate { this.new_time <= state.closing_time }
validate { this.new_time >= state.current_time }
}
// Define logic for handling this 'time_change' action
receive ( act : time_change ) {
// Save the new time into internal state
state.current_time = act.new_time
// If it's past closing time, then cancel the order and send
// out the ExecutionReport
if ( act.new_time >= state.closing_time && state.OrdStatus != OrdStatus.Filled ) then {
state.OrdStatus = OrdStatus.Expired
state.live_order = false
send ExecutionReport {
state with
ExecID = "";
ExecType = ExecType.New;
}
}
}
//
// Define action 'fill'
//
action fill {
fill_price : Price
fill_qty : Qty
validate { this.fill_qty > 0.0 && this.fill_qty <= state.LeavesQty }
validate { this.fill_price > 0.0 }
validate {
(OrdType.Limit in state.OrdType) ==>
( case state.Price
{ Some p:
if ( state.Side == Side.Buy ) then
( this.fill_price <= p )
else ( this.fill_price >= p )
}
{ None: true }
)
}
}
// Receiving fills and sending out
receive ( f : fill ) {
state.LeavesQty = state.LeavesQty - f.fill_qty
state.filledQty = state.filledQty + f.fill_qty
state.AvgPx = ( state.AvgPx * state.filledQty + f.fill_qty * f.fill_price ) / ( f.fill_qty + state.filledQty )
if state.LeavesQty == 0.0 then
state.OrdStatus = OrdStatus.Filled
else
state.OrdStatus = OrdStatus.PartiallyFilled
send ExecutionReport { state with
ExecID = "";
ExecType = ExecType.New;
}
}