Modified the position handling to fix a pricing bug, so that locally handled Portfolio values match those of OANDA (up to slippage).
This commit is contained in:
+58
-44
@@ -126,51 +126,65 @@ class Portfolio(object):
|
||||
print(out_line[:-2])
|
||||
self.backtest_file.write(out_line)
|
||||
|
||||
def execute_signal(self, signal_event):
|
||||
side = signal_event.side
|
||||
currency_pair = signal_event.instrument
|
||||
units = int(self.trade_units)
|
||||
time = signal_event.time
|
||||
|
||||
# If there is no position, create one
|
||||
if currency_pair not in self.positions:
|
||||
if side == "buy":
|
||||
position_type = "long"
|
||||
def execute_signal(self, signal_event):
|
||||
# Check that the prices ticker contains all necessary
|
||||
# currency pairs prior to executing an order
|
||||
execute = True
|
||||
tp = self.ticker.prices
|
||||
for pair in tp:
|
||||
if tp[pair]["ask"] is None or tp[pair]["bid"] is None:
|
||||
execute = False
|
||||
|
||||
# All necessary pricing data is available,
|
||||
# we can execute
|
||||
if execute:
|
||||
side = signal_event.side
|
||||
currency_pair = signal_event.instrument
|
||||
units = int(self.trade_units)
|
||||
time = signal_event.time
|
||||
|
||||
# If there is no position, create one
|
||||
if currency_pair not in self.positions:
|
||||
if side == "buy":
|
||||
position_type = "long"
|
||||
else:
|
||||
position_type = "short"
|
||||
self.add_new_position(
|
||||
position_type, currency_pair,
|
||||
units, self.ticker
|
||||
)
|
||||
|
||||
# If a position exists add or remove units
|
||||
else:
|
||||
position_type = "short"
|
||||
self.add_new_position(
|
||||
position_type, currency_pair,
|
||||
units, self.ticker
|
||||
)
|
||||
ps = self.positions[currency_pair]
|
||||
|
||||
# If a position exists add or remove units
|
||||
if side == "buy" and ps.position_type == "long":
|
||||
add_position_units(currency_pair, units)
|
||||
|
||||
elif side == "sell" and ps.position_type == "long":
|
||||
if units == ps.units:
|
||||
self.close_position(currency_pair)
|
||||
# TODO: Allow units to be added/removed
|
||||
elif units < ps.units:
|
||||
return
|
||||
elif units > ps.units:
|
||||
return
|
||||
|
||||
elif side == "buy" and ps.position_type == "short":
|
||||
if units == ps.units:
|
||||
self.close_position(currency_pair)
|
||||
# TODO: Allow units to be added/removed
|
||||
elif units < ps.units:
|
||||
return
|
||||
elif units > ps.units:
|
||||
return
|
||||
|
||||
elif side == "sell" and ps.position_type == "short":
|
||||
add_position_units(currency_pair, units)
|
||||
|
||||
order = OrderEvent(currency_pair, units, "market", side)
|
||||
self.events.put(order)
|
||||
print("Balance: ", self.balance)
|
||||
else:
|
||||
ps = self.positions[currency_pair]
|
||||
|
||||
if side == "buy" and ps.position_type == "long":
|
||||
add_position_units(currency_pair, units)
|
||||
|
||||
elif side == "sell" and ps.position_type == "long":
|
||||
if units == ps.units:
|
||||
self.close_position(currency_pair)
|
||||
# TODO: Allow units to be added/removed
|
||||
elif units < ps.units:
|
||||
return
|
||||
elif units > ps.units:
|
||||
return
|
||||
|
||||
elif side == "buy" and ps.position_type == "short":
|
||||
if units == ps.units:
|
||||
self.close_position(currency_pair)
|
||||
# TODO: Allow units to be added/removed
|
||||
elif units < ps.units:
|
||||
return
|
||||
elif units > ps.units:
|
||||
return
|
||||
|
||||
elif side == "sell" and ps.position_type == "short":
|
||||
add_position_units(currency_pair, units)
|
||||
|
||||
order = OrderEvent(currency_pair, units, "market", side)
|
||||
self.events.put(order)
|
||||
print("Unable to execute order as price data was insufficient.")
|
||||
|
||||
@@ -212,7 +212,7 @@ class TestPortfolio(unittest.TestCase):
|
||||
)
|
||||
self.assertTrue(rpu)
|
||||
self.assertEqual(ps.units, Decimal("7000"))
|
||||
self.assertEqual(self.port.balance, Decimal("99988.84"))
|
||||
self.assertEqual(self.port.balance, Decimal("99988.83"))
|
||||
|
||||
def test_close_position_long(self):
|
||||
position_type = "long"
|
||||
@@ -265,7 +265,7 @@ class TestPortfolio(unittest.TestCase):
|
||||
cp = self.port.close_position(currency_pair)
|
||||
self.assertTrue(cp)
|
||||
self.assertRaises(ps) # Key doesn't exist
|
||||
self.assertEqual(self.port.balance, Decimal("100026.64"))
|
||||
self.assertEqual(self.port.balance, Decimal("100026.63"))
|
||||
|
||||
def test_close_position_short(self):
|
||||
position_type = "short"
|
||||
@@ -312,13 +312,13 @@ class TestPortfolio(unittest.TestCase):
|
||||
)
|
||||
self.assertTrue(rpu)
|
||||
self.assertEqual(ps.units, Decimal("7000"))
|
||||
self.assertEqual(self.port.balance, Decimal("99988.84"))
|
||||
self.assertEqual(self.port.balance, Decimal("99988.83"))
|
||||
|
||||
# Close the position
|
||||
cp = self.port.close_position(currency_pair)
|
||||
self.assertTrue(cp)
|
||||
self.assertRaises(ps) # Key doesn't exist
|
||||
self.assertEqual(self.port.balance, Decimal("99962.80"))
|
||||
self.assertEqual(self.port.balance, Decimal("99962.77"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -24,7 +24,7 @@ class Position(object):
|
||||
ticker_cur = self.ticker.prices[self.currency_pair]
|
||||
if self.position_type == "long":
|
||||
self.avg_price = Decimal(str(ticker_cur["ask"]))
|
||||
self.cur_price = Decimal(str(ticker_cur["bid"]))
|
||||
self.cur_price = Decimal(str(ticker_cur["bid"]))
|
||||
else:
|
||||
self.avg_price = Decimal(str(ticker_cur["bid"]))
|
||||
self.cur_price = Decimal(str(ticker_cur["ask"]))
|
||||
@@ -83,11 +83,11 @@ class Position(object):
|
||||
ticker_cp = self.ticker.prices[self.currency_pair]
|
||||
ticker_qh = self.ticker.prices[self.quote_home_currency_pair]
|
||||
if self.position_type == "long":
|
||||
remove_price = ticker_cp["ask"]
|
||||
qh_close = ticker_qh["bid"]
|
||||
else:
|
||||
remove_price = ticker_cp["bid"]
|
||||
qh_close = ticker_qh["ask"]
|
||||
else:
|
||||
remove_price = ticker_cp["ask"]
|
||||
qh_close = ticker_qh["bid"]
|
||||
self.units -= dec_units
|
||||
self.update_position_price()
|
||||
# Calculate PnL
|
||||
@@ -99,11 +99,9 @@ class Position(object):
|
||||
ticker_cp = self.ticker.prices[self.currency_pair]
|
||||
ticker_qh = self.ticker.prices[self.quote_home_currency_pair]
|
||||
if self.position_type == "long":
|
||||
remove_price = ticker_cp["ask"]
|
||||
qh_close = ticker_qh["bid"]
|
||||
else:
|
||||
remove_price = ticker_cp["bid"]
|
||||
qh_close = ticker_qh["ask"]
|
||||
else:
|
||||
qh_close = ticker_qh["bid"]
|
||||
self.update_position_price()
|
||||
# Calculate PnL
|
||||
pnl = self.calculate_pips() * qh_close * self.units
|
||||
|
||||
Reference in New Issue
Block a user