Added some more unit tests for Portfolio and Position. Added a README and a requirements file now that qsforex is open-source under an MIT license.
This commit is contained in:
+13
-13
@@ -46,7 +46,7 @@ class Portfolio(object):
|
||||
ps.exposure += exposure
|
||||
ps.avg_price = new_total_cost/new_total_units
|
||||
ps.units = new_total_units
|
||||
ps.update_position_price(remove_price)
|
||||
ps.update_position_price(remove_price, exposure)
|
||||
return True
|
||||
|
||||
def remove_position_units(
|
||||
@@ -57,9 +57,9 @@ class Portfolio(object):
|
||||
else:
|
||||
ps = self.positions[market]
|
||||
ps.units -= units
|
||||
exposure = Decimal(units)
|
||||
exposure = Decimal(str(units))
|
||||
ps.exposure -= exposure
|
||||
ps.update_position_price(remove_price)
|
||||
ps.update_position_price(remove_price, exposure)
|
||||
pnl = ps.calculate_pips() * exposure / remove_price
|
||||
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
|
||||
return True
|
||||
@@ -71,7 +71,7 @@ class Portfolio(object):
|
||||
return False
|
||||
else:
|
||||
ps = self.positions[market]
|
||||
ps.update_position_price(remove_price)
|
||||
ps.update_position_price(remove_price, ps.exposure)
|
||||
pnl = ps.calculate_pips() * ps.exposure / remove_price
|
||||
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
|
||||
del[self.positions[market]]
|
||||
@@ -83,12 +83,12 @@ class Portfolio(object):
|
||||
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 = self.ticker.cur_bid
|
||||
#remove_price = self.ticker.cur_ask
|
||||
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))
|
||||
|
||||
# If there is no position, create one
|
||||
@@ -97,7 +97,7 @@ class Portfolio(object):
|
||||
side, market, units, exposure,
|
||||
add_price, remove_price
|
||||
)
|
||||
order = OrderEvent(market, units, "market", "buy")
|
||||
order = OrderEvent(market, units, "market", side)
|
||||
self.events.put(order)
|
||||
# If a position exists add or remove units
|
||||
else:
|
||||
@@ -114,7 +114,7 @@ class Portfolio(object):
|
||||
if units == ps.units:
|
||||
# Close the position
|
||||
self.close_position(market, remove_price)
|
||||
order = OrderEvent(market, units, "market", "sell")
|
||||
order = OrderEvent(market, units, "market", side)
|
||||
self.events.put(order)
|
||||
elif units < ps.units:
|
||||
# Remove from the position
|
||||
@@ -130,7 +130,7 @@ class Portfolio(object):
|
||||
if side == "buy":
|
||||
new_side = "sell"
|
||||
else:
|
||||
new_side = "sell"
|
||||
new_side = "buy"
|
||||
new_exposure = Decimal(str(units))
|
||||
self.add_new_position(
|
||||
new_side, market, new_units,
|
||||
|
||||
+107
-6
@@ -17,7 +17,7 @@ class TestPortfolio(unittest.TestCase):
|
||||
equity=equity, risk_per_trade=risk_per_trade
|
||||
)
|
||||
|
||||
def test_add_position(self):
|
||||
def test_add_position_long(self):
|
||||
side = "LONG"
|
||||
market = "GBP/USD"
|
||||
units = 2000
|
||||
@@ -38,7 +38,28 @@ class TestPortfolio(unittest.TestCase):
|
||||
self.assertEquals(ps.avg_price, add_price)
|
||||
self.assertEquals(ps.cur_price, remove_price)
|
||||
|
||||
def test_add_position_units(self):
|
||||
def test_add_position_short(self):
|
||||
side = "SHORT"
|
||||
market = "GBP/USD"
|
||||
units = 2000
|
||||
exposure = Decimal(str(units))
|
||||
add_price = Decimal("1.51770")
|
||||
remove_price = Decimal("1.51819")
|
||||
|
||||
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_long(self):
|
||||
side = "LONG"
|
||||
market = "GBP/USD"
|
||||
units = 2000
|
||||
@@ -72,7 +93,41 @@ class TestPortfolio(unittest.TestCase):
|
||||
self.assertTrue(apu)
|
||||
self.assertEqual(ps.avg_price, Decimal("1.518735"))
|
||||
|
||||
def test_remove_position_units(self):
|
||||
def test_add_position_units_short(self):
|
||||
side = "SHORT"
|
||||
market = "GBP/USD"
|
||||
units = 2000
|
||||
exposure = Decimal(str(units))
|
||||
add_price = Decimal("1.51770")
|
||||
remove_price = Decimal("1.51819")
|
||||
|
||||
# Test for no position
|
||||
market = "EUR/USD"
|
||||
apu = self.port.add_position_units(
|
||||
market, units, exposure,
|
||||
add_price, remove_price
|
||||
)
|
||||
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
|
||||
)
|
||||
ps = self.port.positions[market]
|
||||
|
||||
# Test for addition of units
|
||||
add_price = Decimal("1.51878")
|
||||
remove_price = Decimal("1.51928")
|
||||
apu = self.port.add_position_units(
|
||||
market, units, exposure,
|
||||
add_price, remove_price
|
||||
)
|
||||
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))
|
||||
@@ -115,10 +170,56 @@ class TestPortfolio(unittest.TestCase):
|
||||
self.assertTrue(rpu)
|
||||
self.assertEqual(ps.units, 7000)
|
||||
self.assertEqual(ps.exposure, Decimal("7000.00"))
|
||||
self.assertEqual(ps.profit_base, Decimal("5.11127"))
|
||||
self.assertEqual(ps.profit_base, Decimal("2.19054"))
|
||||
self.assertEqual(self.port.balance, Decimal("100002.19"))
|
||||
|
||||
def test_close_position(self):
|
||||
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")
|
||||
|
||||
# Test for no position
|
||||
market = "EUR/USD"
|
||||
apu = self.port.remove_position_units(
|
||||
market, units, remove_price
|
||||
)
|
||||
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
|
||||
)
|
||||
ps = self.port.positions[market]
|
||||
add_price = Decimal("1.51878")
|
||||
remove_price = 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
|
||||
)
|
||||
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")
|
||||
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, Decimal("7000.00"))
|
||||
self.assertEqual(ps.profit_base, Decimal("-5.48201"))
|
||||
self.assertEqual(self.port.balance, Decimal("99994.52"))
|
||||
|
||||
def test_close_position_long(self):
|
||||
side = "LONG"
|
||||
units = 2000
|
||||
exposure = Decimal(str(units))
|
||||
@@ -174,7 +275,7 @@ class TestPortfolio(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(ps.units, 7000)
|
||||
self.assertEqual(ps.exposure, Decimal("7000.00"))
|
||||
self.assertEqual(ps.profit_base, Decimal("5.11127"))
|
||||
self.assertEqual(ps.profit_base, Decimal("2.19054"))
|
||||
self.assertEqual(self.port.balance, Decimal("100001.54"))
|
||||
cp = self.port.close_position(
|
||||
market, remove_price
|
||||
|
||||
@@ -12,8 +12,8 @@ class Position(object):
|
||||
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()
|
||||
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
|
||||
@@ -24,19 +24,19 @@ class Position(object):
|
||||
Decimal("0.00001"), ROUND_HALF_DOWN
|
||||
)
|
||||
|
||||
def calculate_profit_base(self):
|
||||
def calculate_profit_base(self, exposure):
|
||||
pips = self.calculate_pips()
|
||||
return (pips * self.exposure / self.cur_price).quantize(
|
||||
return (pips * exposure / self.cur_price).quantize(
|
||||
Decimal("0.00001"), ROUND_HALF_DOWN
|
||||
)
|
||||
|
||||
def calculate_profit_perc(self):
|
||||
return (self.profit_base / self.exposure * Decimal("100.00")).quantize(
|
||||
def calculate_profit_perc(self, exposure):
|
||||
return (self.profit_base / exposure * Decimal("100.00")).quantize(
|
||||
Decimal("0.00001"), ROUND_HALF_DOWN
|
||||
)
|
||||
|
||||
def update_position_price(self, cur_price):
|
||||
def update_position_price(self, cur_price, exposure):
|
||||
self.cur_price = cur_price
|
||||
self.profit_base = self.calculate_profit_base()
|
||||
self.profit_perc = self.calculate_profit_perc()
|
||||
self.profit_base = self.calculate_profit_base(exposure)
|
||||
self.profit_perc = self.calculate_profit_perc(exposure)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class TestLongGBPUSDPosition(unittest.TestCase):
|
||||
getcontext.prec = 2
|
||||
side = "LONG"
|
||||
market = "GBP/USD"
|
||||
units = 2000
|
||||
units = Decimal(str(2000))
|
||||
exposure = Decimal("2000.00")
|
||||
avg_price = Decimal("1.51819")
|
||||
cur_price = Decimal("1.51770")
|
||||
@@ -23,13 +23,11 @@ class TestLongGBPUSDPosition(unittest.TestCase):
|
||||
self.assertEqual(pos_pips, Decimal("-0.00049"))
|
||||
|
||||
def test_calculate_profit_base(self):
|
||||
profit_base = self.position.calculate_profit_base()
|
||||
#self.assertEqual(profit_base, Decimal("-0.6457139"))
|
||||
profit_base = self.position.calculate_profit_base(self.position.exposure)
|
||||
self.assertEqual(profit_base, Decimal("-0.64571"))
|
||||
|
||||
def test_calculate_profit_perc(self):
|
||||
profit_perc = self.position.calculate_profit_perc()
|
||||
#self.assertEqual(profit_perc, Decimal("-0.032285695"))
|
||||
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
|
||||
self.assertEqual(profit_perc, Decimal("-0.03229"))
|
||||
|
||||
|
||||
@@ -52,11 +50,11 @@ class TestShortGBPUSDPosition(unittest.TestCase):
|
||||
self.assertEqual(pos_pips, Decimal("0.00049"))
|
||||
|
||||
def test_calculate_profit_base(self):
|
||||
profit_base = self.position.calculate_profit_base()
|
||||
profit_base = self.position.calculate_profit_base(self.position.exposure)
|
||||
self.assertEqual(profit_base, Decimal("0.64571"))
|
||||
|
||||
def test_calculate_profit_perc(self):
|
||||
profit_perc = self.position.calculate_profit_perc()
|
||||
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
|
||||
self.assertEqual(profit_perc, Decimal("0.03229"))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user