Decimalised the trading engine to be more realistic

This commit is contained in:
Michael Halls-Moore
2015-03-06 09:57:15 +00:00
parent 30dbcc7bfa
commit 6c77cc1deb
6 changed files with 106 additions and 78 deletions
+9 -8
View File
@@ -1,4 +1,5 @@
from copy import deepcopy
from decimal import Decimal, getcontext, ROUND_HALF_DOWN
from qsforex.event.event import OrderEvent
from qsforex.portfolio.position import Position
@@ -7,7 +8,7 @@ from qsforex.portfolio.position import Position
class Portfolio(object):
def __init__(
self, ticker, events, base="GBP", leverage=20,
equity=100000.0, risk_per_trade=0.02
equity=Decimal("100000.00"), risk_per_trade=Decimal("0.02")
):
self.ticker = ticker
self.events = events
@@ -56,11 +57,11 @@ class Portfolio(object):
else:
ps = self.positions[market]
ps.units -= units
exposure = float(units)
exposure = Decimal(units)
ps.exposure -= exposure
ps.update_position_price(remove_price)
pnl = ps.calculate_pips() * exposure / remove_price
self.balance += pnl
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
return True
def close_position(
@@ -72,7 +73,7 @@ class Portfolio(object):
ps = self.positions[market]
ps.update_position_price(remove_price)
pnl = ps.calculate_pips() * ps.exposure / remove_price
self.balance += pnl
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
del[self.positions[market]]
return True
@@ -83,12 +84,12 @@ class Portfolio(object):
# Check side for correct bid/ask prices
#if side == "buy":
add_price = self.ticker.cur_ask
remove_price = self.ticker.cur_bid
add_price = Decimal(str(self.ticker.cur_ask))
remove_price = Decimal(str(self.ticker.cur_bid))
#else:
#add_price = self.ticker.cur_bid
#remove_price = self.ticker.cur_ask
exposure = float(units)
exposure = Decimal(str(units))
# If there is no position, create one
if market not in self.positions:
@@ -130,7 +131,7 @@ class Portfolio(object):
new_side = "sell"
else:
new_side = "sell"
new_exposure = float(units)
new_exposure = Decimal(str(units))
self.add_new_position(
new_side, market, new_units,
new_exposure, add_price, remove_price
+42 -41
View File
@@ -1,6 +1,6 @@
from decimal import Decimal, getcontext, ROUND_HALF_DOWN
import unittest
#from position import Position
from portfolio import Portfolio
@@ -8,11 +8,12 @@ class TestPortfolio(unittest.TestCase):
def setUp(self):
base = "GBP"
leverage = 20
equity = 100000.0
risk_per_trade = 0.02
equity = Decimal("100000.00")
risk_per_trade = Decimal("0.02")
ticker = {}
events = {}
self.port = Portfolio(
ticker, base=base, leverage=leverage,
ticker, events, base=base, leverage=leverage,
equity=equity, risk_per_trade=risk_per_trade
)
@@ -20,9 +21,9 @@ class TestPortfolio(unittest.TestCase):
side = "LONG"
market = "GBP/USD"
units = 2000
exposure = float(units)
add_price = 1.51819
remove_price = 1.51770
exposure = Decimal(str(units))
add_price = Decimal("1.51819")
remove_price = Decimal("1.51770")
self.port.add_new_position(
side, market, units, exposure,
@@ -41,9 +42,9 @@ class TestPortfolio(unittest.TestCase):
side = "LONG"
market = "GBP/USD"
units = 2000
exposure = float(units)
add_price = 1.51819
remove_price = 1.51770
exposure = Decimal(str(units))
add_price = Decimal("1.51819")
remove_price = Decimal("1.51770")
# Test for no position
market = "EUR/USD"
@@ -62,21 +63,21 @@ class TestPortfolio(unittest.TestCase):
ps = self.port.positions[market]
# Test for addition of units
add_price = 1.51928
remove_price = 1.51878
add_price = Decimal("1.51928")
remove_price = Decimal("1.51878")
apu = self.port.add_position_units(
market, units, exposure,
add_price, remove_price
)
self.assertTrue(apu)
self.assertAlmostEqual(ps.avg_price, 1.518735)
self.assertEqual(ps.avg_price, Decimal("1.518735"))
def test_remove_position_units(self):
side = "LONG"
units = 2000
exposure = float(units)
add_price = 1.51819
remove_price = 1.51770
exposure = Decimal(str(units))
add_price = Decimal("1.51819")
remove_price = Decimal("1.51770")
# Test for no position
market = "EUR/USD"
@@ -92,37 +93,37 @@ class TestPortfolio(unittest.TestCase):
add_price, remove_price
)
ps = self.port.positions[market]
add_price = 1.51928
remove_price = 1.51878
add_price = Decimal("1.51928")
remove_price = Decimal("1.51878")
add_units = 8000
add_exposure = float(add_units)
add_exposure = Decimal(str(add_units))
apu = self.port.add_position_units(
market, add_units, add_exposure,
add_price, remove_price
)
self.assertEqual(ps.units, 10000)
self.assertEqual(ps.exposure, 10000.0)
self.assertAlmostEqual(ps.avg_price, 1.519062)
self.assertEqual(ps.exposure, Decimal("10000.00"))
self.assertEqual(ps.avg_price, Decimal("1.519062"))
# Test removal of (some) of the units
add_price = 1.52134
remove_price = 1.52017
add_price = Decimal("1.52134")
remove_price = Decimal("1.52017")
remove_units = 3000
rpu = self.port.remove_position_units(
market, remove_units, remove_price
)
self.assertTrue(rpu)
self.assertEqual(ps.units, 7000)
self.assertEqual(ps.exposure, 7000.0)
self.assertAlmostEqual(ps.profit_base, 5.102060953709626)
self.assertAlmostEqual(self.port.balance, 100002.18659755158)
self.assertEqual(ps.exposure, Decimal("7000.00"))
self.assertEqual(ps.profit_base, Decimal("5.11127"))
self.assertEqual(self.port.balance, Decimal("100002.19"))
def test_close_position(self):
side = "LONG"
units = 2000
exposure = float(units)
add_price = 1.51819
remove_price = 1.51770
exposure = Decimal(str(units))
add_price = Decimal("1.51819")
remove_price = Decimal("1.51770")
# Test for no position
market = "EUR/USD"
@@ -144,7 +145,7 @@ class TestPortfolio(unittest.TestCase):
)
self.assertTrue(cp)
self.assertRaises(ps) # Key doesn't exist
self.assertAlmostEqual(self.port.balance, 99999.35428609079)
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
@@ -154,33 +155,33 @@ class TestPortfolio(unittest.TestCase):
add_price, remove_price
)
ps = self.port.positions[market]
add_price = 1.51928
remove_price = 1.51878
add_price = Decimal("1.51928")
remove_price = Decimal("1.51878")
add_units = 8000
add_exposure = float(add_units)
add_exposure = Decimal(str(add_units))
apu = self.port.add_position_units(
market, add_units, add_exposure,
add_price, remove_price
)
self.assertEqual(ps.units, 10000)
self.assertEqual(ps.exposure, 10000.0)
self.assertAlmostEqual(ps.avg_price, 1.519062)
add_price = 1.52134
remove_price = 1.52017
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_units = 3000
rpu = self.port.remove_position_units(
market, remove_units, remove_price
)
self.assertEqual(ps.units, 7000)
self.assertEqual(ps.exposure, 7000.0)
self.assertAlmostEqual(ps.profit_base, 5.102060953709626)
self.assertAlmostEqual(self.port.balance, 100001.54088364237)
self.assertEqual(ps.exposure, Decimal("7000.00"))
self.assertEqual(ps.profit_base, Decimal("5.11127"))
self.assertEqual(self.port.balance, Decimal("100001.54"))
cp = self.port.close_position(
market, remove_price
)
self.assertTrue(cp)
self.assertRaises(ps) # Key doesn't exist
self.assertAlmostEqual(self.port.balance, 100006.64294459608)
self.assertEqual(self.port.balance, Decimal("100006.65"))
if __name__ == "__main__":
+18 -8
View File
@@ -1,3 +1,6 @@
from decimal import Decimal, getcontext, ROUND_HALF_DOWN
class Position(object):
def __init__(
self, side, market, units,
@@ -6,24 +9,31 @@ class Position(object):
self.side = side
self.market = market
self.units = units
self.exposure = exposure
self.avg_price = avg_price
self.cur_price = cur_price
self.exposure = Decimal(str(exposure))
self.avg_price = Decimal(str(avg_price))
self.cur_price = Decimal(str(cur_price))
self.profit_base = self.calculate_profit_base()
self.profit_perc = self.calculate_profit_perc()
def calculate_pips(self):
mult = 1.0
getcontext.prec = 6
mult = Decimal("1")
if self.side == "SHORT":
mult = -1.0
return mult * (self.cur_price - self.avg_price)
mult = Decimal("-1")
return (mult * (self.cur_price - self.avg_price)).quantize(
Decimal("0.00001"), ROUND_HALF_DOWN
)
def calculate_profit_base(self):
pips = self.calculate_pips()
return pips * self.exposure / self.cur_price
return (pips * self.exposure / self.cur_price).quantize(
Decimal("0.00001"), ROUND_HALF_DOWN
)
def calculate_profit_perc(self):
return self.profit_base / self.exposure * 100.0
return (self.profit_base / self.exposure * Decimal("100.00")).quantize(
Decimal("0.00001"), ROUND_HALF_DOWN
)
def update_position_price(self, cur_price):
self.cur_price = cur_price
+17 -12
View File
@@ -1,3 +1,4 @@
from decimal import Decimal, getcontext
import unittest
from position import Position
@@ -5,12 +6,13 @@ from position import Position
class TestLongGBPUSDPosition(unittest.TestCase):
def setUp(self):
getcontext.prec = 2
side = "LONG"
market = "GBP/USD"
units = 2000
exposure = 2000.0
avg_price = 1.51819
cur_price = 1.51770
exposure = Decimal("2000.00")
avg_price = Decimal("1.51819")
cur_price = Decimal("1.51770")
self.position = Position(
side, market, units, exposure,
avg_price, cur_price
@@ -18,25 +20,28 @@ class TestLongGBPUSDPosition(unittest.TestCase):
def test_calculate_pips(self):
pos_pips = self.position.calculate_pips()
self.assertAlmostEqual(pos_pips, -0.00049)
self.assertEqual(pos_pips, Decimal("-0.00049"))
def test_calculate_profit_base(self):
profit_base = self.position.calculate_profit_base()
self.assertAlmostEqual(profit_base, -0.6457139)
#self.assertEqual(profit_base, Decimal("-0.6457139"))
self.assertEqual(profit_base, Decimal("-0.64571"))
def test_calculate_profit_perc(self):
profit_perc = self.position.calculate_profit_perc()
self.assertAlmostEqual(profit_perc, -0.032285695)
#self.assertEqual(profit_perc, Decimal("-0.032285695"))
self.assertEqual(profit_perc, Decimal("-0.03229"))
class TestShortGBPUSDPosition(unittest.TestCase):
def setUp(self):
getcontext.prec = 2
side = "SHORT"
market = "GBP/USD"
units = 2000
exposure = 2000.0
avg_price = 1.51819
cur_price = 1.51770
exposure = Decimal("2000.00")
avg_price = Decimal("1.51819")
cur_price = Decimal("1.51770")
self.position = Position(
side, market, units, exposure,
avg_price, cur_price
@@ -44,15 +49,15 @@ class TestShortGBPUSDPosition(unittest.TestCase):
def test_calculate_pips(self):
pos_pips = self.position.calculate_pips()
self.assertAlmostEqual(pos_pips, 0.00049)
self.assertEqual(pos_pips, Decimal("0.00049"))
def test_calculate_profit_base(self):
profit_base = self.position.calculate_profit_base()
self.assertAlmostEqual(profit_base, 0.6457139)
self.assertEqual(profit_base, Decimal("0.64571"))
def test_calculate_profit_perc(self):
profit_perc = self.position.calculate_profit_perc()
self.assertAlmostEqual(profit_perc, 0.032285695)
self.assertEqual(profit_perc, Decimal("0.03229"))
if __name__ == "__main__":
+12 -6
View File
@@ -1,17 +1,23 @@
from qsforex.event.event import SignalEvent
class TestRandomStrategy(object):
class TestStrategy(object):
def __init__(self, instrument, events):
self.instrument = instrument
self.events = events
self.ticks = 0
self.invested = False
def calculate_signals(self, event):
if event.type == 'TICK':
self.ticks += 1
if self.ticks % 2 == 0:
signal = SignalEvent(self.instrument, "market", "buy")
else:
signal = SignalEvent(self.instrument, "market", "sell")
self.events.put(signal)
if self.ticks % 5 == 0:
if self.invested == False:
signal = SignalEvent(self.instrument, "market", "buy")
self.events.put(signal)
self.invested = True
else:
signal = SignalEvent(self.instrument, "market", "sell")
self.events.put(signal)
self.invested = False
+8 -3
View File
@@ -2,11 +2,12 @@ import copy
import Queue
import threading
import time
from decimal import Decimal, getcontext
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.strategy.strategy import TestStrategy
from qsforex.streaming.streaming import StreamingForexPrices
@@ -35,8 +36,12 @@ def trade(events, strategy, portfolio, execution):
if __name__ == "__main__":
# Set the number of decimal places to 2
getcontext().prec = 2
heartbeat = 0.5 # Half a second between polling
events = Queue.Queue()
equity = Decimal("99999.65")
# Trade "Cable"
instrument = "GBP_USD"
@@ -50,12 +55,12 @@ if __name__ == "__main__":
# Create the strategy/signal generator, passing the
# instrument and the events queue
strategy = TestRandomStrategy(instrument, events)
strategy = TestStrategy(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=98499.05)
portfolio = Portfolio(prices, events, equity=equity)
# Create the execution handler making sure to
# provide authentication commands