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