Lots of changes. Modified the Position object to handle more of the actual position calculations instead of the Portfolio. Added more unit tests for both Position and Portfolio. Allowed Positions to trade in currencies other than GBPUSD and in base/quotes which aren't the home currency. Modified the backtester to be single-threaded and added a basic Moving Average Crossover strategy. Also added a basic equity curve output script.

This commit is contained in:
Michael Halls-Moore
2015-04-21 13:01:20 +01:00
parent e74777802b
commit e84512e1e7
11 changed files with 700 additions and 376 deletions
+86 -30
View File
@@ -3,51 +3,107 @@ from decimal import Decimal, getcontext, ROUND_HALF_DOWN
class Position(object):
def __init__(
self, position_type, market,
units, exposure, bid, ask
self, home_currency, position_type,
currency_pair, units, ticker
):
self.home_currency = home_currency # Account denomination (e.g. GBP)
self.position_type = position_type # Long or short
self.market = market
self.currency_pair = currency_pair # Intended traded currency pair
self.units = units
self.exposure = Decimal(str(exposure))
self.ticker = ticker
self.set_up_currencies()
self.profit_base = self.calculate_profit_base()
self.profit_perc = self.calculate_profit_perc()
# Long or short
def set_up_currencies(self):
self.base_currency = self.currency_pair[:3] # For EUR/USD, this is EUR
self.quote_currency = self.currency_pair[3:] # For EUR/USD, this is USD
# For EUR/USD, with account denominated in GBP, this is USD/GBP
self.quote_home_currency_pair = "%s%s" % (self.quote_currency, self.home_currency)
ticker_cur = self.ticker.prices[self.currency_pair]
if self.position_type == "long":
self.avg_price = Decimal(str(ask))
self.cur_price = Decimal(str(bid))
self.avg_price = Decimal(str(ticker_cur["ask"]))
self.cur_price = Decimal(str(ticker_cur["bid"]))
else:
self.avg_price = Decimal(str(bid))
self.cur_price = Decimal(str(ask))
self.profit_base = self.calculate_profit_base(self.exposure)
self.profit_perc = self.calculate_profit_perc(self.exposure)
self.avg_price = Decimal(str(ticker_cur["bid"]))
self.cur_price = Decimal(str(ticker_cur["ask"]))
def calculate_pips(self):
getcontext.prec = 6
mult = Decimal("1")
if self.position_type == "long":
mult = Decimal("1")
elif self.position_type == "short":
mult = Decimal("-1")
return (mult * (self.cur_price - self.avg_price)).quantize(
pips = (mult * (self.cur_price - self.avg_price)).quantize(
Decimal("0.00001"), ROUND_HALF_DOWN
)
return pips
def calculate_profit_base(self, exposure):
pips = self.calculate_pips()
return (pips * exposure / self.cur_price).quantize(
Decimal("0.00001"), ROUND_HALF_DOWN
)
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, bid, ask, exposure):
def calculate_profit_base(self):
pips = self.calculate_pips()
ticker_qh = self.ticker.prices[self.quote_home_currency_pair]
if self.position_type == "long":
self.cur_price = Decimal(str(bid))
qh_close = ticker_qh["bid"]
else:
self.cur_price = Decimal(str(ask))
self.profit_base = self.calculate_profit_base(exposure)
self.profit_perc = self.calculate_profit_perc(exposure)
qh_close = ticker_qh["ask"]
profit = pips * qh_close * self.units
return profit.quantize(
Decimal("0.00001"), ROUND_HALF_DOWN
)
def calculate_profit_perc(self):
return (self.profit_base / self.units * Decimal("100.00")).quantize(
Decimal("0.00001"), ROUND_HALF_DOWN
)
def update_position_price(self):
ticker_cur = self.ticker.prices[self.currency_pair]
if self.position_type == "long":
self.cur_price = Decimal(str(ticker_cur["bid"]))
else:
self.cur_price = Decimal(str(ticker_cur["ask"]))
self.profit_base = self.calculate_profit_base()
self.profit_perc = self.calculate_profit_perc()
def add_units(self, units):
cp = self.ticker.prices[self.currency_pair]
if self.position_type == "long":
add_price = cp["ask"]
else:
add_price = cp["bid"]
new_total_units = self.units + units
new_total_cost = self.avg_price*self.units + add_price*units
self.avg_price = new_total_cost/new_total_units
self.units = new_total_units
self.update_position_price()
def remove_units(self, units):
dec_units = Decimal(str(units))
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"]
self.units -= dec_units
self.update_position_price()
# Calculate PnL
pnl = self.calculate_pips() * qh_close * dec_units
return pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
def close_position(self):
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"]
self.update_position_price()
# Calculate PnL
pnl = self.calculate_pips() * qh_close * self.units
return pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))