commit 03cbd968ea70fa4a38b31b14c94d18e83a9e0db6 Author: Maltysen <10355450+Maltysen@users.noreply.github.com> Date: Wed Apr 1 16:07:27 2026 -0400 commit 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"]))