From 03cbd968ea70fa4a38b31b14c94d18e83a9e0db6 Mon Sep 17 00:00:00 2001 From: Maltysen <10355450+Maltysen@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:07:27 -0400 Subject: [PATCH] commit --- .gitignore | 7 + README.md | 33 ++++ clean | 5 + kalshi.py | 247 ++++++++++++++++++++++++++++++ lightstreamer.js | 378 ++++++++++++++++++++++++++++++++++++++++++++++ mocks.py | 13 ++ nadex.py | 154 +++++++++++++++++++ nadex_ws.js | 43 ++++++ nasdaqdaily.py | 69 +++++++++ package-lock.json | 162 ++++++++++++++++++++ package.json | 8 + piecewise.py | 58 +++++++ requirements.txt | 31 ++++ spdaily.py | 69 +++++++++ test_kalshi.py | 160 ++++++++++++++++++++ test_piecewise.py | 67 ++++++++ test_utils.py | 77 ++++++++++ tradovate.py | 316 ++++++++++++++++++++++++++++++++++++++ utils.py | 20 +++ yahoo.proto | 73 +++++++++ yahoo_pb2.py | 31 ++++ yahoo_stream.py | 30 ++++ 22 files changed, 2051 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 clean create mode 100644 kalshi.py create mode 100644 lightstreamer.js create mode 100644 mocks.py create mode 100644 nadex.py create mode 100755 nadex_ws.js create mode 100644 nasdaqdaily.py create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 piecewise.py create mode 100644 requirements.txt create mode 100644 spdaily.py create mode 100644 test_kalshi.py create mode 100644 test_piecewise.py create mode 100644 test_utils.py create mode 100644 tradovate.py create mode 100644 utils.py create mode 100644 yahoo.proto create mode 100644 yahoo_pb2.py create mode 100644 yahoo_stream.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c722fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.swp +.*session +.vimrc +__pycache__/ +venv/ +node_modules/ +*.auth diff --git a/README.md b/README.md new file mode 100644 index 0000000..713ba40 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Prediction Market Arbitrage + +Supports Kalshi prediction markets, Nadex binary options, and CME event contracts (through tradovate client). +Draws basis future pricing from yahoo finance. + +Currently has code for nasdaq and s&p pairs, but can easily support any contract traded on these platforms. + +## Setup +Provide auth data in `*.auth` files in json format. +Relies on python venv with `requirements.txt`. +If you're going to use nadex, install node and `package.json` for lightstreamer. + +The pair you're considering and the minimum spread to execute a trade is controlled in the runner files +e.g. `nasdaqdaily.py` or `spdaily.py`. + +## Running +Just run `python nasdaqdaily.py` or `python spdaily.py` etc. You can provide "demo" as a sys arg to run in the sandbox environment (for +the trading platforms that support this). + +Log data, of good spreads found and trades executed, are outputted to stdout, so you should run this on +tee (into `systemd-cat` if you're on the hetzner server so we can export from journalctl). + +Login sessions are outputted into `*.session` files, which can be cleaned up using `./clean`. + +## Testing + +Run `pytest -rA`. + +## Todo +- Add fidelity, and equities vs futures (this requires non-piecewise math solveer) +- Reverse engineer and reimplement lightstreamer in python +- Rigorously model execution risk better than just a minimum spread (look into Almgren-Chriss mean-variance and LVaR models) +- Refactor source code layout diff --git a/clean b/clean new file mode 100755 index 0000000..4f615fc --- /dev/null +++ b/clean @@ -0,0 +1,5 @@ +rm -f .nadex_session +rm -f .kalshi_session +rm -f .tradovate_session +rm -f .nadex_demo_session +rm -f .kalshi_demo_session diff --git a/kalshi.py b/kalshi.py new file mode 100644 index 0000000..ed94aa4 --- /dev/null +++ b/kalshi.py @@ -0,0 +1,247 @@ +import aiohttp +from decimal import Decimal as D, ROUND_CEILING +import dill as pickle +import time +import os +import websockets +import requests +import urllib +import json +import aiostream +import asyncio +import pprint +import yahoo_stream +from utils import * +from piecewise import * + +KALSHI_ROOT = "https://trading-api.kalshi.com/v1" if not DEMO else "https://demo-api.kalshi.co/v1" +KALSHI_ORIGIN = "https://kalshi.com" if not DEMO else "https://demo.kalshi.co" +KALSHI_AUTH = read_auth("kalshi") if not DEMO else read_auth("kalshi_demo") +KALSHI_SESSION_FILE = ".kalshi_session" if not DEMO else ".kalshi_demo_session" + +COMMON_HEADERS = { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "accept-language": "en-US,en;q=0.9", + "origin": KALSHI_ORIGIN, + "referer": "https://kalshi.com/" if not DEMO else "https://demo.kalshi.co/", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36", +} + +#TODO: figure out what prices actually applies here +def convert_contract_hundred_lower(data): + rem = 0 + ret = [] + for p, s in data: + s += rem + if s//100: ret.append((p*100, s//100)) + rem = s%100 + return ret + +#TODO: unittests for this +def convert_contract_avg(data): + rem = 0 + remp = 0 + ret = [] + for p, s in data: + if rem: + u = min(100-rem, s) + remp += u*p + rem += u + s -= u + + if rem==100: + ret.append((remp, 1)) + rem=0 + remp=0 + if s: + if s//100: ret.append((p*100, s//100)) + if s%100: + rem = s%100 + remp = rem * p + return ret + +def convert_sp_rulebook(data): + if data['Above/Below/Between']=='above': + return (cents(data["Value"])+1, None) + if data['Above/Below/Between']=='below': + return (None, cents(data["Value"])-1) + return tuple(map(cents, data["Value"].split('-'))) + +def convert_nasdaq_rulebook(data): + if data['Above/Below/Between']=='above': + return (cents(data["Value"])+1, None) + if data['Above/Below/Between']=='below': + return (None, cents(data["Value"])-1) + return tuple(map(cents, data["Value"].split(' and '))) + +def parse_orderbook_message(message): + msg = message["msg"] + mid = msg["market_id"] + bids = [(int(p), s) for p, s in msg.get("yes", [])] + asks = [(100 - int(p), s) for p, s in msg.get("no", [])] + asks.sort() + bids.sort() + bids.reverse() + + return mid, bids, asks + +def calc_fee(p, cs, is_index): + up = D(p) / D(cs) + return int(( + up * (D(100) - up) * + D('0.035' if is_index else '0.07') + * D(cs) / D(100) + ).to_integral_value(rounding=ROUND_CEILING)) + +def apply_btic_shift(lims, btic, bid): + leeway = 100 if bid else -100 + leeway = 0 + return ( + None if lims[0] is None else lims[0]+btic-leeway, + None if lims[1] is None else lims[1]+btic+leeway, + ) + +def conv_ask_to_pc(lims, p, s, trans_hundred, is_index): + fee = calc_fee(p, 100 if trans_hundred else 1, is_index) + return (conv_lim_pc(*lims) + (-p -fee), s, p) + +def conv_bid_to_pc(lims, p, s, trans_hundred, is_index): + fee = calc_fee(p, 100 if trans_hundred else 1, is_index) + return (-conv_lim_pc(*lims) + (p - fee), s, p) + +class KalshiSession: + def __init__(self, s): + self.s = s + + def __repr__(self): + return "kalshi" + + async def login(self): + self.s.headers.clear() + self.s.headers.update(COMMON_HEADERS) + self.s.cookie_jar.clear() + + if os.path.exists(KALSHI_SESSION_FILE): + with open(KALSHI_SESSION_FILE, 'rb') as f: + self.user_id, cookies, csrf_token, self.frontend_session = pickle.load(f) + self.s.headers['x-csrf-token'] = csrf_token + self.s.cookie_jar._cookies = cookies + else: + print("doing new login") + async with self.s.post( + KALSHI_ROOT+"/log_in", + json = KALSHI_AUTH, + ) as resp: + self.user_id = (await resp.json())["user_id"] + self.s.headers['x-csrf-token'] = resp.headers['x-csrf-token'] + self.frontend_session = await self.get_frontend_session() + + with open(KALSHI_SESSION_FILE, 'wb') as f: + pickle.dump((self.user_id, self.s.cookie_jar._cookies, self.s.headers['x-csrf-token'], self.frontend_session), f) + + async def get_frontend_session(self): + async with self.s.post( + f"{KALSHI_ROOT}/users/{self.user_id}/frontend_sessions", + json = {"cancelToken":{"promise":{}}}, + ) as resp: + return (await resp.json())['session_id'] + + def open_websocket(self): + ws_url = ( + "wss://" + KALSHI_ROOT.split("://")[1] + "/ws?csrfToken=" + + urllib.parse.quote_plus(self.s.headers['x-csrf-token']) + + "&frontend_session=" + + urllib.parse.quote_plus(self.frontend_session) + ) + return websockets.connect( + ws_url, + extra_headers = { + **self.s.headers, + "Cookie": self.s.cookie_jar.filter_cookies(ws_url).output(header=''), + }, + origin = KALSHI_ORIGIN, + ) + + async def markets_from_ticker(self, ticker, converter): + lims = {} + async with self.s.get(f"{KALSHI_ROOT}/events/{ticker}") as resp: + for m in (await resp.json())['event']['markets']: + lims[m["id"]] = converter(m['rulebook_variables']) + return lims + + async def execute_order(self, mid, p, s, buy): + async with self.s.post( + f"{KALSHI_ROOT}/users/{self.user_id}/orders", + json = { + "count": s*100, + "price": p//100 if buy else 100-p//100, + "max_cost_cents": 0, + "sell_position_capped": False, + "expiration_unix_ts": int(time.time())+10, + "market_id": mid, + "side": "yes" if buy else "no" + }, + ) as resp: + pprint.pprint("kalshi order") + pprint.pprint(resp) + pprint.pprint(await resp.json()) + pprint.pprint("") + + async def orderbook_stream(self, ticker, converter, btic_ticker, is_index=True, trans_hundred=True): + lims = await self.markets_from_ticker(ticker, converter) + async with self.open_websocket() as ws: + for i, mid in enumerate(lims): + await ws.send(json.dumps({ + "cmd": "subscribe", + "id": i+1, + "params": { + "channels": ["orderbook"], + "market_id": mid, + }, + })) + + book = {} + async with aiostream.stream.ziplatest( + ws, + yahoo_stream.stream_ticker(btic_ticker), + partial=False, + ).stream() as streamer: + async for message, btic_price in streamer: + message = json.loads(message) + + if message["type"]=="subscribed": continue + assert message["type"]=="orderbook_snapshot" + + mid, bids, asks = parse_orderbook_message(message) + + if trans_hundred: + bids = convert_contract_hundred_lower(bids) + asks = convert_contract_hundred_lower(asks) + + bid_shifed_lims = apply_btic_shift(lims[mid], btic_price, True) + ask_shifed_lims = apply_btic_shift(lims[mid], btic_price, False) + book[mid] = Digital( + *lims[mid], + bids = [conv_bid_to_pc(bid_shifed_lims, p, s, trans_hundred, is_index) for p, s in bids], + asks = [conv_ask_to_pc(ask_shifed_lims, p, s, trans_hundred, is_index) for p, s in asks], + exchange = self, + market_id = mid, + ) + + yield [*book.values()] + +async def main(): + async with aiohttp.ClientSession() as ks: + s = KalshiSession(ks) + await s.login() + await s.execute_order("05aed7ff-3506-4729-b78c-9e96c5b2f876", 2000, 1, False) + + # async for book in s.orderbook_stream("INXW-22NOV04", convert_sp_rulebook): + # pprint.pprint(book) + # pass + + +if __name__=="__main__": + asyncio.run(main()) + diff --git a/lightstreamer.js b/lightstreamer.js new file mode 100644 index 0000000..ba66744 --- /dev/null +++ b/lightstreamer.js @@ -0,0 +1,378 @@ +/* + * LIGHTSTREAMER - www.lightstreamer.com + * Lightstreamer JavaScript Client + * Version 6.1.4 build 1640.11 + * Copyright (c) 2004-2022 Weswit Srl. All Rights Reserved. + * Contains: LightstreamerClient, Subscription + * AMD + */ +(function(){function u(){return function(g){return g}}function D(){return function(){}}function G(g){return function(f){this[g]=f}}function M(g){return function(){return this[g]}}function V(g){return function(){return g}} +define("IllegalStateException",[],function(){function g(f){this.name="IllegalStateException";this.message=f}g.prototype={toString:function(){return["[",this.name,this.message,"]"].join("|")}};return g}); +define("Environment",["IllegalStateException"],function(g){var f="undefined"!==typeof window&&navigator&&document,e="undefined"!==typeof importScripts,b="object"==typeof process&&(/node(\.exe)?$/.test(process.execPath)||process.node&&process.v8||process.versions&&process.versions.node&&process.versions.v8);if(f&&!document.getElementById)throw new g("Not supported browser");var d={isBrowserDocument:function(){return f},isBrowser:function(){return!b&&(f||e)},isNodeJS:function(){return!f&&b},isWebWorker:function(){return!f&& +!b&&e},browserDocumentOrDie:function(){if(!this.isBrowserDocument())throw new g("Trying to load a browser-only module on non-browser environment");}};d.isBrowserDocument=d.isBrowserDocument;d.isBrowser=d.isBrowser;d.isNodeJS=d.isNodeJS;d.isWebWorker=d.isWebWorker;d.browserDocumentOrDie=d.browserDocumentOrDie;return d}); +define("Helpers",["Environment"],function(g){var f=/^\s*([\s\S]*?)\s*$/,e=/,/,b=/\./,d={getTimeStamp:function(){return(new Date).getTime()},randomG:function(b){return Math.round(Math.random()*(b||1E3))},trim:function(b){return b.replace(f,"$1")},getNumber:function(d,a){if(d){if(!d.replace)return d;a?(d=d.replace(b,""),d=d.replace(e,".")):d=d.replace(e,"");return new Number(d)}return 0},isArray:function(b){return b.join&&"function"==typeof b.join},addEvent:function(b,a,c){if(!g.isBrowserDocument())return!1; +"undefined"!=typeof b.addEventListener?b.addEventListener(a,c,!1):"undefined"!=typeof b.attachEvent&&b.attachEvent("on"+a,c);return!0},removeEvent:function(b,a,c){if(!g.isBrowserDocument())return!1;"undefined"!=typeof b.removeEventListener?b.removeEventListener(a,c,!1):"undefined"!=typeof b.detachEvent&&b.detachEvent("on"+a,c);return!0}};d.getTimeStamp=d.getTimeStamp;d.randomG=d.randomG;d.trim=d.trim;d.getNumber=d.getNumber;d.isArray=d.isArray;d.addEvent=d.addEvent;d.removeEvent=d.removeEvent;return d}); +define("BrowserDetection",["Environment"],function(g){function f(b){var d=a;return function(){null===d&&(d=-1=a:e==a:!0:!1}}function d(a){return function(){var b=a.exec(c);return b&&2<=b.length?b[1]:null}}function h(a){return function(){return!a()}}var a= +g.isBrowser()?null:!1,c=g.isBrowser()?navigator.userAgent.toLowerCase():null,l=a;g={isProbablyRekonq:f("rekonq"),isProbablyAWebkit:f("webkit"),isProbablyPlaystation:f("playstation 3"),isProbablyChrome:b(f("chrome/"),d(RegExp("chrome/([0-9]+)","g"))),isProbablyAKhtml:function(){null===l&&(l=document.childNodes&&!document.all&&!navigator.CC&&!navigator.sC);return l},isProbablyKonqueror:b(f("konqueror"),d(RegExp("konqueror/([0-9.]+)","g"))),isProbablyIE:b(f("msie"),d(RegExp("msie\\s([0-9]+)[.;]","g"))), +isProbablyFX:b(f("firefox"),d(/firefox\/(\d+\.?\d*)/)),isProbablyOldOpera:b(function(){return"undefined"!=typeof opera},function(){if(opera.version){var a=opera.version(),a=a.replace(RegExp("[^0-9.]+","g"),"");return parseInt(a)}return 7})};g.isProbablyAndroidBrowser=e([f("android"),g.isProbablyAWebkit,h(g.isProbablyChrome)]);g.isProbablyOperaMobile=e([g.isProbablyOldOpera,f("opera mobi")]);g.isProbablyApple=b(e([f("safari"),function(b){var c=a;return function(){if(null===c){c=!1;for(var a=0;af.length?a+="*":null!=f.charAt?a+=f:f.message?(a+=f.message,f.stack&&(a+="\n"+f.stack+"\n")):f[0]==f?a+=f:g.isArray(f)? +(a+="(",a+=this.te(f),a+=")"):a+=f;a+=" "}catch(n){a+="missing-parameter "}return a+"}"}};f.prototype.debug=f.prototype.debug;f.prototype.isDebugLogEnabled=f.prototype.isDebugLogEnabled;f.prototype.logDebug=f.prototype.logDebug;f.prototype.info=f.prototype.info;f.prototype.isInfoLogEnabled=f.prototype.isInfoLogEnabled;f.prototype.logInfo=f.prototype.logInfo;f.prototype.warn=f.prototype.warn;f.prototype.isWarnEnabled=f.prototype.isWarnEnabled;f.prototype.logWarn=f.prototype.logWarn;f.prototype.error= +f.prototype.error;f.prototype.isErrorEnabled=f.prototype.isErrorEnabled;f.prototype.logError=f.prototype.logError;f.prototype.logErrorExc=f.prototype.logErrorExc;f.prototype.fatal=f.prototype.fatal;f.prototype.isFatalEnabled=f.prototype.isFatalEnabled;f.prototype.logFatal=f.prototype.logFatal;return f}); +define("IllegalArgumentException",[],function(){function g(f){this.name="IllegalArgumentException";this.message=f}g.prototype={toString:function(){return["[",this.name,this.message,"]"].join("|")}};return g}); +define("LoggerManager",["LoggerProxy","IllegalArgumentException"],function(g,f){var e={},b=null,d={setLoggerProvider:function(d){if(d&&!d.getLogger)throw new f("The given object is not a LoggerProvider");b=d;for(var a in e)b?e[a].co(b.getLogger(a)):e[a].co(null)},getLoggerProxy:function(d){e[d]||(e[d]=b?new g(b.getLogger(d)):new g);return e[d]},resolve:u()};d.setLoggerProvider=d.setLoggerProvider;d.getLoggerProxy=d.getLoggerProxy;d.resolve=d.resolve;return d}); +define("lsA",["Environment"],function(g){return{Td:1E3,hp:200,dp:1,yc:0,ep:2,Wo:3,gp:4,Vk:"1640.11",rg:!g.isBrowserDocument()||"http:"!=document.location.protocol&&"https:"!=document.location.protocol?"file:":document.location.protocol,ab:"lightstreamer.stream",Re:"lightstreamer.protocol",Ud:"lightstreamer.session",ug:"lightstreamer.subscriptions",Uk:"lightstreamer.actions",Qa:"lightstreamer.sharing",cp:"lightstreamer.flash",nC:"lightstreamer.stats",Vd:"Lightstreamer_",Zk:"lightstreamer",zc:"UNORDERED_MESSAGES", +vg:{length:-1,toString:V("[UNCHANGED]")},CONNECTING:"CONNECTING",Fb:"CONNECTED:",tg:"STREAM-SENSING",Zh:"WS-STREAMING",qg:"HTTP-STREAMING",Te:"STALLED",xg:"WS-POLLING",Sd:"HTTP-POLLING",ac:"DISCONNECTED",wg:"DISCONNECTED:WILL-RETRY",cl:"WS",Yh:"HTTP",al:"RAW",Xk:"DISTINCT",pg:"COMMAND",$k:"MERGE"}}); +define("lsF",["LoggerManager","Helpers","lsA"],function(g,f,e){function b(a){this.Ha=null;this.nt(a)}function d(a,c){return"var callFun \x3d "+function(a,c){window.name!=a||(window!=top||window.Lightstreamer&&window.Lightstreamer.Ot)||(window.name=c,window.close())}.toString()+"; callFun('"+a+"', '"+c+"');"}function h(a,c,b,h){this.log=a;this.wy=c;this.Oh=b;this.aq=h}var a=0,c=0,l=!1,n=!1,k=g.getLoggerProxy(e.Qa),m=[];b.prototype={nt:function(a){k.logDebug(g.resolve(3));this.Ha=a;this.pA()|| +this.Vb()},Vb:function(){k.logDebug(g.resolve(4));this.Ha=null;delete m[this.Nz]},pA:function(){try{return this.Ha?!0:!1}catch(a){return!1}},Rh:function(){return this.PB()},PB:function(){k.logDebug(g.resolve(5));var a=1;try{if(null==this.Ha)return a=2,new h("null",a,!1,!0);if(this.Ha.closed)return a=3,this.Vb(),new h("closed",a,!1,!0);if(!this.Ha.Lightstreamer)return a=4,this.Vb(),new h("not global",a,!1,!1);a=5;return new h("OK",a,!0,!1)}catch(c){return this.Vb(),new h("exception "+a+" "+c,6,!1, +!0)}},Zm:function(b,h,e){var t=null;try{m[b]&&(t=m[b])}catch(s){t=null}if(t&&(delete m[b],this.Hr(t,b,h)))return!0;a:{var t="javascript:"+('eval("'+d(b,b+"__TRASH")+'; ")'),E=null;k.logDebug(g.resolve(1));if(n)t=!1;else{try{var C;if(window.oC){var y=!0;-5>c-a&&(y=!1);window.Zt&&y?(a++,C=e>f.getTimeStamp()?window.Zt(t,b,"height\x3d100,width\x3d100",!0):!1):(l||(l=!0,k.logWarn(g.resolve(0))),a=0,C=null)}else C=e>f.getTimeStamp()?window.open(t,b,"height\x3d100,width\x3d100",!0):!1;E=C}catch(x){k.logDebug(g.resolve(2), +x);t=!1;break a}if(E)try{c++}catch(w){n=!0}t=E}}if(!1===t)return k.logDebug(g.resolve(6)),!1;if(!t)return k.logDebug(g.resolve(7)),!0;k.logDebug(g.resolve(8));this.Hr(t,b,h);return!0},Hr:function(a,c,b){try{k.logDebug(g.resolve(9));if(a.closed)return k.logDebug(g.resolve(10)),!1;var h=a;if(b){if(a==a.top&&!a.Lightstreamer){k.logDebug(g.resolve(11));try{b=trashName,a.name!=c&&a.name!=b||a.close()}catch(l){k.logDebug(g.resolve(12),l)}return!1}h=a.parent;if(null==h)return k.logDebug(g.resolve(13)),!1}if(!h.Lightstreamer)return k.logDebug(g.resolve(14)), +!1;if(!h.Lightstreamer.Ot)return k.logDebug(g.resolve(15)),!1;k.logDebug(g.resolve(16));this.Ha=h;this.Nz=c;m[c]=a}catch(d){return k.logDebug(g.resolve(17),d),!1}return!0}};h.prototype.toString=function(){return["[|TestResult",this.log,this.wy,this.Oh,this.aq,"]"].join("|")};b.$t=h;return b}); +define("Executor",["Helpers","EnvironmentStatus","Environment"],function(g,f,e){function b(){}function d(a,b){return a.time===b.time?a.Cn-b.Cn:a.time-b.time}function h(){y=!1;c()}function a(){if(t)clearInterval(t);else if(e.isBrowserDocument()&&"undefined"!=typeof postMessage){C=function(){window.postMessage("Lightstreamer.run",E)};var a=function(a){("Lightstreamer.run"==a.data&&"*"==E||a.origin==E)&&h()};g.addEvent(window,"message",a);y||(y=!0,C());!1==y&&(g.removeEvent(window,"message",a),C=b)}else e.isNodeJS()&& +("undefined"!=typeof process&&process.nextTick)&&(C=function(){process.nextTick(h)});t=setInterval(c,l)}function c(){if(f.Wh)clearInterval(t);else{m=g.getTimeStamp();if(0=k.length&&(s=0);0=q&&(q=m+p,k=[].concat(k))}}var l=50,n=!1,k=[],m=g.getTimeStamp(),p=108E5,q=m+p,r=[],t=null,s=0,E=!e.isBrowserDocument()|| +"http:"!=document.location.protocol&&"https:"!=document.location.protocol?"*":document.location.protocol+"//"+document.location.hostname+(document.location.port?":"+document.location.port:""),C=b,y=!1,x={toString:function(){return["[|Executor",l,k.length,"]"].join("|")},getQueueLength:function(){return k.length},packTask:function(a,b,c){return{of:a,Rl:b||null,Ae:c||null,Cn:s++}},addPackedTimedTask:function(a,b,c){a.step=c?b:null;a.time=m+parseInt(b);if(isNaN(a.time))throw"Executor error time: "+a.time; +k.push(a);n=!0},addRepetitiveTask:function(a,b,c,d){return this.addTimedTask(a,b,c,d,!0)},stopRepetitiveTask:function(a){a&&(a.of=null,a.step=null)},addTimedTask:function(a,b,c,d,e){a=this.packTask(a,c,d);this.addPackedTimedTask(a,b,e);0!=b||y||(y=!0,C());return a},modifyTaskParam:function(a,b,c){a.Ae[b]=c},modifyAllTaskParams:function(a,b){a.Ae=b},delayTask:function(a,b){a.time+=b;n=!0},executeTask:function(a,b){try{var c=b||a.Ae;a.Rl?c?a.of.apply(a.Rl,c):a.of.apply(a.Rl):c?a.of.apply(null,c):a.of()}catch(d){}}}; +e.isWebWorker()?setTimeout(a,1):a();x.getQueueLength=x.getQueueLength;x.packTask=x.packTask;x.addPackedTimedTask=x.addPackedTimedTask;x.addRepetitiveTask=x.addRepetitiveTask;x.stopRepetitiveTask=x.stopRepetitiveTask;x.addTimedTask=x.addTimedTask;x.modifyTaskParam=x.modifyTaskParam;x.modifyAllTaskParams=x.modifyAllTaskParams;x.delayTask=x.delayTask;x.executeTask=x.executeTask;return x}); +define("Inheritance",["IllegalStateException"],function(g){function f(b,d,e){if(d)return e?d.apply(b,e):d.apply(b)}var e={Tt:function(b,d,f,a){for(var c in d.prototype)if(!b.prototype[c])b.prototype[c]=d.prototype[c];else if(a){var l;a:{l=d.prototype;var n=void 0;for(n in l)if(l[c]==l[n]&&c!=n){l=n;break a}l=null}if(l){if(b.prototype[l]&&b.prototype[l]!==b.prototype[c]&&d.prototype[l]!==d.prototype[l])throw new g("Can't solve alias collision, try to minify the classes again ("+l+", "+c+")");b.prototype[l]= +b.prototype[c]}}f||(b.prototype._super_=d,b.prototype._callSuperConstructor=e._callSuperConstructor,b.prototype._callSuperMethod=e._callSuperMethod)},_callSuperMethod:function(b,d,e){return f(this,b.prototype._super_.prototype[d],e)},_callSuperConstructor:function(b,d){f(this,b.prototype._super_,d)}};return e.Tt}); +define("CookieManager",["Helpers","Environment"],function(g,f){var e=!1,b={areCookiesEnabled:function(){return e},getAllCookiesAsSingleString:function(){return this.areCookiesEnabled()?document.cookie.toString():null},writeCookie:function(b,e){this.Nt(b,e,"")},Nt:function(b,e,a){this.areCookiesEnabled()&&(document.cookie=encodeURIComponent(b)+"\x3d"+e+"; "+a+"path\x3d/;")},readCookie:function(b){if(!this.areCookiesEnabled())return null;b=encodeURIComponent(b)+"\x3d";for(var e=this.getAllCookiesAsSingleString(), +e=e.split(";"),a=0;a=this.tf&&this.clean()},dismiss:function(){this.tf--;0>=this.tf&&g.addTimedTask(this.bC,this.timeout,this,[this.Bo])},touch:function(){this.Bo++;0>this.tf&&(this.tf=0);this.tf++}};f.prototype.touch=f.prototype.touch;f.prototype.dismiss=f.prototype.dismiss;f.prototype.clean=f.prototype.clean;f.prototype.initTouches= +f.prototype.initTouches;return f});define("lsAW","lsAa lsAX Executor Dismissable Inheritance lsA Helpers lsG".split(" "),function(g,f,e,b,d,h,a,c){function l(a){this._callSuperConstructor(l);this.ea=a;this.Fk=null}var n=[],k=h.Td+h.hp,m=6E4;l.prototype={start:function(){this.Fk&&e.stopRepetitiveTask(this.Fk);this.Fk=e.addRepetitiveTask(this.Sp,m,this);e.addTimedTask(this.Sp,0,this)},clean:function(){e.stopRepetitiveTask(this.Fk);for(var a=0;a=b[l].indexOf("_")&&this.Vu(b[l])},od:function(a,c,b){if(!c){c=a.split("_");if(2!=c.length)return!1;a=c[0];c=c[1]}var l=this.ea.Wf(c,a);return l?b?b-l[h.yc]>k?(this.ea.Dl(c,a),!1):!0:!0:!1},Vu:function(a){for(var c=this.ea.Xj(a),b=0;bf.Td||c++;return c},Is:function(){this.jj=h.getTimeStamp()+this.Fq;this.ea.gC(this.T,this.id,[this.jj,this.rd, +this.host,f.Vk,f.rg])},cA:function(){if(this.xh)p.logDebug(l.resolve(23)),this.xh=!1;else{var a=!1;if(this.to){p.logDebug(l.resolve(24),this);var c=this.ea.Xj(this.T);if(c){p.logDebug(l.resolve(26),this.T);for(var b=0;bthis.jj?a|=this.Hy(c[b],d[t]):this.ph[c[b]]&&delete this.ph[c[b]]):p.logDebug(l.resolve(27),c[b])}}else p.logDebug(l.resolve(25), +this)}a||(p.logDebug(l.resolve(29)),this.ea.il(this.T,this.id),this.Is())}},Hy:function(a,c){p.logDebug(l.resolve(30),a,c);if(this.ph[a])if(this.ph[a]!=c)p.logInfo(l.resolve(21)),this.Li();else return!1;this.ph[a]=c;return!0},cw:function(){return n.fk("LSF__"+n.mc()+"_"+this.id+"_"+this.T)},Li:function(){this.clean();this.to&&g.executeTask(this.to)},Fr:function(){this.ea.Dl(this.T,this.id);this.ea.Ah(this.T,this.id);this.xh=!0},clean:function(){p.logInfo(l.resolve(22),this);g.stopRepetitiveTask(this.Ao); +g.stopRepetitiveTask(this.yk);c.disposeFrame(this.rd);this.rd=this.yk=this.Ao=null;this.Ms(this.ff);this.Fr()},unloadEvent:function(){this.clean()},preUnloadEvent:function(){this.Fr()},ia:function(){this.clean();a.removeBeforeUnloadHandler(this);a.removeUnloadHandler(this);e.stop(this.Xv)}};m.prototype.unloadEvent=m.prototype.unloadEvent;m.prototype.preUnloadEvent=m.prototype.preUnloadEvent;return m}); +define("lsAR","lsF Executor LoggerManager BrowserDetection Inheritance CookieManager lsAY Helpers lsA lsG".split(" "),function(g,f,e,b,d,h,a,c,l,n){function k(a,c,b,l,d){this._callSuperConstructor(k,[a]);this.appName=c;this.ml=this.oh=this.ce=null;this.ho=!0;this.Sf={};this.Vc={};this.ln=0;this.Hc=d||5E3;this.be=null;this.Fo=!1;this.Mk=this.Ik=0;this.Eo=!1;this.fC=b;this.jg=l;h.areCookiesEnabled()&&f.addRepetitiveTask(this.$u,6E4,this)}function m(a){for(var c in a)return c} +var p=m({Vb:!0}),q=m({Zm:!0}),r=m({Rh:!0}),t=e.getLoggerProxy(l.Qa),s=g.$t;k.prototype={Vb:function(){this._callSuperMethod(k,p);this.oh=this.ce=null},qf:function(){return null!=this.Ha?(this.At(),null!==this.Ha?this.Ha:null):null},Hv:function(a){this.ho=!a},qA:function(a,c){var l=null;if((this.Eo||null==a)&&this.fC)t.logDebug(e.resolve(31)),l=this.Sv(),this.Eo=!1;else if(null!=a)t.logDebug(e.resolve(32)),this.nt(a),this.Eo=!0;else return 10==this.ln&&t.logDebug(e.resolve(33)),10>=this.ln&&this.ln++, +null;t.logDebug(e.resolve(34));var d=this.At();t.logDebug(e.resolve(35),d);if(null!=this.Ha){t.logDebug(e.resolve(36));this.Mk=0;try{return this.Sf["LS6__"+n.mc()+"_"+this.ce+"_"+this.appName]="OK",this.Ha}catch(h){t.logDebug(e.resolve(37))}}if(b.isProbablyOldOpera()&&c&&l&&"null"==l.log)return t.logDebug(e.resolve(38)),f.executeTask(c),null;this.Mk++;10<=this.Mk&&(this.Mk=0,c&&this.gy()?(t.logDebug(e.resolve(39)),f.executeTask(c)):(t.logDebug(e.resolve(40)),this.Fo=!0));return null},Zm:function(a, +c,b){return!1===this._callSuperMethod(k,q,[a,!0,c])?!1:this.Bt(b)},gy:function(){if(this.vv)return t.logDebug(e.resolve(41)),!0;if(b.isProbablyOldOpera())return t.logDebug(e.resolve(42)),!0;if(b.isProbablyChrome())return t.logDebug(e.resolve(43)),!0;if(b.isProbablyApple(7,!1))return t.logDebug(e.resolve(44)),!0},xw:function(){if(!h.areCookiesEnabled())return null;this.ml=null;var b=l.Td+(this.ho?l.hp:0),d=a.Am(),k=d.Xj(this.appName);if(!k)return t.logDebug(e.resolve(45)),null;for(var q=0;qb)this.Vc[r]?g>2*l.Td?(this.Vc[r]=null,t.logDebug(e.resolve(49),r)):(this.Vc[r]=s[l.yc],this.jg&&f.executeTask(this.jg,[p]),t.logDebug(e.resolve(50),r)):t.logDebug(e.resolve(51),r);else{if(this.ho)if(!this.Vc[r]){t.logDebug(e.resolve(52),r);this.Vc[r]=s[l.yc];this.jg&&f.executeTask(this.jg, +[p]);continue}else if(this.Vc[r]==s[l.yc]){t.logDebug(e.resolve(53),r);this.jg&&f.executeTask(this.jg,[p]);continue}this.ml=n+l.Td-g;t.logDebug(e.resolve(54),r);return{L:s,id:k[q]}}}else t.logDebug(e.resolve(46),r)}return null},Sv:function(){var a=this.xw();if(!a)return!1;var c=a.L,b=c[l.dp],a=this.Zm(b,this.ml,a.id);this.Sf[b]=!1===a||!a.Oh&&!1==a.aq?!1:a.log?a.log:"unknown";c[l.ep]&&c[l.ep]!=location.host&&(this.vv=!0);return a},Rh:function(){var a=this._callSuperMethod(k,r);a.Oh||(this.ce=null); +return a},At:function(){return this.ce?this.Bt(this.ce):this.OB()},Bt:function(a){var c=this.Rh();if(!c.Oh)return c;t.logDebug(e.resolve(55));c=0;try{var b=this.Ha.Lightstreamer["_"+a];if(!b)return c=6,t.logDebug(e.resolve(56),a),this.Vb(),new s(a+" not IN global",c,!1,!1);if(!b.lsEngine)return c=7,t.logDebug(e.resolve(57),a),this.Vb(),new s(a+" not IN ITS global",c,!1,!1);this.ce=a;this.oh=b.lsEngine;c=8;return new s("OK",c,!0,!1)}catch(l){return t.logDebug(e.resolve(58),c,l),this.Vb(),new s("exception "+ +c+" "+l,9,!1,!0)}},OB:function(){var a=this.Rh();if(!a.Oh)return a;t.logDebug(e.resolve(59));try{var c=this.Ha.Lightstreamer,b;for(b in c)try{if(0==b.indexOf("_")&&c[b].lsEngine&&c[b].lsEngine.kf==this.appName)return this.oh=c[b].lsEngine,this.ce=this.oh.Xg(),new s("OK",10,!0,!1)}catch(l){}}catch(d){return t.logDebug(e.resolve(60),d),this.Vb(),new s("exception "+d,11,!1,!0)}},ht:function(a){this.Hc=a;this.ui&&(a=this.be,this.rt(),this.qt(a))},qt:function(a){this.ui||(t.logDebug(e.resolve(61)),this.be= +a,this.ui=f.addRepetitiveTask(this.od,this.Hc,this))},rt:function(){t.logDebug(e.resolve(62));f.stopRepetitiveTask(this.ui);delete this.be;delete this.ui},od:function(){null===this.qf()&&this.be&&f.executeTask(this.be,[!1]);this.be&&f.executeTask(this.be,[!0])},$u:function(){var a=document.cookie.toString();this.Zu(a);this.dv(a)},Zu:function(a){var c=this.Sf;this.Sf={};for(var b in c)c[b]&&-1e)throw new g("The given value is not valid. Use a positive number or 0");}else if(0>=e)throw new g("The given value is not valid. Use a positive number");return f};f.prototype.checkBool=function(e, +b){if(!0===e||!1===e||b&&!e)return!0===e;throw new g("The given value is not valid. Use true or false");};return f});define("lsH",["LoggerManager","lsG","Inheritance","Setter","lsA"],function(g,f,e,b,d){function h(a){this.P="lsH";this.parent=null;this.Cp=!1;a&&this.zi(a)}var a=g.getLoggerProxy(d.Uk),c=g.getLoggerProxy(d.Qa);h.prototype={Zi:function(a){return this.Ok[a]},Z:function(c,b){var d=this.Zi(c),h=this[d];this[d]=f.cf(b);a.logDebug(g.resolve(64),this.parent,c,this.bj(d));this.parent&&this.Cp&&this.Ze(c);h!=this[d]&&this.Tr(c)},bj:function(a){return this.lq&&this.lq[a]?"[...]":this[a]},v:function(c, +b){var d=this.Zi(c);b!=this[d]&&(this[d]=b,a.logInfo(g.resolve(63),c,this.bj(d)),this.Ze(c),this.Tr(c))},cg:function(a,c){this.parent=a;this.Cp=c},Ze:function(a){var b=this.Zi(a);c.logDebug(g.resolve(65),a,this.bj(b));return this.parent&&this.parent.Ze&&!this.parent.Ze(this.P,a,f.cf(this[b]))?!1:!0},Tr:function(c){var b=this.Zi(c);!this.parent||!this.parent.Vr||this.Rr&&this.Rr[b]||(a.logDebug(g.resolve(66),c,this.bj(b)),this.parent.Vr(c,this))},zi:function(a){var c=this.Ok,b;for(b in c)this.Z(b, +a[c[b]])}};e(h,b,!1,!0);return h});define("lsJ",["lsH","Inheritance","lsG"],function(g,f,e){function b(a){this.Om=null;this.Lg=!1;this.Rr=d;this.Ok=h;this._callSuperConstructor(b,arguments);this.P="lsJ"}var d={Om:!0,Lg:!0},h={Lg:"connectionRequested",Om:"isLocalEngine"},h=e.getReverse(h);f(b,g);return b}); +define("lsL","IllegalArgumentException lsA lsH Inheritance Global Environment lsG".split(" "),function(g,f,e,b,d,h,a){function c(){this.Ql=5E5;this.ej=19E3;this.Dd=this.Qc=this.zf=0;this.Fd=3E3;this.xk=2E3;this.bf=4E3;this.Pn=5E3;this.io=!0;this.qm=null;this.Zp=this.Xn=!1;this.ck=0;this.cm=!0;this.jo=5E3;this.fh=this.wk=null;this.Ai=this.Ro=!0;this.Si=2E3;this.vo=4E3;this.Ok=n;this._callSuperConstructor(c,arguments);this.P="lsL"}var l={};l[f.qg]=!0;l[f.xg]=!0; +l[f.Sd]=!0;l[f.Zh]=!0;l[f.cl]=!0;l[f.Yh]=!0;var n={Ql:"contentLength",ej:"idleMillis",zf:"keepaliveMillis",Qc:"maxBandwidth",Dd:"pollingMillis",Fd:"reconnectTimeout",xk:"stalledTimeout",bf:"connectTimeout",Pn:"retryTimeout",io:"slowingEnabled",qm:"forcedTransport",Xn:"serverInstanceAddressIgnored",Zp:"cookieHandlingRequired",ck:"reverseHeartbeatMillis",cm:"earlyWSOpenEnabled",jo:"spinFixTimeout",wk:"spinFixEnabled",Ro:"xDomainStreamingEnabled",Ai:"corsXHREnabled",Si:"forceBindTimeout",vo:"switchCheckTimeout", +fh:"httpExtraHeaders"},n=a.getReverse(n);c.prototype={HA:function(a){this.v("contentLength",this.checkPositiveNumber(a))},sw:M("Ql"),OA:function(a){this.v("idleMillis",this.checkPositiveNumber(a,!0))},Gw:M("ej"),QA:function(a){this.v("keepaliveMillis",this.checkPositiveNumber(a,!0))},Jw:M("zf"),SA:function(a){a="unlimited"==(new String(a)).toLowerCase()?0:this.checkPositiveNumber(a,!1,!0);this.v("maxBandwidth",a)},Lw:function(){return 0>=this.Qc?"unlimited":this.Qc},XA:function(a){this.v("pollingMillis", +this.checkPositiveNumber(a,!0))},Uw:M("Dd"),Yw:M("Fd"),jB:function(a){this.v("stalledTimeout",this.checkPositiveNumber(a))},er:M("xk"),FA:function(a){this.v("connectTimeout",this.checkPositiveNumber(a))},pw:M("bf"),$A:function(a){this.v("retryTimeout",this.checkPositiveNumber(a))},bx:M("Pn"),fB:function(a){this.v("slowingEnabled",this.checkBool(a))},ey:M("io"),MA:function(a){if(null!==a&&!l[a])throw new g("The given value is not valid. Use one of: HTTP-STREAMING, HTTP-POLLING, WS-STREAMING, WS-POLLING, WS, HTTP or null"); +this.v("forcedTransport",a)},Ew:M("qm"),dB:function(a){this.v("serverInstanceAddressIgnored",this.checkBool(a))},dy:M("Xn"),IA:function(a){if(a&&!h.isBrowser())throw new g("cookieHandlingRequired is only supported on Browsers");this.v("cookieHandlingRequired",this.checkBool(a))},Qb:M("Zp"),KA:function(a){this.v("earlyWSOpenEnabled",this.checkBool(a))},Rx:M("cm"),aB:function(a){this.v("reverseHeartbeatMillis",this.checkPositiveNumber(a,!0))},cx:M("ck"),NA:function(a){if(a){var c="",b;for(b in a)c+= +b+"\n"+a[b]+"\n";this.v("httpExtraHeaders",c)}else this.v("httpExtraHeaders",null)},sf:function(){if(!this.fh)return this.fh;for(var a={},c=this.fh.split("\n"),b=0;b=e;a-=e);a=parseInt(f.substring(k,h-1))-parseInt(f.substring(a,a+2))+d-parseInt(f.substring(c,c+h-k));b=unescape("%"+a.toString(16))+b;h+=3;c+=3;l+=a}return b}return{ux:function(){return g("2844232422362353182342452312352492633183053182412392513042362492412532492362342352342462472452423042312312313182482758859157156756051950251650550051450653351251952051650852152653954583", +116,2,621)},xC:function(){return g("2844232422362353182342452312352492633183053182412392513042362492412532492362342352342462472452423042312312313182482393182292342362492382392362383182422532332342512492422422492342402770",6,7,350)}}}); +define("ASSERT",["LoggerManager"],function(g){var f=g.getLoggerProxy("weswit.test"),e=0,b={},d={VOID:b,getFailures:function(){return e},compareArrays:function(b,a,c){if(b.length!=a.length)return this.Lb(),f.logError(g.resolve(480),b,a),!1;if(c)for(d=0;dc?b:a.length,d=this.Aq(a,a.indexOf("://"));if(null!=d&&isNaN(d.substring(1)))return"The given server address has not a valid port";d=a.indexOf("/",c);d=d=b)return null;if(-1=b)return null;b+=1}else if(b!=a.lastIndexOf(":"))return null;var d=a.indexOf("/",c+3);return-1=d?c+b:c.substring(0,d)+b+c.substring(d)}c=0==a.toLowerCase().indexOf("https://")?"https://"+c:"http://"+c;"/"!=c.substr(c.length-1)&&(c+="/");return c},Vw:function(h,l, +e,k,s,n,m,y,x,w,K){K=K&&b.isBrowserDocument()&&!f.Gm()?"LS_domain\x3d"+f.mc()+"\x26":"";h="LS_phase\x3d"+h+"\x26"+K+(y?"LS_cause\x3d"+y+"\x26":"");s||n?(h+="LS_polling\x3dtrue\x26",y=w=0,n&&(w=Number(e.Dd),null==x||isNaN(x)||(w+=x),y=e.ej),isNaN(w)||(h+="LS_polling_millis\x3d"+w+"\x26"),isNaN(y)||(h+="LS_idle_millis\x3d"+y+"\x26")):(0b&&r.logWarn(a.resolve(124))},Di:function(b){r.logDebug(a.resolve(131),this);var d=this.Dk[b];d&&this.s[d]?this.s[d].$j(b):(r.logError(a.resolve(121),this),c.fail());this.nr(b)},Kk:function(b,c,d){r.logDebug(a.resolve(132),this,d);b&&this.s[b]&&this.s[b].Kk(c,d)},fd:function(b,c,d){r.logDebug(a.resolve(133),this,c);var e=++this.wt,l=h.ba({LS_table:b,LS_op:"reconf",LS_win_phase:e},c);this.Be[b]=e;c=new k(this.X.fa,b,e,this,c,d);this.X.g.vA(b,l,c)},Pf:function(a,b){this.Be[a]== +b&&delete this.Be[a]},ky:function(a,b){return this.Be[a]&&this.Be[a]==b?!0:!1},Op:function(a){delete this.de[a];delete this.ik[a];delete this.Be[a]},Mp:function(){this.de={};this.ik={};this.wt=0;this.Be={}},Ci:function(b){r.logDebug(a.resolve(134),this,b);if(this.s[b]){var c=this.s[b].Le,d;for(d in c)this.Di(d);this.s[b].ia();delete this.s[b]}},tu:function(a){this.ik[a]=!0},hA:function(a){this.de[a]&&(this.de[a].hk=!0)},by:function(a){return this.ik[a]},Sy:function(a){var b=this.s,c;for(c in b)b[c].onStatusChange(a); +return!0},Ty:function(a,b){var c=this.s,d;for(d in c)c[d].Of(a,b)},hr:function(b,d,h,e,l,k){c.verifyOk(this.X.re())||r.logError(a.resolve(122));l=this.s[e];if(!l)r.logError(a.resolve(123),this,e),c.fail();else if(this.X.re()){var f;l.Qx(d,h,b)?f=l.Dm(d):(f=this.Jy++,this.Dk[f]=e,e=this.uw(k,f),l.gi(f,d,e.add,h,b),this.de[f]=new n(e.remove));r.logDebug(a.resolve(135));l.os(b,h,d,f)}},Ak:function(a,b,c){3<=c&&this.Jp(1);(a=this.s[a].nx(b))&&this.X.g.uA(b,a,this,2<=c)},uw:function(a,b){this.Rs++;var c= +{LS_table:b,LS_req_phase:this.Rs,LS_win_phase:this.u};h.ba(a,c);return{add:h.ba(a,{LS_op:"add"}),remove:h.ba(c,{LS_op:"delete"})}},Ur:function(a){var b=this.s;this.s={};for(var c in b)b[c].ds(a)},Py:function(){var a=this.s,b;for(b in a)a[b].es()},cv:function(){for(var b in this.s)this.s[b].jh()||(r.logDebug(a.resolve(136),this),this.Ci(b))},rc:function(){this.Mp();var a=this.s;this.u++;for(var b in a)a[b].rc(this.u)},Tc:function(){this.Mp();var a=this.s;this.u++;for(var b in a)a[b].Tc(this.u)},Tz:function(a, +b){this.de[a]&&this.X.re()&&this.X.g.wA(a,b,this)},ia:function(){d.stopRepetitiveTask(this.bv);f.removeBeforeUnloadHandler(this);f.removeUnloadHandler(this)},unloadEvent:function(){this.Ur(!1)},preUnloadEvent:function(){this.Py()}};q.prototype.unloadEvent=q.prototype.unloadEvent;q.prototype.preUnloadEvent=q.prototype.preUnloadEvent;return q}); +define("lsl",["LoggerManager","BrowserDetection","Helpers","lsA"],function(g,f,e,b){var d=f.isProbablyFX(1.5,!0)?10:50,h=d,a=0,c=0,l=0,n=null,k=null,m=null,p=g.getLoggerProxy(b.Ud);return{da:function(){h=d;l=c=a=0;m=k=n=null},Dx:function(){n=a;k=c;m=l;var b=e.getTimeStamp();l||(l=b);6E4<=b-l&&(a=0,l=b);c&&1E3>b-c&&a++;c=b},dk:function(){k!=c&&(a=n,c=k,l=m)},Ep:function(){if(0!=c){if(!h)return!1;if(a>=h)return p.logError(g.resolve(137)),h=0,!1}return!0}}}); +define("lsAH",["Environment","lsG"],function(g,f){function e(b,d,h,a,c,e){this.VA(b);this.dg(d);this.setData(h);this.qk(a);this.Hh(c);this.Jh(e)}e.bu="GET";e.$h="POST";e.prototype={toString:function(){return["[",this.cc,this.ai,this.Ac,this.yg,"]"].join("|")},VA:function(b){for(;b&&"/"==b.substring(b.length-1);)b=b.substring(0,b.length-1);this.cc=b},dg:function(b){for(;b&&"/"==b.substring(0,1);)b=b.substring(1);this.ai=b},qk:function(b){this.yg=b||e.$h},Hh:function(b){this.mv=b||!1},Jh:function(b){this.yq= +b||null},setData:function(b){this.Ac=b||null},Mi:function(b){this.Ac?this.kv(b)||(this.Ac+=b):this.setData(b)},kv:function(b){return this.Ac&&-1this.c.Dd||this.c.Z("pollingMillis",a),b=this.Xw());n.verifyOk(6==this.a||4==this.a)||p.logError(e.resolve(156));4!=this.a&&b&&0a-this.Cr&&this.Wm?b.modifyTaskParam(this.Wm,0,this.na):(this.Cr=a,this.Wm=this.xd(this.c.zf))}},UB:function(){this.Fc(9)&&this.xd(this.c.xk)}, +TB:function(){this.Fc(10)&&this.xd(this.c.Fd)},SB:function(){n.verifyValue(this.a,3)||p.logError(e.resolve(163));this.xd(this.c.xk)},hw:function(){return this.K?this.c.bf+this.c.ej:0b?a-b:0;return a},Nu:function(){this.Ge||(p.logError(e.resolve(164),this),n.fail(),this.Fd=null);var a=f.getTimeStamp()-this.Ge,b=this.c.bf;this.Fd=(a>b?b:a)+b},yz:function(a, +b){!g.isUnloaded()&&this.ha(b)&&""!==a&&(null==a?(d.dk(),this.za("nullresp")):this.aa.lp(b,a))},wn:function(a,b,c,e){!g.isUnloaded()&&this.ha(b)&&(d.dk(),this.za("failure."+a,!1,c,e))},vn:function(a){this.ha(a)&&(d.dk(),this.za("wrongend"))},uq:function(){this.za("eval")},vz:function(){this.cd||this.ad||this.d.wz(this.J)},Cz:function(){q.isDebugLogEnabled()&&q.logDebug(e.resolve(170));this.S();8==this.a&&(this.ng=1)},tz:function(a){q.isDebugLogEnabled()&&q.logDebug(e.resolve(171),a);this.bg=a;this.c.Z("maxBandwidth", +a)},fz:function(){q.isDebugLogEnabled()&&q.logDebug(e.resolve(172));this.za("error41",!0)},jz:function(){q.isDebugLogEnabled()&&q.logDebug(e.resolve(173));this.S()},nz:function(a,b,c,d,h,k){q.isDebugLogEnabled()&&q.logDebug(e.resolve(174));var f=this.kk;null==b||this.Km||(f=b=l.gv(f,b));f!=this.Kd&&(this.d.Fj(this.Kd),this.Kd=f,this.d.sn(this.Kd));d&&(this.K?this.c.Z("idleMillis",d):this.c.Z("keepaliveMillis",d));2==this.a?this.pb=a:(n.verifyValue(this.pb,a)||p.logError(e.resolve(165)),this.Nu()); +this.tc.vB(this.K);this.S();3==this.a?(this.d.rc(c),this.Gb.Z("serverSocketName",h),this.Gb.Z("serverInstanceAddress",this.Kd),this.ol&&(this.si(),this.ol=!1)):this.d.ns(c);k&&this.d.iz(k)},kz:function(a){q.isDebugLogEnabled()&&q.logDebug(e.resolve(175));this.on(a)},Bz:function(){d.dk();this.za("syncerror",!0)},as:function(a){q.isDebugLogEnabled()&&q.logDebug(e.resolve(176),a);this.Ra("end",!0,!0)},yn:function(a,b){this.S();this.d.yn(a,b)},Ub:function(a){this.S();this.d.Ub(a)},Tb:function(a){this.S(); +this.d.Tb(a)},Bd:function(a){this.S();this.d.Bd(a)},pn:function(a,b){this.S();this.d.pn(a,b)},rn:function(a,b){this.S();this.d.rn(a,b)},qn:function(a,b,c,d){this.S();this.d.qn(a,b,d,c)},xe:function(a,b){this.S();this.d.xe(a,b)},Nf:function(a,b,c,d){this.S();this.d.Nf(a,b,d,c)},Qf:function(a,b,c){this.S();this.d.Qf(a,b,c)},Of:function(a,b){this.as(b);this.d.Of(a,b)},onUnsubscription:function(a){this.S();this.d.onUnsubscription(a)},onSubscription:function(a,b,c,d,e){this.S();this.d.onSubscription(a, +b,c,d,e)},Pf:function(a,b){this.S();this.d.Pf(a,b)},Ti:function(b){p.logDebug(e.resolve(177),this);var d=l.Dw(b,this.tc.tm());b=new a(b,this,this.Na,this.c);this.Q.Dc(this.pb,d,c.Yk,b)},sA:function(a){p.logDebug(e.resolve(178),this);a=l.ww(this.pb,a);this.Q.Dc(this.pb,a,c.Oe,null,this.Oc())},si:function(){1!=this.a&&11!=this.a&&(2==this.a?this.ol=!0:0>=this.bg&&0>=this.c.Qc||this.bg!=this.c.Qc&&this.Q.Dc(null,l.qw(this.c),c.Wk,null))}};return m}); +define("lsAJ",[],function(){function g(){this.Hn=!1;this.Rj=0;this.To=!1}g.prototype={zq:function(f,e){if(!e&&!this.ay(f))return null;0==this.Rj&&"/*"==f.substring(0,2)&&(this.To=!0);var b=-1;if(e&&!this.To)b=f.length;else{b=f.lastIndexOf(";\n");if(0>b)return null;b+=2}var d=f.substring(this.Rj,b);0==this.Rj&&this.To&&(d=d.substring(2,d.length));this.Rj=b;return d},po:function(f){return this.zq(f,!1)},oo:function(f){return this.zq(f,!0)},ay:function(f){if(this.Hn)return!0;var e=f.indexOf("setPhase("), +b=f.indexOf("setPhase(ph)");if(-1=b)return this.Hn=!0;e=f.indexOf("setPhase(",e+1);if(-1e)return this.Hn=!0}return!1}};return g}); +define("lsAN","lsAI Inheritance Executor BrowserDetection EnvironmentStatus lsAJ Environment LoggerManager lsG lsA".split(" "),function(g,f,e,b,d,h,a,c,l,n){function k(){this._callSuperConstructor(k);this.k=!1;this.Ab=this.W=this.hc=this.t=null;this.mo=!1;this.P=k}function m(a){return function(){e.executeTask(a)}}var p=c.getLoggerProxy(n.ab),q=a.isBrowser()?2:3,r;a.isNodeJS()&&(r=l.Aj("xmlhttprequest").XMLHttpRequest);var t=null;g.gc(k,{va:function(){if(null!== +t)return t;b.isProbablyIE(9,!0)?t=!1:"undefined"!=typeof XMLHttpRequest?"undefined"!=typeof(new XMLHttpRequest).withCredentials&&(t=!0):!a.isBrowser()&&r&&(t=!0);null===t&&(t=!1);return t},wf:function(){return!b.isProbablyOldOpera()&&!b.isProbablyPlaystation()},xa:!0,wa:!0,ec:function(){return"file:"!=n.rg},fc:!1,ic:!0});k.prototype={toString:function(){return["[|XSXHRConnection",this.k,this.t,this.hc,"]"].join("|")},$:function(){if(this.k){p.logDebug(c.resolve(179));this.t=null;if(this.W)try{this.W.abort()}catch(a){p.logDebug(c.resolve(180))}this.Wa()}}, +ra:function(a,b,d,l,k){if(this.k)return null;this.W=r?new r:new XMLHttpRequest;this.Ab=new h;d=e.packTask(this.un,this,[b,d,k,l]);this.W.onreadystatechange=m(d);this.t=b;this.hc=null;p.logDebug(c.resolve(181),a.Ua());try{this.W.open(a.yg,a.Ua(),!0);this.W.withCredentials=a.mv;var f=a.yq;if(f)for(var g in f)this.W.setRequestHeader(g,f[g]);this.W.setRequestHeader("Content-Type","application/x-www-form-urlencoded");this.W.send(a.getData());this.k=!0}catch(n){return p.logDebug(c.resolve(182),n),!1}return!0}, +un:function(a,b,h,l){this.t!=a||d.isUnloaded()||(a=null,this.vf()&&b&&(3==this.W.readyState?a=this.Ab.po(this.W.responseText):4==this.W.readyState&&(a=this.Ab.oo(this.W.responseText)),p.isDebugLogEnabled()&&p.logDebug(c.resolve(183),a),null!=a&&e.executeTask(b,[a,this.t])),4==this.W.readyState&&(this.vf()||(this.mo?(l&&e.executeTask(l,["status0",this.t,!1,!0]),this.mo=!1):b&&e.executeTask(b,[null,this.t])),p.logDebug(c.resolve(184)),this.Wa(),""==a&&h&&e.addTimedTask(this.qh,100,this,[this.t,h])))}, +qh:function(a,b){e.executeTask(b,[a])},Wa:function(){this.k=!1;this.t=null;this.W&&(delete this.W.onreadystatechange,delete this.W)},vf:function(){try{if(null===this.hc){if(this.W.readyState=this.W.status;0==this.W.status&&(this.mo=!0)}return this.hc}catch(a){return p.logDebug(c.resolve(185),a),!1}}};f(k,g);return k}); +define("lsAE","lsAI Inheritance Executor EnvironmentStatus lsAJ LoggerManager lsA".split(" "),function(g,f,e,b,d,h,a){function c(){this._callSuperConstructor(c);this.k=!1;this.pa=this.Ab=this.t=null;this.Sj=0;this.P=c}function l(a){return function(){e.executeTask(a)}}var n=h.getLoggerProxy(a.ab),k=null;g.gc(c,{va:function(){return null!==k?k:k="undefined"!=typeof XDomainRequest?!0:!1},wf:!0,xa:!0,wa:!1,ec:!1,fc:!1,ic:!1});c.prototype={toString:function(){return["[|IEXSXHRConnection", +this.k,this.t,"]"].join("|")},$:function(){if(this.k){n.logDebug(h.resolve(186));this.t=null;if(this.pa)try{this.pa.abort()}catch(a){n.logDebug(h.resolve(187))}this.Wa()}},ra:function(a,b,c,k,f){if(this.k)return null;this.Sj=0;this.pa=new XDomainRequest;this.Ab=new d;f=e.packTask(this.nA,this,[b,c,f,k]);var g=e.packTask(this.zp,this,[b,k,"xdr.err"]);k=e.packTask(this.zp,this,[b,k,"xdr.timeout"]);c=e.packTask(this.Gs,this,[b,c,!1]);this.pa.onload=l(f);this.pa.onerror=l(g);this.pa.ontimeout=l(k);this.pa.onprogress= +l(c);this.t=b;n.logDebug(h.resolve(188),a.Ua());try{this.pa.open(a.yg,a.Ua()),this.pa.send(a.getData()),this.k=!0}catch(E){return n.logDebug(h.resolve(189),E),!1}return!0},zp:function(a,c,d){this.t!=a||b.isUnloaded()||(n.logDebug(h.resolve(190)),e.executeTask(c,[d,a]))},Gs:function(a,c,d){this.t!=a||b.isUnloaded()||(this.Sj++,c&&(a=d?this.Ab.oo(String(this.pa.responseText)):this.Ab.po(String(this.pa.responseText)),n.isDebugLogEnabled()&&n.logDebug(h.resolve(191),a),null!=a&&e.executeTask(c,[a,this.t])))}, +nA:function(a,c,d,l){this.t!=a||b.isUnloaded()||(0==this.Sj&&-1this.sender.readyState)return!1;this.hc=200<=this.sender.status&&299>=this.sender.status}return this.hc}catch(b){return n.logDebug(a.resolve(209),b),!1}},ms:function(c){b.isUnloaded()||(c!=this.a||!this.sender)||4!=this.sender.readyState&&"complete"!=this.sender.readyState||(c=null,this.vf()&&(c=this.sender.responseText,c=c.toString(),"/*"==c.substring(0,2)&&(c=c.substring(2, +c.length-2))),n.isDebugLogEnabled()&&n.logDebug(a.resolve(210),c),this.Wa(),this.response&&d.executeTask(this.response,[c,this.t]),this.xi())},gz:function(){b.isUnloaded()||(myFrameHandler.disable(),n.logDebug(a.resolve(211)),this.Wa(),this.error&&d.executeTask(this.error,["xhr.unknown",this.t]),this.xi())},xi:function(){try{delete this.sender.onreadystatechange}catch(b){n.logDebug(a.resolve(212),b)}try{delete this.sender}catch(c){n.logDebug(a.resolve(213),c)}this.response=this.error=null;this.zd&& +this.zd.dismiss()},Wa:function(){this.k=!1;this.a++}};f(l,g);return l}); +define("lsAM","lsAI lsAL Inheritance EnvironmentStatus Executor BrowserDetection lsAJ Environment LoggerManager lsA".split(" "),function(g,f,e,b,d,h,a,c,l,n){function k(){this._callSuperConstructor(k);this.Ab=null;this.P=k}var m=l.getLoggerProxy(n.ab),p=null;g.gc(k,{va:function(){return null!==p?p:p=c.isBrowserDocument()?h.isProbablyIE()?!1:"undefined"!=typeof XMLHttpRequest?"undefined"!=typeof(new XMLHttpRequest).addEventListener:!1:!1}, +wf:function(){return!h.isProbablyOldOpera()},xa:!1,wa:!1,ec:!0,fc:!1,ic:!0});k.prototype={toString:function(){return["[|XHRStreamingConnection",this.k,this.a,this.t,"]"].join("|")},ra:function(b,c,d,e,h){b=this._callSuperMethod(k,this.bi,[b,c,d,e]);m.logDebug(l.resolve(214));b&&(this.Ab=new a,this.Up=h);return b},ms:function(a){!b.isUnloaded()&&(a==this.a&&this.sender)&&(a=null,this.vf()&&this.response&&(3==this.sender.readyState?a=this.Ab.po(this.sender.responseText):4==this.sender.readyState&&(a= +this.Ab.oo(this.sender.responseText)),m.isDebugLogEnabled()&&m.logDebug(l.resolve(215),a),null!=a&&d.executeTask(this.response,[a,this.t])),4==this.sender.readyState&&(!this.vf()&&this.response&&d.executeTask(this.response,[null,this.t]),this.Wa(),this.xi(),m.isDebugLogEnabled()&&m.logDebug(l.resolve(216)),""==a&&this.Up&&d.addTimedTask(this.qh,100,this,[this.t])))},qh:function(a){d.executeTask(this.Up,[a])}};e(k,f);return k}); +define("lsE",["Executor","IFrameHandler","Global","BrowserDetection","lsG"],function(g,f,e,b,d){function h(b){this.km=b;this.mb=!1;this.$m=d.mc();this.iq=this.Pi=!1;this.wj=-1;this.oq=b=d.fk(this.Wq()+"_"+this.$m);g.addTimedTask(this.Xu,3E3,this);var h="about:blank";this.zj()&&(this.wj=++a,h=e.ua(this.wj,"EQCallback_"+b,this.vx(),"Q"),h="javascript:(function(){document.open();"+("document.domain\x3d'"+d.mc()+"';")+("parent."+h+"(window);")+"document.close();})()");try{this.lc=f.getFrameWindow(b, +!0,h),this.Ey()?g.addTimedTask(this.am,1,this):this.zj()||this.am()}catch(n){}}var a=0;h.prototype={Vq:V(null),Xh:V(!0),ia:function(){g.addTimedTask(f.disposeFrame,0,f,[this.oq]);null!==this.wj&&e.Cl(this.wj,"EQCallback_"+this.oq,"Q");this.iq=!0},hg:function(){return this.Pi||this.iq?!1:d.mc()==this.$m?!0:this.jn()?!1:!0},wd:M("mb"),am:function(){var a=this.Vq();this.zj()?this.lc.document.write("\x3cscript\x3edocument.domain\x3d'"+this.$m+"';\x3c/script\x3e"):b.isProbablyOldOpera()&&!a||this.lc.document.open(); +a&&this.lc.document.write(a);this.zj()||b.isProbablyOldOpera()&&!a||this.lc.document.close();this.mb=this.Xh()},vx:function(){var a=this;return function(b){a.lc=b;a.am()}},zj:function(){return b.isProbablyIE()&&!d.Gm()},jn:function(){return b.isProbablyIE()||b.isProbablyOldOpera()||b.isProbablyKonqueror(4.4,!0)},Ey:function(){return b.isProbablyKonqueror()},gu:function(a,b){this.Pi=!0;this.km&&(this.km.Ae=[a,b],g.executeTask(this.km))},Xu:function(){this.mb||this.gu(5)}};return h}); +define("lsAG","LoggerManager Executor lsE Inheritance Dismissable lsA Helpers".split(" "),function(g,f,e,b,d,h,a){function c(a,b){this.iu=a;this._callSuperConstructor(c,[b]);this.Mg=null;this.initTouches()}function l(a,b,c){try{a.appendChild(b),b.src=c}catch(d){}}var n=g.getLoggerProxy(h.ab);c.prototype={toString:V("[JSONPFrame]"),Eu:function(a,b){try{var c=this.rw();if(!c)return c;var d=this.lc.document.createElement("script");d.id=a;d.type="text/javascript";f.addTimedTask(l,50, +null,[c,d,b])}catch(e){return n.logDebug(g.resolve(217),e),!1}return!0},uv:function(a){var b=this.lc.document.getElementById(a);f.addTimedTask(function(){b&&b.parentNode&&b.parentNode.removeChild(b)},4E3)},clean:function(){this.ia()},rw:function(){if(this.Mg)return this.Mg;this.Mg=this.lc.document.getElementsByTagName("BODY")[0];if(!this.Mg){if(this.hf)return 2E3=C){x.logDebug(n.resolve(231));l.addTimedTask(this.Ul,3E3,this,[b,d,"offline"]);return}C--;0==C&&l.addTimedTask(t,2E4,null,[y])}b=this.wq(d,this.Ul,c);null!==b&&(b?this.Tl():!1===b&&x.logWarn(n.resolve(230)))}},Zd:function(a){if(!this._callSuperMethod(r,s.bindSession,[a]))return!1;this.We&&this.We.$();this.Uv();this.li(this.na,a);return!0},Uv:function(){if(!m.isLoaded()&&(null===this.c.wk&&(k.isProbablyAndroidBrowser()|| +k.isProbablyApple())||!0===this.c.wk)){var a=this.Ye,b=this;m.addOnloadHandler(function(){l.addTimedTask(function(){a==b.Ye&&b.a==e.Xt&&b.Ti("spinfix")},b.c.jo)})}},li:function(a,b){if(a==this.na){this.xj||this.K||(this.xj=new d);var c=this.wq(null,this.li,b);null!==c&&(c?this.Fg():!1!==c||this.K||this.Uc(this.J,"streaming.unavailable"))}},ne:function(){this.a!=e.dl&&(this.a!=e.Rd&&this.a!=e.bl)&&(0a||39b.length?this.sc(a)&&this.g.jz():this.sc(null,a)&&this.g.yn(b,c||!1)},Ub:function(a, +b){this.sc(null,a)&&this.g.Ub(b)},Tb:function(a,b){this.sc(null,a)&&this.g.Tb(b)},Bd:function(a,b){this.sc(null,a)&&this.g.Bd(b)},lz:function(c,d,e,h){if(this.sc())if(b.verifyValue(e.substring(0,3),"MSG")||a.logError(f.resolve(249),e),e=e.substr(3),39==c)for(c=parseInt(h),d=parseInt(d),c=d-c+1;c<=d;c++)this.g.xe(e,c);else 38==c?this.g.xe(e,d):0>=c?this.g.qn(e,c,h,d):this.g.Nf(e,c,h,d)},za:function(a,b,c,d){null!=c&&isNaN(c)?this.lz(a,b,c,d):null!=c?this.sc(null,b)&&this.g.Qf(c,a,d):this.sc(b,null, +null,!0)&&this.g.Of(a,d)},$y:function(b,c,d,e,h,g,r){if(this.sc(null,4==b||5==b||9==b?null:c))if(4==b)this.g.pn(d,c);else if(5==b)this.g.rn(d,c);else if(8==b)this.g.onUnsubscription(d);else if(6==b)this.g.onSubscription(d,e,h,g+1,r+1);else 9==b?this.g.Pf(d,c):a.logDebug(f.resolve(252),b)}};return h}); +define("lsU",["lsO"],function(g){function f(e,b,d){this.Nd=b;this.Wd=d;this.sa=e}f.prototype={lh:function(){var e=this.sa.Nc(this.Nd);return e?e.Rm(this.Nd):!0},wu:function(){return this.sa.by(this.Nd)},Ad:function(){if(this.Wd==g.Qd){var e=this.sa.Nc(this.Nd);e&&e.Xr(this.Nd);this.sa.tu(this.Nd)}else this.Wd==g.Se&&this.sa.hA(this.Nd)},Bj:function(){this.sa.Op(this.Nd)}};return f}); +define("lsv",["LoggerManager","Global","Helpers","ASSERT","lsA"],function(g,f,e,b,d){function h(a,b){this.Zj=0;this.Ma=null;this.gh=!1;this.c=a;this.e=null;this.Ju(b)}var a=g.getLoggerProxy(d.Re),c=g.getLoggerProxy(d.Ud);h.prototype={toString:function(){return["[","lsv",this.Ma,this.Zj,0.5,7E3,"]"].join("|")},Vx:function(a){return null!=this.Ma&&this.Ma>a},$d:G("e"),tm:function(){return null!=this.Ma&&02*this.Ma&&(this.gh=!this.gh))return c.logInfo(g.resolve(254)),7E3this.Ma)return this.Ma=0,c.logDebug(g.resolve(258)),!1;if(this.Vx(7E3))return c.logInfo(g.resolve(255)),!0;c.logDebug(g.resolve(259));return!1}};return h}); +define("lsP",["LoggerManager","lsO","ASSERT","lsA"],function(g,f,e,b){function d(a){this.Bb=[];this.keys={};this.ki=a;this.Ay=0}var h=g.getLoggerProxy(b.ug);d.prototype={toString:function(){return["[|ControlRequestBatch",this.ki,this.Bb.length,"]"].join("|")},rp:function(a,b){this.keys[a]=b;this.Bb.push(a)},Xe:function(a,b){var d=a.Wd;if(d==f.Qe||d==f.gd||d==f.Pe){if(this.ki!=d)return h.logError(g.resolve(260),this),e.fail(),!1;this.rp(this.Ay++,a);return!0}if(this.ki!= +f.Qd)return h.logError(g.resolve(261),this),e.fail(),!1;var n;switch(d){case f.Wk:n="C";break;case f.Yk:n="F";break;case f.Xo:n="X"+a.getKey();break;default:n=a.getKey()}var k=this.keys[n];h.logDebug(g.resolve(265),this,n,a);if(k){if(d==f.Wk||d==f.Yk){b||(h.logDebug(g.resolve(266)),this.so(n,a));return}if(d==f.Se){k.Ee?(h.logDebug(g.resolve(267)),b||this.so(n,a)):k.Wd==f.Se?h.logDebug(g.resolve(268)):(h.logDebug(g.resolve(269)),e.verifyNotOk(b)||h.logError(g.resolve(262),this),b||this.gA(n));return}if(d== +f.Oe){for(;k&&a.Ee!=k.Ee;)h.logDebug(g.resolve(270)),n+="_",k=this.keys[n];if(k){h.logDebug(g.resolve(271));return}}else{b||(h.logDebug(g.resolve(272)),this.so(n,a));return}}h.logDebug(g.resolve(273));this.rp(n,a)},getLength:function(){return this.Bb.length},so:function(a,b){this.keys[a]=b},Nn:function(a){if(this.Bb.length<=a)return h.logError(g.resolve(263)),null;var b=this.Bb[a];this.Bb.splice(a,1);a=this.keys[b];delete this.keys[b];return a},gA:function(a){if(!this.keys[a])return h.logError(g.resolve(264)), +null;for(var b=0;b=this.Bb.length?null:this.keys[this.Bb[a]]},sd:M("ki")};return d}); +define("lsN","lsO lsP LoggerManager lsAH Executor lsz lsA ASSERT".split(" "),function(g,f,e,b,d,h,a,c){function l(){this.a=this.Xc=this.Ic=this.Da=null}function n(a,b,c,d){this.Jm=this.Fi=this.nj=this.Sl=this.cn=this.Ka=this.kc=null;this.Bh=this.Zf=0;this.a=this.status=this.f=1;this.Vh=0;this.o=null;this.sa=a;this.c=b;this.Ta=c;this.U=null;this.Xf(d);this.Bc()}var k=e.getLoggerProxy(a.ug),m={1:"IDLE",2:"STAND BY",3:"WAITING RESP"}; +n.prototype={toString:function(){return["[|ControlConnectionHandler",m[this.status],this.o,this.Bh,"]"].join("|")},ZA:function(a){this.Bh=a;k.logDebug(e.resolve(291),this)},uB:function(a){this.Zf=a;k.logInfo(e.resolve(281),this);1==this.status&&this.at(this.f)},at:function(a){1==this.status&&(this.f==a&&0!=this.Zf)&&(k.logDebug(e.resolve(292),this),this.Dc(null,"",g.Pe))},st:function(){k.logInfo(e.resolve(282),this);this.Zf=0},Xf:function(a){this.kc=new h(h.Rt,!1,!this.c.Ai||a);this.Ka=null},$:function(){k.logDebug(e.resolve(293)); +this.Ka&&this.Ka.$()},ga:function(a){this.f++;1==a&&0c;){c++;this.ga(2);k.logDebug(e.resolve(299),b,this);var d=null;null!=this.o?(k.logDebug(e.resolve(300)),d=this.Ys(this.o)): +(k.logDebug(e.resolve(301)),d=this.rA());if(1==d)k.logInfo(e.resolve(284)),this.o=null;else{if(2==d){k.logInfo(e.resolve(285));this.Sa(200,"later");return}if(3==d){k.logWarn(e.resolve(278));this.o&&this.o.nn(!0);this.o=null;this.Sa(!1,"no");return}if(4==d){k.logInfo(e.resolve(286));this.ga(3);this.o.nn();this.Sa(4E3,"http");return}if(5==d)k.logInfo(e.resolve(287)),this.ga(3),this.o.nn(),this.o=null,this.ga(1);else{k.logInfo(e.resolve(288));this.$();this.ga(1);return}}}this.Sa(!1,"limit")}},rA:function(){for(var a= +0;a=a.getLength()))if(this.Da=new f(a.sd()),this.Xc=this.Ic.Hx(a,d,h),d="",h=this.Ic.qq(a,c,!0),null===h)this.Xc=this.Da=null;else{var l=this.Ic.vm(this.Xc.getFile()),g=this.Ic.ym(h)+h.length;0b&&k.logWarn(e.resolve(280),d);do d+=h,this.Da.Xe(a.shift()),l+=g,0=this.getLength()},nn:function(a){for(var b=0,c=null;c=this.Da.Bm(b);)(c=c.Ks)&&d.addTimedTask(c.Ad,0,c,[a]),b++}};return n}); +define("lsS",["Inheritance","lsW","lsG"],function(g,f){function e(b,a,c,d,f,g,m){this._callSuperConstructor(e,[a]);this.Wn=d;this.jb=f;this.jk=g;this.a=c;this.sa=b;this.No=m}var b,d;for(d in{Ad:!0})b=d;e.prototype={Ad:function(d){this._callSuperMethod(e,b,[d]);d||(this.sa.yA(this.jk,this.jb),this.No||this.sa.Ky(this.jk,this.jb))},verifySuccess:function(){return this.sa.Wu(this.a)&&this.Wn.q[this.jb]&&null!=this.Wn.q[this.jb].yh?!1:!0},ee:function(){this.sa.mA(this.jb,this)},Bj:D(), +Gv:G("No")};g(e,f);return e}); +define("lsT",["lsS","lsO","LoggerManager","lsA"],function(g,f,e,b){function d(a,b,d){this.di=!1;this.uj=0;this.Jd={};this.dd={};this.Gt=0;this.Q=a;this.r=b;this.af=d}var h=e.getLoggerProxy(b.ug);d.prototype={$:function(){this.di=!1;this.Jd={};this.Gt=0;this.dd={};this.uj++;h.logDebug(e.resolve(315))},hl:function(){h.logDebug(e.resolve(316));if(!this.di){for(var a in this.Jd){var b=this.Jd[a],d;for(d in b.q)if(null!=b.q[d].yh){var f=new g(this,this.af, +this.uj,b,d);this.Un(d,query,f)}}this.di=!0}},zg:function(a,c,d,f){h.logDebug(e.resolve(317));var k=this.Jd[c];null==k&&(k={Ff:0,q:{}},this.Jd[c]=k);k.Ff++;a={LS_message:a};var m=!1;d&&(a.LS_outcome="",m=!0);c!=b.zc&&(a.LS_sequence=encodeURIComponent(c),m=!0);f&&(a.LS_max_wait=f,m=!0);m&&(a.LS_ack="",a.LS_msg_prog=c==b.zc?this.Kr(k.Ff):k.Ff);f={};f.yh=a;f.mh=d;k.q[k.Ff]=f;this.di&&(h.logDebug(e.resolve(318),a),c=new g(this,this.af,this.uj,k,k.Ff,c,m),this.Un(k.Ff,a,c))},Kr:function(a){var b=++this.Gt; +this.dd[b]=a;return b},Ch:function(a){return this.dd[a]?this.dd[a]:a},eA:function(a){for(var b in this.dd)if(this.dd[b]==a){delete this.dd[b];break}},Pv:function(a){for(var b in this.dd)if(this.dd[b]==a)return b},Wu:function(a){return a==this.uj},mA:function(a,b){var d=b.Wn.q[a].yh;h.logDebug(e.resolve(319),d);this.Un(a,d,b)},Un:function(a,b,d){this.Q.Dc(a,b,f.Qe,d)},mu:function(a,c){c=a==b.zc?this.Ch(c):c;h.logInfo(e.resolve(310),a,c);var d=this.Jd[a];d.q[c]&&(null!=d.q[c].yh&&(h.logDebug(e.resolve(320)), +d.q[c].yh=null),null==d.q[c].mh&&(h.logDebug(e.resolve(321)),this.Ue(a,c)))},Ky:function(a,b){h.logDebug(e.resolve(322),a,b);this.Ue(a,b)},Ue:function(a,c){h.logDebug(e.resolve(323));var d=this.Jd[a];d&&d.q[c]&&(delete d.q[c],a==b.zc&&this.eA(c))},vb:function(a,b){var d=this.Jd[a];return d&&d.q[b]&&d.q[b].mh?d.q[b].mh:null},yA:function(a,b){h.logDebug(e.resolve(324),a,b);var d=this.vb(a,b);if(d){var f=this.r.td(d.kb);f&&f.ls(d.Ga)}},fu:function(a,c){c=a==b.zc?this.Ch(c):c;h.logInfo(e.resolve(311), +a,c);var d=this.vb(a,c);if(d){var f=this.r.td(d.kb);f&&f.js(d.Ga)}this.Ue(a,c)},Oy:function(a,c){c=a==b.zc?this.Ch(c):c;h.logInfo(e.resolve(312),a,c);var d=this.vb(a,c);if(d){var f=this.r.td(d.kb);f&&f.xe(d.Ga)}this.Ue(a,c)},Ny:function(a,c,d,f){f=a==b.zc?this.Ch(f):f;h.logInfo(e.resolve(313),a,f);var g=this.vb(a,f);if(g){var m=this.r.td(g.kb);m&&m.ks(g.Ga,c,d)}this.Ue(a,f)},Qy:function(a,c,d,f){f=a==b.zc?this.Ch(f):f;h.logInfo(e.resolve(314),a,f);var g=this.vb(a,f);if(g){var m=this.r.td(g.kb);m&& +m.Nf(g.Ga,c,d)}this.Ue(a,f)}};return d}); +define("lsi",["LoggerManager","Executor","Global","ASSERT","lsA"],function(g,f,e,b,d){function h(a){this.Ta=a;this.ub=[];this.ir=!1;this.lsc={};this.lsc.LS_window=e["_"+a];this.tv=this.aw(this.lsc)}var a=g.getLoggerProxy(d.ab);h.prototype={toString:function(){return"[EvalQueue|"+this.ub.length+"]"},aw:function(){eval("var lsc \x3d arguments[0]");return function(a){with(lsc)eval(a)}},lp:function(b,d){this.hg()&&(this.ub.push({Jj:b,sb:d}),a.isDebugLogEnabled()&&a.logDebug(g.resolve(326)), +f.addTimedTask(this.Ei,0,this))},$d:G("e"),Ei:function(){for(a.isDebugLogEnabled()&&a.logDebug(g.resolve(327),this.ub.length);0n.length)I(l[k],a);else if(b&&b[l[k]]){if(n[y.yc]!=b[l[k]])return A.logInfo(z.resolve(412)), +!0;A.logInfo(z.resolve(413))}else{var p=Number(n[y.yc])+y.Td+2E3-e;-6E4>=p?(A.logInfo(z.resolve(414)),I(l[k],a)):(pp?c.hi:p)}}if(d)return A.logInfo(z.resolve(415)),c;A.logInfo(z.resolve(416));return!1},zr:function(){return 1==this.f},Sx:function(){return 7==this.f},nd:function(a){a==this.f?N.verifyOk(2==a||1==a):(1==this.f&&7!=a&&f.ua(this.Hk,"lsPage",this,"P"),7!=a&&6!=a&&5!=a&&1!=a&&8!=a||this.wv(),1==a&&f.Cl(this.Hk,"lsPage","P"),this.f=a,this.ka++)}, +Wx:function(){return 5==this.f?!0:4==this.f?!1:null},Xz:function(a,c){if(!this.nb){var d=b.packTask(this.kt,this);this.nb=new e(c||null,a,!0,d,this.Hc);this.Wc=new n(this,this.Hk);this.Ck=new q(this.Wc,new t(this),!1);this.nb.XB(b.packTask(this.ee,this,[this.ka]))}},wv:function(){this.nb&&(this.nb.Ku(),this.nb=null,this.Wc&&(this.Wc.ia(),this.Wc=null),q.remove(this.Ck),this.Ck=null)},kt:function(a){2500a&&(a=50);return this.Jf=this.Jf&&this.Jf=b||b>this.ja.wm()+1)throw new f("the specified field position is out of bounds");return b+1},Sw:function(){return this.Pd.length-2},yw:function(b){return this.ja.getName(b)}};b.prototype.getItemName=b.prototype.zm;b.prototype.getItemPos= +b.prototype.Wi;b.prototype.getValue=b.prototype.getValue;b.prototype.isValueChanged=b.prototype.yr;b.prototype.isSnapshot=b.prototype.fy;b.prototype.forEachChangedField=b.prototype.forEachChangedField;b.prototype.forEachField=b.prototype.Hq;return b});define("lsX",[],function(){function g(){this.Xb=null;this.xb=0}g.prototype={mt:G("Xb"),wm:function(){return this.Xb?this.xb+this.Xb.xb:this.xb},Ld:G("xb")};return g}); +define("lsY",["Inheritance","lsX"],function(g,f){function e(b){this._callSuperConstructor(e);this.list=b;for(var d={},h=0;hthis.xb&&this.Xb?this.Xb.getName(b-this.xb):this.list[b-1]||null},Mc:M("list")};g(e,f);return e}); +define("lsZ",["Inheritance","lsX"],function(g,f){function e(b){this._callSuperConstructor(e);this.name=b}e.prototype={rm:M("name"),me:function(b){return this.Xb?(b=this.Xb.me(b),null!==b?b+this.xb:null):null},getName:function(b){return this.Xb?this.Xb.getName(b-this.xb):null},Mc:M("name")};g(e,f);return e}); +define("Matrix",[],function(){function g(f){this.ya=f||{}}g.prototype={insert:function(f,e,b){this.ya[e]||(this.ya[e]={});this.ya[e][b]=f},get:function(f,e){return this.ya[f]&&"undefined"!=typeof this.ya[f][e]?this.ya[f][e]:null},del:function(f,e){if(this.ya[f]){this.ya[f][e]&&delete this.ya[f][e];for(var b in this.ya[f])return;delete this.ya[f]}},insertRow:function(f,e){this.ya[e]=f},getRow:function(f){return this.ya[f]?this.ya[f]:null},delRow:function(f){this.ya[f]&&delete this.ya[f]},getEntireMatrix:M("ya")}; +g.prototype.insert=g.prototype.insert;g.prototype.get=g.prototype.get;g.prototype.del=g.prototype.del;g.prototype.insertRow=g.prototype.insertRow;g.prototype.getRow=g.prototype.getRow;g.prototype.delRow=g.prototype.delRow;g.prototype.getEntireMatrix=g.prototype.getEntireMatrix;return g}); +define("Subscription","lsAc lsAb lsY lsZ Inheritance Setter Matrix Executor lsA EventDispatcher IllegalArgumentException IllegalStateException LoggerManager lsG ASSERT Helpers".split(" "),function(g,f,e,b,d,h,a,c,l,n,k,m,p,q,r,t){function s(b,c,d){this._callSuperConstructor(s);b=(new String(b)).toUpperCase();if(!b||!y[b])throw new k("The given value is not a valid subscription mode. Admitted values are MERGE, DISTINCT, RAW, COMMAND"); +this.hd=b;this.ja=this.Jc=this.Mb=this.Rb=this.xf=this.yf=null;this.nc="RAW"===b?null:"yes";this.bk=this.Je=this.Bi=this.ci=this.kp=this.pp=this.De=this.Ya=null;this.we=new a;this.Sc=new a;this.d=null;this.Za=1;this.Ek=0;this.bc=null;this.Cb=0;this.yb=null;this.zh;this.Vj;this.ek=this.Dj=0;this.rb=this.hd==l.pg?2:1;this.Ho=this.keyCode=this.eb=null;this.Aa={};this.Nh=this.He=this.Md=null;this.BB=l.$k;if(c){if(!d||!t.isArray(d))throw new k("Please specify a valid field list");t.isArray(c)?this.Kh(c): +this.Kh([c]);this.pk(d)}else if(d)throw new k("Please specify a valid item or item list");}function E(a,b){for(var c=0;c=a||a>(c?b.wm():b.xb)?!1:a},Ds:function(){this.rb=null==this.Nh?2:3},jm:function(a){this.dispatchEvent("onEndOfSnapshot",[this.Rb.getName(a),a])},Fl:function(b){var c=this.Rb.getName(b);2==this.rb?this.Sc=new a:3==this.rb&&(this.Sc=new a,this.Os(b));this.dispatchEvent("onClearSnapshot",[c,b])},xy:function(a,b){this.dispatchEvent("onItemLostUpdates",[this.Rb.getName(a),a,b])},zA:function(a,b){this.dispatchEvent("onSubscriptionError",[a,b])},update:function(a,b,c){r.verifyValue(4, +this.Za);var d=a[1],e=new String(d);1!=this.rb&&(e=this.Jz(a,d,c));3!=this.rb||c||this.zx(a);1==this.rb?this.Kt(this.we,d,a,!0):this.Kt(this.Sc,e,a,!0);a=new f(this.Rb.getName(d),d,this.ja,b,a);this.dispatchEvent("onItemUpdate",[a]);"DELETE"==this.Sc.get(e,this.eb)&&this.Sc.delRow(e)},Kt:function(a,b,c,d){var e=c.length-2,h=1,f=2;for(c.Go={};h<=e;h++,f++)c[f]!==x?a.insert(c[f],b,h):d&&(c[f]=a.get(b,h),c.Go[f]=!0)},Jz:function(a,b,c){var d;if("undefined"==typeof a[this.keyCode+1]||"undefined"==typeof a[this.eb+ +1])return w.logWarn(p.resolve(433)),null;d=a[this.keyCode+1]==x?b+" "+this.we.get(b,this.keyCode):b+" "+a[this.keyCode+1];if(c)a[this.keyCode+1]=x,a[this.eb+1]==this.Sc.get(d,this.eb)?a[this.eb+1]=x:(a.Gc.push(this.eb),a.Gc.sort(C));else{a.Gc=[];for(c=2;ca.length))for(c=a.length;c")[1].split()[0]) + low += 1 + breaks[market["epic"]] = low + + proc = await self.init_lightstreamer([*breaks.keys()]) + + book = {} + async for line in proc.stdout: + data = json.loads(line.strip().decode()) + epic = data["id"] + br = breaks[epic] + asks = [] + bids = [] + for p, s in data["asks"]: + p = cents(p) + asks.append((conv_lim_pc(br, None).add_conditional(-100) + (-p - 100), s, p)) + for p, s in data["bids"]: + p = cents(p) + bids.append(((-conv_lim_pc(br, None)).add_conditional(-100) + (p - 100), s, p)) + book[epic] = Digital( + low = br, + high = None, + bids = bids, + asks = asks, + exchange = self, + market_id = epic, + ) + yield [*book.values()] + +async def main(): + async with aiohttp.ClientSession() as rs: + s = NadexSession(rs) + await s.login() + await s.execute_order("NB.D.U500.OPT-1-16-04Nov22.IP", 600, 1, True) + # async for book in s.orderbook_stream(160809): + # pprint(book) + # pass + +if __name__=="__main__": + asyncio.run(main()) + + diff --git a/nadex_ws.js b/nadex_ws.js new file mode 100755 index 0000000..bdf0522 --- /dev/null +++ b/nadex_ws.js @@ -0,0 +1,43 @@ +var fs = require("fs"); + +var stdin_buf = fs.readFileSync(0); +({username, security_token, marketids, servername} = JSON.parse(stdin_buf.toString().trim())); + +requirejs = require("requirejs"); +requirejs.config({ nodeRequire: require}); +requirejs("./lightstreamer.js"); + +requirejs(["LightstreamerClient","Subscription"], (LightstreamerClient, Subscription) => { + var client = new LightstreamerClient(servername, 'InVisionProvider'); + client.connectionDetails.setUser(username); + client.connectionDetails.setPassword("XST-"+security_token); + client.connect(); + + var sub = new Subscription( + "MERGE", + marketids.map((mid)=>"V2-F-BD1,AK1,BS1,AS1,BD2,AK2,BS2,AS2,BD3,AK3,BS3,AS3,BD4,AK4,BS4,AS4,BD5,AK5,BS5,AS5|"+mid), + ['displayOffer', 'displayBid', 'bidSize', 'offerSize', 'displayOffer2', 'displayBid2', 'bidSize2', 'offerSize2', 'displayOffer3', 'displayBid3', 'bidSize3', 'offerSize3', 'displayOffer4', 'displayBid4', 'bidSize4', 'offerSize4', 'displayOffer5', 'displayBid5', 'bidSize5', 'offerSize5'] + ) + sub.setRequestedSnapshot('yes'); + sub.setRequestedMaxFrequency(1); + sub.addListener({ + onItemUpdate: function(update) { + let bids = [[], [], [], [], []]; + let asks = [[], [], [], [], []]; + update.forEachField(function (name, pos, value) { + let b = !(name.toLowerCase().includes("bid")); + let s = name.includes("Size"); + let p = parseInt(name.slice(-1)) || 1; + (b^s ? bids : asks)[p-1][s+0] = (s ? parseInt(value) : value); + }); + let ret = { + id: update.getItemName().split("|")[1], + bids: bids.filter((a)=>a[0]), + asks: asks.filter((a)=>a[0]), + }; + console.log(JSON.stringify(ret)); + } + }); + client.subscribe(sub); +}); + diff --git a/nasdaqdaily.py b/nasdaqdaily.py new file mode 100644 index 0000000..03060e1 --- /dev/null +++ b/nasdaqdaily.py @@ -0,0 +1,69 @@ +from pprint import pprint +import asyncio +import aiostream +import aiohttp +import kalshi +from nadex import NadexSession +from decimal import Decimal as D, ROUND_CEILING +import os +import sys +from utils import * +from piecewise import * +from decimal import Decimal + +async def check(ops): + cps = [] + for o, q in ops: + if (q>0 and not o.asks) or (q<0 and not o.bids): return False + cps.append(o.asks[0][0] if q>0 else o.bids[0][0]) + + cc = min_sum_pc(cps) + + global best + if cc>best: + updlog(D(cc)/D(100)) + u = [o.asks if q>0 else o.bids for o, q in ops] + for i, (o, q) in enumerate(ops): + updlog([o.low, o.high, u[i][0], o.exchange, o.market_id], q) + updlog() + best = cc + + if cc>2000: + await asyncio.gather(*[ + o.exchange.execute_order(o.market_id, u[i][0][-1], 1, q>0) + for i, (o, q) in enumerate(ops) + ]) + return True + + return False + +async def main(): + async with aiohttp.ClientSession() as kcs, aiohttp.ClientSession() as ncs: + ks = kalshi.KalshiSession(kcs) + ns = NadexSession(ncs) + await asyncio.gather(ks.login(), ns.login()) + + global best + best = -10000000 + upd = 0 + async with aiostream.stream.ziplatest( + ks.orderbook_stream("NASDAQ100D-23MAR14", kalshi.convert_nasdaq_rulebook, "NQTM23.CME"), + ns.orderbook_stream(160809 if DEMO else 157992), + partial=False + ).stream() as streamer: + async for kbook, nbook in streamer: + upd +=1 + if upd%100==0: updlog(upd) + + for k1 in kbook: + for n1 in nbook: + for ord in [(-1, 1), (1, -1), (1, 1), (-1, -1)]: + if await check([(k1, ord[0]), (n1, ord[1])]): + return + for n2 in nbook: + for ord in [(-1, 1, -1), (-1, -1, 1)]: + if await check([(k1, ord[0]), (n1, ord[1]), (n2, ord[2])]): + return + +if __name__=="__main__": + asyncio.run(main()) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d37c555 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,162 @@ +{ + "name": "predict_arb", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "faye-websocket": "^0.11.4", + "requirejs": "^2.3.6", + "ws": "^8.10.0", + "xmlhttprequest": "^1.8.0" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ws": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz", + "integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", + "engines": { + "node": ">=0.4.0" + } + } + }, + "dependencies": { + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "ws": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz", + "integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==", + "requires": {} + }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..09292cb --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "faye-websocket": "^0.11.4", + "requirejs": "^2.3.6", + "ws": "^8.10.0", + "xmlhttprequest": "^1.8.0" + } +} diff --git a/piecewise.py b/piecewise.py new file mode 100644 index 0000000..ec51f3f --- /dev/null +++ b/piecewise.py @@ -0,0 +1,58 @@ +from typing import NamedTuple + +class Piecewise_Constant(NamedTuple): + start: int + steps: list[tuple[int, int]] + + def __rmul__(self, c): + return Piecewise_Constant( + self.start*c, + [(i, j*c) for i, j in self.steps] + ) + + def __neg__(self): + return (-1)*self + + def __add__(self, a): + return Piecewise_Constant( + self.start+a, + [(i, j+a) for i, j in self.steps] + ) + + def add_conditional(self, fee): + return Piecewise_Constant( + self.start + fee if self.start else 0, + [(i, j+fee if j else 0) for i, j in self.steps] + ) + +# assumes contract size of $100 +def conv_lim_pc(low, high): + s = 10000 if low is None else 0 + t = [] + if low is not None: + t.append((low, 10000)) + if high is not None: + t.append((high + 1, 0)) + return Piecewise_Constant(s, t) + +def min_sum_pc(funcs): + sum = 0 + evs = [] + for f in funcs: + sum += f.start + last = f.start + for step in f.steps: + evs.append((step[0], step[1]-last)) + last = step[1] + evs.sort() + i = 0 + ans = sum + while i0 and not o.asks) or (q<0 and not o.bids): return False + cps.append(o.asks[0][0] if q>0 else o.bids[0][0]) + + cc = min_sum_pc(cps) + + global best + if cc>best: + updlog(D(cc)/D(100)) + u = [o.asks if q>0 else o.bids for o, q in ops] + for i, (o, q) in enumerate(ops): + updlog([o.low, o.high, u[i][0], o.exchange, o.market_id], q) + updlog() + best = cc + + if cc>15000: + await asyncio.gather(*[ + o.exchange.execute_order(o.market_id, u[i][0][-1], 1, q>0) + for i, (o, q) in enumerate(ops) + ]) + return True + + return False + +async def main(): + async with aiohttp.ClientSession() as kcs, aiohttp.ClientSession() as ncs: + ks = kalshi.KalshiSession(kcs) + ns = NadexSession(ncs) + await asyncio.gather(ks.login(), ns.login()) + + global best + best = -10000000 + upd = 0 + async with aiostream.stream.ziplatest( + ks.orderbook_stream("INXD-23MAR15", kalshi.convert_sp_rulebook, "ESTM23.CME"), + ns.orderbook_stream(160809 if DEMO else 157993), + partial=False + ).stream() as streamer: + async for kbook, nbook in streamer: + upd +=1 + if upd%100==0: updlog(upd) + + for k1 in kbook: + for n1 in nbook: + for ord in [(-1, 1), (1, -1), (1, 1), (-1, -1)]: + if await check([(k1, ord[0]), (n1, ord[1])]): + return + for n2 in nbook: + for ord in [(-1, 1, -1), (-1, -1, 1)]: + if await check([(k1, ord[0]), (n1, ord[1]), (n2, ord[2])]): + return + +if __name__=="__main__": + asyncio.run(main()) diff --git a/test_kalshi.py b/test_kalshi.py new file mode 100644 index 0000000..1b7c045 --- /dev/null +++ b/test_kalshi.py @@ -0,0 +1,160 @@ +import pytest +from kalshi import * +import kalshi +from piecewise import * +import json +from mocks import MockResponse + +class TestConvertHundredLower: + def test_empty(self): + assert convert_contract_hundred_lower([]) == [] + + def test_single(self): + assert convert_contract_hundred_lower([(20, 100)]) == [(2000, 1)] + assert convert_contract_hundred_lower([(20, 110)]) == [(2000, 1)] + assert convert_contract_hundred_lower([(20, 310)]) == [(2000, 3)] + assert convert_contract_hundred_lower([(20, 10)]) == [] + + def test_two(self): + assert convert_contract_hundred_lower([(50, 10), (20, 100)]) == [(2000, 1)] + assert convert_contract_hundred_lower([(50, 10), (20, 90)]) == [(2000, 1)] + assert convert_contract_hundred_lower([(50, 10), (20, 91)]) == [(2000, 1)] + assert convert_contract_hundred_lower([(50, 10), (20, 89)]) == [] + assert convert_contract_hundred_lower([(50, 100), (20, 91)]) == [(5000, 1)] + assert convert_contract_hundred_lower([(50, 109), (20, 91)]) == [(5000, 1), (2000, 1)] + assert convert_contract_hundred_lower([(50, 109), (20, 92)]) == [(5000, 1), (2000, 1)] + assert convert_contract_hundred_lower([(50, 109), (20, 90)]) == [(5000, 1)] + assert convert_contract_hundred_lower([(50, 209), (20, 192)]) == [(5000, 2), (2000, 2)] + assert convert_contract_hundred_lower([(50, 209), (20, 190)]) == [(5000, 2), (2000, 1)] + + def test_more(self): + assert convert_contract_hundred_lower([(50, 110), (20, 230), (3, 60)]) == [(5000, 1), (2000, 2), (300, 1)] + assert convert_contract_hundred_lower([(50, 110), (20, 229), (3, 60)]) == [(5000, 1), (2000, 2)] + assert convert_contract_hundred_lower([(50, 110), (20, 229), (3, 160)]) == [(5000, 1), (2000, 2), (300, 1)] + assert convert_contract_hundred_lower([(50, 111), (20, 229), (3, 160)]) == [(5000, 1), (2000, 2), (300, 2)] + +class TestConvertSPRulebook: + def test_or_lower(self): + assert convert_sp_rulebook({ + "Above/Below/Between": "below", + "Cap_strike": "NA", + "Date": "November 04, 2022", + "Floor_strike": "NA", + "Value": "3600", + "contract_ticker": "INX" + }) == (None, 359999) + + def test_or_higher(self): + assert convert_sp_rulebook({ + "Above/Below/Between": "above", + "Cap_strike": "NA", + "Date": "November 04, 2022", + "Floor_strike": "NA", + "Value": "4249.99", + "contract_ticker": "INX" + }) == (425000, None) + + def test_between(self): + assert convert_sp_rulebook({ + "Above/Below/Between": "between", + "Cap_strike": "3999.99", + "Date": "November 04, 2022", + "Floor_strike": "3950", + "Value": "3950-3999.99", + "contract_ticker": "INX" + }) == (395000, 399999) + +class TestParseOrderbookMessage: + def test_empty(self): + assert parse_orderbook_message({ "type": "orderbook_snapshot", "sid": 3, "seq": 1, "msg": { "market_id": "id123" }}) == ("id123", [], []) + assert parse_orderbook_message({ "type": "orderbook_snapshot", "sid": 3, "seq": 1, "msg": { "market_id": "id123", "no": [] }}) == ("id123", [], []) + + def test_yes(self): + assert parse_orderbook_message({ "type": "orderbook_snapshot", "sid": 5, "seq": 1, "msg": { "market_id": "id123", "yes": [ [ 1, 100 ], [ 3, 500 ], [ 9, 10 ], [ 10, 20 ] ]} }) == ("id123", [(10, 20), (9, 10), (3, 500), (1, 100)], []) + + def test_no(self): + assert parse_orderbook_message({ "type": "orderbook_snapshot", "sid": 5, "seq": 1, "msg": { "market_id": "id123", "no": [ [ 5, 2500 ], [ 25, 1000 ], [ 65, 150 ], [ 66, 100 ], [ 68, 8 ], [ 75, 128 ] ] } }) == ("id123", [], [(25, 128), (32, 8), (34, 100), (35, 150), (75, 1000), (95, 2500)]) + + def test_both(self): + assert parse_orderbook_message({ "type": "orderbook_snapshot", "sid": 5, "seq": 1, "msg": { "market_id": "id123", "yes": [ [ 1, 100 ], [ 3, 500 ], [ 9, 10 ], [ 10, 20 ] ], "no": [ [ 5, 2500 ], [ 25, 1000 ], [ 65, 150 ], [ 66, 100 ], [ 68, 8 ], [ 75, 128 ] ] } }) == ("id123", [(10, 20), (9, 10), (3, 500), (1, 100)], [(25, 128), (32, 8), (34, 100), (35, 150), (75, 1000), (95, 2500)]) + +class TestCalcFee: + def test_one_normal(self): + for p, ans in [[1, 1], [5, 1], [10, 1], [15, 1], [20, 2], [25, 2], [30, 2], [35, 2], [40, 2], [45, 2], [50, 2], [55, 2], [60, 2], [65, 2], [70, 2], [75, 2], [80, 2], [85, 1], [90, 1], [95, 1], [99, 1]]: + assert calc_fee(p, 1, False) == ans + + def test_100_normal(self): + for p, ans in [[1, 7], [5, 34], [10, 63], [15, 90], [20, 112], [25, 132], [30, 147], [35, 160], [40, 168], [45, 174], [50, 175], [55, 174], [60, 168], [65, 160], [70, 147], [75, 132], [80, 112], [85, 90], [90, 63], [95, 34], [99, 7]]: + assert calc_fee(p*100, 100, False) == ans + + def test_one_index(self): + for p, ans in [[1, 1], [5, 1], [10, 1], [15, 1], [20, 1], [25, 1], [30, 1], [35, 1], [40, 1], [45, 1], [50, 1], [55, 1], [60, 1], [65, 1], [70, 1], [75, 1], [80, 1], [85, 1], [90, 1], [95, 1], [99, 1]]: + assert calc_fee(p, 1, True) == ans + + def test_100_index(self): + for p, ans in [[1, 4], [5, 17], [10, 32], [15, 45], [20, 56], [25, 66], [30, 74], [35, 80], [40, 84], [45, 87], [50, 88], [55, 87], [60, 84], [65, 80], [70, 74], [75, 66], [80, 56], [85, 45], [90, 32], [95, 17], [99, 4]]: + assert calc_fee(p*100, 100, True) == ans + +class TestConvToPC: + @staticmethod + def mock_calcfee(p, cs, is_index): + assert p==2000 + assert cs==100 + assert is_index==True + return 103 + + def setup_method(self): + self._calc_fee = kalshi.calc_fee + kalshi.calc_fee = TestConvToPC.mock_calcfee + + def teardown_method(self): + kalshi.calc_fee = self._calc_fee + + def test_ask_left(self): + assert conv_ask_to_pc((None, 30000), 2000, 3, True, True) == (Piecewise_Constant(10000-2000-103, [(30001, -2000-103)]), 3, 2000) + + def test_ask_right(self): + assert conv_ask_to_pc((30000, None), 2000, 3, True, True) == (Piecewise_Constant(-2000-103, [(30000, 10000-2000-103)]), 3, 2000) + + def test_ask_both(self): + assert conv_ask_to_pc((30000, 50000), 2000, 3, True, True) == (Piecewise_Constant(-2000-103, [(30000, 10000-2000-103), (50001, -2000-103)]), 3, 2000) + + def test_bid_left(self): + assert conv_bid_to_pc((None, 30000), 2000, 3, True, True) == (Piecewise_Constant(-10000+2000-103, [(30001, 2000-103)]), 3, 2000) + + def test_bid_right(self): + assert conv_bid_to_pc((30000, None), 2000, 3, True, True) == (Piecewise_Constant(2000-103, [(30000, -10000+2000-103)]), 3, 2000) + + def test_bid_both(self): + assert conv_bid_to_pc((30000, 50000), 2000, 3, True, True) == (Piecewise_Constant(2000-103, [(30000, -10000+2000-103), (50001, 2000-103)]), 3, 2000) + +@pytest.mark.asyncio +async def test_markets_from_ticker(monkeypatch): + def mock_get(self, url): + assert url=="https://trading-api.kalshi.com/v1/events/INXW-22NOV04" + return MockResponse(json.loads("""{"event":{"ticker":"INXW-22NOV04","series_ticker":"INX","target_datetime":"2022-11-04T20:00:00Z","mutually_exclusive":true,"mutually_exclusive_side":"yes","title":"What will the value of the S&P 500 be at the end of Nov 4, 2022","category":"Financials","tags":["Stocks"],"min_tick_size":"$0.01","settle_details":"The market will close at 4:00 PM ET on || Date ||. The market will expire at the sooner of the first 7:00 PM ET after the release of the data, or one week after || Date ||.\\n\\nPursuant to the Kalshi Rulebook, the Exchange has modified the Source Agency and Underlying for indices markets. See the rules for more information.","settlement_sources":[{"url":"https://www.google.com/finance/quote/.INX:INDEXSP?hl=en","name":"For example, Google Finance"}],"description_context":"","underlying":"If the end-of-day S&P 500 index value for || Date || is ||Above/Below/Between|| ||Value||, then the market resolves to Yes.","metrics_tags":["Nasdaq","QQQ","Nasdaq-100","S&P","S&P 500","Standard & Poor's","Stock Market","Stocks","Equities","Tech","SPY","SPX"],"mini_title":"TEMP","sub_title":"On Nov 4, 2022","markets":[{"id":"222299db-a2ce-444b-ace7-ea4449c8ba2e","ticker_name":"INXW-22NOV04-T3600","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":2,"yes_ask":30,"last_price":1,"previous_yes_bid":1,"previous_yes_ask":3,"previous_price":5,"volume":4604,"recent_volume":1614,"open_interest":3850,"liquidity":386074,"dollar_volume":2302,"dollar_recent_volume":807,"dollar_open_interest":1925,"sub_title":"3,599.99 or lower","title":"Will the S&P 500 be below 3600 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"3599.99 or below","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"below","Cap_strike":"NA","Date":"November 04, 2022","Floor_strike":"NA","Value":"3600","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"95d88ef0-94d3-4acd-9c60-848d87f2dc42","ticker_name":"INXW-22NOV04-B3625","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":4,"yes_ask":8,"last_price":9,"previous_yes_bid":2,"previous_yes_ask":25,"previous_price":1,"volume":8094,"recent_volume":4788,"open_interest":5600,"liquidity":552367,"dollar_volume":4047,"dollar_recent_volume":2394,"dollar_open_interest":2800,"sub_title":"3,600 to 3,649.99","title":"Will the S&P 500 be between 3600 and 3649.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"3600 to 3649.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"3649.99","Date":"November 04, 2022","Floor_strike":"3600","Value":"3600-3649.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"095be654-b0dc-442d-b3f8-3a684ae41242","ticker_name":"INXW-22NOV04-B3675","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":10,"yes_ask":30,"last_price":27,"previous_yes_bid":3,"previous_yes_ask":25,"previous_price":25,"volume":5302,"recent_volume":4722,"open_interest":5074,"liquidity":388560,"dollar_volume":2651,"dollar_recent_volume":2361,"dollar_open_interest":2537,"sub_title":"3,650 to 3,699.99","title":"Will the S&P 500 be between 3650 and 3699.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"3650 to 3699.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"3699.99","Date":"November 04, 2022","Floor_strike":"3650","Value":"3650-3699.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"478093bf-8dc5-420d-af28-58a88d9a2404","ticker_name":"INXW-22NOV04-B3725","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":32,"yes_ask":38,"last_price":36,"previous_yes_bid":26,"previous_yes_ask":74,"previous_price":75,"volume":16386,"recent_volume":10326,"open_interest":9230,"liquidity":367970,"dollar_volume":8193,"dollar_recent_volume":5163,"dollar_open_interest":4615,"sub_title":"3,700 to 3,749.99","title":"Will the S&P 500 be between 3700 and 3749.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"3700 to 3749.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"3749.99","Date":"November 04, 2022","Floor_strike":"3700","Value":"3700-3749.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"86f602c6-25a4-4e81-959b-db217827002c","ticker_name":"INXW-22NOV04-B3775","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":5,"yes_ask":31,"last_price":26,"previous_yes_bid":10,"previous_yes_ask":25,"previous_price":25,"volume":5322,"recent_volume":4194,"open_interest":2736,"liquidity":330050,"dollar_volume":2661,"dollar_recent_volume":2097,"dollar_open_interest":1368,"sub_title":"3,750 to 3,799.99","title":"Will the S&P 500 be between 3750 and 3799.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"3750 to 3799.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"3799.99","Date":"November 04, 2022","Floor_strike":"3750","Value":"3750-3799.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"dcff5f0e-18c4-4584-82fa-88bc2896694a","ticker_name":"INXW-22NOV04-B3825","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":6,"yes_ask":9,"last_price":6,"previous_yes_bid":10,"previous_yes_ask":25,"previous_price":25,"volume":2922,"recent_volume":2458,"open_interest":2200,"liquidity":475270,"dollar_volume":1461,"dollar_recent_volume":1229,"dollar_open_interest":1100,"sub_title":"3,800 to 3,849.99","title":"Will the S&P 500 be between 3800 and 3849.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"3800 to 3849.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"3849.99","Date":"November 04, 2022","Floor_strike":"3800","Value":"3800-3849.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"92c30c3b-8f7d-4595-9768-d90cff3bc380","ticker_name":"INXW-22NOV04-B3875","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":0,"yes_ask":2,"last_price":2,"previous_yes_bid":11,"previous_yes_ask":30,"previous_price":34,"volume":3448,"recent_volume":892,"open_interest":2434,"liquidity":327136,"dollar_volume":1724,"dollar_recent_volume":446,"dollar_open_interest":1217,"sub_title":"3,850 to 3,899.99","title":"Will the S&P 500 be between 3850 and 3899.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"3850 to 3899.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"3899.99","Date":"November 04, 2022","Floor_strike":"3850","Value":"3850-3899.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"77b607b9-e6bd-49f4-b7ea-cc5467b69821","ticker_name":"INXW-22NOV04-B3925","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":0,"yes_ask":1,"last_price":1,"previous_yes_bid":10,"previous_yes_ask":20,"previous_price":14,"volume":814,"recent_volume":332,"open_interest":508,"liquidity":328122,"dollar_volume":407,"dollar_recent_volume":166,"dollar_open_interest":254,"sub_title":"3,900 to 3,949.99","title":"Will the S&P 500 be between 3900 and 3949.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"3900 to 3949.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"3949.99","Date":"November 04, 2022","Floor_strike":"3900","Value":"3900-3949.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"fbb706b8-f6db-4b1a-aee2-3960aa112bcc","ticker_name":"INXW-22NOV04-B3975","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":0,"yes_ask":1,"last_price":1,"previous_yes_bid":10,"previous_yes_ask":14,"previous_price":11,"volume":960,"recent_volume":220,"open_interest":920,"liquidity":329353,"dollar_volume":480,"dollar_recent_volume":110,"dollar_open_interest":460,"sub_title":"3,950 to 3,999.99","title":"Will the S&P 500 be between 3950 and 3999.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"3950 to 3999.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"3999.99","Date":"November 04, 2022","Floor_strike":"3950","Value":"3950-3999.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"c790e6cf-a724-497c-88f5-beab9827b7ec","ticker_name":"INXW-22NOV04-B4025","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":0,"yes_ask":1,"last_price":1,"previous_yes_bid":5,"previous_yes_ask":8,"previous_price":6,"volume":3042,"recent_volume":682,"open_interest":2562,"liquidity":340043,"dollar_volume":1521,"dollar_recent_volume":341,"dollar_open_interest":1281,"sub_title":"4,000 to 4,049.99","title":"Will the S&P 500 be between 4000 and 4049.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"4000 to 4049.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"4049.99","Date":"November 04, 2022","Floor_strike":"4000","Value":"4000-4049.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"b3132a7c-8b6a-4d10-97b5-93e5185a0115","ticker_name":"INXW-22NOV04-B4075","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":0,"yes_ask":1,"last_price":1,"previous_yes_bid":0,"previous_yes_ask":1,"previous_price":1,"volume":708,"recent_volume":168,"open_interest":640,"liquidity":326516,"dollar_volume":354,"dollar_recent_volume":84,"dollar_open_interest":320,"sub_title":"4,050 to 4,099.99","title":"Will the S&P 500 be between 4050 and 4099.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"4050 to 4099.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"4099.99","Date":"November 04, 2022","Floor_strike":"4050","Value":"4050-4099.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"8e5817ba-13a0-4091-b7ad-4f06107c4b88","ticker_name":"INXW-22NOV04-B4125","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":0,"yes_ask":1,"last_price":1,"previous_yes_bid":0,"previous_yes_ask":1,"previous_price":1,"volume":640,"recent_volume":590,"open_interest":640,"liquidity":327015,"dollar_volume":320,"dollar_recent_volume":295,"dollar_open_interest":320,"sub_title":"4,100 to 4,149.99","title":"Will the S&P 500 be between 4100 and 4149.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"4100 to 4149.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"4149.99","Date":"November 04, 2022","Floor_strike":"4100","Value":"4100-4149.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"baaebca4-d251-4e10-848f-4a221b4a3ff1","ticker_name":"INXW-22NOV04-B4175","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":0,"yes_ask":1,"last_price":1,"previous_yes_bid":1,"previous_yes_ask":20,"previous_price":1,"volume":2660,"recent_volume":2620,"open_interest":2640,"liquidity":326435,"dollar_volume":1330,"dollar_recent_volume":1310,"dollar_open_interest":1320,"sub_title":"4,150 to 4,199.99","title":"Will the S&P 500 be between 4150 and 4199.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"4150 to 4199.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"4199.99","Date":"November 04, 2022","Floor_strike":"4150","Value":"4150-4199.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"389b8a4a-1f6d-42ac-bddc-1d4a1db071b9","ticker_name":"INXW-22NOV04-B4225","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":0,"yes_ask":1,"last_price":1,"previous_yes_bid":0,"previous_yes_ask":5,"previous_price":1,"volume":420,"recent_volume":10,"open_interest":420,"liquidity":324700,"dollar_volume":210,"dollar_recent_volume":5,"dollar_open_interest":210,"sub_title":"4,200 to 4,249.99","title":"Will the S&P 500 be between 4200 and 4249.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"4200 to 4249.99","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"between","Cap_strike":"4249.99","Date":"November 04, 2022","Floor_strike":"4200","Value":"4200-4249.99","contract_ticker":"INX"},"risk_limit_cents":0},{"id":"1671f853-2757-4268-81fe-5f7ebe0a021c","ticker_name":"INXW-22NOV04-T4249.99","create_date":"2022-10-28T20:05:34.870432Z","list_date":"2022-10-28T20:30:00Z","open_date":"2022-10-28T20:30:00Z","close_date":"2022-11-04T20:00:00Z","expiration_date":"2022-11-11T23:00:00Z","status":"active","expiration_value":"","result":"","yes_bid":0,"yes_ask":1,"last_price":1,"previous_yes_bid":0,"previous_yes_ask":1,"previous_price":0,"volume":200,"recent_volume":200,"open_interest":200,"liquidity":326150,"dollar_volume":100,"dollar_recent_volume":100,"dollar_open_interest":100,"sub_title":"4,250 or higher","title":"Will the S&P 500 be above 4249.99 at the end of Nov 4, 2022?","mini_title":"S&P500 value","name":"4250.0 or above","can_close_early":true,"rulebook_variables":{"Above/Below/Between":"above","Cap_strike":"NA","Date":"November 04, 2022","Floor_strike":"NA","Value":"4249.99","contract_ticker":"INX"},"risk_limit_cents":0}]}}""") + , 200) + + with monkeypatch.context() as m: + m.setattr("aiohttp.ClientSession.get", mock_get) + async with aiohttp.ClientSession() as s: + ks = KalshiSession(s) + lims = await ks.markets_from_ticker("INXW-22NOV04", convert_sp_rulebook) + assert lims=={ + "222299db-a2ce-444b-ace7-ea4449c8ba2e": (None, 359999), + "95d88ef0-94d3-4acd-9c60-848d87f2dc42": (360000, 364999), + "095be654-b0dc-442d-b3f8-3a684ae41242": (365000, 369999), + "478093bf-8dc5-420d-af28-58a88d9a2404": (370000, 374999), + "86f602c6-25a4-4e81-959b-db217827002c": (375000, 379999), + "dcff5f0e-18c4-4584-82fa-88bc2896694a": (380000, 384999), + "92c30c3b-8f7d-4595-9768-d90cff3bc380": (385000, 389999), + "77b607b9-e6bd-49f4-b7ea-cc5467b69821": (390000, 394999), + "fbb706b8-f6db-4b1a-aee2-3960aa112bcc": (395000, 399999), + "c790e6cf-a724-497c-88f5-beab9827b7ec": (400000, 404999), + "b3132a7c-8b6a-4d10-97b5-93e5185a0115": (405000, 409999), + "8e5817ba-13a0-4091-b7ad-4f06107c4b88": (410000, 414999), + "baaebca4-d251-4e10-848f-4a221b4a3ff1": (415000, 419999), + "389b8a4a-1f6d-42ac-bddc-1d4a1db071b9": (420000, 424999), + "1671f853-2757-4268-81fe-5f7ebe0a021c": (425000, None), + } + diff --git a/test_piecewise.py b/test_piecewise.py new file mode 100644 index 0000000..c6fecce --- /dev/null +++ b/test_piecewise.py @@ -0,0 +1,67 @@ +import pytest +from piecewise import * + +class TestPiecewiseConstant: + def test_rmul(self): + pc = Piecewise_Constant(7, [(-10, 5), (2, 3)]) + assert 3*pc == Piecewise_Constant(21, [(-10, 15), (2, 9)]) + + def test_neg(self): + pc = Piecewise_Constant(7, [(-10, 5), (1, 0), (2, 3)]) + assert -pc == Piecewise_Constant(-7, [(-10, -5), (1, 0), (2, -3)]) + + def test_add(self): + pc = Piecewise_Constant(7, [(-10, 5), (1, 0), (2, 3)]) + assert pc+11 == Piecewise_Constant(18, [(-10, 16), (1, 11), (2, 14)]) + + def test_add_conditional_nzstart(self): + pc = Piecewise_Constant(7, [(-10, 5), (1, 0), (2, 3)]) + assert pc.add_conditional(-3) == Piecewise_Constant(4, [(-10, 2), (1, 0), (2, 0)]) + + def test_add_conditional_zstart(self): + pc = Piecewise_Constant(0, [(-10, 5), (1, 0), (2, 3)]) + assert pc.add_conditional(-3) == Piecewise_Constant(0, [(-10, 2), (1, 0), (2, 0)]) + +class TestConvLimPC: + def test_empty(self): + assert conv_lim_pc(None, None) == Piecewise_Constant(10000, []) + + def test_no_low(self): + assert conv_lim_pc(None, 3000) == Piecewise_Constant(10000, [(3001, 0)]) + + def test_no_high(self): + assert conv_lim_pc(3000, None) == Piecewise_Constant(0, [(3000, 10000)]) + + def test_full(self): + assert conv_lim_pc(3000, 5000) == Piecewise_Constant(0, [(3000, 10000), (5001, 0)]) + +class TestMinSumPC: + def test_none(self): + assert min_sum_pc([]) == 0 + + def test_single_single(self): + assert min_sum_pc([Piecewise_Constant(0, [])]) == 0 + assert min_sum_pc([Piecewise_Constant(-10, [])]) == -10 + assert min_sum_pc([Piecewise_Constant(10, [])]) == 10 + + def test_single_mult(self): + assert min_sum_pc([Piecewise_Constant(0, [(-20, 40)])]) == 0 + assert min_sum_pc([Piecewise_Constant(50, [(-20, 40)])]) == 40 + assert min_sum_pc([Piecewise_Constant(-10, [(-20, 40), (1, 70)])]) == -10 + assert min_sum_pc([Piecewise_Constant(100, [(-20, 70), (1, 40)])]) == 40 + assert min_sum_pc([Piecewise_Constant(100, [(-20, 70), (1, 40), (17, 0)])]) == 0 + + def test_mult_single(self): + assert min_sum_pc([Piecewise_Constant(0, []), Piecewise_Constant(10, [])]) == 10 + assert min_sum_pc([Piecewise_Constant(0, []), Piecewise_Constant(-10, [])]) == -10 + assert min_sum_pc([Piecewise_Constant(-10, []), Piecewise_Constant(0, [])]) == -10 + assert min_sum_pc([Piecewise_Constant(20, []), Piecewise_Constant(30, [])]) == 50 + + def test_mult_mult(self): + assert min_sum_pc([Piecewise_Constant(0, [(5, 11)]), Piecewise_Constant(0, [(4, 7)])]) == 0 + assert min_sum_pc([Piecewise_Constant(0, [(5, 11)]), Piecewise_Constant(100, [(4, 7)])]) == 7 + assert min_sum_pc([Piecewise_Constant(100, [(5, 11)]), Piecewise_Constant(0, [(4, 7)])]) == 18 + assert min_sum_pc([Piecewise_Constant(100, [(5, 11), (10, 200)]), Piecewise_Constant(100, [(5, 7), (8, 200)])]) == 18 + assert min_sum_pc([Piecewise_Constant(0, [(5, 11), (10, 200)]), Piecewise_Constant(0, [(6, -20), (8, 200)])]) == -9 + + diff --git a/test_utils.py b/test_utils.py new file mode 100644 index 0000000..b87a2b3 --- /dev/null +++ b/test_utils.py @@ -0,0 +1,77 @@ +import pytest +from utils import * +import importlib +import sys + +class Test_Cents: + def test_zeros(self): + assert cents("0") == 0 + assert cents("0.") == 0 + assert cents("00.") == 0 + assert cents("00") == 0 + assert cents("0.0") == 0 + assert cents("00.0") == 0 + assert cents("0.00") == 0 + assert cents("00.00") == 0 + assert cents(".0") == 0 + assert cents(".00") == 0 + + def test_less_than_dollar(self): + assert cents("0.99") == 99 + assert cents("0.9") == 90 + assert cents("0.90") == 90 + assert cents("0.02") == 2 + assert cents(".99") == 99 + assert cents(".9") == 90 + assert cents(".90") == 90 + assert cents(".02") == 2 + + def test_integral(self): + assert cents("1") == 100 + assert cents("3") == 300 + assert cents("25") == 2500 + assert cents("2.0") == 200 + assert cents("2.") == 200 + assert cents("12.") == 1200 + assert cents("2.00") == 200 + + def test_more_than_dollar(self): + assert cents("2.99") == 299 + assert cents("2.90") == 290 + assert cents("2.9") == 290 + assert cents("1.75") == 175 + assert cents("64.75") == 6475 + assert cents("64.05") == 6405 + assert cents("64.1") == 6410 + +class Test_Demo: + def reload(self): + global DEMO + importlib.reload(sys.modules['utils']) + from utils import DEMO + + def test_empty(self): + sys.argv = ["name.py"] + self.reload() + assert DEMO == False + + def test_bad(self): + sys.argv = ["name.py", "gmainus"] + self.reload() + assert DEMO == False + + def test_good(self): + sys.argv = ["name.py", "demo"] + self.reload() + assert DEMO == True + + def test_mult_bad(self): + sys.argv = ["name.py", "gmainus", "hdemo"] + self.reload() + assert DEMO == False + + def test_mult_good(self): + sys.argv = ["name.py", "stupid", "demo"] + self.reload() + assert DEMO == True + diff --git a/tradovate.py b/tradovate.py new file mode 100644 index 0000000..68aaf07 --- /dev/null +++ b/tradovate.py @@ -0,0 +1,316 @@ +import base64 +import aiohttp +import websockets +import os +import hmac +import asyncio +import operator +from decimal import Decimal as D +import dill as pickle +import hashlib +import json +import requests +import datetime +import time +from utils import * +from piecewise import * + +TRADOVATE_AUTH = read_auth("tradovate") +TRADOVATE_SESSION_FILE = ".tradovate_session" +TRADOVATE_DEVICEID = "b21da153-4e25-4679-b958-053cc5dc8eeb" +# My Own: "76fdc8d9-e156-46a4-b2b2-187087fcd35e" +TRADOVATE_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" +# My Own: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", +TRADOVATE_ROOT = "https://demo.tradovateapi.com/v1" if DEMO else "https://live.tradovateapi.com/v1" +TRADOVATE_LIVE_ROOT = "https://live.tradovateapi.com/v1" +TRADOVATE_WS = "wss://demo.tradovateapi.com/v1/websocket" if DEMO else "wss://live.tradovateapi.com/v1/websocket" +TRADOVATE_MD_WS = "wss://md-demo.tradovateapi.com/v1/websocket" if DEMO else "wss://md.tradovateapi.com/v1/websocket" +TRADOVATE_APPID = "tradovate_trader(web)" +TRADOVATE_ORIGIN = "https://trader.tradovate.com" + +COMMON_HEADERS = { + "accept": "*/*", + "accept-encoding": "gzip, deflate, br", + "accept-language": "en-US,en;q=0.9", + "cache-control": "no-cache", + "origin": TRADOVATE_ORIGIN, + "pragma": "no-cache", + "referer": TRADOVATE_ORIGIN+"/", + "user-agent": TRADOVATE_USER_AGENT, +} + +def conv_ask_to_pc(lims, p, s): + # TODO: make sure this is correct + fee = 11 * 5 + return (conv_lim_pc(*lims) + (-p - fee), s, p) + +def convert_contract_hundred_lower(data): + rem = 0 + ret = [] + for p, s in data: + s += rem + if s//5: ret.append((p*5, s//5)) + rem = s%5 + return ret + +class TradovateSocket: + # TODO: figure out where the weird r query param comes from + def __init__(self, url, cookie_jar): + self.context = websockets.connect( + url, + extra_headers = { + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9", + "Cache-Control": "no-cache", + "Connection": "Upgrade", + "Pragma": "no-cache", + "Upgrade": "websocket", + "User-Agent": TRADOVATE_USER_AGENT, + "Cookie": cookie_jar.filter_cookies(url).output(header=''), + }, + origin = TRADOVATE_ORIGIN, + ) + self.id = 1 + self.cur_time = time.time() + + async def __aenter__(self): + self.ws = await self.context.__aenter__() + return self + + async def __aexit__(self, *args, **kwargs): + await self.context.__aexit__(*args, **kwargs) + + async def send(self, op, query="", body=""): + msg = "%s\n%d\n%s\n%s" % (op, self.id, query, body) + self.id += 1 + await self.ws.send(msg) + + async def stream(self): + async for msg in self.ws: + print(msg) + now = time.time() + + if now - self.cur_time > 2.5: + await self.ws.send('[]') + self.cur_time = now + + if msg[0]!='a': continue + yield msg[1:] + +class TradovateSession: + def __init__(self, s): + self.s = s + self.ticker_lookup = {} + + def __repr__(self): + return "tradovate" + + @staticmethod + def make_login_payload(username, password): + ts = str(int(time.time()*1000 - 1581e9)) + + sec = hmac.new( + "035a1259-11e7-485a-aeae-9b6016579351".encode(), + (ts + TRADOVATE_DEVICEID + username + password + TRADOVATE_APPID).encode(), + digestmod = hashlib.sha256 + ).hexdigest() + + return { + "name": username, + "password": password, + "appId": TRADOVATE_APPID, + "appVersion": "1.221209.1", + "deviceId": TRADOVATE_DEVICEID, + # "userAgent": "Dart/2.17.6; ec/1.221209.0", # This depends on version too + "environment": "demo", + "cid": 1, # This is client id + "sec": sec, + "chl": ts, + } + + # Making descision not to allow not to cache logins + # a) They have limited logins allowed, so until we can detect auth fail, this would be bad + # b) EC seems to have short login window anyways + # Still should allow it for development work when I'm slamming them + # TODO: renew these tokens, expiry is every 80 min + async def login(self, use_cached=False): + self.s.headers.clear() + self.s.headers.update(COMMON_HEADERS) + self.s.cookie_jar.clear() + + if use_cached and os.path.exists(TRADOVATE_SESSION_FILE): + with open(TRADOVATE_SESSION_FILE, 'rb') as f: + self.user_id, self.access_token, self.mdaccess_token, cookies = pickle.load(f) + self.s.headers['authorization'] = "Bearer " + self.access_token + self.s.cookie_jar._cookies = cookies + else: + print("doing new login") + async with self.s.post( + TRADOVATE_LIVE_ROOT + "/auth/accesstokenrequest", + data = json.dumps(TradovateSession.make_login_payload(**TRADOVATE_AUTH)), + headers = { + "content-type": "text/plain; charset=utf-8", + }, + ) as resp: + data = await resp.json() + self.access_token = data["accessToken"] + self.mdaccess_token = data["mdAccessToken"] + self.user_id = data["userId"] + self.s.headers['authorization'] = "Bearer " + self.access_token + + with open(TRADOVATE_SESSION_FILE, 'wb') as f: + pickle.dump((self.user_id, self.access_token, self.mdaccess_token, self.s.cookie_jar._cookies), f) + + async def get_ldep(self): + async with self.s.get( + TRADOVATE_ROOT + "/account/ldeps", + params = {'masterids': self.user_id}, + ) as resp: + data = await resp.json() + self.ldep_id = data[0]['id'] + + async def setup_ordering(self): + await self.get_ldep() + + self.order_sock = TradovateSocket(TRADOVATE_WS, self.s.cookie_jar) + + await self.order_sock.__aenter__() + await self.order_sock.send("authorize", body=self.access_token) + + async def order_ws_log(): + async for msg in self.order_sock.stream(): + print("ORDER LOG: " + msg) + print() + + asyncio.create_task(order_ws_log()) + +## Only myster here is tickers and what accountId is in this case +# user/registeraudituseraction +# 209 +# +# {"accountId":1368262,"actionType":"OrderTicketBuy","details":"ORDERTICKET OECES27Z2 C38000: Order Ticket Buy, Buy 3 Limit 15.00, TIF Day"} +# +# +# order/placeorder +# 210 +# +# {"accountId":1368262,"action":"Buy","symbol":"OECES27Z2 C38000","orderQty":3,"orderType":"Limit","price":15,"timeInForce":"Day","text":"Ticket"} + + async def execute_order(self, mid, p, s, buy): + p = D(p/5)/100 + s *= 5 + assert buy + + name = self.ticker_lookup[mid] + + audit_msg = { + "accountId": self.ldep_id, + "actionType": "OrderTicketBuy", + "details": "ORDERTICKET %s: Order Ticket Buy, Buy %d Limit %0.2f, TIF Day" + % (name, s, p), + } + + order_msg = { + "accountId": self.ldep_id, + "action": "Buy", + "symbol": name, + "orderQty": s, + "orderType": "Limit", + "price": float(p), + "timeInForce": "Day", + "text": "Ticket", + } + + print(json.dumps(audit_msg)) + print(json.dumps(order_msg)) + await self.order_sock.send("user/registeraudituseraction", body=json.dumps(audit_msg)) + await self.order_sock.send("order/placeorder", body=json.dumps(order_msg)) + + + async def markets_from_maturity(self, maturityid): + async with self.s.get( + TRADOVATE_ROOT + "/contract/deps", + params = {'masterid': maturityid}, + ) as resp: + data = await resp.json() + + lims = {} + for contract in data: + mid = contract['id'] + if mid%10!=5: continue + strike = cents(contract['strikePrice']) + put = contract['isPut'] + + self.ticker_lookup[mid] = contract['name'] + + # TODO: figure out boundary + if put: + lims[mid] = (None, strike) + else: + lims[mid] = (strike, None) + print(lims) + return lims + + async def orderbook_stream(self, maturityid): + lims = await self.markets_from_maturity(maturityid) + + book = {} + + async with TradovateSocket(TRADOVATE_MD_WS, self.s.cookie_jar) as ws: + await ws.send("authorize", body=self.mdaccess_token) + for symbol in lims: + await ws.send("md/subscribedom", body='{"symbol":"%d"}' %symbol) + + async for msg in ws.stream(): + data = json.loads(msg) + + doms = [] + for ev in data: + if ev.get('e')!='md': continue + for dom_ev in ev.get('d', {}).get('doms', []): + ts = datetime.datetime.fromisoformat(dom_ev['timestamp'][:-1]) + doms.append((ts, dom_ev)) + if not doms: continue + doms.sort(key=operator.itemgetter(0)) + + for ts, upd in doms: + mid = upd['contractId'] + + prices = [(cents(o['price']), o['size']) for o in upd['offers']] + prices.sort() + + prices = convert_contract_hundred_lower(prices) + + lim = lims[mid] + book[mid] = Digital( + *lim, + bids = [], + asks = [conv_ask_to_pc(lim, p, s) for p, s in prices], + exchange = self, + market_id = mid, + ) + + print(book[mid]) + if prices: + await self.execute_order(mid, book[mid].asks[0][-1], 4, True) + import sys + sys.exit(0) + + yield [*book.values()] + +async def main(): + # print(TradovateSession.make_login_payload(**TRADOVATE_AUTH)) + # return + async with aiohttp.ClientSession() as ts: + s = TradovateSession(ts) + await s.login(use_cached=True) + await s.setup_ordering() + async for book in s.orderbook_stream(maturityid=51047): + print(book) + # await asyncio.sleep(10000) + # print(await s.test_req()) + + +if __name__=="__main__": + asyncio.run(main()) + diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..45481c6 --- /dev/null +++ b/utils.py @@ -0,0 +1,20 @@ +import collections +from decimal import Decimal as D +import sys +import ast +import datetime + +DEMO = "demo" in [*map(str.lower, sys.argv[1:])] + +# assumes contract size of 100 +Digital = collections.namedtuple("Digital", "low high bids asks exchange market_id") + +def cents(m): + return int(D(m)*100) + +def updlog(*o): + print("["+datetime.datetime.now().time().isoformat().split('.')[0]+"] ", *o) + +def read_auth(name): + with open(name+".auth") as f: + return ast.literal_eval(f.read()) diff --git a/yahoo.proto b/yahoo.proto new file mode 100644 index 0000000..b0d18ee --- /dev/null +++ b/yahoo.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; + +message PricingData { + + enum QuoteType { + NONE = 0; + ALTSYMBOL = 5; + HEARTBEAT = 7; + EQUITY = 8; + INDEX = 9; + MUTUALFUND = 11; + MONEYMARKET = 12; + OPTION = 13; + CURRENCY = 14; + WARRANT = 15; + BOND = 17; + FUTURE = 18; + ETF = 20; + COMMODITY = 23; + ECNQUOTE = 28; + CRYPTOCURRENCY = 41; + INDICATOR = 42; + INDUSTRY = 1000; + }; + + enum OptionType { + CALL = 0; + PUT = 1; + }; + + enum MarketHoursType { + PRE_MARKET = 0; + REGULAR_MARKET = 1; + POST_MARKET = 2; + EXTENDED_HOURS_MARKET = 3; + }; + + string id = 1; + float price = 2; + sint64 time = 3; + string currency = 4; + string exchange = 5; + QuoteType quoteType = 6; + MarketHoursType marketHours = 7; + float changePercent = 8; + sint64 dayVolume = 9; + float dayHigh = 10; + float dayLow = 11; + float change = 12; + string shortName = 13; + sint64 expireDate = 14; + float openPrice = 15; + float previousClose = 16; + float strikePrice = 17; + string underlyingSymbol = 18; + sint64 openInterest = 19; + OptionType optionsType = 20; + sint64 miniOption = 21; + sint64 lastSize = 22; + float bid = 23; + sint64 bidSize = 24; + float ask = 25; + sint64 askSize = 26; + sint64 priceHint = 27; + sint64 vol_24hr = 28; + sint64 volAllCurrencies = 29; + string fromcurrency = 30; + string lastMarket = 31; + double circulatingSupply = 32; + double marketcap = 33; + string components = 34; + repeated string indices = 35; +}; diff --git a/yahoo_pb2.py b/yahoo_pb2.py new file mode 100644 index 0000000..cd35769 --- /dev/null +++ b/yahoo_pb2.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: yahoo.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0byahoo.proto\"\xfc\x08\n\x0bPricingData\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05price\x18\x02 \x01(\x02\x12\x0c\n\x04time\x18\x03 \x01(\x12\x12\x10\n\x08\x63urrency\x18\x04 \x01(\t\x12\x10\n\x08\x65xchange\x18\x05 \x01(\t\x12)\n\tquoteType\x18\x06 \x01(\x0e\x32\x16.PricingData.QuoteType\x12\x31\n\x0bmarketHours\x18\x07 \x01(\x0e\x32\x1c.PricingData.MarketHoursType\x12\x15\n\rchangePercent\x18\x08 \x01(\x02\x12\x11\n\tdayVolume\x18\t \x01(\x12\x12\x0f\n\x07\x64\x61yHigh\x18\n \x01(\x02\x12\x0e\n\x06\x64\x61yLow\x18\x0b \x01(\x02\x12\x0e\n\x06\x63hange\x18\x0c \x01(\x02\x12\x11\n\tshortName\x18\r \x01(\t\x12\x12\n\nexpireDate\x18\x0e \x01(\x12\x12\x11\n\topenPrice\x18\x0f \x01(\x02\x12\x15\n\rpreviousClose\x18\x10 \x01(\x02\x12\x13\n\x0bstrikePrice\x18\x11 \x01(\x02\x12\x18\n\x10underlyingSymbol\x18\x12 \x01(\t\x12\x14\n\x0copenInterest\x18\x13 \x01(\x12\x12,\n\x0boptionsType\x18\x14 \x01(\x0e\x32\x17.PricingData.OptionType\x12\x12\n\nminiOption\x18\x15 \x01(\x12\x12\x10\n\x08lastSize\x18\x16 \x01(\x12\x12\x0b\n\x03\x62id\x18\x17 \x01(\x02\x12\x0f\n\x07\x62idSize\x18\x18 \x01(\x12\x12\x0b\n\x03\x61sk\x18\x19 \x01(\x02\x12\x0f\n\x07\x61skSize\x18\x1a \x01(\x12\x12\x11\n\tpriceHint\x18\x1b \x01(\x12\x12\x10\n\x08vol_24hr\x18\x1c \x01(\x12\x12\x18\n\x10volAllCurrencies\x18\x1d \x01(\x12\x12\x14\n\x0c\x66romcurrency\x18\x1e \x01(\t\x12\x12\n\nlastMarket\x18\x1f \x01(\t\x12\x19\n\x11\x63irculatingSupply\x18 \x01(\x01\x12\x11\n\tmarketcap\x18! \x01(\x01\x12\x12\n\ncomponents\x18\" \x01(\t\x12\x0f\n\x07indices\x18# \x03(\t\"\x80\x02\n\tQuoteType\x12\x08\n\x04NONE\x10\x00\x12\r\n\tALTSYMBOL\x10\x05\x12\r\n\tHEARTBEAT\x10\x07\x12\n\n\x06\x45QUITY\x10\x08\x12\t\n\x05INDEX\x10\t\x12\x0e\n\nMUTUALFUND\x10\x0b\x12\x0f\n\x0bMONEYMARKET\x10\x0c\x12\n\n\x06OPTION\x10\r\x12\x0c\n\x08\x43URRENCY\x10\x0e\x12\x0b\n\x07WARRANT\x10\x0f\x12\x08\n\x04\x42OND\x10\x11\x12\n\n\x06\x46UTURE\x10\x12\x12\x07\n\x03\x45TF\x10\x14\x12\r\n\tCOMMODITY\x10\x17\x12\x0c\n\x08\x45\x43NQUOTE\x10\x1c\x12\x12\n\x0e\x43RYPTOCURRENCY\x10)\x12\r\n\tINDICATOR\x10*\x12\r\n\x08INDUSTRY\x10\xe8\x07\"\x1f\n\nOptionType\x12\x08\n\x04\x43\x41LL\x10\x00\x12\x07\n\x03PUT\x10\x01\"a\n\x0fMarketHoursType\x12\x0e\n\nPRE_MARKET\x10\x00\x12\x12\n\x0eREGULAR_MARKET\x10\x01\x12\x0f\n\x0bPOST_MARKET\x10\x02\x12\x19\n\x15\x45XTENDED_HOURS_MARKET\x10\x03\x62\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'yahoo_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _PRICINGDATA._serialized_start=16 + _PRICINGDATA._serialized_end=1164 + _PRICINGDATA_QUOTETYPE._serialized_start=776 + _PRICINGDATA_QUOTETYPE._serialized_end=1032 + _PRICINGDATA_OPTIONTYPE._serialized_start=1034 + _PRICINGDATA_OPTIONTYPE._serialized_end=1065 + _PRICINGDATA_MARKETHOURSTYPE._serialized_start=1067 + _PRICINGDATA_MARKETHOURSTYPE._serialized_end=1164 +# @@protoc_insertion_point(module_scope) diff --git a/yahoo_stream.py b/yahoo_stream.py new file mode 100644 index 0000000..dd15f6c --- /dev/null +++ b/yahoo_stream.py @@ -0,0 +1,30 @@ +import websockets +import json +import yahoo_pb2 +import base64 +import asyncio +from utils import * +import requests + +async def stream_ticker(ticker): + resp = requests.get("https://query1.finance.yahoo.com/v7/finance/spark?symbols="+ticker+"&range=1d&interval=5m&indicators=close&includeTimestamps=false&includePrePost=false&corsDomain=finance.yahoo.com&.tsrc=finance", headers={ + "origin": "https://finance.yahoo.com", + "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", + }) + data = resp.json()["spark"]["result"][0]["response"][0]["meta"] + yield cents(str(data["regularMarketPrice"])) + + async with websockets.connect("wss://streamer.finance.yahoo.com/") as ws: + await ws.send(json.dumps({"subscribe": [ticker]})) + + async for message in ws: + pricing_data = yahoo_pb2.PricingData() + pricing_data.ParseFromString(base64.b64decode(message)) + yield cents(str(pricing_data.price)) + +if __name__=="__main__": + async def main(): + async for p in stream_ticker("ESTH23.CME"): + print(p) + asyncio.run(main()) + # asyncio.run(stream_ticker(["ES=F", "YM=F", "NQ=F", "RTY=F", "CL=F", "GC=F", "EST=F", "ESTZ22.CME"]))