commit e223aa575125109459cbc04e5a57c0ac5b1e480c Author: Michael Halls-Moore Date: Tue Jan 27 18:29:27 2015 +0000 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..11ef5d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +*~ +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/portfolio/__init__.py b/portfolio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/portfolio/portfolio.py b/portfolio/portfolio.py new file mode 100644 index 0000000..f279ff6 --- /dev/null +++ b/portfolio/portfolio.py @@ -0,0 +1,129 @@ +from copy import deepcopy + +from position import Position + + +class Portfolio(object): + def __init__( + self, ticker, base="GBP", leverage=20, + equity=100000.0, risk_per_trade=0.02 + ): + self.ticker = ticker + 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.positions = {} + + def calc_risk_position_size(self): + return self.equity * self.risk_per_trade + + def add_new_position( + self, side, market, units, exposure, + add_price, remove_price + ): + ps = Position( + side, market, units, exposure, + add_price, remove_price + ) + self.positions[market] = ps + + def add_position_units( + self, market, units, exposure, + add_price, remove_price + ): + if market not in self.positions: + return False + else: + ps = self.positions[market] + 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) + return True + + def remove_position_units( + self, market, units, remove_price + ): + if market not in self.positions: + return False + else: + ps = self.positions[market] + ps.units -= units + ps.exposure -= float(units) + ps.update_position_price(self, remove_price) + pnl = ps.profit_base + self.balance += pnl + return True + + def close_position( + self, market, remove_price + ): + if market not in self.positions: + return False + else: + ps = self.positions[market] + ps.update_position_price(self, remove_price) + pnl = ps.profit_base + self.balance += pnl + del[self.positions[market]] + return True + + def execute_signal(self, signal_event): + side = signal_event.side + market = signal_event.market + units = self.risk_per_trade + + # 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 + exposure = float(units) + + # If there is no position, create one + if market not in self.positions: + self.add_new_position( + side, market, units, exposure, + add_price, remove_price + ) + # If a position exists add or remove units + else: + # Check if the sides equal + if side == ps[market].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 units == ps.units: + # Close the position + self.close_position(market, remove_price) + 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) + + if side == "LONG": + new_side = "SHORT" + else: + new_side = "LONG" + new_exposure = float(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 new file mode 100644 index 0000000..f06e5da --- /dev/null +++ b/portfolio/portfolio_test.py @@ -0,0 +1,68 @@ +import unittest + +#from position import Position +from portfolio import Portfolio + + +class TestPortfolio(unittest.TestCase): + def setUp(self): + base = "GBP" + leverage = 20 + equity = 100000.0 + risk_per_trade = 0.02 + ticker = {} + self.port = Portfolio( + ticker, base=base, leverage=leverage, + equity=equity, risk_per_trade=risk_per_trade + ) + + def test_add_position(self): + side = "LONG" + market = "GBP/USD" + units = 2000 + exposure = float(units) + add_price = 1.51819 + remove_price = 1.51770 + + self.port.add_new_position( + side, market, units, exposure, + add_price, remove_price + ) + ps = self.port.positions[market] + + self.assertEquals(ps.side, side) + 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) + + def test_add_position_units(self): + side = "LONG" + units = 2000 + exposure = float(units) + add_price = 1.51928 + remove_price = 1.51878 + + # Test for no position + market = "EUR/USD" + apu = self.port.add_position_units( + market, units, exposure, + add_price, remove_price + ) + self.assertFalse(apu) + + # Test for real position + market = "GBP/USD" + apu = self.port.add_position_units( + market, units, exposure, + add_price, remove_price + ) + self.assertTrue(apu) + ps = self.port.positions[market] + + self.assertAlmostEqual(ps.avg_price, 1.518735) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/portfolio/position.py b/portfolio/position.py new file mode 100644 index 0000000..21728e9 --- /dev/null +++ b/portfolio/position.py @@ -0,0 +1,32 @@ +class Position(object): + def __init__( + self, side, market, units, + exposure, avg_price, cur_price + ): + self.side = side + self.market = market + self.units = units + self.exposure = exposure + self.avg_price = avg_price + self.cur_price = cur_price + self.profit_base = self.calculate_profit_base() + self.profit_perc = self.calculate_profit_perc() + + def calculate_pips(self): + mult = 1.0 + if self.side == "SHORT": + mult = -1.0 + return mult * (self.cur_price - self.avg_price) + + def calculate_profit_base(self): + pips = self.calculate_pips() + return pips * self.exposure / self.cur_price + + def calculate_profit_perc(self): + return self.profit_base / self.exposure * 100.0 + + def update_position_price(self, cur_price): + self.cur_price = cur_price + self.profit_base = self.calculate_profit_base() + self.profit_perc = self.calculate_profit_perc() + diff --git a/portfolio/position_test.py b/portfolio/position_test.py new file mode 100644 index 0000000..0954868 --- /dev/null +++ b/portfolio/position_test.py @@ -0,0 +1,59 @@ +import unittest + +from position import Position + + +class TestLongGBPUSDPosition(unittest.TestCase): + def setUp(self): + side = "LONG" + market = "GBP/USD" + units = 2000 + exposure = 2000.0 + avg_price = 1.51819 + cur_price = 1.51770 + self.position = Position( + side, market, units, exposure, + avg_price, cur_price + ) + + def test_calculate_pips(self): + pos_pips = self.position.calculate_pips() + self.assertAlmostEqual(pos_pips, -0.00049) + + def test_calculate_profit_base(self): + profit_base = self.position.calculate_profit_base() + self.assertAlmostEqual(profit_base, -0.6457139) + + def test_calculate_profit_perc(self): + profit_perc = self.position.calculate_profit_perc() + self.assertAlmostEqual(profit_perc, -0.032285695) + + +class TestShortGBPUSDPosition(unittest.TestCase): + def setUp(self): + side = "SHORT" + market = "GBP/USD" + units = 2000 + exposure = 2000.0 + avg_price = 1.51819 + cur_price = 1.51770 + self.position = Position( + side, market, units, exposure, + avg_price, cur_price + ) + + def test_calculate_pips(self): + pos_pips = self.position.calculate_pips() + self.assertAlmostEqual(pos_pips, 0.00049) + + def test_calculate_profit_base(self): + profit_base = self.position.calculate_profit_base() + self.assertAlmostEqual(profit_base, 0.6457139) + + def test_calculate_profit_perc(self): + profit_perc = self.position.calculate_profit_perc() + self.assertAlmostEqual(profit_perc, 0.032285695) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file