#!/usr/bin/python -O
# This program is Copyright (C) 2007 by Peter McCluskey.
# Use this at your own risk. It is only designed for one special purpose.
# It is released under the terms and conditions of GNU GPL license,
# version 2 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) or
# higher (http://www.gnu.org/copyleft/gpl.html).
#
# To configure, make sure that amm_quantity has the quantity you want for
# each contract id that the program will use, and edit the contract_id_list
# to list only the contract ids you want to trade (there are 2
# contract_id_list entries near the end of the program that you may need to
# edit, one for play.intrade.com and the other for www.intrade.com)
# Also, edit the variables trade_log_file_name through webmaster_email_addr
# to values that are appropriate for your system. The two file names need
# to be in directories you can write to. If you don't understand the remaining
# three variables and don't need email reports of errors that cause the
# program to give up, then set smtp_server to something invalid such as '???'.
#
# To test it with play.intrade.com, run it with something like this:
#./intrade_amm.py --server=play --user=xxx --password=xxxxxxxx
#
import sys, re, string, os, math, time, getopt
import xml.parsers.expat, urllib, smtplib, commands, signal, socket
#
# amm_quantity sets the order size to trade for each contract id
#
amm_quantity = {
'565196' : 115, # DEM.PRES-GOVT.DEBT
'565197' : 115, # NONDEM.PRES-GOVT.DEBT
'565198' : 115, # DEM.PRES-TROOPS.IRAQ
'565199' : 115, # NONDEM.PRES-TROOPS.IRAQ
'565200' : 38, # DEM.PRES-OIL.FUTURES
'565201' : 38, # DEM.PRES-T.BONDS
'437134' : 10,
'416496' : 1, # Democratic Party Nominee to win Nevadas Electoral College Votes in 2008 Election
'68364' : 2, # Democratic Party Nominee to win Nevadas Electoral College Votes in 2008 Election, play sys
'142210' : 2, # Adult Talkativeness. Men v Women., play sys
'174323' : 2, # The Google Lunar X Prize to be won on/before 31 December 2012
'97129' : 1,
'219617' : 1, # JAP.TARGET.DEC09.>25%
'65543210' : 1, # bogus
}
trade_log_file_name = '/var/www/html/amm/amm_trade.log'
msg_log_file_name = '/home/pcm/amm_msg.log'
smtp_server = 'mail.myisp.com'
from_email_addr = 'my_email_address'
webmaster_email_addr = 'webmaster_email_address'
def write_msg_log(msg1):
fd = open(msg_log_file_name, 'a')
fd.write(str(msg1) + " %s\n" % time.ctime())
def handle_error(msg1, warn_only = 0):
write_msg_log(msg1)
errormsg = ''
body = "From: %s\nTo: %s\nSubject: intrade amm error\n\n%s %s" \
% (from_email_addr, webmaster_email_addr, time.ctime(), msg1)
try:
mailer = smtplib.SMTP(smtp_server)
except socket.error, msg:
errormsg = "error while trying to mail via server %s: %s\n" \
% (smtp_server, msg)
try:
err_dict = mailer.sendmail(from_email_addr, [webmaster_email_addr],
body)
except smtplib.SMTPException, msg:
errormsg += 'Error sending mail: ' + `msg.__class__`
if hasattr(msg, 'args'):
errormsg = errormsg + ' ' + `msg.args`
else:
for (email, code_msg) in err_dict.items():
errormsg = errormsg + 'Error sending mail ' + `code_msg` + "\n"
mailer.quit()
if errormsg:
write_msg_log('handle_error done %s' % errormsg)
if not warn_only:
raise NotImplementedError
#
# an Order is a bid or ask that is sent to Intrade
#
class Order:
def __init__(self, is_buy, quantity, price, contract_id):
self.is_buy = is_buy
self.quantity = quantity
self.quantity_remain = quantity
if type(price) != type(''):
price = '%.4g' % price
self.limitprice = price
self.contract_id = str(contract_id)
def ContractID(self):
return self.contract_id
def SetID(self, id):
self.order_id = id
def ID(self):
return self.order_id
def Is_Buy(self):
return self.is_buy
def __str__(self):
return "%-4s %d of %d %s @ %s" \
% (("Sell", "Buy")[self.is_buy], self.quantity_remain,
self.quantity, self.contract_id, self.limitprice)
__repr__ = __str__
def Apply(self, trade):
self.quantity_remain = int(self.quantity_remain) - int(trade.Quantity())
if self.quantity_remain < 0:
handle_error('Apply quantity ' + str(self))
def Remain_Qty(self):
return self.quantity_remain
def Price(self):
return self.limitprice
#
# an OrderExecution is the result of a trade
#
class OrderExecution:
def __init__(self, order_id, quantity, contract_id, side, price,
msg_id, executionTime, order_dict):
self.orderID = order_id
try:
self.order = order_dict[order_id]
except KeyError:
write_msg_log("known order ids %s\nWARNING!! Unknown order id %s" \
% (order_dict.keys(), order_id))
self.order = None
##handle_error("Unknown order id " + order_id)
self.msg_id = msg_id
self.quantity = int(quantity)
self.contract_id = contract_id
self.side = side
self.price = price
self.time = executionTime
# verify it matches what was submitted?
if self.order:
self.order.Apply(self)
def __str__(self):
return "OrderID %s %s %s %s %s" \
% (self.orderID, self.side, self.quantity, self.contract_id,
self.price)
def executionTime(self):
return self.time
def Contract(self):
return self.contract_id
def From_Order(self):
return self.order
def Quantity(self):
return self.quantity
def Is_Buy(self):
return self.side == 'B'
shock_price_list = [
49.9,
49.8,
49.7,
49.6,
49.5,
49.4,
49.3,
49.2,
49.0,
48.8,
48.6,
48.4,
48.2,
48.0,
47.6,
47.2,
46.6,
46.0,
45.2,
44.4,
43.6,
42.8,
42.0,
41.0,
40.0,
38.0,
36.0,
32.0,
26.0,
20.0,
16.0,
12.0,
8.0,
4.0,
1.0,
]
MIN_SPREAD = 0.01
#
# ContractAMM handles market making operations specific to one contract
#
class ContractAMM:
def __init__(self, contract_id):
self.contract_id = contract_id
if contract_id in ('565200', '565201', '68364'):
self.spread_factor = 0.05
self.price_list = shock_price_list
else:
self.spread_factor = 0.1
self.price_list = None
self.counter = 0
self.min_value = 0
self.max_value = 100
try:
self.quantity = amm_quantity[contract_id]
except KeyError:
handle_error('unknown contract id ' + contract_id)
self.has_created_orders = 0
self.counter_restored = 0
self.partial_exec = [0,0]
def Has_Orders(self):
return self.has_created_orders
def Has_Restored_Counter(self):
return self.counter_restored
# automated market maker algorithm
# based on Robin Hanson's ifextropy.html paper
def updateOrders(self, trade):
if trade is None:
dir = 0 # initial order creation
else:
mm_order = trade.From_Order()
if mm_order is None:
handle_error("bogus order " + str(mm_order))
if mm_order.Remain_Qty() > 0:
return []
if mm_order.Is_Buy():
dir = 1
else:
dir = -1
self.counter += dir
counter = self.counter
bid = self.Market_Maker_Price(counter + 0.5)
ask = self.Market_Maker_Price(counter - 0.5)
buy_order = Order(True, self.quantity - self.partial_exec[0], bid, self.contract_id)
sell_order = Order(False, self.quantity - self.partial_exec[1], ask, self.contract_id)
self.partial_exec = [0,0]
#sys.stderr.write("Market_Maker orders changed to %s %s, counter %d for %d\n" % (bid,ask, counter, dir))
#print 'submit orders', buy_order, sell_order
self.has_created_orders = 1
if abs(bid - ask) < MIN_SPREAD or bid < self.min_value + MIN_SPREAD \
or ask > self.max_value - MIN_SPREAD:
if bid < 50: # too high/low to make a market
return (sell_order, )
return (buy_order, )
return (buy_order, sell_order)
def Market_Maker_Price(self, counter):
if self.price_list:
try:
px = self.price_list[int(abs(counter))]
except IndexError:
px = 0
if counter < 0:
px = 100 - px
return px
spread_factor = self.spread_factor
px = 1.0/(1.0 + math.exp(spread_factor * counter))
#dist_from_limit = max(0.0001, min(abs(px), abs(1.0 - px)))
#round_places = min(3, int(2 - math.log10(dist_from_limit)))
round_places = 3
r = round(px, round_places)
r_px = self.min_value + (self.max_value - self.min_value) * r
# hack to handle limited precision with systems that only allow
# prices to nearest 0.1:
if r_px < 0.1/spread_factor:
if r_px < MIN_SPREAD:
write_msg_log('counter %s r_px %s' % (counter, r_px))
return 0
return self.Market_Maker_Price(counter - 1) - 0.1
elif r_px > 100-0.1/spread_factor:
if r_px > 100-MIN_SPREAD:
write_msg_log('counter %s r_px %s' % (counter, r_px))
return 100
return self.Market_Maker_Price(counter + 1) + 0.1
return r_px
def CalcMaxLoss(self, qty):
counter = 0
tot = 0
while 1:
bid = self.Market_Maker_Price(counter + 0.5)
ask = self.Market_Maker_Price(counter - 0.5)
if abs(bid - ask) < MIN_SPREAD or bid < self.min_value + MIN_SPREAD \
or ask > self.max_value - MIN_SPREAD:
break
tot += bid*qty*0.1
print '%7.1f %9.0f %5.2f' % (bid*qty*0.1, tot, bid)
counter += 1
return tot
def CalcSpent(self):
counter = 0
if self.counter > 0:
dir = 1
else:
dir = -1
tot = 0
qty = self.quantity
while abs(counter) < abs(self.counter):
px = self.Market_Maker_Price(counter + dir*0.5)
tot += px*qty*0.1
print '%7.1f %9.0f %5.2f' % (px*qty*0.1, tot, px)
counter += dir
return tot
def Restore(self, order_dict, last_bid, partial_exec):
found_orders = 0
bid_px = None
ask_px = None
for (key, order1) in order_dict.items():
if order1.ContractID() == self.contract_id:
found_orders += 1
if order1.Is_Buy():
bid_px = float(order1.Price()) - 0.00001
else:
ask_px = float(order1.Price()) + 0.00001
if found_orders == 2:
if bid_px < self.Market_Maker_Price(self.counter):
dir = 1
else:
dir = -1
while not (bid_px < self.Market_Maker_Price(self.counter) < ask_px):
self.counter += dir
write_msg_log('Restore counter set to %s for %s' \
% (self.counter, self.contract_id))
self.has_created_orders = 1
self.counter_restored = 1
elif last_bid.has_key(self.contract_id):
write_msg_log('restore %s from %s' % (self.contract_id, float(last_bid[self.contract_id])))
self.RestoreFrom1Price(float(last_bid[self.contract_id]), True)
if not self.has_created_orders:
try:
self.partial_exec = partial_exec[self.contract_id]
except KeyError:
pass
def RestoreFromTrades(self, trade_list):
last_trade = trade_list[0]
for trade1 in trade_list:
if trade1.executionTime() > last_trade.executionTime():
last_trade = trade1
# what about partial executions????
self.RestoreFrom1Price(float(last_trade.price), last_trade.Is_Buy())
def RestoreFrom1Price(self, px, is_bid):
bid_incr = 0.99*is_bid
ask_incr = 0.99*(not is_bid)
if self.Market_Maker_Price(self.counter + bid_incr) < px < self.Market_Maker_Price(self.counter - ask_incr):
dir = 0
elif px < self.Market_Maker_Price(self.counter):
dir = 1
else:
dir = -1
while not (self.Market_Maker_Price(self.counter + bid_incr) - 0.00001 < px < self.Market_Maker_Price(self.counter - ask_incr) + 0.00001):
if self.Market_Maker_Price(self.counter + bid_incr) < MIN_SPREAD \
or self.Market_Maker_Price(self.counter - ask_incr) > 100-MIN_SPREAD:
break
self.counter += dir
#print self.Market_Maker_Price(self.counter + bid_incr), px, self.Market_Maker_Price(self.counter - ask_incr), 'is_bid', is_bid, 'dir', dir, bid_incr, self.counter
#if is_bid == (dir > 0):
# self.counter += 1
self.counter_restored = 1
write_msg_log('Restore counter set to %s for %s' \
% (self.counter, self.contract_id))
def alarmHandler(*args):
# signal handler for SIGALRM, just raise an exception
raise IOError("TimeOut")
LOGIN_PERIOD = 1000*60*60*4 # 4 hours expressed in milliseconds
tags_with_lists = {
'order' : 1,
'ordID' : 1,
'trade' : 1,
'msg' : 1,
'position' : 1,
}
class IntradeInterface:
def __init__(self, option_dict, contract_dict, secondary = 0):
if secondary: # creating trades for test purposes
self.user_id = option_dict['user2']
self.password = option_dict['password2']
else:
self.user_id = option_dict['user']
self.password = option_dict['password']
self.username = None
self.session = None
self.next_login_t = None
self.last_getx_time = 1
self.order_dict = {}
self.pos_dict = {}
if option_dict.has_key('dummy'):
return
try:
server_name = option_dict['server']
except KeyError:
handle_error('no server specified')
if server_name == 'play':
method = 'http'
self.login_url = 'https://api.intrade.com/aav2/intersite/intersiteLoginXML.jsp?username=%s&password=%s' \
% (self.user_id, self.password)
#self.login_url = ('https://api.tradesports.com/aav2/intersite/intersiteLoginXML.jsp',
# urllib.urlencode([('username', self.user_id),
# ('password', self.password)]))
else:
method = 'https'
self.login_url = None
self.server_url = '%s://%s.intrade.com/xml/handler.jsp' \
% (method, server_name)
self.getLogin()
self.last_order_id = 0
self.got_msg = {}
if option_dict.has_key('checkbal'):
return
if not secondary:
self.RestoreOld(contract_dict)
def RestoreOld(self, contract_dict):
self.GetOrders()
self.GetPositions(contract_dict)
(last_bid, partial_exec) = self.RestoreOrders()
if self.order_dict or self.pos_dict or last_bid:
for (contract_id, amm) in contract_dict.items():
amm.Restore(self.order_dict, last_bid, partial_exec)
if not amm.Has_Orders() and not amm.Has_Restored_Counter():
trade_list = self.getTradesForContract(contract_id)
if trade_list:
amm.RestoreFromTrades(trade_list)
else:
write_msg_log("nothing to restore for %s" % contract_id)
def __del__(self):
if self.user_id == 'amm' and self.login_url is not None:
req = '
| %s | %s | %s | at | %s | %s |