-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathbitmm.go
283 lines (237 loc) · 7.58 KB
/
bitmm.go
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
// Toy trading system for the Bitfinex cryptocurrency exchange
package main
import (
"bitmm/bitfinex"
"flag"
"fmt"
"log"
"math"
"os"
"os/exec"
"time"
"code.google.com/p/gcfg"
"github.com/grd/stat"
)
// Config stores user configuration
type Config struct {
Sec struct {
Symbol string // Instrument to trade
TradeNum int // Number of trades to use in calculations
WeightDuration int // Number of seconds back for a 50% weight
MinPos float64 // Min order size
MaxPos float64 // Maximum Position size
MinEdge float64 // Minimum edge for position entry
StdMult float64 // Multiplier for standard deviation
ExitPercent float64 // Percent of edge for position exit
MinChange float64 // Minumum change required to update prices
}
}
var (
client = bitfinex.New(os.Getenv("BITFINEX_KEY"), os.Getenv("BITFINEX_SECRET"))
apiErrors = false // Set to true on any error
liveOrders = false // Set to true on any order
orderTheo = 0.0 // Theo value on which the live orders are based
orderPos = 0.0 // Position on which the live orders are based
cfg Config
)
func main() {
fmt.Println("\nInitializing...")
// Set file for logging
logFile, err := os.OpenFile("bitmm.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
defer logFile.Close()
log.SetOutput(logFile)
// Get config info
configFile := flag.String("config", "bitmm.gcfg", "Configuration file")
flag.Parse()
err = gcfg.ReadFileInto(&cfg, *configFile)
if err != nil {
log.Fatal(err)
}
// Check for input to break loop
inputChan := make(chan rune)
go checkStdin(inputChan)
// Run loop until user input is received
runMainLoop(inputChan)
}
// Check for any user input
func checkStdin(inputChan chan<- rune) {
var ch rune
fmt.Scanf("%c", &ch)
inputChan <- ch
}
// Infinite loop
func runMainLoop(inputChan <-chan rune) {
positionChan := make(chan float64)
var (
trades bitfinex.Trades
orders bitfinex.Orders
start time.Time
position float64
theo float64
stdev float64
lastTrade int
)
for {
// Record time for each iteration
start = time.Now()
// Cancel orders and exit if anything entered by user
select {
case <-inputChan:
exit()
return
default: // Continue if nothing on chan
}
// Check trades
trades = getTrades()
// If new trades check position and do calculations
if !apiErrors && trades[0].TID != lastTrade {
go checkPosition(positionChan)
// Do calcs on trade data while waiting for position data
theo = calculateTheo(trades)
stdev = calculateStdev(trades)
position = <-positionChan
// Reset for next iteration
lastTrade = trades[0].TID
}
// Send orders if necessary
if !apiErrors && (math.Abs(theo-orderTheo) >= cfg.Sec.MinChange ||
math.Abs(position-orderPos) >= cfg.Sec.MinPos || !liveOrders) {
orders = sendOrders(theo, position, stdev)
}
// Print results
if !apiErrors {
printResults(orders, position, stdev, theo, start)
}
// Reset for next iteration
apiErrors = false
}
}
// Send orders to the exchange
func sendOrders(theo, position, stdev float64) bitfinex.Orders {
if liveOrders {
cancelAll()
}
liveOrders = true
orderTheo = theo
orderPos = position
// Send new order request to the exchange
params := calculateOrderParams(position, theo, stdev)
orders, err := client.MultipleNewOrders(params)
checkErr(err, "MultipleNewOrders")
if orders.Message != "" || len(orders.Orders) == 0 || orders.Orders[0].ID == 0 {
cancelAll()
log.Printf("Order Problem %s\n", orders.Message)
}
return orders
}
// Calculate parameters for orders
func calculateOrderParams(position, theo, stdev float64) []bitfinex.OrderParams {
var params []bitfinex.OrderParams
if math.Abs(position) < cfg.Sec.MinPos { // No position
params = []bitfinex.OrderParams{
{cfg.Sec.Symbol, cfg.Sec.MaxPos, theo - math.Max(stdev, cfg.Sec.MinEdge), "bitfinex", "buy", "limit"},
{cfg.Sec.Symbol, cfg.Sec.MaxPos, theo + math.Max(stdev, cfg.Sec.MinEdge), "bitfinex", "sell", "limit"},
}
} else if position < (-1*cfg.Sec.MaxPos)+cfg.Sec.MinPos { // Max short postion
params = []bitfinex.OrderParams{
{cfg.Sec.Symbol, -1 * position, theo - math.Max(stdev, cfg.Sec.MinEdge)*cfg.Sec.ExitPercent, "bitfinex", "buy", "limit"},
}
} else if position > cfg.Sec.MaxPos-cfg.Sec.MinPos { // Max long postion
params = []bitfinex.OrderParams{
{cfg.Sec.Symbol, position, theo + math.Max(stdev, cfg.Sec.MinEdge)*cfg.Sec.ExitPercent, "bitfinex", "sell", "limit"},
}
} else if (-1*cfg.Sec.MaxPos)+cfg.Sec.MinPos <= position && position <= -1*cfg.Sec.MinPos { // Partial short
params = []bitfinex.OrderParams{
{cfg.Sec.Symbol, cfg.Sec.MaxPos, theo - math.Max(stdev, cfg.Sec.MinEdge), "bitfinex", "buy", "limit"},
{cfg.Sec.Symbol, -1 * position, theo - math.Max(stdev, cfg.Sec.MinEdge)*cfg.Sec.ExitPercent, "bitfinex", "buy", "limit"},
{cfg.Sec.Symbol, cfg.Sec.MaxPos + position, theo + math.Max(stdev, cfg.Sec.MinEdge), "bitfinex", "sell", "limit"},
}
} else if cfg.Sec.MinPos <= position && position <= cfg.Sec.MaxPos-cfg.Sec.MinPos { // Partial long
params = []bitfinex.OrderParams{
{cfg.Sec.Symbol, cfg.Sec.MaxPos - position, theo - math.Max(stdev, cfg.Sec.MinEdge), "bitfinex", "buy", "limit"},
{cfg.Sec.Symbol, position, theo + math.Max(stdev, cfg.Sec.MinEdge)*cfg.Sec.ExitPercent, "bitfinex", "sell", "limit"},
{cfg.Sec.Symbol, cfg.Sec.MaxPos, theo + math.Max(stdev, cfg.Sec.MinEdge), "bitfinex", "sell", "limit"},
}
}
return params
}
// Get position data
func checkPosition(positionChan chan<- float64) {
var position float64
posSlice, err := client.ActivePositions()
checkErr(err, "ActivePositions")
for _, pos := range posSlice {
if pos.Symbol == cfg.Sec.Symbol {
position = pos.Amount
}
}
positionChan <- position
}
// Get trade data
func getTrades() bitfinex.Trades {
trades, err := client.Trades(cfg.Sec.Symbol, cfg.Sec.TradeNum)
checkErr(err, "Trades")
return trades
}
// Calculate a volume and time weighted average of traded prices
func calculateTheo(trades bitfinex.Trades) float64 {
mostRecent := trades[0].Timestamp
var weight, timeDivisor, sum, weightTotal float64
for _, trade := range trades {
timeDivisor = float64(mostRecent - trade.Timestamp + cfg.Sec.WeightDuration)
weight = trade.Amount / timeDivisor
sum += trade.Price * weight
weightTotal += weight
}
return sum / weightTotal
}
// Calculate standard deviation
func calculateStdev(trades bitfinex.Trades) float64 {
x := make(stat.Float64Slice, len(trades)-1)
for i := 1; i < len(trades); i++ {
x[i-1] = trades[i-1].Price - trades[i].Price
}
return cfg.Sec.StdMult * stat.Sd(x)
}
// Called on any error
func checkErr(err error, methodName string) {
if err != nil {
cancelAll()
log.Printf("%s Error: %s\n", methodName, err)
apiErrors = true
}
}
// Call on exit
func exit() {
cancelAll()
fmt.Println("\nCancelled all orders.")
}
// Cancel all orders
func cancelAll() {
cancelled := false
for !cancelled {
cancelled, _ = client.CancelAll()
}
liveOrders = false
}
// Print results
func printResults(orders bitfinex.Orders, position, stdev, theo float64, start time.Time) {
clearScreen()
fmt.Printf("\nPosition: %.2f\n", position)
fmt.Printf("Stdev: %.4f\n", stdev)
fmt.Printf("Theo: %.4f\n", theo)
fmt.Println("\nActive orders:")
for _, order := range orders.Orders {
fmt.Printf("%7.2f %s @ %6.4f\n", order.Amount, cfg.Sec.Symbol, order.Price)
}
fmt.Printf("\n%v processing time...", time.Since(start))
}
// Clear the terminal between prints
func clearScreen() {
c := exec.Command("clear")
c.Stdout = os.Stdout
c.Run()
}