Modified the Position handling to use long/short instead of buy/sell for side/position_type. Also modified the unit tests for both Portfolio and Position to reflect these changes. Added a basic historical backtesting capability via backtest.py and using CSV tick data for currency pairs.

This commit is contained in:
Michael Halls-Moore
2015-04-17 12:34:31 +01:00
parent d9a7444fc2
commit e74777802b
12 changed files with 519 additions and 192 deletions
View File
+77
View File
@@ -0,0 +1,77 @@
import copy
import Queue
import threading
import time
from decimal import Decimal, getcontext
from qsforex.execution.execution import SimulatedExecution
from qsforex.portfolio.portfolio import Portfolio
from qsforex import settings
from qsforex.strategy.strategy import TestStrategy
from qsforex.data.price import HistoricCSVPriceHandler
def trade(events, strategy, portfolio, execution, heartbeat):
"""
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__":
# Set the number of decimal places to 2
getcontext().prec = 2
heartbeat = 0.0 # Half a second between polling
events = Queue.Queue()
equity = settings.EQUITY
# Load the historic CSV tick data files
pairs = ["GBPUSD"]
csv_dir = settings.CSV_DATA_DIR
if csv_dir is None:
print "No historic data directory provided - backtest terminating."
sys.exit()
# Create the historic tick data streaming class
prices = HistoricCSVPriceHandler(pairs, events, csv_dir)
# Create the strategy/signal generator, passing the
# instrument and the events queue
strategy = TestStrategy(pairs[0], events)
# Create the portfolio object to track trades
portfolio = Portfolio(prices, events, equity=equity)
# Create the simulated execution handler
execution = SimulatedExecution()
# 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, heartbeat
)
)
price_thread = threading.Thread(target=prices.stream_to_queue, args=[])
# Start both threads
trade_thread.start()
price_thread.start()
View File
+86
View File
@@ -0,0 +1,86 @@
from abc import ABCMeta, abstractmethod
import datetime
from decimal import Decimal, getcontext, ROUND_HALF_DOWN
import os
import os.path
import numpy as np
import pandas as pd
from qsforex.event.event import TickEvent
class PriceHandler(object):
"""
PriceHandler is an abstract base class providing an interface for
all subsequent (inherited) data handlers (both live and historic).
The goal of a (derived) PriceHandler object is to output a set of
bid/ask/timestamp "ticks" for each currency pair and place them into
an event queue.
This will replicate how a live strategy would function as current
tick data would be streamed via a brokerage. Thus a historic and live
system will be treated identically by the rest of the QSForex
backtesting suite.
"""
__metaclass__ = ABCMeta
@abstractmethod
def stream_to_queue(self):
"""
Streams a sequence of tick data events (timestamp, bid, ask)
tuples to the events queue.
"""
raise NotImplementedError("Should implement stream_to_queue()")
class HistoricCSVPriceHandler(PriceHandler):
"""
HistoricCSVPriceHandler is designed to read CSV files of
tick data for each requested currency pair and stream those
to the provided events queue.
"""
def __init__(self, pairs, events_queue, csv_dir):
"""
Initialises the historic data handler by requesting
the location of the CSV files and a list of symbols.
It will be assumed that all files are of the form
'pair.csv', where "pair" is the currency pair. For
GBP/USD the filename is GBPUSD.csv.
Parameters:
pairs - The list of currency pairs to obtain.
events_queue - The events queue to send the ticks to.
csv_dir - Absolute directory path to the CSV files.
"""
self.pairs = pairs
self.events_queue = events_queue
self.csv_dir = csv_dir
self.cur_bid = None
self.cur_ask = None
def _open_convert_csv_files(self):
"""
Opens the CSV files from the data directory, converting
them into pandas DataFrames within a pairs dictionary.
"""
pair_path = os.path.join(self.csv_dir, '%s.csv' % self.pairs[0])
self.pair = pd.io.parsers.read_csv(
pair_path, header=True, index_col=0, parse_dates=True,
names=("Time", "Ask", "Bid", "AskVolume", "BidVolume")
).iterrows()
def stream_to_queue(self):
self._open_convert_csv_files()
for index, row in self.pair:
self.cur_bid = Decimal(str(row["Bid"])).quantize(
Decimal("0.00001", ROUND_HALF_DOWN)
)
self.cur_ask = Decimal(str(row["Ask"])).quantize(
Decimal("0.00001", ROUND_HALF_DOWN)
)
tev = TickEvent(self.pairs[0], index, row["Bid"], row["Ask"])
self.events_queue.put(tev)
+30 -1
View File
@@ -1,8 +1,37 @@
from abc import ABCMeta, abstractmethod
import httplib
import urllib
class Execution(object):
class ExecutionHandler(object):
"""
Provides an abstract base class to handle all execution in the
backtesting and live trading system.
"""
__metaclass__ = ABCMeta
@abstractmethod
def execute_order(self):
"""
Send the order to the brokerage.
"""
raise NotImplementedError("Should implement execute_order()")
class SimulatedExecution(object):
"""
Provides a simulated execution handling environment. This class
actually does nothing - it simply receives an order to execute.
Instead, the Portfolio object actually provides fill handling.
This will be modified in later versions.
"""
def execute_order(self, event):
pass
class OANDAExecutionHandler(ExecutionHandler):
def __init__(self, domain, access_token, account_id):
self.domain = domain
self.access_token = access_token
+58 -55
View File
@@ -24,116 +24,119 @@ class Portfolio(object):
return self.equity * self.risk_per_trade
def add_new_position(
self, side, market, units, exposure,
add_price, remove_price
self, position_type, market, units,
exposure, bid, ask
):
ps = Position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
self.positions[market] = ps
def add_position_units(
self, market, units, exposure,
add_price, remove_price
self, market, units,
exposure, bid, ask
):
if market not in self.positions:
return False
else:
ps = self.positions[market]
if ps.position_type == "long":
add_price = ask
else:
add_price = bid
new_total_units = ps.units + units
new_total_cost = ps.avg_price*ps.units + add_price*units
ps.exposure += exposure
ps.avg_price = new_total_cost/new_total_units
ps.units = new_total_units
ps.update_position_price(remove_price, exposure)
ps.update_position_price(bid, ask, exposure)
return True
def remove_position_units(
self, market, units, remove_price
self, market, units, bid, ask
):
if market not in self.positions:
return False
else:
ps = self.positions[market]
if ps.position_type == "long":
remove_price = bid
else:
remove_price = ask
ps.units -= units
exposure = Decimal(str(units))
ps.exposure -= exposure
ps.update_position_price(remove_price, exposure)
ps.update_position_price(bid, ask, exposure)
pnl = ps.calculate_pips() * exposure / remove_price
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
return True
def close_position(
self, market, remove_price
self, market, bid, ask
):
if market not in self.positions:
return False
else:
ps = self.positions[market]
ps.update_position_price(remove_price, ps.exposure)
ps.update_position_price(bid, ask, ps.exposure)
if ps.position_type == "long":
remove_price = bid
else:
remove_price = ask
pnl = ps.calculate_pips() * ps.exposure / remove_price
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
del[self.positions[market]]
return True
def execute_signal(self, signal_event):
def execute_signal(self, signal_event):
side = signal_event.side
market = signal_event.instrument
units = int(self.trade_units)
# Check side for correct bid/ask prices
if side == "buy":
add_price = Decimal(str(self.ticker.cur_ask))
remove_price = Decimal(str(self.ticker.cur_bid))
else:
add_price = Decimal(str(self.ticker.cur_bid))
remove_price = Decimal(str(self.ticker.cur_ask))
exposure = Decimal(str(units))
bid = Decimal(str(self.ticker.cur_bid))
ask = Decimal(str(self.ticker.cur_ask))
# If there is no position, create one
if market not in self.positions:
if side == "buy":
position_type = "long"
else:
position_type = "short"
self.add_new_position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
order = OrderEvent(market, units, "market", side)
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.side:
# Add to the position
add_position_units(
market, units, exposure,
add_price, remove_price
)
else:
# Check if the units close out the position
if side == "buy" and ps.position_type == "long":
add_position_units(market, units, exposure, bid, ask)
elif side == "sell" and ps.position_type == "long":
if units == ps.units:
# Close the position
self.close_position(market, remove_price)
order = OrderEvent(market, units, "market", side)
self.events.put(order)
self.close_position(market, bid, ask)
# TODO: Allow units to be added/removed
elif units < ps.units:
# Remove from the position
self.remove_position_units(
market, units, remove_price
)
else: # units > ps.units
# Close the position and add a new one with
# additional units of opposite side
new_units = units - ps.units
self.close_position(market, remove_price)
return
elif units > ps.units:
return
elif side == "buy" and ps.position_type == "short":
if units == ps.units:
self.close_position(market, bid, ask)
# TODO: Allow units to be added/removed
elif units < ps.units:
return
elif units > ps.units:
return
if side == "buy":
new_side = "sell"
else:
new_side = "buy"
new_exposure = Decimal(str(units))
self.add_new_position(
new_side, market, new_units,
new_exposure, add_price, remove_price
)
elif side == "sell" and ps.position_type == "short":
add_position_units(market, units, exposure, bid, ask)
order = OrderEvent(market, units, "market", side)
self.events.put(order)
print "Balance: %0.2f" % self.balance
+166 -91
View File
@@ -18,200 +18,199 @@ class TestPortfolio(unittest.TestCase):
)
def test_add_position_long(self):
side = "LONG"
position_type = "long"
market = "GBP/USD"
units = 2000
exposure = Decimal(str(units))
add_price = Decimal("1.51819")
remove_price = Decimal("1.51770")
units = Decimal("2000")
exposure = Decimal("2000.00")
bid = Decimal("1.51770")
ask = Decimal("1.51819")
self.port.add_new_position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
self.assertEquals(ps.side, side)
self.assertEquals(ps.position_type, position_type)
self.assertEquals(ps.market, market)
self.assertEquals(ps.units, units)
self.assertEquals(ps.exposure, exposure)
self.assertEquals(ps.avg_price, add_price)
self.assertEquals(ps.cur_price, remove_price)
self.assertEquals(ps.avg_price, ask)
self.assertEquals(ps.cur_price, bid)
def test_add_position_short(self):
side = "SHORT"
position_type = "short"
market = "GBP/USD"
units = 2000
exposure = Decimal(str(units))
add_price = Decimal("1.51770")
remove_price = Decimal("1.51819")
units = Decimal("2000")
exposure = Decimal("2000.00")
bid = Decimal("1.51770")
ask = Decimal("1.51819")
self.port.add_new_position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
self.assertEquals(ps.side, side)
self.assertEquals(ps.position_type, position_type)
self.assertEquals(ps.market, market)
self.assertEquals(ps.units, units)
self.assertEquals(ps.exposure, exposure)
self.assertEquals(ps.avg_price, add_price)
self.assertEquals(ps.cur_price, remove_price)
self.assertEquals(ps.avg_price, bid)
self.assertEquals(ps.cur_price, ask)
def test_add_position_units_long(self):
side = "LONG"
position_type = "long"
market = "GBP/USD"
units = 2000
exposure = Decimal(str(units))
add_price = Decimal("1.51819")
remove_price = Decimal("1.51770")
units = Decimal("2000")
exposure = Decimal("2000.00")
bid = Decimal("1.51770")
ask = Decimal("1.51819")
# Test for no position
market = "EUR/USD"
apu = self.port.add_position_units(
market, units, exposure,
add_price, remove_price
bid, ask
)
self.assertFalse(apu)
# Add a position and test for real position
market = "GBP/USD"
self.port.add_new_position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
# Test for addition of units
add_price = Decimal("1.51928")
remove_price = Decimal("1.51878")
bid = Decimal("1.51878")
ask = Decimal("1.51928")
apu = self.port.add_position_units(
market, units, exposure,
add_price, remove_price
bid, ask
)
self.assertTrue(apu)
self.assertEqual(ps.avg_price, Decimal("1.518735"))
def test_add_position_units_short(self):
side = "SHORT"
position_type = "short"
market = "GBP/USD"
units = 2000
exposure = Decimal(str(units))
add_price = Decimal("1.51770")
remove_price = Decimal("1.51819")
units = Decimal("2000")
exposure = Decimal("2000.00")
bid = Decimal("1.51770")
ask = Decimal("1.51819")
# Test for no position
market = "EUR/USD"
apu = self.port.add_position_units(
market, units, exposure,
add_price, remove_price
bid, ask
)
self.assertFalse(apu)
# Add a position and test for real position
market = "GBP/USD"
self.port.add_new_position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
# Test for addition of units
add_price = Decimal("1.51878")
remove_price = Decimal("1.51928")
bid = Decimal("1.51878")
ask = Decimal("1.51928")
apu = self.port.add_position_units(
market, units, exposure,
add_price, remove_price
bid, ask
)
self.assertTrue(apu)
self.assertEqual(ps.avg_price, Decimal("1.51824"))
def test_remove_position_units_long(self):
side = "LONG"
units = 2000
exposure = Decimal(str(units))
add_price = Decimal("1.51819")
remove_price = Decimal("1.51770")
position_type = "long"
units = Decimal("2000")
exposure = Decimal("2000.00")
bid = Decimal("1.51770")
ask = Decimal("1.51819")
# Test for no position
market = "EUR/USD"
apu = self.port.remove_position_units(
market, units, remove_price
market, units, bid, ask
)
self.assertFalse(apu)
# Add a position and then add units to it
market = "GBP/USD"
self.port.add_new_position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
add_price = Decimal("1.51928")
remove_price = Decimal("1.51878")
bid = Decimal("1.51878")
ask = Decimal("1.51928")
add_units = 8000
add_exposure = Decimal(str(add_units))
apu = self.port.add_position_units(
market, add_units, add_exposure,
add_price, remove_price
bid, ask
)
self.assertEqual(ps.units, 10000)
self.assertEqual(ps.exposure, Decimal("10000.00"))
self.assertEqual(ps.avg_price, Decimal("1.519062"))
# Test removal of (some) of the units
add_price = Decimal("1.52134")
remove_price = Decimal("1.52017")
bid = Decimal("1.52017")
ask = Decimal("1.52134")
remove_units = 3000
rpu = self.port.remove_position_units(
market, remove_units, remove_price
market, remove_units, bid, ask
)
self.assertTrue(rpu)
self.assertEqual(ps.units, 7000)
self.assertEqual(ps.exposure, Decimal("7000.00"))
self.assertEqual(ps.profit_base, Decimal("2.19054"))
self.assertEqual(self.port.balance, Decimal("100002.19"))
def test_remove_position_units_short(self):
side = "SHORT"
units = 2000
exposure = Decimal(str(units))
add_price = Decimal("1.51770")
remove_price = Decimal("1.51819")
position_type = "short"
units = Decimal("2000")
exposure = Decimal("2000.00")
bid = Decimal("1.51770")
ask = Decimal("1.51819")
# Test for no position
market = "EUR/USD"
apu = self.port.remove_position_units(
market, units, remove_price
market, units, bid, ask
)
self.assertFalse(apu)
# Add a position and then add units to it
market = "GBP/USD"
self.port.add_new_position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
add_price = Decimal("1.51878")
remove_price = Decimal("1.51928")
bid = Decimal("1.51878")
ask = Decimal("1.51928")
add_units = 8000
add_exposure = Decimal(str(add_units))
apu = self.port.add_position_units(
market, add_units, add_exposure,
add_price, remove_price
bid, ask
)
self.assertEqual(ps.units, 10000)
self.assertEqual(ps.exposure, Decimal("10000.00"))
self.assertEqual(ps.avg_price, Decimal("1.518564"))
# Test removal of (some) of the units
add_price = Decimal("1.52017")
remove_price = Decimal("1.52134")
bid = Decimal("1.52017")
ask = Decimal("1.52134")
remove_units = 3000
rpu = self.port.remove_position_units(
market, remove_units, remove_price
market, remove_units, bid, ask
)
self.assertTrue(rpu)
self.assertEqual(ps.units, 7000)
@@ -220,16 +219,16 @@ class TestPortfolio(unittest.TestCase):
self.assertEqual(self.port.balance, Decimal("99994.52"))
def test_close_position_long(self):
side = "LONG"
units = 2000
exposure = Decimal(str(units))
add_price = Decimal("1.51819")
remove_price = Decimal("1.51770")
position_type = "long"
units = Decimal("2000")
exposure = Decimal("2000.00")
bid = Decimal("1.51770")
ask = Decimal("1.51819")
# Test for no position
market = "EUR/USD"
cp = self.port.close_position(
market, remove_price
market, bid, ask
)
self.assertFalse(cp)
@@ -237,12 +236,12 @@ class TestPortfolio(unittest.TestCase):
# Will lose money on the spread
market = "GBP/USD"
self.port.add_new_position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
cp = self.port.close_position(
market, remove_price
market, bid, ask
)
self.assertTrue(cp)
self.assertRaises(ps) # Key doesn't exist
@@ -252,38 +251,114 @@ class TestPortfolio(unittest.TestCase):
# close the position. Balance should be as expected
# for a multi-leg transaction.
self.port.add_new_position(
side, market, units, exposure,
add_price, remove_price
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
add_price = Decimal("1.51928")
remove_price = Decimal("1.51878")
# Add 8000 units
bid = Decimal("1.51878")
ask = Decimal("1.51928")
add_units = 8000
add_exposure = Decimal(str(add_units))
apu = self.port.add_position_units(
market, add_units, add_exposure,
add_price, remove_price
market, add_units,
add_exposure, bid, ask
)
self.assertEqual(ps.units, 10000)
self.assertEqual(ps.exposure, Decimal("10000.00"))
self.assertEqual(ps.avg_price, Decimal("1.519062"))
add_price = Decimal("1.52134")
remove_price = Decimal("1.52017")
# Remove 3000 units
bid = Decimal("1.52017")
ask = Decimal("1.52134")
remove_units = 3000
rpu = self.port.remove_position_units(
market, remove_units, remove_price
market, remove_units, bid, ask
)
self.assertEqual(ps.units, 7000)
self.assertEqual(ps.exposure, Decimal("7000.00"))
self.assertEqual(ps.profit_base, Decimal("2.19054"))
self.assertEqual(self.port.balance, Decimal("100001.54"))
# Close the position
cp = self.port.close_position(
market, remove_price
market, bid, ask
)
self.assertTrue(cp)
self.assertRaises(ps) # Key doesn't exist
self.assertEqual(self.port.balance, Decimal("100006.65"))
def test_close_position_short(self):
position_type = "short"
units = Decimal("2000")
exposure = Decimal("2000.00")
bid = Decimal("1.51770")
ask = Decimal("1.51819")
# Test for no position
market = "EUR/USD"
cp = self.port.close_position(
market, bid, ask
)
self.assertFalse(cp)
# Add a position and then close it
# Will lose money on the spread
market = "GBP/USD"
self.port.add_new_position(
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
cp = self.port.close_position(
market, bid, ask
)
self.assertTrue(cp)
self.assertRaises(ps) # Key doesn't exist
self.assertEqual(self.port.balance, Decimal("99999.35"))
# Add 2000, add another 8000, remove 3000 and then
# close the position. Balance should be as expected
# for a multi-leg transaction.
self.port.add_new_position(
position_type, market, units,
exposure, bid, ask
)
ps = self.port.positions[market]
# Add 8000 units
bid = Decimal("1.51878")
ask = Decimal("1.51928")
add_units = 8000
add_exposure = Decimal(str(add_units))
apu = self.port.add_position_units(
market, add_units,
add_exposure, bid, ask
)
self.assertEqual(ps.units, 10000)
self.assertEqual(ps.exposure, Decimal("10000.00"))
self.assertEqual(ps.avg_price, Decimal("1.518564"))
# Remove 3000 units
bid = Decimal("1.52017")
ask = Decimal("1.52134")
remove_units = 3000
rpu = self.port.remove_position_units(
market, remove_units, bid, ask
)
self.assertEqual(ps.units, 7000)
self.assertEqual(ps.exposure, Decimal("7000.00"))
self.assertEqual(ps.profit_base, Decimal("-5.48201"))
self.assertEqual(self.port.balance, Decimal("99993.87"))
# Close the position
cp = self.port.close_position(
market, bid, ask
)
self.assertTrue(cp)
self.assertRaises(ps) # Key doesn't exist
self.assertEqual(self.port.balance, Decimal("99981.08"))
if __name__ == "__main__":
unittest.main()
+20 -9
View File
@@ -3,22 +3,31 @@ from decimal import Decimal, getcontext, ROUND_HALF_DOWN
class Position(object):
def __init__(
self, side, market, units,
exposure, avg_price, cur_price
self, position_type, market,
units, exposure, bid, ask
):
self.side = side
self.position_type = position_type # Long or short
self.market = market
self.units = units
self.exposure = Decimal(str(exposure))
self.avg_price = Decimal(str(avg_price))
self.cur_price = Decimal(str(cur_price))
# Long or short
if self.position_type == "long":
self.avg_price = Decimal(str(ask))
self.cur_price = Decimal(str(bid))
else:
self.avg_price = Decimal(str(bid))
self.cur_price = Decimal(str(ask))
self.profit_base = self.calculate_profit_base(self.exposure)
self.profit_perc = self.calculate_profit_perc(self.exposure)
def calculate_pips(self):
getcontext.prec = 6
mult = Decimal("1")
if self.side == "SHORT":
if self.position_type == "long":
mult = Decimal("1")
elif self.position_type == "short":
mult = Decimal("-1")
return (mult * (self.cur_price - self.avg_price)).quantize(
Decimal("0.00001"), ROUND_HALF_DOWN
@@ -35,8 +44,10 @@ class Position(object):
Decimal("0.00001"), ROUND_HALF_DOWN
)
def update_position_price(self, cur_price, exposure):
self.cur_price = cur_price
def update_position_price(self, bid, ask, exposure):
if self.position_type == "long":
self.cur_price = Decimal(str(bid))
else:
self.cur_price = Decimal(str(ask))
self.profit_base = self.calculate_profit_base(exposure)
self.profit_perc = self.calculate_profit_perc(exposure)
+60 -24
View File
@@ -7,55 +7,91 @@ from position import Position
class TestLongGBPUSDPosition(unittest.TestCase):
def setUp(self):
getcontext.prec = 2
side = "LONG"
position_type = "long"
market = "GBP/USD"
units = Decimal(str(2000))
units = Decimal("2000")
exposure = Decimal("2000.00")
avg_price = Decimal("1.51819")
cur_price = Decimal("1.51770")
bid = Decimal("1.50328")
ask = Decimal("1.50349")
self.position = Position(
side, market, units, exposure,
avg_price, cur_price
position_type, market,
units, exposure, bid, ask
)
def test_calculate_pips(self):
def test_calculate_init_pips(self):
pos_pips = self.position.calculate_pips()
self.assertEqual(pos_pips, Decimal("-0.00049"))
self.assertEqual(pos_pips, Decimal("-0.00021"))
def test_calculate_profit_base(self):
def test_calculate_init_profit_base(self):
profit_base = self.position.calculate_profit_base(self.position.exposure)
self.assertEqual(profit_base, Decimal("-0.64571"))
self.assertEqual(profit_base, Decimal("-0.27939"))
def test_calculate_profit_perc(self):
def test_calculate_init_profit_perc(self):
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
self.assertEqual(profit_perc, Decimal("-0.03229"))
self.assertEqual(profit_perc, Decimal("-0.01397"))
def test_calculate_updated_values(self):
"""
Check that after the bid/ask prices move, that the updated
pips, profit and percentage profit calculations are correct.
"""
bid = Decimal("1.50486")
ask = Decimal("1.50586")
self.position.update_position_price(bid, ask, self.position.exposure)
# Check pips
pos_pips = self.position.calculate_pips()
self.assertEqual(pos_pips, Decimal("0.00137"))
# Check profit base
profit_base = self.position.calculate_profit_base(self.position.exposure)
self.assertEqual(profit_base, Decimal("1.82077"))
# Check profit percentage
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
self.assertEqual(profit_perc, Decimal("0.09104"))
class TestShortGBPUSDPosition(unittest.TestCase):
def setUp(self):
getcontext.prec = 2
side = "SHORT"
position_type = "short"
market = "GBP/USD"
units = 2000
units = Decimal("2000")
exposure = Decimal("2000.00")
avg_price = Decimal("1.51819")
cur_price = Decimal("1.51770")
bid = Decimal("1.50328")
ask = Decimal("1.50349")
self.position = Position(
side, market, units, exposure,
avg_price, cur_price
position_type, market,
units, exposure, bid, ask
)
def test_calculate_pips(self):
def test_calculate_init_pips(self):
pos_pips = self.position.calculate_pips()
self.assertEqual(pos_pips, Decimal("0.00049"))
self.assertEqual(pos_pips, Decimal("-0.00021"))
def test_calculate_profit_base(self):
def test_calculate_init_profit_base(self):
profit_base = self.position.calculate_profit_base(self.position.exposure)
self.assertEqual(profit_base, Decimal("0.64571"))
self.assertEqual(profit_base, Decimal("-0.27935"))
def test_calculate_profit_perc(self):
def test_calculate_init_profit_perc(self):
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
self.assertEqual(profit_perc, Decimal("0.03229"))
self.assertEqual(profit_perc, Decimal("-0.01397"))
def test_calculate_updated_values(self):
"""
Check that after the bid/ask prices move, that the updated
pips, profit and percentage profit calculations are correct.
"""
bid = Decimal("1.50486")
ask = Decimal("1.50586")
self.position.update_position_price(bid, ask, self.position.exposure)
# Check pips
pos_pips = self.position.calculate_pips()
self.assertEqual(pos_pips, Decimal("-0.00258"))
# Check profit base
profit_base = self.position.calculate_profit_base(self.position.exposure)
self.assertEqual(profit_base, Decimal("-3.42661"))
# Check profit percentage
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
self.assertEqual(profit_perc, Decimal("-0.17133"))
if __name__ == "__main__":
+7 -1
View File
@@ -1,3 +1,4 @@
from decimal import Decimal
import os
@@ -14,8 +15,13 @@ ENVIRONMENTS = {
}
}
CSV_DATA_DIR = os.environ.get('QSFOREX_CSV_DATA_DIR', None)
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)
ACCOUNT_ID = os.environ.get('OANDA_API_ACCOUNT_ID', None)
BASE_CURRENCY = "GBP"
EQUITY = Decimal("100000.00")
+2 -2
View File
@@ -10,7 +10,7 @@ class TestStrategy(object):
def calculate_signals(self, event):
if event.type == 'TICK':
if self.ticks % 200 == 0:
if self.ticks % 5 == 0:
if self.invested == False:
signal = SignalEvent(self.instrument, "market", "buy")
self.events.put(signal)
@@ -19,4 +19,4 @@ class TestStrategy(object):
signal = SignalEvent(self.instrument, "market", "sell")
self.events.put(signal)
self.invested = False
self.ticks += 1
self.ticks += 1
+13 -9
View File
@@ -4,14 +4,14 @@ import threading
import time
from decimal import Decimal, getcontext
from qsforex.execution.execution import Execution
from qsforex.execution.execution import OANDAExecutionHandler
from qsforex.portfolio.portfolio import Portfolio
from qsforex.settings import STREAM_DOMAIN, API_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID
from qsforex import settings
from qsforex.strategy.strategy import TestStrategy
from qsforex.streaming.streaming import StreamingForexPrices
def trade(events, strategy, portfolio, execution):
def trade(events, strategy, portfolio, execution, heartbeat):
"""
Carries out an infinite while loop that polls the
events queue and directs each event to either the
@@ -39,9 +39,9 @@ if __name__ == "__main__":
# Set the number of decimal places to 2
getcontext().prec = 2
heartbeat = 0.5 # Half a second between polling
heartbeat = 0.0 # Half a second between polling
events = Queue.Queue()
equity = Decimal("99949.82")
equity = settings.EQUITY
# Trade "Cable"
instrument = "GBP_USD"
@@ -49,8 +49,8 @@ if __name__ == "__main__":
# Create the OANDA market price streaming class
# making sure to provide authentication commands
prices = StreamingForexPrices(
STREAM_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID,
instrument, events
settings.STREAM_DOMAIN, settings.ACCESS_TOKEN,
settings.ACCOUNT_ID, instrument, events
)
# Create the strategy/signal generator, passing the
@@ -64,13 +64,17 @@ if __name__ == "__main__":
# Create the execution handler making sure to
# provide authentication commands
execution = Execution(API_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID)
execution = OANDAExecutionHandler(
settings.API_DOMAIN,
settings.ACCESS_TOKEN,
settings.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
events, strategy, portfolio, execution, heartbeat
)
)
price_thread = threading.Thread(target=prices.stream_to_queue, args=[])