Modified the Position handling to use long/short instead of buy/sell for side/position_type. Also modified the unit tests for both Portfolio and Position to reflect these changes. Added a basic historical backtesting capability via backtest.py and using CSV tick data for currency pairs.
This commit is contained in:
+58
-55
@@ -24,116 +24,119 @@ class Portfolio(object):
|
||||
return self.equity * self.risk_per_trade
|
||||
|
||||
def add_new_position(
|
||||
self, side, market, units, exposure,
|
||||
add_price, remove_price
|
||||
self, position_type, market, units,
|
||||
exposure, bid, ask
|
||||
):
|
||||
ps = Position(
|
||||
side, market, units, exposure,
|
||||
add_price, remove_price
|
||||
position_type, market, units,
|
||||
exposure, bid, ask
|
||||
)
|
||||
self.positions[market] = ps
|
||||
|
||||
def add_position_units(
|
||||
self, market, units, exposure,
|
||||
add_price, remove_price
|
||||
self, market, units,
|
||||
exposure, bid, ask
|
||||
):
|
||||
if market not in self.positions:
|
||||
return False
|
||||
else:
|
||||
ps = self.positions[market]
|
||||
if ps.position_type == "long":
|
||||
add_price = ask
|
||||
else:
|
||||
add_price = bid
|
||||
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, exposure)
|
||||
ps.update_position_price(bid, ask, exposure)
|
||||
return True
|
||||
|
||||
def remove_position_units(
|
||||
self, market, units, remove_price
|
||||
self, market, units, bid, ask
|
||||
):
|
||||
if market not in self.positions:
|
||||
return False
|
||||
else:
|
||||
ps = self.positions[market]
|
||||
if ps.position_type == "long":
|
||||
remove_price = bid
|
||||
else:
|
||||
remove_price = ask
|
||||
ps.units -= units
|
||||
exposure = Decimal(str(units))
|
||||
ps.exposure -= exposure
|
||||
ps.update_position_price(remove_price, exposure)
|
||||
ps.update_position_price(bid, ask, exposure)
|
||||
pnl = ps.calculate_pips() * exposure / remove_price
|
||||
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
|
||||
return True
|
||||
|
||||
def close_position(
|
||||
self, market, remove_price
|
||||
self, market, bid, ask
|
||||
):
|
||||
if market not in self.positions:
|
||||
return False
|
||||
else:
|
||||
ps = self.positions[market]
|
||||
ps.update_position_price(remove_price, ps.exposure)
|
||||
ps.update_position_price(bid, ask, ps.exposure)
|
||||
if ps.position_type == "long":
|
||||
remove_price = bid
|
||||
else:
|
||||
remove_price = ask
|
||||
pnl = ps.calculate_pips() * ps.exposure / remove_price
|
||||
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
|
||||
del[self.positions[market]]
|
||||
return True
|
||||
|
||||
def execute_signal(self, signal_event):
|
||||
def execute_signal(self, signal_event):
|
||||
side = signal_event.side
|
||||
market = signal_event.instrument
|
||||
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 = Decimal(str(self.ticker.cur_bid))
|
||||
remove_price = Decimal(str(self.ticker.cur_ask))
|
||||
exposure = Decimal(str(units))
|
||||
bid = Decimal(str(self.ticker.cur_bid))
|
||||
ask = Decimal(str(self.ticker.cur_ask))
|
||||
|
||||
# If there is no position, create one
|
||||
if market not in self.positions:
|
||||
if side == "buy":
|
||||
position_type = "long"
|
||||
else:
|
||||
position_type = "short"
|
||||
self.add_new_position(
|
||||
side, market, units, exposure,
|
||||
add_price, remove_price
|
||||
position_type, market, units,
|
||||
exposure, bid, ask
|
||||
)
|
||||
order = OrderEvent(market, units, "market", side)
|
||||
self.events.put(order)
|
||||
|
||||
# If a position exists add or remove units
|
||||
else:
|
||||
ps = self.positions[market]
|
||||
# Check if the sides equal
|
||||
if side == ps.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 side == "buy" and ps.position_type == "long":
|
||||
add_position_units(market, units, exposure, bid, ask)
|
||||
|
||||
elif side == "sell" and ps.position_type == "long":
|
||||
if units == ps.units:
|
||||
# Close the position
|
||||
self.close_position(market, remove_price)
|
||||
order = OrderEvent(market, units, "market", side)
|
||||
self.events.put(order)
|
||||
self.close_position(market, bid, ask)
|
||||
# TODO: Allow units to be added/removed
|
||||
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)
|
||||
return
|
||||
elif units > ps.units:
|
||||
return
|
||||
|
||||
elif side == "buy" and ps.position_type == "short":
|
||||
if units == ps.units:
|
||||
self.close_position(market, bid, ask)
|
||||
# TODO: Allow units to be added/removed
|
||||
elif units < ps.units:
|
||||
return
|
||||
elif units > ps.units:
|
||||
return
|
||||
|
||||
if side == "buy":
|
||||
new_side = "sell"
|
||||
else:
|
||||
new_side = "buy"
|
||||
new_exposure = Decimal(str(units))
|
||||
self.add_new_position(
|
||||
new_side, market, new_units,
|
||||
new_exposure, add_price, remove_price
|
||||
)
|
||||
elif side == "sell" and ps.position_type == "short":
|
||||
add_position_units(market, units, exposure, bid, ask)
|
||||
|
||||
order = OrderEvent(market, units, "market", side)
|
||||
self.events.put(order)
|
||||
|
||||
print "Balance: %0.2f" % self.balance
|
||||
Reference in New Issue
Block a user