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 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
@@ -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