Added remaining trading system around portfolio in order to compare with OANDA.

This commit is contained in:
Michael Halls-Moore
2015-02-03 13:33:33 +00:00
parent dbc973567a
commit b0b19d603d
13 changed files with 255 additions and 15 deletions
View File
View File
+28
View File
@@ -0,0 +1,28 @@
class Event(object):
pass
class TickEvent(Event):
def __init__(self, instrument, time, bid, ask):
self.type = 'TICK'
self.instrument = instrument
self.time = time
self.bid = bid
self.ask = ask
class SignalEvent(Event):
def __init__(self, instrument, order_type, side):
self.type = 'SIGNAL'
self.instrument = instrument
self.order_type = order_type
self.side = side
class OrderEvent(Event):
def __init__(self, instrument, units, order_type, side):
self.type = 'ORDER'
self.instrument = instrument
self.units = units
self.order_type = order_type
self.side = side
View File
+33
View File
@@ -0,0 +1,33 @@
import httplib
import urllib
class Execution(object):
def __init__(self, domain, access_token, account_id):
self.domain = domain
self.access_token = access_token
self.account_id = account_id
self.conn = self.obtain_connection()
def obtain_connection(self):
return httplib.HTTPSConnection(self.domain)
def execute_order(self, event):
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Bearer " + self.access_token
}
params = urllib.urlencode({
"instrument" : event.instrument,
"units" : event.units,
"type" : event.order_type,
"side" : event.side
})
self.conn.request(
"POST",
"/v1/accounts/%s/orders" % str(self.account_id),
params, headers
)
response = self.conn.getresponse().read()
print response
+23 -15
View File
@@ -1,20 +1,22 @@
from copy import deepcopy
from position import Position
from qsforex.event.event import OrderEvent
from qsforex.portfolio.position import Position
class Portfolio(object):
def __init__(
self, ticker, base="GBP", leverage=20,
self, ticker, events, base="GBP", leverage=20,
equity=100000.0, risk_per_trade=0.02
):
self.ticker = ticker
self.events = events
self.base = base
self.leverage = leverage
self.equity = equity
self.balance = deepcopy(self.equity)
self.risk_per_trade = risk_per_trade
self.trade_units = self.calc_risk_position_size()
self.trade_units = 100000#self.calc_risk_position_size()
self.positions = {}
def calc_risk_position_size(self):
@@ -76,16 +78,16 @@ class Portfolio(object):
def execute_signal(self, signal_event):
side = signal_event.side
market = signal_event.market
units = self.risk_per_trade
market = signal_event.instrument
units = int(self.trade_units)
# Check side for correct bid/ask prices
if side == "LONG":
add_price = self.ticker.cur_ask
remove_price = self.ticker.cur_bid
else:
add_price = self.ticker.cur_bid
remove_price = self.ticker.cur_ask
#if side == "buy":
add_price = self.ticker.cur_ask
remove_price = self.ticker.cur_bid
#else:
#add_price = self.ticker.cur_bid
#remove_price = self.ticker.cur_ask
exposure = float(units)
# If there is no position, create one
@@ -94,10 +96,13 @@ class Portfolio(object):
side, market, units, exposure,
add_price, remove_price
)
order = OrderEvent(market, units, "market", "buy")
self.events.put(order)
# If a position exists add or remove units
else:
ps = self.positions[market]
# Check if the sides equal
if side == ps[market].side:
if side == ps.side:
# Add to the position
add_position_units(
market, units, exposure,
@@ -108,6 +113,8 @@ class Portfolio(object):
if units == ps.units:
# Close the position
self.close_position(market, remove_price)
order = OrderEvent(market, units, "market", "sell")
self.events.put(order)
elif units < ps.units:
# Remove from the position
self.remove_position_units(
@@ -119,12 +126,13 @@ class Portfolio(object):
new_units = units - ps.units
self.close_position(market, remove_price)
if side == "LONG":
new_side = "SHORT"
if side == "buy":
new_side = "sell"
else:
new_side = "LONG"
new_side = "sell"
new_exposure = float(units)
self.add_new_position(
new_side, market, new_units,
new_exposure, add_price, remove_price
)
print "Balance: %0.2f" % self.balance
+21
View File
@@ -0,0 +1,21 @@
import os
ENVIRONMENTS = {
"streaming": {
"real": "stream-fxtrade.oanda.com",
"practice": "stream-fxpractice.oanda.com",
"sandbox": "stream-sandbox.oanda.com"
},
"api": {
"real": "api-fxtrade.oanda.com",
"practice": "api-fxpractice.oanda.com",
"sandbox": "api-sandbox.oanda.com"
}
}
DOMAIN = "practice"
STREAM_DOMAIN = ENVIRONMENTS["streaming"][DOMAIN]
API_DOMAIN = ENVIRONMENTS["api"][DOMAIN]
ACCESS_TOKEN = os.environ.get('OANDA_API_ACCESS_TOKEN', None)
ACCOUNT_ID = os.environ.get('OANDA_API_ACCOUNT_ID', None)
View File
+18
View File
@@ -0,0 +1,18 @@
from qsforex.event.event import SignalEvent
class TestRandomStrategy(object):
def __init__(self, instrument, events):
self.instrument = instrument
self.events = events
self.ticks = 0
def calculate_signals(self, event):
if event.type == 'TICK':
self.ticks += 1
if self.ticks == 2:
signal = SignalEvent(self.instrument, "market", "buy")
self.events.put(signal)
if self.ticks == 10:
signal = SignalEvent(self.instrument, "market", "sell")
self.events.put(signal)
View File
+57
View File
@@ -0,0 +1,57 @@
import requests
import json
from qsforex.event.event import TickEvent
class StreamingForexPrices(object):
def __init__(
self, domain, access_token,
account_id, instruments, events_queue
):
self.domain = domain
self.access_token = access_token
self.account_id = account_id
self.instruments = instruments
self.events_queue = events_queue
self.cur_bid = None
self.cur_ask = None
def connect_to_stream(self):
try:
s = requests.Session()
url = "https://" + self.domain + "/v1/prices"
headers = {'Authorization' : 'Bearer ' + self.access_token}
params = {'instruments' : self.instruments, 'accountId' : self.account_id}
req = requests.Request('GET', url, headers=headers, params=params)
pre = req.prepare()
resp = s.send(pre, stream=True, verify=False)
return resp
except Exception as e:
s.close()
print "Caught exception when connecting to stream\n" + str(e)
def stream_to_queue(self):
response = self.connect_to_stream()
if response.status_code != 200:
return
for line in response.iter_lines(1):
if line:
try:
msg = json.loads(line)
except Exception as e:
print "Caught exception when converting message into json\n" + str(e)
return
if msg.has_key("instrument") or msg.has_key("tick"):
print msg
instrument = msg["tick"]["instrument"]
time = msg["tick"]["time"]
bid = msg["tick"]["bid"]
ask = msg["tick"]["ask"]
self.cur_bid = bid
self.cur_ask = ask
tev = TickEvent(instrument, time, bid, ask)
self.events_queue.put(tev)
View File
+75
View File
@@ -0,0 +1,75 @@
import copy
import Queue
import threading
import time
from qsforex.execution.execution import Execution
from qsforex.portfolio.portfolio import Portfolio
from qsforex.settings import STREAM_DOMAIN, API_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID
from qsforex.strategy.strategy import TestRandomStrategy
from qsforex.streaming.streaming import StreamingForexPrices
def trade(events, strategy, portfolio, execution):
"""
Carries out an infinite while loop that polls the
events queue and directs each event to either the
strategy component of the execution handler. The
loop will then pause for "heartbeat" seconds and
continue.
"""
while True:
try:
event = events.get(False)
except Queue.Empty:
pass
else:
if event is not None:
if event.type == 'TICK':
strategy.calculate_signals(event)
elif event.type == 'SIGNAL':
portfolio.execute_signal(event)
elif event.type == 'ORDER':
execution.execute_order(event)
time.sleep(heartbeat)
if __name__ == "__main__":
heartbeat = 0.5 # Half a second between polling
events = Queue.Queue()
# Trade "Cable"
instrument = "GBP_USD"
# Create the OANDA market price streaming class
# making sure to provide authentication commands
prices = StreamingForexPrices(
STREAM_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID,
instrument, events
)
# Create the strategy/signal generator, passing the
# instrument and the events queue
strategy = TestRandomStrategy(instrument, events)
# Create the portfolio object that will be used to
# compare the OANDA positions with the local, to
# ensure backtesting integrity.
portfolio = Portfolio(prices, events, equity=98505.02)
# Create the execution handler making sure to
# provide authentication commands
execution = Execution(API_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID)
# Create two separate threads: One for the trading loop
# and another for the market price streaming class
trade_thread = threading.Thread(
target=trade, args=(
events, strategy, portfolio, execution
)
)
price_thread = threading.Thread(target=prices.stream_to_queue, args=[])
# Start both threads
trade_thread.start()
price_thread.start()