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:
@@ -0,0 +1,77 @@
|
|||||||
|
import copy
|
||||||
|
import Queue
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from decimal import Decimal, getcontext
|
||||||
|
|
||||||
|
from qsforex.execution.execution import SimulatedExecution
|
||||||
|
from qsforex.portfolio.portfolio import Portfolio
|
||||||
|
from qsforex import settings
|
||||||
|
from qsforex.strategy.strategy import TestStrategy
|
||||||
|
from qsforex.data.price import HistoricCSVPriceHandler
|
||||||
|
|
||||||
|
|
||||||
|
def trade(events, strategy, portfolio, execution, heartbeat):
|
||||||
|
"""
|
||||||
|
Carries out an infinite while loop that polls the
|
||||||
|
events queue and directs each event to either the
|
||||||
|
strategy component of the execution handler. The
|
||||||
|
loop will then pause for "heartbeat" seconds and
|
||||||
|
continue.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
event = events.get(False)
|
||||||
|
except Queue.Empty:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if event is not None:
|
||||||
|
if event.type == 'TICK':
|
||||||
|
strategy.calculate_signals(event)
|
||||||
|
elif event.type == 'SIGNAL':
|
||||||
|
portfolio.execute_signal(event)
|
||||||
|
elif event.type == 'ORDER':
|
||||||
|
execution.execute_order(event)
|
||||||
|
time.sleep(heartbeat)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Set the number of decimal places to 2
|
||||||
|
getcontext().prec = 2
|
||||||
|
|
||||||
|
heartbeat = 0.0 # Half a second between polling
|
||||||
|
events = Queue.Queue()
|
||||||
|
equity = settings.EQUITY
|
||||||
|
|
||||||
|
# Load the historic CSV tick data files
|
||||||
|
pairs = ["GBPUSD"]
|
||||||
|
csv_dir = settings.CSV_DATA_DIR
|
||||||
|
if csv_dir is None:
|
||||||
|
print "No historic data directory provided - backtest terminating."
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# Create the historic tick data streaming class
|
||||||
|
prices = HistoricCSVPriceHandler(pairs, events, csv_dir)
|
||||||
|
|
||||||
|
# Create the strategy/signal generator, passing the
|
||||||
|
# instrument and the events queue
|
||||||
|
strategy = TestStrategy(pairs[0], events)
|
||||||
|
|
||||||
|
# Create the portfolio object to track trades
|
||||||
|
portfolio = Portfolio(prices, events, equity=equity)
|
||||||
|
|
||||||
|
# Create the simulated execution handler
|
||||||
|
execution = SimulatedExecution()
|
||||||
|
|
||||||
|
# Create two separate threads: One for the trading loop
|
||||||
|
# and another for the market price streaming class
|
||||||
|
trade_thread = threading.Thread(
|
||||||
|
target=trade, args=(
|
||||||
|
events, strategy, portfolio, execution, heartbeat
|
||||||
|
)
|
||||||
|
)
|
||||||
|
price_thread = threading.Thread(target=prices.stream_to_queue, args=[])
|
||||||
|
|
||||||
|
# Start both threads
|
||||||
|
trade_thread.start()
|
||||||
|
price_thread.start()
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
import datetime
|
||||||
|
from decimal import Decimal, getcontext, ROUND_HALF_DOWN
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from qsforex.event.event import TickEvent
|
||||||
|
|
||||||
|
|
||||||
|
class PriceHandler(object):
|
||||||
|
"""
|
||||||
|
PriceHandler is an abstract base class providing an interface for
|
||||||
|
all subsequent (inherited) data handlers (both live and historic).
|
||||||
|
|
||||||
|
The goal of a (derived) PriceHandler object is to output a set of
|
||||||
|
bid/ask/timestamp "ticks" for each currency pair and place them into
|
||||||
|
an event queue.
|
||||||
|
|
||||||
|
This will replicate how a live strategy would function as current
|
||||||
|
tick data would be streamed via a brokerage. Thus a historic and live
|
||||||
|
system will be treated identically by the rest of the QSForex
|
||||||
|
backtesting suite.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__metaclass__ = ABCMeta
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def stream_to_queue(self):
|
||||||
|
"""
|
||||||
|
Streams a sequence of tick data events (timestamp, bid, ask)
|
||||||
|
tuples to the events queue.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("Should implement stream_to_queue()")
|
||||||
|
|
||||||
|
|
||||||
|
class HistoricCSVPriceHandler(PriceHandler):
|
||||||
|
"""
|
||||||
|
HistoricCSVPriceHandler is designed to read CSV files of
|
||||||
|
tick data for each requested currency pair and stream those
|
||||||
|
to the provided events queue.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, pairs, events_queue, csv_dir):
|
||||||
|
"""
|
||||||
|
Initialises the historic data handler by requesting
|
||||||
|
the location of the CSV files and a list of symbols.
|
||||||
|
|
||||||
|
It will be assumed that all files are of the form
|
||||||
|
'pair.csv', where "pair" is the currency pair. For
|
||||||
|
GBP/USD the filename is GBPUSD.csv.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
pairs - The list of currency pairs to obtain.
|
||||||
|
events_queue - The events queue to send the ticks to.
|
||||||
|
csv_dir - Absolute directory path to the CSV files.
|
||||||
|
"""
|
||||||
|
self.pairs = pairs
|
||||||
|
self.events_queue = events_queue
|
||||||
|
self.csv_dir = csv_dir
|
||||||
|
self.cur_bid = None
|
||||||
|
self.cur_ask = None
|
||||||
|
|
||||||
|
def _open_convert_csv_files(self):
|
||||||
|
"""
|
||||||
|
Opens the CSV files from the data directory, converting
|
||||||
|
them into pandas DataFrames within a pairs dictionary.
|
||||||
|
"""
|
||||||
|
pair_path = os.path.join(self.csv_dir, '%s.csv' % self.pairs[0])
|
||||||
|
self.pair = pd.io.parsers.read_csv(
|
||||||
|
pair_path, header=True, index_col=0, parse_dates=True,
|
||||||
|
names=("Time", "Ask", "Bid", "AskVolume", "BidVolume")
|
||||||
|
).iterrows()
|
||||||
|
|
||||||
|
def stream_to_queue(self):
|
||||||
|
self._open_convert_csv_files()
|
||||||
|
for index, row in self.pair:
|
||||||
|
self.cur_bid = Decimal(str(row["Bid"])).quantize(
|
||||||
|
Decimal("0.00001", ROUND_HALF_DOWN)
|
||||||
|
)
|
||||||
|
self.cur_ask = Decimal(str(row["Ask"])).quantize(
|
||||||
|
Decimal("0.00001", ROUND_HALF_DOWN)
|
||||||
|
)
|
||||||
|
tev = TickEvent(self.pairs[0], index, row["Bid"], row["Ask"])
|
||||||
|
self.events_queue.put(tev)
|
||||||
+30
-1
@@ -1,8 +1,37 @@
|
|||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
import httplib
|
import httplib
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
|
|
||||||
class Execution(object):
|
class ExecutionHandler(object):
|
||||||
|
"""
|
||||||
|
Provides an abstract base class to handle all execution in the
|
||||||
|
backtesting and live trading system.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__metaclass__ = ABCMeta
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def execute_order(self):
|
||||||
|
"""
|
||||||
|
Send the order to the brokerage.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("Should implement execute_order()")
|
||||||
|
|
||||||
|
|
||||||
|
class SimulatedExecution(object):
|
||||||
|
"""
|
||||||
|
Provides a simulated execution handling environment. This class
|
||||||
|
actually does nothing - it simply receives an order to execute.
|
||||||
|
|
||||||
|
Instead, the Portfolio object actually provides fill handling.
|
||||||
|
This will be modified in later versions.
|
||||||
|
"""
|
||||||
|
def execute_order(self, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OANDAExecutionHandler(ExecutionHandler):
|
||||||
def __init__(self, domain, access_token, account_id):
|
def __init__(self, domain, access_token, account_id):
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
self.access_token = access_token
|
self.access_token = access_token
|
||||||
|
|||||||
+58
-55
@@ -24,116 +24,119 @@ class Portfolio(object):
|
|||||||
return self.equity * self.risk_per_trade
|
return self.equity * self.risk_per_trade
|
||||||
|
|
||||||
def add_new_position(
|
def add_new_position(
|
||||||
self, side, market, units, exposure,
|
self, position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
):
|
):
|
||||||
ps = Position(
|
ps = Position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
self.positions[market] = ps
|
self.positions[market] = ps
|
||||||
|
|
||||||
def add_position_units(
|
def add_position_units(
|
||||||
self, market, units, exposure,
|
self, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
):
|
):
|
||||||
if market not in self.positions:
|
if market not in self.positions:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
ps = self.positions[market]
|
ps = self.positions[market]
|
||||||
|
if ps.position_type == "long":
|
||||||
|
add_price = ask
|
||||||
|
else:
|
||||||
|
add_price = bid
|
||||||
new_total_units = ps.units + units
|
new_total_units = ps.units + units
|
||||||
new_total_cost = ps.avg_price*ps.units + add_price*units
|
new_total_cost = ps.avg_price*ps.units + add_price*units
|
||||||
ps.exposure += exposure
|
ps.exposure += exposure
|
||||||
ps.avg_price = new_total_cost/new_total_units
|
ps.avg_price = new_total_cost/new_total_units
|
||||||
ps.units = new_total_units
|
ps.units = new_total_units
|
||||||
ps.update_position_price(remove_price, exposure)
|
ps.update_position_price(bid, ask, exposure)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def remove_position_units(
|
def remove_position_units(
|
||||||
self, market, units, remove_price
|
self, market, units, bid, ask
|
||||||
):
|
):
|
||||||
if market not in self.positions:
|
if market not in self.positions:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
ps = self.positions[market]
|
ps = self.positions[market]
|
||||||
|
if ps.position_type == "long":
|
||||||
|
remove_price = bid
|
||||||
|
else:
|
||||||
|
remove_price = ask
|
||||||
ps.units -= units
|
ps.units -= units
|
||||||
exposure = Decimal(str(units))
|
exposure = Decimal(str(units))
|
||||||
ps.exposure -= exposure
|
ps.exposure -= exposure
|
||||||
ps.update_position_price(remove_price, exposure)
|
ps.update_position_price(bid, ask, exposure)
|
||||||
pnl = ps.calculate_pips() * exposure / remove_price
|
pnl = ps.calculate_pips() * exposure / remove_price
|
||||||
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
|
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def close_position(
|
def close_position(
|
||||||
self, market, remove_price
|
self, market, bid, ask
|
||||||
):
|
):
|
||||||
if market not in self.positions:
|
if market not in self.positions:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
ps = self.positions[market]
|
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
|
pnl = ps.calculate_pips() * ps.exposure / remove_price
|
||||||
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
|
self.balance += pnl.quantize(Decimal("0.01", ROUND_HALF_DOWN))
|
||||||
del[self.positions[market]]
|
del[self.positions[market]]
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def execute_signal(self, signal_event):
|
def execute_signal(self, signal_event):
|
||||||
side = signal_event.side
|
side = signal_event.side
|
||||||
market = signal_event.instrument
|
market = signal_event.instrument
|
||||||
units = int(self.trade_units)
|
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))
|
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 there is no position, create one
|
||||||
if market not in self.positions:
|
if market not in self.positions:
|
||||||
|
if side == "buy":
|
||||||
|
position_type = "long"
|
||||||
|
else:
|
||||||
|
position_type = "short"
|
||||||
self.add_new_position(
|
self.add_new_position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
order = OrderEvent(market, units, "market", side)
|
|
||||||
self.events.put(order)
|
|
||||||
# If a position exists add or remove units
|
# If a position exists add or remove units
|
||||||
else:
|
else:
|
||||||
ps = self.positions[market]
|
ps = self.positions[market]
|
||||||
# Check if the sides equal
|
|
||||||
if side == ps.side:
|
if side == "buy" and ps.position_type == "long":
|
||||||
# Add to the position
|
add_position_units(market, units, exposure, bid, ask)
|
||||||
add_position_units(
|
|
||||||
market, units, exposure,
|
elif side == "sell" and ps.position_type == "long":
|
||||||
add_price, remove_price
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Check if the units close out the position
|
|
||||||
if units == ps.units:
|
if units == ps.units:
|
||||||
# Close the position
|
self.close_position(market, bid, ask)
|
||||||
self.close_position(market, remove_price)
|
# TODO: Allow units to be added/removed
|
||||||
order = OrderEvent(market, units, "market", side)
|
|
||||||
self.events.put(order)
|
|
||||||
elif units < ps.units:
|
elif units < ps.units:
|
||||||
# Remove from the position
|
return
|
||||||
self.remove_position_units(
|
elif units > ps.units:
|
||||||
market, units, remove_price
|
return
|
||||||
)
|
|
||||||
else: # units > ps.units
|
elif side == "buy" and ps.position_type == "short":
|
||||||
# Close the position and add a new one with
|
if units == ps.units:
|
||||||
# additional units of opposite side
|
self.close_position(market, bid, ask)
|
||||||
new_units = units - ps.units
|
# TODO: Allow units to be added/removed
|
||||||
self.close_position(market, remove_price)
|
elif units < ps.units:
|
||||||
|
return
|
||||||
|
elif units > ps.units:
|
||||||
|
return
|
||||||
|
|
||||||
if side == "buy":
|
elif side == "sell" and ps.position_type == "short":
|
||||||
new_side = "sell"
|
add_position_units(market, units, exposure, bid, ask)
|
||||||
else:
|
|
||||||
new_side = "buy"
|
order = OrderEvent(market, units, "market", side)
|
||||||
new_exposure = Decimal(str(units))
|
self.events.put(order)
|
||||||
self.add_new_position(
|
|
||||||
new_side, market, new_units,
|
|
||||||
new_exposure, add_price, remove_price
|
|
||||||
)
|
|
||||||
print "Balance: %0.2f" % self.balance
|
print "Balance: %0.2f" % self.balance
|
||||||
+166
-91
@@ -18,200 +18,199 @@ class TestPortfolio(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_add_position_long(self):
|
def test_add_position_long(self):
|
||||||
side = "LONG"
|
position_type = "long"
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
units = 2000
|
units = Decimal("2000")
|
||||||
exposure = Decimal(str(units))
|
exposure = Decimal("2000.00")
|
||||||
add_price = Decimal("1.51819")
|
bid = Decimal("1.51770")
|
||||||
remove_price = Decimal("1.51770")
|
ask = Decimal("1.51819")
|
||||||
|
|
||||||
self.port.add_new_position(
|
self.port.add_new_position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
ps = self.port.positions[market]
|
ps = self.port.positions[market]
|
||||||
|
|
||||||
self.assertEquals(ps.side, side)
|
self.assertEquals(ps.position_type, position_type)
|
||||||
self.assertEquals(ps.market, market)
|
self.assertEquals(ps.market, market)
|
||||||
self.assertEquals(ps.units, units)
|
self.assertEquals(ps.units, units)
|
||||||
self.assertEquals(ps.exposure, exposure)
|
self.assertEquals(ps.exposure, exposure)
|
||||||
self.assertEquals(ps.avg_price, add_price)
|
self.assertEquals(ps.avg_price, ask)
|
||||||
self.assertEquals(ps.cur_price, remove_price)
|
self.assertEquals(ps.cur_price, bid)
|
||||||
|
|
||||||
def test_add_position_short(self):
|
def test_add_position_short(self):
|
||||||
side = "SHORT"
|
position_type = "short"
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
units = 2000
|
units = Decimal("2000")
|
||||||
exposure = Decimal(str(units))
|
exposure = Decimal("2000.00")
|
||||||
add_price = Decimal("1.51770")
|
bid = Decimal("1.51770")
|
||||||
remove_price = Decimal("1.51819")
|
ask = Decimal("1.51819")
|
||||||
|
|
||||||
self.port.add_new_position(
|
self.port.add_new_position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
ps = self.port.positions[market]
|
ps = self.port.positions[market]
|
||||||
|
|
||||||
self.assertEquals(ps.side, side)
|
self.assertEquals(ps.position_type, position_type)
|
||||||
self.assertEquals(ps.market, market)
|
self.assertEquals(ps.market, market)
|
||||||
self.assertEquals(ps.units, units)
|
self.assertEquals(ps.units, units)
|
||||||
self.assertEquals(ps.exposure, exposure)
|
self.assertEquals(ps.exposure, exposure)
|
||||||
self.assertEquals(ps.avg_price, add_price)
|
self.assertEquals(ps.avg_price, bid)
|
||||||
self.assertEquals(ps.cur_price, remove_price)
|
self.assertEquals(ps.cur_price, ask)
|
||||||
|
|
||||||
def test_add_position_units_long(self):
|
def test_add_position_units_long(self):
|
||||||
side = "LONG"
|
position_type = "long"
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
units = 2000
|
units = Decimal("2000")
|
||||||
exposure = Decimal(str(units))
|
exposure = Decimal("2000.00")
|
||||||
add_price = Decimal("1.51819")
|
bid = Decimal("1.51770")
|
||||||
remove_price = Decimal("1.51770")
|
ask = Decimal("1.51819")
|
||||||
|
|
||||||
# Test for no position
|
# Test for no position
|
||||||
market = "EUR/USD"
|
market = "EUR/USD"
|
||||||
apu = self.port.add_position_units(
|
apu = self.port.add_position_units(
|
||||||
market, units, exposure,
|
market, units, exposure,
|
||||||
add_price, remove_price
|
bid, ask
|
||||||
)
|
)
|
||||||
self.assertFalse(apu)
|
self.assertFalse(apu)
|
||||||
|
|
||||||
# Add a position and test for real position
|
# Add a position and test for real position
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
self.port.add_new_position(
|
self.port.add_new_position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
ps = self.port.positions[market]
|
ps = self.port.positions[market]
|
||||||
|
|
||||||
# Test for addition of units
|
# Test for addition of units
|
||||||
add_price = Decimal("1.51928")
|
bid = Decimal("1.51878")
|
||||||
remove_price = Decimal("1.51878")
|
ask = Decimal("1.51928")
|
||||||
apu = self.port.add_position_units(
|
apu = self.port.add_position_units(
|
||||||
market, units, exposure,
|
market, units, exposure,
|
||||||
add_price, remove_price
|
bid, ask
|
||||||
)
|
)
|
||||||
self.assertTrue(apu)
|
self.assertTrue(apu)
|
||||||
self.assertEqual(ps.avg_price, Decimal("1.518735"))
|
self.assertEqual(ps.avg_price, Decimal("1.518735"))
|
||||||
|
|
||||||
def test_add_position_units_short(self):
|
def test_add_position_units_short(self):
|
||||||
side = "SHORT"
|
position_type = "short"
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
units = 2000
|
units = Decimal("2000")
|
||||||
exposure = Decimal(str(units))
|
exposure = Decimal("2000.00")
|
||||||
add_price = Decimal("1.51770")
|
bid = Decimal("1.51770")
|
||||||
remove_price = Decimal("1.51819")
|
ask = Decimal("1.51819")
|
||||||
|
|
||||||
# Test for no position
|
# Test for no position
|
||||||
market = "EUR/USD"
|
market = "EUR/USD"
|
||||||
apu = self.port.add_position_units(
|
apu = self.port.add_position_units(
|
||||||
market, units, exposure,
|
market, units, exposure,
|
||||||
add_price, remove_price
|
bid, ask
|
||||||
)
|
)
|
||||||
self.assertFalse(apu)
|
self.assertFalse(apu)
|
||||||
|
|
||||||
# Add a position and test for real position
|
# Add a position and test for real position
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
self.port.add_new_position(
|
self.port.add_new_position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
ps = self.port.positions[market]
|
ps = self.port.positions[market]
|
||||||
|
|
||||||
# Test for addition of units
|
# Test for addition of units
|
||||||
add_price = Decimal("1.51878")
|
bid = Decimal("1.51878")
|
||||||
remove_price = Decimal("1.51928")
|
ask = Decimal("1.51928")
|
||||||
apu = self.port.add_position_units(
|
apu = self.port.add_position_units(
|
||||||
market, units, exposure,
|
market, units, exposure,
|
||||||
add_price, remove_price
|
bid, ask
|
||||||
)
|
)
|
||||||
self.assertTrue(apu)
|
self.assertTrue(apu)
|
||||||
self.assertEqual(ps.avg_price, Decimal("1.51824"))
|
self.assertEqual(ps.avg_price, Decimal("1.51824"))
|
||||||
|
|
||||||
def test_remove_position_units_long(self):
|
def test_remove_position_units_long(self):
|
||||||
side = "LONG"
|
position_type = "long"
|
||||||
units = 2000
|
units = Decimal("2000")
|
||||||
exposure = Decimal(str(units))
|
exposure = Decimal("2000.00")
|
||||||
add_price = Decimal("1.51819")
|
bid = Decimal("1.51770")
|
||||||
remove_price = Decimal("1.51770")
|
ask = Decimal("1.51819")
|
||||||
|
|
||||||
# Test for no position
|
# Test for no position
|
||||||
market = "EUR/USD"
|
market = "EUR/USD"
|
||||||
apu = self.port.remove_position_units(
|
apu = self.port.remove_position_units(
|
||||||
market, units, remove_price
|
market, units, bid, ask
|
||||||
)
|
)
|
||||||
self.assertFalse(apu)
|
self.assertFalse(apu)
|
||||||
|
|
||||||
# Add a position and then add units to it
|
# Add a position and then add units to it
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
self.port.add_new_position(
|
self.port.add_new_position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
ps = self.port.positions[market]
|
ps = self.port.positions[market]
|
||||||
add_price = Decimal("1.51928")
|
bid = Decimal("1.51878")
|
||||||
remove_price = Decimal("1.51878")
|
ask = Decimal("1.51928")
|
||||||
add_units = 8000
|
add_units = 8000
|
||||||
add_exposure = Decimal(str(add_units))
|
add_exposure = Decimal(str(add_units))
|
||||||
apu = self.port.add_position_units(
|
apu = self.port.add_position_units(
|
||||||
market, add_units, add_exposure,
|
market, add_units, add_exposure,
|
||||||
add_price, remove_price
|
bid, ask
|
||||||
)
|
)
|
||||||
self.assertEqual(ps.units, 10000)
|
self.assertEqual(ps.units, 10000)
|
||||||
self.assertEqual(ps.exposure, Decimal("10000.00"))
|
self.assertEqual(ps.exposure, Decimal("10000.00"))
|
||||||
self.assertEqual(ps.avg_price, Decimal("1.519062"))
|
self.assertEqual(ps.avg_price, Decimal("1.519062"))
|
||||||
|
|
||||||
# Test removal of (some) of the units
|
# Test removal of (some) of the units
|
||||||
add_price = Decimal("1.52134")
|
bid = Decimal("1.52017")
|
||||||
remove_price = Decimal("1.52017")
|
ask = Decimal("1.52134")
|
||||||
remove_units = 3000
|
remove_units = 3000
|
||||||
rpu = self.port.remove_position_units(
|
rpu = self.port.remove_position_units(
|
||||||
market, remove_units, remove_price
|
market, remove_units, bid, ask
|
||||||
)
|
)
|
||||||
self.assertTrue(rpu)
|
self.assertTrue(rpu)
|
||||||
self.assertEqual(ps.units, 7000)
|
self.assertEqual(ps.units, 7000)
|
||||||
self.assertEqual(ps.exposure, Decimal("7000.00"))
|
self.assertEqual(ps.exposure, Decimal("7000.00"))
|
||||||
self.assertEqual(ps.profit_base, Decimal("2.19054"))
|
self.assertEqual(ps.profit_base, Decimal("2.19054"))
|
||||||
self.assertEqual(self.port.balance, Decimal("100002.19"))
|
self.assertEqual(self.port.balance, Decimal("100002.19"))
|
||||||
|
|
||||||
def test_remove_position_units_short(self):
|
def test_remove_position_units_short(self):
|
||||||
side = "SHORT"
|
position_type = "short"
|
||||||
units = 2000
|
units = Decimal("2000")
|
||||||
exposure = Decimal(str(units))
|
exposure = Decimal("2000.00")
|
||||||
add_price = Decimal("1.51770")
|
bid = Decimal("1.51770")
|
||||||
remove_price = Decimal("1.51819")
|
ask = Decimal("1.51819")
|
||||||
|
|
||||||
# Test for no position
|
# Test for no position
|
||||||
market = "EUR/USD"
|
market = "EUR/USD"
|
||||||
apu = self.port.remove_position_units(
|
apu = self.port.remove_position_units(
|
||||||
market, units, remove_price
|
market, units, bid, ask
|
||||||
)
|
)
|
||||||
self.assertFalse(apu)
|
self.assertFalse(apu)
|
||||||
|
|
||||||
# Add a position and then add units to it
|
# Add a position and then add units to it
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
self.port.add_new_position(
|
self.port.add_new_position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
ps = self.port.positions[market]
|
ps = self.port.positions[market]
|
||||||
add_price = Decimal("1.51878")
|
bid = Decimal("1.51878")
|
||||||
remove_price = Decimal("1.51928")
|
ask = Decimal("1.51928")
|
||||||
add_units = 8000
|
add_units = 8000
|
||||||
add_exposure = Decimal(str(add_units))
|
add_exposure = Decimal(str(add_units))
|
||||||
apu = self.port.add_position_units(
|
apu = self.port.add_position_units(
|
||||||
market, add_units, add_exposure,
|
market, add_units, add_exposure,
|
||||||
add_price, remove_price
|
bid, ask
|
||||||
)
|
)
|
||||||
self.assertEqual(ps.units, 10000)
|
self.assertEqual(ps.units, 10000)
|
||||||
self.assertEqual(ps.exposure, Decimal("10000.00"))
|
self.assertEqual(ps.exposure, Decimal("10000.00"))
|
||||||
self.assertEqual(ps.avg_price, Decimal("1.518564"))
|
self.assertEqual(ps.avg_price, Decimal("1.518564"))
|
||||||
|
|
||||||
# Test removal of (some) of the units
|
# Test removal of (some) of the units
|
||||||
add_price = Decimal("1.52017")
|
bid = Decimal("1.52017")
|
||||||
remove_price = Decimal("1.52134")
|
ask = Decimal("1.52134")
|
||||||
remove_units = 3000
|
remove_units = 3000
|
||||||
rpu = self.port.remove_position_units(
|
rpu = self.port.remove_position_units(
|
||||||
market, remove_units, remove_price
|
market, remove_units, bid, ask
|
||||||
)
|
)
|
||||||
self.assertTrue(rpu)
|
self.assertTrue(rpu)
|
||||||
self.assertEqual(ps.units, 7000)
|
self.assertEqual(ps.units, 7000)
|
||||||
@@ -220,16 +219,16 @@ class TestPortfolio(unittest.TestCase):
|
|||||||
self.assertEqual(self.port.balance, Decimal("99994.52"))
|
self.assertEqual(self.port.balance, Decimal("99994.52"))
|
||||||
|
|
||||||
def test_close_position_long(self):
|
def test_close_position_long(self):
|
||||||
side = "LONG"
|
position_type = "long"
|
||||||
units = 2000
|
units = Decimal("2000")
|
||||||
exposure = Decimal(str(units))
|
exposure = Decimal("2000.00")
|
||||||
add_price = Decimal("1.51819")
|
bid = Decimal("1.51770")
|
||||||
remove_price = Decimal("1.51770")
|
ask = Decimal("1.51819")
|
||||||
|
|
||||||
# Test for no position
|
# Test for no position
|
||||||
market = "EUR/USD"
|
market = "EUR/USD"
|
||||||
cp = self.port.close_position(
|
cp = self.port.close_position(
|
||||||
market, remove_price
|
market, bid, ask
|
||||||
)
|
)
|
||||||
self.assertFalse(cp)
|
self.assertFalse(cp)
|
||||||
|
|
||||||
@@ -237,12 +236,12 @@ class TestPortfolio(unittest.TestCase):
|
|||||||
# Will lose money on the spread
|
# Will lose money on the spread
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
self.port.add_new_position(
|
self.port.add_new_position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
ps = self.port.positions[market]
|
ps = self.port.positions[market]
|
||||||
cp = self.port.close_position(
|
cp = self.port.close_position(
|
||||||
market, remove_price
|
market, bid, ask
|
||||||
)
|
)
|
||||||
self.assertTrue(cp)
|
self.assertTrue(cp)
|
||||||
self.assertRaises(ps) # Key doesn't exist
|
self.assertRaises(ps) # Key doesn't exist
|
||||||
@@ -252,38 +251,114 @@ class TestPortfolio(unittest.TestCase):
|
|||||||
# close the position. Balance should be as expected
|
# close the position. Balance should be as expected
|
||||||
# for a multi-leg transaction.
|
# for a multi-leg transaction.
|
||||||
self.port.add_new_position(
|
self.port.add_new_position(
|
||||||
side, market, units, exposure,
|
position_type, market, units,
|
||||||
add_price, remove_price
|
exposure, bid, ask
|
||||||
)
|
)
|
||||||
ps = self.port.positions[market]
|
ps = self.port.positions[market]
|
||||||
add_price = Decimal("1.51928")
|
|
||||||
remove_price = Decimal("1.51878")
|
# Add 8000 units
|
||||||
|
bid = Decimal("1.51878")
|
||||||
|
ask = Decimal("1.51928")
|
||||||
add_units = 8000
|
add_units = 8000
|
||||||
add_exposure = Decimal(str(add_units))
|
add_exposure = Decimal(str(add_units))
|
||||||
apu = self.port.add_position_units(
|
apu = self.port.add_position_units(
|
||||||
market, add_units, add_exposure,
|
market, add_units,
|
||||||
add_price, remove_price
|
add_exposure, bid, ask
|
||||||
)
|
)
|
||||||
self.assertEqual(ps.units, 10000)
|
self.assertEqual(ps.units, 10000)
|
||||||
self.assertEqual(ps.exposure, Decimal("10000.00"))
|
self.assertEqual(ps.exposure, Decimal("10000.00"))
|
||||||
self.assertEqual(ps.avg_price, Decimal("1.519062"))
|
self.assertEqual(ps.avg_price, Decimal("1.519062"))
|
||||||
add_price = Decimal("1.52134")
|
|
||||||
remove_price = Decimal("1.52017")
|
# Remove 3000 units
|
||||||
|
bid = Decimal("1.52017")
|
||||||
|
ask = Decimal("1.52134")
|
||||||
remove_units = 3000
|
remove_units = 3000
|
||||||
rpu = self.port.remove_position_units(
|
rpu = self.port.remove_position_units(
|
||||||
market, remove_units, remove_price
|
market, remove_units, bid, ask
|
||||||
)
|
)
|
||||||
self.assertEqual(ps.units, 7000)
|
self.assertEqual(ps.units, 7000)
|
||||||
self.assertEqual(ps.exposure, Decimal("7000.00"))
|
self.assertEqual(ps.exposure, Decimal("7000.00"))
|
||||||
self.assertEqual(ps.profit_base, Decimal("2.19054"))
|
self.assertEqual(ps.profit_base, Decimal("2.19054"))
|
||||||
self.assertEqual(self.port.balance, Decimal("100001.54"))
|
self.assertEqual(self.port.balance, Decimal("100001.54"))
|
||||||
|
|
||||||
|
# Close the position
|
||||||
cp = self.port.close_position(
|
cp = self.port.close_position(
|
||||||
market, remove_price
|
market, bid, ask
|
||||||
)
|
)
|
||||||
self.assertTrue(cp)
|
self.assertTrue(cp)
|
||||||
self.assertRaises(ps) # Key doesn't exist
|
self.assertRaises(ps) # Key doesn't exist
|
||||||
self.assertEqual(self.port.balance, Decimal("100006.65"))
|
self.assertEqual(self.port.balance, Decimal("100006.65"))
|
||||||
|
|
||||||
|
def test_close_position_short(self):
|
||||||
|
position_type = "short"
|
||||||
|
units = Decimal("2000")
|
||||||
|
exposure = Decimal("2000.00")
|
||||||
|
bid = Decimal("1.51770")
|
||||||
|
ask = Decimal("1.51819")
|
||||||
|
|
||||||
|
# Test for no position
|
||||||
|
market = "EUR/USD"
|
||||||
|
cp = self.port.close_position(
|
||||||
|
market, bid, ask
|
||||||
|
)
|
||||||
|
self.assertFalse(cp)
|
||||||
|
|
||||||
|
# Add a position and then close it
|
||||||
|
# Will lose money on the spread
|
||||||
|
market = "GBP/USD"
|
||||||
|
self.port.add_new_position(
|
||||||
|
position_type, market, units,
|
||||||
|
exposure, bid, ask
|
||||||
|
)
|
||||||
|
ps = self.port.positions[market]
|
||||||
|
cp = self.port.close_position(
|
||||||
|
market, bid, ask
|
||||||
|
)
|
||||||
|
self.assertTrue(cp)
|
||||||
|
self.assertRaises(ps) # Key doesn't exist
|
||||||
|
self.assertEqual(self.port.balance, Decimal("99999.35"))
|
||||||
|
|
||||||
|
# Add 2000, add another 8000, remove 3000 and then
|
||||||
|
# close the position. Balance should be as expected
|
||||||
|
# for a multi-leg transaction.
|
||||||
|
self.port.add_new_position(
|
||||||
|
position_type, market, units,
|
||||||
|
exposure, bid, ask
|
||||||
|
)
|
||||||
|
ps = self.port.positions[market]
|
||||||
|
|
||||||
|
# Add 8000 units
|
||||||
|
bid = Decimal("1.51878")
|
||||||
|
ask = Decimal("1.51928")
|
||||||
|
add_units = 8000
|
||||||
|
add_exposure = Decimal(str(add_units))
|
||||||
|
apu = self.port.add_position_units(
|
||||||
|
market, add_units,
|
||||||
|
add_exposure, bid, ask
|
||||||
|
)
|
||||||
|
self.assertEqual(ps.units, 10000)
|
||||||
|
self.assertEqual(ps.exposure, Decimal("10000.00"))
|
||||||
|
self.assertEqual(ps.avg_price, Decimal("1.518564"))
|
||||||
|
|
||||||
|
# Remove 3000 units
|
||||||
|
bid = Decimal("1.52017")
|
||||||
|
ask = Decimal("1.52134")
|
||||||
|
remove_units = 3000
|
||||||
|
rpu = self.port.remove_position_units(
|
||||||
|
market, remove_units, bid, ask
|
||||||
|
)
|
||||||
|
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("99993.87"))
|
||||||
|
|
||||||
|
# Close the position
|
||||||
|
cp = self.port.close_position(
|
||||||
|
market, bid, ask
|
||||||
|
)
|
||||||
|
self.assertTrue(cp)
|
||||||
|
self.assertRaises(ps) # Key doesn't exist
|
||||||
|
self.assertEqual(self.port.balance, Decimal("99981.08"))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
+20
-9
@@ -3,22 +3,31 @@ from decimal import Decimal, getcontext, ROUND_HALF_DOWN
|
|||||||
|
|
||||||
class Position(object):
|
class Position(object):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, side, market, units,
|
self, position_type, market,
|
||||||
exposure, avg_price, cur_price
|
units, exposure, bid, ask
|
||||||
):
|
):
|
||||||
self.side = side
|
self.position_type = position_type # Long or short
|
||||||
self.market = market
|
self.market = market
|
||||||
self.units = units
|
self.units = units
|
||||||
self.exposure = Decimal(str(exposure))
|
self.exposure = Decimal(str(exposure))
|
||||||
self.avg_price = Decimal(str(avg_price))
|
|
||||||
self.cur_price = Decimal(str(cur_price))
|
# Long or short
|
||||||
|
if self.position_type == "long":
|
||||||
|
self.avg_price = Decimal(str(ask))
|
||||||
|
self.cur_price = Decimal(str(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_base = self.calculate_profit_base(self.exposure)
|
||||||
self.profit_perc = self.calculate_profit_perc(self.exposure)
|
self.profit_perc = self.calculate_profit_perc(self.exposure)
|
||||||
|
|
||||||
def calculate_pips(self):
|
def calculate_pips(self):
|
||||||
getcontext.prec = 6
|
getcontext.prec = 6
|
||||||
mult = Decimal("1")
|
mult = Decimal("1")
|
||||||
if self.side == "SHORT":
|
if self.position_type == "long":
|
||||||
|
mult = Decimal("1")
|
||||||
|
elif self.position_type == "short":
|
||||||
mult = Decimal("-1")
|
mult = Decimal("-1")
|
||||||
return (mult * (self.cur_price - self.avg_price)).quantize(
|
return (mult * (self.cur_price - self.avg_price)).quantize(
|
||||||
Decimal("0.00001"), ROUND_HALF_DOWN
|
Decimal("0.00001"), ROUND_HALF_DOWN
|
||||||
@@ -35,8 +44,10 @@ class Position(object):
|
|||||||
Decimal("0.00001"), ROUND_HALF_DOWN
|
Decimal("0.00001"), ROUND_HALF_DOWN
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_position_price(self, cur_price, exposure):
|
def update_position_price(self, bid, ask, exposure):
|
||||||
self.cur_price = cur_price
|
if self.position_type == "long":
|
||||||
|
self.cur_price = Decimal(str(bid))
|
||||||
|
else:
|
||||||
|
self.cur_price = Decimal(str(ask))
|
||||||
self.profit_base = self.calculate_profit_base(exposure)
|
self.profit_base = self.calculate_profit_base(exposure)
|
||||||
self.profit_perc = self.calculate_profit_perc(exposure)
|
self.profit_perc = self.calculate_profit_perc(exposure)
|
||||||
|
|
||||||
|
|||||||
+60
-24
@@ -7,55 +7,91 @@ from position import Position
|
|||||||
class TestLongGBPUSDPosition(unittest.TestCase):
|
class TestLongGBPUSDPosition(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
getcontext.prec = 2
|
getcontext.prec = 2
|
||||||
side = "LONG"
|
position_type = "long"
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
units = Decimal(str(2000))
|
units = Decimal("2000")
|
||||||
exposure = Decimal("2000.00")
|
exposure = Decimal("2000.00")
|
||||||
avg_price = Decimal("1.51819")
|
bid = Decimal("1.50328")
|
||||||
cur_price = Decimal("1.51770")
|
ask = Decimal("1.50349")
|
||||||
self.position = Position(
|
self.position = Position(
|
||||||
side, market, units, exposure,
|
position_type, market,
|
||||||
avg_price, cur_price
|
units, exposure, bid, ask
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_calculate_pips(self):
|
def test_calculate_init_pips(self):
|
||||||
pos_pips = self.position.calculate_pips()
|
pos_pips = self.position.calculate_pips()
|
||||||
self.assertEqual(pos_pips, Decimal("-0.00049"))
|
self.assertEqual(pos_pips, Decimal("-0.00021"))
|
||||||
|
|
||||||
def test_calculate_profit_base(self):
|
def test_calculate_init_profit_base(self):
|
||||||
profit_base = self.position.calculate_profit_base(self.position.exposure)
|
profit_base = self.position.calculate_profit_base(self.position.exposure)
|
||||||
self.assertEqual(profit_base, Decimal("-0.64571"))
|
self.assertEqual(profit_base, Decimal("-0.27939"))
|
||||||
|
|
||||||
def test_calculate_profit_perc(self):
|
def test_calculate_init_profit_perc(self):
|
||||||
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
|
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
|
||||||
self.assertEqual(profit_perc, Decimal("-0.03229"))
|
self.assertEqual(profit_perc, Decimal("-0.01397"))
|
||||||
|
|
||||||
|
def test_calculate_updated_values(self):
|
||||||
|
"""
|
||||||
|
Check that after the bid/ask prices move, that the updated
|
||||||
|
pips, profit and percentage profit calculations are correct.
|
||||||
|
"""
|
||||||
|
bid = Decimal("1.50486")
|
||||||
|
ask = Decimal("1.50586")
|
||||||
|
self.position.update_position_price(bid, ask, self.position.exposure)
|
||||||
|
# Check pips
|
||||||
|
pos_pips = self.position.calculate_pips()
|
||||||
|
self.assertEqual(pos_pips, Decimal("0.00137"))
|
||||||
|
# Check profit base
|
||||||
|
profit_base = self.position.calculate_profit_base(self.position.exposure)
|
||||||
|
self.assertEqual(profit_base, Decimal("1.82077"))
|
||||||
|
# Check profit percentage
|
||||||
|
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
|
||||||
|
self.assertEqual(profit_perc, Decimal("0.09104"))
|
||||||
|
|
||||||
|
|
||||||
class TestShortGBPUSDPosition(unittest.TestCase):
|
class TestShortGBPUSDPosition(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
getcontext.prec = 2
|
getcontext.prec = 2
|
||||||
side = "SHORT"
|
position_type = "short"
|
||||||
market = "GBP/USD"
|
market = "GBP/USD"
|
||||||
units = 2000
|
units = Decimal("2000")
|
||||||
exposure = Decimal("2000.00")
|
exposure = Decimal("2000.00")
|
||||||
avg_price = Decimal("1.51819")
|
bid = Decimal("1.50328")
|
||||||
cur_price = Decimal("1.51770")
|
ask = Decimal("1.50349")
|
||||||
self.position = Position(
|
self.position = Position(
|
||||||
side, market, units, exposure,
|
position_type, market,
|
||||||
avg_price, cur_price
|
units, exposure, bid, ask
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_calculate_pips(self):
|
def test_calculate_init_pips(self):
|
||||||
pos_pips = self.position.calculate_pips()
|
pos_pips = self.position.calculate_pips()
|
||||||
self.assertEqual(pos_pips, Decimal("0.00049"))
|
self.assertEqual(pos_pips, Decimal("-0.00021"))
|
||||||
|
|
||||||
def test_calculate_profit_base(self):
|
def test_calculate_init_profit_base(self):
|
||||||
profit_base = self.position.calculate_profit_base(self.position.exposure)
|
profit_base = self.position.calculate_profit_base(self.position.exposure)
|
||||||
self.assertEqual(profit_base, Decimal("0.64571"))
|
self.assertEqual(profit_base, Decimal("-0.27935"))
|
||||||
|
|
||||||
def test_calculate_profit_perc(self):
|
def test_calculate_init_profit_perc(self):
|
||||||
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
|
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
|
||||||
self.assertEqual(profit_perc, Decimal("0.03229"))
|
self.assertEqual(profit_perc, Decimal("-0.01397"))
|
||||||
|
|
||||||
|
def test_calculate_updated_values(self):
|
||||||
|
"""
|
||||||
|
Check that after the bid/ask prices move, that the updated
|
||||||
|
pips, profit and percentage profit calculations are correct.
|
||||||
|
"""
|
||||||
|
bid = Decimal("1.50486")
|
||||||
|
ask = Decimal("1.50586")
|
||||||
|
self.position.update_position_price(bid, ask, self.position.exposure)
|
||||||
|
# Check pips
|
||||||
|
pos_pips = self.position.calculate_pips()
|
||||||
|
self.assertEqual(pos_pips, Decimal("-0.00258"))
|
||||||
|
# Check profit base
|
||||||
|
profit_base = self.position.calculate_profit_base(self.position.exposure)
|
||||||
|
self.assertEqual(profit_base, Decimal("-3.42661"))
|
||||||
|
# Check profit percentage
|
||||||
|
profit_perc = self.position.calculate_profit_perc(self.position.exposure)
|
||||||
|
self.assertEqual(profit_perc, Decimal("-0.17133"))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+7
-1
@@ -1,3 +1,4 @@
|
|||||||
|
from decimal import Decimal
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
@@ -14,8 +15,13 @@ ENVIRONMENTS = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CSV_DATA_DIR = os.environ.get('QSFOREX_CSV_DATA_DIR', None)
|
||||||
|
|
||||||
DOMAIN = "practice"
|
DOMAIN = "practice"
|
||||||
STREAM_DOMAIN = ENVIRONMENTS["streaming"][DOMAIN]
|
STREAM_DOMAIN = ENVIRONMENTS["streaming"][DOMAIN]
|
||||||
API_DOMAIN = ENVIRONMENTS["api"][DOMAIN]
|
API_DOMAIN = ENVIRONMENTS["api"][DOMAIN]
|
||||||
ACCESS_TOKEN = os.environ.get('OANDA_API_ACCESS_TOKEN', None)
|
ACCESS_TOKEN = os.environ.get('OANDA_API_ACCESS_TOKEN', None)
|
||||||
ACCOUNT_ID = os.environ.get('OANDA_API_ACCOUNT_ID', None)
|
ACCOUNT_ID = os.environ.get('OANDA_API_ACCOUNT_ID', None)
|
||||||
|
|
||||||
|
BASE_CURRENCY = "GBP"
|
||||||
|
EQUITY = Decimal("100000.00")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class TestStrategy(object):
|
|||||||
|
|
||||||
def calculate_signals(self, event):
|
def calculate_signals(self, event):
|
||||||
if event.type == 'TICK':
|
if event.type == 'TICK':
|
||||||
if self.ticks % 200 == 0:
|
if self.ticks % 5 == 0:
|
||||||
if self.invested == False:
|
if self.invested == False:
|
||||||
signal = SignalEvent(self.instrument, "market", "buy")
|
signal = SignalEvent(self.instrument, "market", "buy")
|
||||||
self.events.put(signal)
|
self.events.put(signal)
|
||||||
@@ -19,4 +19,4 @@ class TestStrategy(object):
|
|||||||
signal = SignalEvent(self.instrument, "market", "sell")
|
signal = SignalEvent(self.instrument, "market", "sell")
|
||||||
self.events.put(signal)
|
self.events.put(signal)
|
||||||
self.invested = False
|
self.invested = False
|
||||||
self.ticks += 1
|
self.ticks += 1
|
||||||
|
|||||||
+13
-9
@@ -4,14 +4,14 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from decimal import Decimal, getcontext
|
from decimal import Decimal, getcontext
|
||||||
|
|
||||||
from qsforex.execution.execution import Execution
|
from qsforex.execution.execution import OANDAExecutionHandler
|
||||||
from qsforex.portfolio.portfolio import Portfolio
|
from qsforex.portfolio.portfolio import Portfolio
|
||||||
from qsforex.settings import STREAM_DOMAIN, API_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID
|
from qsforex import settings
|
||||||
from qsforex.strategy.strategy import TestStrategy
|
from qsforex.strategy.strategy import TestStrategy
|
||||||
from qsforex.streaming.streaming import StreamingForexPrices
|
from qsforex.streaming.streaming import StreamingForexPrices
|
||||||
|
|
||||||
|
|
||||||
def trade(events, strategy, portfolio, execution):
|
def trade(events, strategy, portfolio, execution, heartbeat):
|
||||||
"""
|
"""
|
||||||
Carries out an infinite while loop that polls the
|
Carries out an infinite while loop that polls the
|
||||||
events queue and directs each event to either the
|
events queue and directs each event to either the
|
||||||
@@ -39,9 +39,9 @@ if __name__ == "__main__":
|
|||||||
# Set the number of decimal places to 2
|
# Set the number of decimal places to 2
|
||||||
getcontext().prec = 2
|
getcontext().prec = 2
|
||||||
|
|
||||||
heartbeat = 0.5 # Half a second between polling
|
heartbeat = 0.0 # Half a second between polling
|
||||||
events = Queue.Queue()
|
events = Queue.Queue()
|
||||||
equity = Decimal("99949.82")
|
equity = settings.EQUITY
|
||||||
|
|
||||||
# Trade "Cable"
|
# Trade "Cable"
|
||||||
instrument = "GBP_USD"
|
instrument = "GBP_USD"
|
||||||
@@ -49,8 +49,8 @@ if __name__ == "__main__":
|
|||||||
# Create the OANDA market price streaming class
|
# Create the OANDA market price streaming class
|
||||||
# making sure to provide authentication commands
|
# making sure to provide authentication commands
|
||||||
prices = StreamingForexPrices(
|
prices = StreamingForexPrices(
|
||||||
STREAM_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID,
|
settings.STREAM_DOMAIN, settings.ACCESS_TOKEN,
|
||||||
instrument, events
|
settings.ACCOUNT_ID, instrument, events
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create the strategy/signal generator, passing the
|
# Create the strategy/signal generator, passing the
|
||||||
@@ -64,13 +64,17 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Create the execution handler making sure to
|
# Create the execution handler making sure to
|
||||||
# provide authentication commands
|
# provide authentication commands
|
||||||
execution = Execution(API_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID)
|
execution = OANDAExecutionHandler(
|
||||||
|
settings.API_DOMAIN,
|
||||||
|
settings.ACCESS_TOKEN,
|
||||||
|
settings.ACCOUNT_ID
|
||||||
|
)
|
||||||
|
|
||||||
# Create two separate threads: One for the trading loop
|
# Create two separate threads: One for the trading loop
|
||||||
# and another for the market price streaming class
|
# and another for the market price streaming class
|
||||||
trade_thread = threading.Thread(
|
trade_thread = threading.Thread(
|
||||||
target=trade, args=(
|
target=trade, args=(
|
||||||
events, strategy, portfolio, execution
|
events, strategy, portfolio, execution, heartbeat
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
price_thread = threading.Thread(target=prices.stream_to_queue, args=[])
|
price_thread = threading.Thread(target=prices.stream_to_queue, args=[])
|
||||||
|
|||||||
Reference in New Issue
Block a user