diff --git a/README.md b/README.md index 45cc834c..9c529b8e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,25 @@ # qbt - Quantopian Backtesting Services +##Development Quickstart +Navigate to your mongodb installation and start your db server: + ./bin/mongodb + +Install the necessary python libraries: + easy_install tornado pymongo + +Create a development database with sample data, will create one qbt user: + python qbt_data_bootstrap.py --user_email=... --password=... + +To run the qbt against a local mongodb instance, navigate to the source dir and run + python qbt_server.py --mongodb_dbname=qbt + +To see all the available options: + python qbt_server.py --help +or + python qbt_data_bootstrap.py --help + + + qbt uses tornado to accept synchronous requests for backtesting sessions. The client of a backtesting session first invokes the _backtest_ endpoint: http://serverip/backtest?startdate=<>&enddate=<>... @@ -13,17 +33,22 @@ A backtesting session is comprised of: - an event stream delivered via zeromq ## Pre-requisites +You need to have the tornado and pymongo eggs installed: + easy_install tornado pymongo -### MongoDB Server ### +You need to have mongodb installed and running. Find your system at http://www.mongodb.org/downloads and set it up. + +### Database and Collections expected in MongoDB ### QBT requires a running mongodb instance with a few collections: -- user collection. See handlers.BaseHandler for code using this collection. Documents must have: +- user collection. See handlers.BaseHandler and handlers.LoginHandler for code using this collection. Documents must have: - email - standard email address - - encrypted_password - an sha2 hex digest of the password - - salt - a secret other than the password, which can be set in a secure cookie to maintain a session. if unset, will be set to sha2 hex of datetime.utcnow()--password + - salt - sha256 hex of: datetime.utcnow()--password + - encrypted_password - an sha256 hex digest of: salt--password - _id - standard issue mongodb primary key +## Authenticating ## Requesting a Backtest diff --git a/qbt_data_bootstrap.py b/qbt_data_bootstrap.py new file mode 100644 index 00000000..de4dd2d1 --- /dev/null +++ b/qbt_data_bootstrap.py @@ -0,0 +1,29 @@ +import tornado.auth +import tornado.httpserver +import tornado.ioloop +from tornado.options import define, options +import tornado.web +import pymongo +import bson +import hashlib +import base64 +import uuid +import os +import logging + +import qbt_server + +define("user_email", default="qbt@quantopian.com", help="email address for qbt user") +define("password", default="foobar", help="password for qbt user") + +def db_main(): + tornado.options.parse_command_line() + connection, db = qbt_server.connect_db() + + #create a user for testing + salt, encrypted_password = qbt_server.encrypt_password(None, options.password) + + db.users.insert({'email':options.user_email, 'encrypted_password':encrypted_password, 'salt':salt}) + +if __name__ == "__main__": + db_main() diff --git a/qbt_server.py b/qbt_server.py index ed8e39a9..cfd7f837 100644 --- a/qbt_server.py +++ b/qbt_server.py @@ -10,19 +10,38 @@ import base64 import uuid import os import logging +import datetime -logging.getLogger('qbt').setLevel(logging.DEBUG) -logger = logging.getLogger('qbt') +logging.getLogger().setLevel(logging.DEBUG) +logger = logging.getLogger() define("port", default=8888, help="run the qbt on the given port", type=int) define("mongodb_host", default="127.0.0.1", help="mongodb host address") define("mongodb_port", default=27017, help="connect to the mongodb on the given port", type=int) -define("mongodb_dbname", default="blog", help="database name") -define("mongodb_user", default="blog", help="database user") -define("mongodb_password", default="blog", help="database password") +define("mongodb_dbname", default="qbt", help="database name") +define("mongodb_user", default="qbt", help="database user") +define("mongodb_password", default="qbt", help="database password") HASH_ALGO = 'sha256' +def connect_db(): + connection = pymongo.Connection(options.mongodb_host, options.mongodb_port) + db = connection[options.mongodb_dbname] + db.authenticate(options.mongodb_user, options.mongodb_password) + return connection, db + +def encrypt_password(salt, password): + if(salt == None): + h1 = hashlib.new(HASH_ALGO) + h1.update(str(datetime.datetime.utcnow())+"--"+password) + salt = h1.hexdigest() + + h2 = hashlib.new(HASH_ALGO) + h2.update(salt+"--"+password) + encrypted_password = h2.hexdigest() + + return salt, encrypted_password + class Application(tornado.web.Application): def __init__(self): handlers = [ @@ -41,9 +60,7 @@ class Application(tornado.web.Application): tornado.web.Application.__init__(self, handlers, **settings) # Have one global connection to the blog DB across all handlers - self.connection = pymongo.Connection(options.mongodb_host, options.mongodb_port) - self.db = self.connection[options.mongodb_dbname] - self.db.authenticate(options.mongodb_user, options.mongodb_password) + self.connection, self.db = connect_db() class BaseHandler(tornado.web.RequestHandler): @@ -90,9 +107,7 @@ class LoginHandler(BaseHandler): #calculate password hash - h.update(user_record['salt']+"--"+password) - encrypted_password = h.hexdigest() - logger.debug("checking {password} as {encrypted_password} against {record}".format(password=password, encrypted_password=encrypted_password, record=user_record['encrypted_password'])) + salt, encrypted_password = encrypt_password(user_record['salt'], password) if (user_record['encrypted_password'] == encrypted_password): #we have a match, so set the secure cookie to the salt logger.debug("setting user_id cookie to {id}".format(id=user_record['_id'])) diff --git a/qbt_server.pyc b/qbt_server.pyc new file mode 100644 index 00000000..6ce86ad1 Binary files /dev/null and b/qbt_server.pyc differ