diff --git a/portfolio/portfolio.py b/portfolio/portfolio.py index ea08242..789247a 100644 --- a/portfolio/portfolio.py +++ b/portfolio/portfolio.py @@ -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 diff --git a/portfolio/portfolio_test.py b/portfolio/portfolio_test.py index 6ed0f93..4c747a8 100644 --- a/portfolio/portfolio_test.py +++ b/portfolio/portfolio_test.py @@ -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__": diff --git a/portfolio/position.py b/portfolio/position.py index ca50880..4d6a28f 100644 --- a/portfolio/position.py +++ b/portfolio/position.py @@ -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 diff --git a/portfolio/position_test.py b/portfolio/position_test.py index 0954868..2e6eec9 100644 --- a/portfolio/position_test.py +++ b/portfolio/position_test.py @@ -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__": diff --git a/strategy/strategy.py b/strategy/strategy.py index 2e432fe..f765d5e 100644 --- a/strategy/strategy.py +++ b/strategy/strategy.py @@ -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) \ No newline at end of file + 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 + \ No newline at end of file diff --git a/trading/trading.py b/trading/trading.py index 2187bf8..236b9ee 100644 --- a/trading/trading.py +++ b/trading/trading.py @@ -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