diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b3368251b --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.bundle +db/*.sqlite3 +log/*.log +*.log +tmp/**/* +tmp/* +doc/api +doc/app +*.swp +*~ +.DS_Store +*.pyc +.tddium +.tddium-deploy-key diff --git a/README.md b/README.md index 004b8e174..bb2f92a4d 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,15 @@ A backtesting session is comprised of: ## Pre-requisites You need to have the tornado and pymongo eggs installed: - easy_install tornado pymongo + + easy_install tornado pymongo pyzmq You need to have mongodb installed and running. Find your system at http://www.mongodb.org/downloads and set it up. +You need to have zeromq installed - http://www.zeromq.org/intro:get-the-software. If you are on mac, get homebrew https://github.com/mxcl/homebrew then: + + brew install zeromq + ### Database and Collections expected in MongoDB ### QBT requires a running mongodb instance with a few collections: diff --git a/backtest/util.py b/backtest/util.py new file mode 100644 index 000000000..2d3f7990b --- /dev/null +++ b/backtest/util.py @@ -0,0 +1,35 @@ +""" +Small classes to assist with db access, timezone calculations, and so on. +""" + +class DocWrap(): + """ + Provides attribute access style on top of dictionary results from pymongo. + Allows you to access result['field'] as result.field. + Aliases result['_id'] to result.id. + + """ + def __init__(self, store=None): + if(store == None): + self.store = {} + else: + self.store = store.copy() + if(self.store.has_key('_id')): + self.store['id'] = self.store['_id'] + del(self.store['_id']) + + def __setitem__(self,key,value): + if(key == '_id'): + self.store['id'] = value + else: + self.store[key] = value + + def __getitem__(self, key): + if self.store.has_key(key): + return self.store[key] + + def __getattr__(self,attrname): + if self.store.has_key(attrname): + return self.store[attrname] + else: + raise AttributeError("No attribute named {name}".format(name=attrname)) \ No newline at end of file diff --git a/qbt.py b/qbt.py new file mode 100644 index 000000000..44b5b47a1 --- /dev/null +++ b/qbt.py @@ -0,0 +1,36 @@ +""" +qbt runs backtests using multiple processes and zeromq messaging. + +Backtest is the primary process. It maintains both server and client sockets: +zmq sockets for internal processing: +==================================== + - data sink, ZMQ.PULL. Port = port_start + 1 + - backtest will connect to socket, and then spawn one process per datasource, passing the data sink url as a startup arg. Each + datasource process will bind to the socket, and start processing + - backtest is responsible for merging the data events from all sources into a serialized stream and relaying it to the + aggregators, merging agg results, and transmitting consolidated stream to event feed. + - agg source, ZMQ.PUSH. Port = port_start + 2 + - agg sink, ZMQ.PULL. Port = port_start + 3 + - control source, ZMQ.PUB. Port = port_start + 4 + - all child processes must subscribe to this socket. Control commands: + - START -- begin processing + - KILL -- exit immediately + +zmq sockets for backtest clients: +================================= + - orders sink, ZMQ.RESP. Port = port_start + 5 + - backtest will connect (can you bind?) to this socket and await orders from the client. Order data will be processed against the streaming datafeed. + - event feed, ZMQ.RESP. Port = port_start + 6 + - backtest will bind to this socket and respond to requests from client for more data. Response data will be the queue of events that + transpired since the last request. + + +""" +import multiprocessing +import zmq + +class Backtest(object): + + def __init__(self, port_start=10000): + + 40B8729046 \ No newline at end of file diff --git a/qbt_server.py b/qbt_server.py index cfd7f837d..d1d9c98f6 100644 --- a/qbt_server.py +++ b/qbt_server.py @@ -11,8 +11,8 @@ import os import logging import datetime +import multiprocessing -logging.getLogger().setLevel(logging.DEBUG) logger = logging.getLogger() define("port", default=8888, help="run the qbt on the given port", type=int) @@ -47,6 +47,7 @@ def __init__(self): handlers = [ (r"/", MainHandler), (r"/login", LoginHandler), + (r"/backtest", BacktestHandler) ] settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), @@ -81,8 +82,8 @@ def get_current_user(self): class MainHandler(BaseHandler): @tornado.web.authenticated def get(self): - self.write("Hello, world") - + self.write("Hello, world. Try launching a backtest.") + class LoginHandler(BaseHandler): def get(self): self.write('
' @@ -113,6 +114,17 @@ def authenticate(self, username, password): logger.debug("setting user_id cookie to {id}".format(id=user_record['_id'])) self.set_secure_cookie(u"user_id", unicode(user_record['_id'])) +class BacktestHandler(BaseHandler): + @tornado.web.authenticated + def get(self): + self.write('' + '' + '
') + @tornado.web.authenticated + def post(self): + + + def main(): tornado.options.parse_command_line() http_server = tornado.httpserver.HTTPServer(Application()) diff --git a/qbt_test_client.py b/qbt_test_client.py new file mode 100644 index 000000000..e69de29bb