├── README.md ├── cancelbot.py └── volarbbot.py /README.md: -------------------------------------------------------------------------------- 1 | # Options-stat-arb 2 | A bot for an algorithmic trading competition that trades options using statistical arbitrage and delta and vega hedging 3 | 4 | -------------------------------------------------------------------------------- /cancelbot.py: -------------------------------------------------------------------------------- 1 | from tradersbot import * 2 | 3 | t = TradersBot('127.0.0.1', 'trader0', 'trader0') 4 | current_log = [[],[]] 5 | old_log = [[],[]] 6 | old_log2 = [[],[]] 7 | old_log3 = [[],[]] 8 | 9 | def process(msg, order): 10 | 11 | print("updated") 12 | 13 | global current_log, old_log, old_log2, old_log3 14 | 15 | old_log3 = old_log2 16 | old_log2 = old_log 17 | old_log = current_log 18 | 19 | current_log = [[],[]] 20 | 21 | for order_id in msg['trader_state']['open_orders']: 22 | current_log[0].append(msg['trader_state']['open_orders'][order_id]['order_id']) 23 | print(msg['trader_state']['open_orders'][order_id]['ticker']) 24 | current_log[1].append(msg['trader_state']['open_orders'][order_id]['ticker']) 25 | 26 | for i in range(len(old_log3[0])): 27 | order.addCancel(old_log3[1][i], old_log3[0][i]) 28 | print("cancelled") 29 | 30 | print(msg['trader_state']['open_orders']) 31 | 32 | t.onTraderUpdate = process 33 | t.run() -------------------------------------------------------------------------------- /volarbbot.py: -------------------------------------------------------------------------------- 1 | from tradersbot import * 2 | 3 | from time import sleep 4 | from time import time 5 | 6 | import blackscholes 7 | import numpy as np 8 | from scipy.stats import norm 9 | import math 10 | import copy 11 | 12 | t = TradersBot('127.0.0.1', 'trader0', 'trader0') 13 | 14 | DEFAULT_POSITION_LIMIT = 5000 15 | 16 | class Options(): 17 | def __init__(self, default=None): 18 | self.data = default or {} 19 | 20 | def get(self, key): 21 | return self.data.get(key) 22 | 23 | def set(self, key, val): 24 | if key not in self.data: 25 | print("Invalid option: %s" % key) 26 | else: 27 | print("Set %s to %s" % (key, val)) 28 | self.data[key] = val 29 | 30 | class BaseBot(): 31 | def __init__(self): 32 | self.options = Options({ 33 | 'delay': 1, 34 | 'position_limit': DEFAULT_POSITION_LIMIT, 35 | 'order_quantity': 10, 36 | }) 37 | self.count = 0 38 | self.deltas = 0 39 | self.vegas = 0 40 | self.elapsedTime = 0 41 | self.topBid = {} 42 | self.topAsk = {} 43 | self.lastPrices = {"TMXFUT": 100, 44 | 'T80P': -1, 45 | 'T80C': -1, 46 | 'T81P': -1, 47 | 'T81C': -1, 48 | 'T82P': -1, 49 | 'T82C': -1, 50 | 'T83P': -1, 51 | 'T83C': -1, 52 | 'T84P': -1, 53 | 'T84C': -1, 54 | 'T85P': -1, 55 | 'T85C': -1, 56 | 'T86P': -1, 57 | 'T86C': -1, 58 | 'T87P': -1, 59 | 'T87C': -1, 60 | 'T88P': -1, 61 | 'T88C': -1, 62 | 'T89P': -1, 63 | 'T89C': -1, 64 | 'T90P': -1, 65 | 'T90C': -1, 66 | 'T91P': -1, 67 | 'T91C': -1, 68 | 'T92P': -1, 69 | 'T92C': -1, 70 | 'T93P': -1, 71 | 'T93C': -1, 72 | 'T94P': -1, 73 | 'T94C': -1, 74 | 'T95P': -1, 75 | 'T95C': -1, 76 | 'T96P': -1, 77 | 'T96C': -1, 78 | 'T97P': -1, 79 | 'T97C': -1, 80 | 'T98P': -1, 81 | 'T98C': -1, 82 | 'T99P': -1, 83 | 'T99C': -1, 84 | 'T100P': -1, 85 | 'T100C': -1, 86 | 'T101P': -1, 87 | 'T101C': -1, 88 | 'T102P': -1, 89 | 'T102C': -1, 90 | 'T103P': -1, 91 | 'T103C': -1, 92 | 'T104P': -1, 93 | 'T104C': -1, 94 | 'T105P': -1, 95 | 'T105C': -1, 96 | 'T106P': -1, 97 | 'T106C': -1, 98 | 'T107P': -1, 99 | 'T107C': -1, 100 | 'T108P': -1, 101 | 'T108C': -1, 102 | 'T109P': -1, 103 | 'T109C': -1, 104 | 'T110P': -1, 105 | 'T110C': -1, 106 | 'T111P': -1, 107 | 'T111C': -1, 108 | 'T112P': -1, 109 | 'T112C': -1, 110 | 'T113P': -1, 111 | 'T113C': -1, 112 | 'T114P': -1, 113 | 'T114C': -1, 114 | 'T115P': -1, 115 | 'T115C': -1, 116 | 'T116P': -1, 117 | 'T116C': -1, 118 | 'T117P': -1, 119 | 'T117C': -1, 120 | 'T118P': -1, 121 | 'T118C': -1, 122 | 'T119P': -1, 123 | 'T119C': -1, 124 | 'T120P': -1, 125 | 'T120C': -1, } 126 | self.positions = {'T80P': 0, 127 | 'T80C': 0, 128 | 'T81P': 0, 129 | 'T81C': 0, 130 | 'T82P': 0, 131 | 'T82C': 0, 132 | 'T83P': 0, 133 | 'T83C': 0, 134 | 'T84P': 0, 135 | 'T84C': 0, 136 | 'T85P': 0, 137 | 'T85C': 0, 138 | 'T86P': 0, 139 | 'T86C': 0, 140 | 'T87P': 0, 141 | 'T87C': 0, 142 | 'T88P': 0, 143 | 'T88C': 0, 144 | 'T89P': 0, 145 | 'T89C': 0, 146 | 'T90P': 0, 147 | 'T90C': 0, 148 | 'T91P': 0, 149 | 'T91C': 0, 150 | 'T92P': 0, 151 | 'T92C': 0, 152 | 'T93P': 0, 153 | 'T93C': 0, 154 | 'T94P': 0, 155 | 'T94C': 0, 156 | 'T95P': 0, 157 | 'T95C': 0, 158 | 'T96P': 0, 159 | 'T96C': 0, 160 | 'T97P': 0, 161 | 'T97C': 0, 162 | 'T98P': 0, 163 | 'T98C': 0, 164 | 'T99P': 0, 165 | 'T99C': 0, 166 | 'T100P': 0, 167 | 'T100C': 0, 168 | 'T101P': 0, 169 | 'T101C': 0, 170 | 'T102P': 0, 171 | 'T102C': 0, 172 | 'T103P': 0, 173 | 'T103C': 0, 174 | 'T104P': 0, 175 | 'T104C': 0, 176 | 'T105P': 0, 177 | 'T105C': 0, 178 | 'T106P': 0, 179 | 'T106C': 0, 180 | 'T107P': 0, 181 | 'T107C': 0, 182 | 'T108P': 0, 183 | 'T108C': 0, 184 | 'T109P': 0, 185 | 'T109C': 0, 186 | 'T110P': 0, 187 | 'T110C': 0, 188 | 'T111P': 0, 189 | 'T111C': 0, 190 | 'T112P': 0, 191 | 'T112C': 0, 192 | 'T113P': 0, 193 | 'T113C': 0, 194 | 'T114P': 0, 195 | 'T114C': 0, 196 | 'T115P': 0, 197 | 'T115C': 0, 198 | 'T116P': 0, 199 | 'T116C': 0, 200 | 'T117P': 0, 201 | 'T117C': 0, 202 | 'T118P': 0, 203 | 'T118C': 0, 204 | 'T119P': 0, 205 | 'T119C': 0, 206 | 'T120P': 0, 207 | 'T120C': 0, 208 | 'TMXFUT': 0, } 209 | self.expect_positions = {'T80P': 0, 210 | 'T80C': 0, 211 | 'T81P': 0, 212 | 'T81C': 0, 213 | 'T82P': 0, 214 | 'T82C': 0, 215 | 'T83P': 0, 216 | 'T83C': 0, 217 | 'T84P': 0, 218 | 'T84C': 0, 219 | 'T85P': 0, 220 | 'T85C': 0, 221 | 'T86P': 0, 222 | 'T86C': 0, 223 | 'T87P': 0, 224 | 'T87C': 0, 225 | 'T88P': 0, 226 | 'T88C': 0, 227 | 'T89P': 0, 228 | 'T89C': 0, 229 | 'T90P': 0, 230 | 'T90C': 0, 231 | 'T91P': 0, 232 | 'T91C': 0, 233 | 'T92P': 0, 234 | 'T92C': 0, 235 | 'T93P': 0, 236 | 'T93C': 0, 237 | 'T94P': 0, 238 | 'T94C': 0, 239 | 'T95P': 0, 240 | 'T95C': 0, 241 | 'T96P': 0, 242 | 'T96C': 0, 243 | 'T97P': 0, 244 | 'T97C': 0, 245 | 'T98P': 0, 246 | 'T98C': 0, 247 | 'T99P': 0, 248 | 'T99C': 0, 249 | 'T100P': 0, 250 | 'T100C': 0, 251 | 'T101P': 0, 252 | 'T101C': 0, 253 | 'T102P': 0, 254 | 'T102C': 0, 255 | 'T103P': 0, 256 | 'T103C': 0, 257 | 'T104P': 0, 258 | 'T104C': 0, 259 | 'T105P': 0, 260 | 'T105C': 0, 261 | 'T106P': 0, 262 | 'T106C': 0, 263 | 'T107P': 0, 264 | 'T107C': 0, 265 | 'T108P': 0, 266 | 'T108C': 0, 267 | 'T109P': 0, 268 | 'T109C': 0, 269 | 'T110P': 0, 270 | 'T110C': 0, 271 | 'T111P': 0, 272 | 'T111C': 0, 273 | 'T112P': 0, 274 | 'T112C': 0, 275 | 'T113P': 0, 276 | 'T113C': 0, 277 | 'T114P': 0, 278 | 'T114C': 0, 279 | 'T115P': 0, 280 | 'T115C': 0, 281 | 'T116P': 0, 282 | 'T116C': 0, 283 | 'T117P': 0, 284 | 'T117C': 0, 285 | 'T118P': 0, 286 | 'T118C': 0, 287 | 'T119P': 0, 288 | 'T119C': 0, 289 | 'T120P': 0, 290 | 'T120C': 0, 291 | 'TMXFUT': 0, } 292 | self.maxPos = 20000 293 | self.priceChange = {} 294 | self.pnl = 0 295 | self.avg_vol = {} 296 | for i in range(80,121): 297 | x = float(i) 298 | self.avg_vol[x] = 0.15 299 | #self.avg_vol = {90.: 0.15, 95.: 0.15, 100.: 0.15, 105.: 0.15, 110.: 0.15} 300 | 301 | self.iv_diff_threshold = 0.0001 302 | 303 | def value(self): 304 | val = 0 305 | for key in self.positions: 306 | val += self.positions[key] * self.lastPrices[key] 307 | return val 308 | 309 | def volatility_smile(self): 310 | prices = [] 311 | for i in range(41): 312 | prices.append(80.+i) 313 | ivs = [] 314 | deltas = [] 315 | vegas = [] 316 | calls = [] 317 | puts = [] 318 | if 'TMXFUT' in self.lastPrices: 319 | for p in prices: 320 | s = self.lastPrices['TMXFUT'] 321 | k = p 322 | r = 0 323 | t = 0 324 | big_T = (1. / 12.) * (900. - self.elapsedTime) / 900. 325 | #What is 12? 326 | call = self.lastPrices['T' + str(int(p)) + 'C'] 327 | put = self.lastPrices['T' + str(int(p)) + 'P'] 328 | eps = .000001 329 | o = blackscholes.invert_scalar(s, k, r, t, big_T, call, put, eps) 330 | ivs.append(o) 331 | d1 = (math.log(s / k) + (r + o * o / 2) * (big_T - t)) / (o * math.sqrt(big_T - t)) 332 | deltas.append(norm.cdf(d1) * 100.) 333 | vegas.append(s * norm.pdf(d1) * math.sqrt(big_T - t)) 334 | calls.append(call) 335 | puts.append(put) 336 | return prices, ivs, deltas, vegas, calls, puts 337 | pass 338 | 339 | def delta(self): 340 | delta = 0 341 | prices = [] 342 | for i in range(41): 343 | prices.append(80.+i) 344 | deltas = self.volatility_smile()[2] 345 | for p in prices: 346 | call_name = 'T' + str(int(p)) + 'C' 347 | put_name = 'T' + str(int(p)) + 'P' 348 | delta -= self.positions[put_name] * deltas[prices.index(p)] 349 | delta += self.positions[call_name] * deltas[prices.index(p)] 350 | delta += self.positions["TMXFUT"] * 100 351 | return delta 352 | 353 | def arbitrage(self, msg, order): 354 | 355 | prices = [] 356 | for i in range(41): 357 | prices.append(80.+i) 358 | 359 | for p in prices: 360 | k = p 361 | call_name = 'T' + str(int(p)) + 'C' 362 | put_name = 'T' + str(int(p)) + 'P' 363 | q = self.options.get("order_quantity") 364 | 365 | if ( 366 | call_name in self.topBid and call_name in self.topAsk and put_name in self.topBid and put_name in self.topAsk and 'TMXFUT' in self.topBid and 'TMXFUT' in self.topAsk): 367 | big_T = (1. / 12.) * (900. - self.elapsedTime) / 900. 368 | call = self.lastPrices[call_name] 369 | put = self.lastPrices[put_name] 370 | iv = blackscholes.invert_scalar(self.lastPrices['TMXFUT'], k, 0, 0, big_T, call, put, .00001) 371 | 372 | diff = abs(iv - self.avg_vol[k]) 373 | 374 | if (iv < self.avg_vol[k] - self.iv_diff_threshold): 375 | if (self.positions[call_name] < self.maxPos * diff): 376 | order.addBuy(call_name, q, self.topAsk[call_name] + 5) 377 | print("YES") 378 | if (self.positions[put_name] < self.maxPos * diff): 379 | order.addBuy(put_name, q, self.topAsk[put_name] + 5) 380 | print("YES") 381 | 382 | elif (iv > self.avg_vol[k] + self.iv_diff_threshold * diff): 383 | if (self.positions[call_name] > -self.maxPos): 384 | order.addSell(call_name, q, max(0, self.topBid[call_name] - 5)) 385 | print("YES") 386 | if (self.positions[put_name] > -self.maxPos * diff): 387 | order.addSell(put_name, q, max(0, self.topBid[put_name] - 5)) 388 | print("YES") 389 | 390 | def arbitrage2(self,msg,order): 391 | 392 | mindiscrep = 30 393 | 394 | prices = [] 395 | for i in range(41): 396 | prices.append(80.+i) 397 | 398 | ivs = self.volatility_smile()[2] 399 | 400 | coefs = self.fitIvs(ivs) 401 | 402 | expected = [] 403 | for i in range(80,121): 404 | expected.append(coefs[0]*i**4+coefs[1]*i**3+coefs[2]*i*i+coefs[3]*i+coefs[4]) 405 | 406 | for j in range(len(prices)): 407 | call_name = 'T' + str(int(j+80)) + 'C' 408 | put_name = 'T' + str(int(j+80)) + 'P' 409 | if prices[j] - expected[j] > mindiscrep: 410 | print("buy") 411 | if call_name in self.topAsk: 412 | order.addBuy(call_name, 1, self.topAsk[call_name] + 5) 413 | if put_name in self.topBid: 414 | order.addSell(put_name, 1, self.topBid[put_name] - 5) 415 | elif expected[j] - prices[j] > mindiscrep: 416 | print("sell") 417 | if call_name in self.topBid: 418 | order.addSell(call_name, 1, max(0, self.topBid[call_name] - 5)) 419 | if put_name in self.topAsk: 420 | order.addBuy(put_name, 1, self.topAsk[put_name] + 5) 421 | 422 | def hedge_the_fuck(self, msg, order): 423 | if self.delta() is not None: 424 | print("N") 425 | n = min(500, math.floor(abs(self.delta()) / 100)) 426 | print(n) 427 | if n < 0: 428 | order.addBuy("TMXFUT", n, self.topAsk["TMXFUT"] + 5) 429 | print("YES") 430 | if n > 0: 431 | order.addSell("TMXFUT", n, max(0, self.topBid["TMXFUT"] - 5)) 432 | print("YES") 433 | print("Order made.") 434 | else: 435 | print("No such orders.") 436 | 437 | def update(self, msg): 438 | if type(msg.get('elapsed_time')) == int: 439 | self.elapsedTime = msg.get('elapsed_time') 440 | 441 | # Update internal positions 442 | 443 | if msg.get('market_states'): 444 | for ticker, state in msg['market_states'].iteritems(): 445 | #print(ticker) 446 | if len(state['bids']): 447 | self.topBid[ticker] = max(map(float, state['bids'].keys())) 448 | if len(state['asks']): 449 | self.topAsk[ticker] = min(map(float, state['asks'].keys())) 450 | self.lastPrices[ticker] = state['last_price'] 451 | self.priceChange[ticker] = 0 452 | 453 | # Update internal book for a single ticker 454 | if msg.get('market_state'): 455 | state = msg['market_state'] 456 | ticker = state['ticker'] 457 | #print(ticker) 458 | 459 | if len(state['bids']): 460 | self.topBid[ticker] = max(map(float, state['bids'].keys())) 461 | if len(state['asks']): 462 | self.topAsk[ticker] = min(map(float, state['asks'].keys())) 463 | 464 | self.lastPrices[ticker] = state['last_price'] 465 | 466 | else: 467 | return None 468 | 469 | def update_positions(self, msg, order): 470 | if msg.get('trader_state'): 471 | self.positions = msg['trader_state']['positions'] 472 | if msg.get('message_type') == 'TRADER UPDATE': 473 | self.cash = msg["trader_state"]["cash"]["USD"] 474 | self.pnl = msg["trader_state"]["pnl"]["USD"] 475 | 476 | def process(self, msg, order): 477 | allInit = True 478 | for x in self.lastPrices: 479 | if x == -1: 480 | allInit = False 481 | 482 | if allInit: 483 | print(msg['market_state']) 484 | if msg.get('market_state') or msg.get('trader_state') or msg.get('market_states'): 485 | self.update(msg) 486 | if self.count % 10 == 0: 487 | self.arbitrage2(msg, order) 488 | #print(self.pnl) 489 | 490 | # print(self.positions) 491 | self.count += 1 492 | 493 | 494 | def fitIvs(self, ivs): 495 | x = [] 496 | for i in range(41): 497 | x.append(80.+i) 498 | return np.polyfit(x,ivs,4) 499 | def place_orders(self, order): 500 | pass 501 | 502 | 503 | def g(msg, order): 504 | pass 505 | global tokens 506 | global tick 507 | global ticks 508 | global ids 509 | if 'orders' in msg: 510 | for trade in msg['orders']: 511 | print(trade['ticker'],trade['price']) 512 | order_id = trade['order_id'] 513 | ticker = trade['ticker'] 514 | token = msg['token'] 515 | time = tokens[token] 516 | if time in ids: 517 | ids[time].append((order_id, ticker)) 518 | else: 519 | ids[time] = [(order_id,ticker)] 520 | b = BaseBot() 521 | 522 | t.onMarketUpdate = b.process 523 | t.onTraderUpdate = b.update_positions 524 | t.run() --------------------------------------------------------------------------------