Added remaining trading system around portfolio in order to compare with OANDA.
This commit is contained in:
@@ -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
|
||||||
@@ -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
@@ -1,20 +1,22 @@
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from position import Position
|
from qsforex.event.event import OrderEvent
|
||||||
|
from qsforex.portfolio.position import Position
|
||||||
|
|
||||||
|
|
||||||
class Portfolio(object):
|
class Portfolio(object):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, ticker, base="GBP", leverage=20,
|
self, ticker, events, base="GBP", leverage=20,
|
||||||
equity=100000.0, risk_per_trade=0.02
|
equity=100000.0, risk_per_trade=0.02
|
||||||
):
|
):
|
||||||
self.ticker = ticker
|
self.ticker = ticker
|
||||||
|
self.events = events
|
||||||
self.base = base
|
self.base = base
|
||||||
self.leverage = leverage
|
self.leverage = leverage
|
||||||
self.equity = equity
|
self.equity = equity
|
||||||
self.balance = deepcopy(self.equity)
|
self.balance = deepcopy(self.equity)
|
||||||
self.risk_per_trade = risk_per_trade
|
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 = {}
|
self.positions = {}
|
||||||
|
|
||||||
def calc_risk_position_size(self):
|
def calc_risk_position_size(self):
|
||||||
@@ -76,16 +78,16 @@ class Portfolio(object):
|
|||||||
|
|
||||||
def execute_signal(self, signal_event):
|
def execute_signal(self, signal_event):
|
||||||
side = signal_event.side
|
side = signal_event.side
|
||||||
market = signal_event.market
|
market = signal_event.instrument
|
||||||
units = self.risk_per_trade
|
units = int(self.trade_units)
|
||||||
|
|
||||||
# Check side for correct bid/ask prices
|
# Check side for correct bid/ask prices
|
||||||
if side == "LONG":
|
#if side == "buy":
|
||||||
add_price = self.ticker.cur_ask
|
add_price = self.ticker.cur_ask
|
||||||
remove_price = self.ticker.cur_bid
|
remove_price = self.ticker.cur_bid
|
||||||
else:
|
#else:
|
||||||
add_price = self.ticker.cur_bid
|
#add_price = self.ticker.cur_bid
|
||||||
remove_price = self.ticker.cur_ask
|
#remove_price = self.ticker.cur_ask
|
||||||
exposure = float(units)
|
exposure = float(units)
|
||||||
|
|
||||||
# If there is no position, create one
|
# If there is no position, create one
|
||||||
@@ -94,10 +96,13 @@ class Portfolio(object):
|
|||||||
side, market, units, exposure,
|
side, market, units, exposure,
|
||||||
add_price, remove_price
|
add_price, remove_price
|
||||||
)
|
)
|
||||||
|
order = OrderEvent(market, units, "market", "buy")
|
||||||
|
self.events.put(order)
|
||||||
# If a position exists add or remove units
|
# If a position exists add or remove units
|
||||||
else:
|
else:
|
||||||
|
ps = self.positions[market]
|
||||||
# Check if the sides equal
|
# Check if the sides equal
|
||||||
if side == ps[market].side:
|
if side == ps.side:
|
||||||
# Add to the position
|
# Add to the position
|
||||||
add_position_units(
|
add_position_units(
|
||||||
market, units, exposure,
|
market, units, exposure,
|
||||||
@@ -108,6 +113,8 @@ class Portfolio(object):
|
|||||||
if units == ps.units:
|
if units == ps.units:
|
||||||
# Close the position
|
# Close the position
|
||||||
self.close_position(market, remove_price)
|
self.close_position(market, remove_price)
|
||||||
|
order = OrderEvent(market, units, "market", "sell")
|
||||||
|
self.events.put(order)
|
||||||
elif units < ps.units:
|
elif units < ps.units:
|
||||||
# Remove from the position
|
# Remove from the position
|
||||||
self.remove_position_units(
|
self.remove_position_units(
|
||||||
@@ -119,12 +126,13 @@ class Portfolio(object):
|
|||||||
new_units = units - ps.units
|
new_units = units - ps.units
|
||||||
self.close_position(market, remove_price)
|
self.close_position(market, remove_price)
|
||||||
|
|
||||||
if side == "LONG":
|
if side == "buy":
|
||||||
new_side = "SHORT"
|
new_side = "sell"
|
||||||
else:
|
else:
|
||||||
new_side = "LONG"
|
new_side = "sell"
|
||||||
new_exposure = float(units)
|
new_exposure = float(units)
|
||||||
self.add_new_position(
|
self.add_new_position(
|
||||||
new_side, market, new_units,
|
new_side, market, new_units,
|
||||||
new_exposure, add_price, remove_price
|
new_exposure, add_price, remove_price
|
||||||
)
|
)
|
||||||
|
print "Balance: %0.2f" % self.balance
|
||||||
+21
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user