def test_total_values_for_two_separate_transactions(): """ Tests 'total_book_cost', 'total_market_value', 'total_gain' and 'total_perc_gain' for single transactions in two separate assets. """ ph = PositionHandler() # Asset 1 asset1 = Equity('Amazon, Inc.', 'AMZN') dt1 = pd.Timestamp('2015-05-06') trans_pos_1 = Transaction(asset1.symbol, quantity=75, dt=dt1, price=483.45, order_id=1, commission=15.97) ph.transact_position(trans_pos_1) # Asset 2 asset2 = Equity('Microsoft, Inc.', 'MSFT') dt2 = pd.Timestamp('2015-05-07') trans_pos_2 = Transaction(asset2.symbol, quantity=250, dt=dt2, price=142.58, order_id=2, commission=8.35) ph.transact_position(trans_pos_2) # Check all total values assert ph.total_book_cost() == 71928.07 assert ph.total_market_value() == 71903.75 assert ph.total_unrealised_gain() == -24.31999999999971 assert ph.total_unrealised_percentage_gain() == -0.03381155646190282
def test_portfolio_to_dict_for_two_holdings(): """ Test portfolio_to_dict for two holdings. """ start_dt = pd.Timestamp('2017-10-05 08:00:00', tz=pytz.UTC) asset1_dt = pd.Timestamp('2017-10-06 08:00:00', tz=pytz.UTC) asset2_dt = pd.Timestamp('2017-10-07 08:00:00', tz=pytz.UTC) update_dt = pd.Timestamp('2017-10-08 08:00:00', tz=pytz.UTC) asset1 = Equity("AAA Inc.", "EQ:AAA", tax_exempt=False) asset2 = Equity("BBB Inc.", "EQ:BBB", tax_exempt=False) port = Portfolio(start_dt, portfolio_id='1234') port.subscribe_funds(start_dt, 100000.0) tn_asset1 = Transaction(asset=asset1.symbol, quantity=100, dt=asset1_dt, price=567.0, order_id=1, commission=15.78) port.transact_asset(tn_asset1) tn_asset2 = Transaction(asset=asset2.symbol, quantity=100, dt=asset2_dt, price=123.0, order_id=2, commission=7.64) port.transact_asset(tn_asset2) port.update_market_value_of_asset(asset2.symbol, 134.0, update_dt) test_holdings = { asset1.symbol: { "quantity": 100, "book_cost": 56715.78, "market_value": 56700.0, "gain": -15.78, "perc_gain": -0.027822944513854874 }, asset2.symbol: { "quantity": 100, "book_cost": 12307.64, "market_value": 13400.0, "gain": 1092.3600000000006, "perc_gain": 8.8754627207165679 } } port_holdings = port.portfolio_to_dict() # This is needed because we're not using Decimal # datatypes and have to compare slightly differing # floating point representations for asset in (asset1.symbol, asset2.symbol): for key, val in test_holdings[asset].items(): assert port_holdings[asset][key] == pytest.approx( test_holdings[asset][key])
def test_transact_position_current_position(): """ Tests the 'transact_position' method for a transaction with a current asset and checks that all objects are set correctly. """ # Create the PositionHandler, Transaction and # carry out a transaction ph = PositionHandler() asset = Equity('Amazon, Inc.', 'AMZN') dt = pd.Timestamp('2015-05-06') transaction_long = Transaction(asset, quantity=100, dt=dt, price=960.0, order_id=123, commission=26.83) transaction_long_again = Transaction(asset, quantity=200, dt=dt, price=990.0, order_id=234, commission=18.53) ph.transact_position(transaction_long) ph.transact_position(transaction_long_again) # Check that the position object is set correctly pos = ph.positions[asset] assert pos.quantity == 300 assert pos.direction == 1.0 assert pos.book_cost_pu == 980.1512000000001 assert pos.book_cost == 294045.36000000004
def test_position_long_short_positive_gain(): """ Tests that the quantity and book cost are correctly calculated for an initial long position with an additional short transaction in the same asset, where the short does not completely eliminate the position and the result is a gain. """ asset = Equity('Apple, Inc.', 'AAPL') position = Position(asset, quantity=100, book_cost_pu=950.0, current_price=950.0) dt = pd.Timestamp('2015-05-06') transaction = Transaction(asset, quantity=-50, dt=dt, price=960.0, order_id=123, commission=None) position.update(transaction) assert position.quantity == 50 assert position.book_cost_pu == 950.0 assert position.direction == 1.0 assert position.current_price == 960.0 assert position.market_value == 48000.0 assert position.unrealised_gain == 500.0 assert position.unrealised_percentage_gain == 1.0526315789473684
def test_position_short_long_excess_cover(): """ Tests that the quantity and book cost are correctly calculated for an initial short position with an additional long transaction in the same asset, where the long position is in excess of the short position. """ asset = Equity('Apple, Inc.', 'AAPL') position = Position(asset, quantity=-100, book_cost_pu=700.0, current_price=700.0) dt = pd.Timestamp('2015-05-06') transaction = Transaction(asset, quantity=175, dt=dt, price=873.0, order_id=123, commission=None) position.update(transaction) assert position.quantity == 75 assert position.book_cost_pu == 873.0 assert position.direction == 1.0 assert position.current_price == 873.0 assert position.market_value == 65475.0 assert position.unrealised_gain == 0.0 assert position.unrealised_percentage_gain == 0.0
def test_transact_position_quantity_zero(): """ Tests the 'transact_position' method for a transaction with net zero quantity after the transaction to ensure deletion of the position. """ # Create the PositionHandler, Transaction and # carry out a transaction ph = PositionHandler() asset = Equity('Amazon, Inc.', 'AMZN') dt = pd.Timestamp('2015-05-06') transaction_long = Transaction(asset, quantity=100, dt=dt, price=960.0, order_id=123, commission=26.83) transaction_close = Transaction(asset, quantity=-100, dt=dt, price=980.0, order_id=234, commission=18.53) # Go long and then close, then check that the # positions OrderedDict is empty ph.transact_position(transaction_long) ph.transact_position(transaction_close) od = OrderedDict() assert ph.positions == od
def test_position_long_twice(): """ Tests that the quantity and book cost are correctly calculated for an initial long position with an additional long transaction in the same asset. """ asset = Equity('Apple, Inc.', 'AAPL') position = Position(asset, quantity=100, book_cost_pu=950.0, current_price=950.0) dt = pd.Timestamp('2015-05-06') transaction = Transaction(asset, quantity=100, dt=dt, price=960.0, order_id=123, commission=None) position.update(transaction) assert position.quantity == 200 assert position.book_cost_pu == 955.0 assert position.direction == 1.0 assert position.current_price == 960.0 assert position.market_value == 192000.0 assert position.unrealised_gain == 1000.0 assert position.unrealised_percentage_gain == 0.5235602094240838
def test_transact_position_new_position(): """ Tests the 'transact_position' method for a transaction with a brand new asset and checks that all objects are set correctly. """ # Create the PositionHandler, Transaction and # carry out a transaction ph = PositionHandler() asset = Equity('Amazon, Inc.', 'AMZN') dt = pd.Timestamp('2015-05-06') transaction = Transaction(asset, quantity=100, dt=dt, price=960.0, order_id=123, commission=26.83) ph.transact_position(transaction) # Check that the position object is set correctly pos = ph.positions[asset] assert pos.quantity == 100 assert pos.direction == 1.0 assert pos.book_cost_pu == 960.2683000000001 assert pos.book_cost == 96026.83
def test_check_set_position_new_asset(): """ Checks the _check_set_position method when a new asset is added to the PositionHandler and when it is checked subsequently. """ # Create PositionHandler, Asset and OrderedDict # positions list ph = PositionHandler() asset = Equity('Amazon, Inc.', 'AMZN') od = OrderedDict() assert ph.positions == od # Check that the position is set for new asset pos = ph._check_set_position(asset) assert pos.asset == asset # Check that the OrderedDict is correctly set # for new asset od[asset] = pos assert ph.positions == od # Check that it works for a current asset pos = ph._check_set_position(asset) assert pos.asset == asset assert ph.positions == od
def test_update_position_for_non_none_values(): """ Tests the 'update_position' method for non-None values when updating a Position entity. """ ph = PositionHandler() # Asset 1 asset1 = Equity('Amazon, Inc.', 'AMZN') dt1 = pd.Timestamp('2015-05-06') trans_pos_1 = Transaction(asset1, quantity=75, dt=dt1, price=483.45, order_id=1, commission=13.76) ph.transact_position(trans_pos_1) # Update values manually quantity = 100 current_price = 504.32 current_dt = pd.Timestamp('2015-05-07') book_cost_pu = 23.65 ph.update_position(asset1, quantity=quantity, current_price=current_price, current_dt=current_dt, book_cost_pu=book_cost_pu) assert ph.positions[asset1].quantity == quantity assert ph.positions[asset1].current_price == current_price assert ph.positions[asset1].current_dt == current_dt assert ph.positions[asset1].book_cost_pu == book_cost_pu
def test_update_book_cost_for_commission_for_incorrect_asset(): """ Tests that the 'update_book_cost_for_commission' method, when provided with a transaction with an asset that does not match the position's asset, raises an Exception. """ asset1 = Equity('Apple, Inc.', 'AAPL') asset2 = Equity('Amazon, Inc.', 'AMZN') position = Position(asset1, quantity=100, book_cost_pu=950.0, current_price=950.0) with pytest.raises(Exception): position.update_book_cost_for_commission(asset2, 23.00)
def test_update_book_cost_for_commission_zero_position(): """ Tests that 'update_book_cost_for_commission' returns None when some positive commission is provided, given that the Position itself has zero quantity. """ asset = Equity('Apple, Inc.', 'AAPL') position = Position(asset, quantity=0, book_cost_pu=0.0, current_price=0.0) assert position.update_book_cost_for_commission(asset, 15.0) is None
def test_update_market_value_of_asset_not_in_list(): """ Test update_market_value_of_asset for asset not in list. """ start_dt = pd.Timestamp('2017-10-05 08:00:00', tz=pytz.UTC) later_dt = pd.Timestamp('2017-10-06 08:00:00', tz=pytz.UTC) port = Portfolio(start_dt) asset = Equity("Acme Inc.", "AAA", tax_exempt=False) update = port.update_market_value_of_asset(asset, 54.34, later_dt) assert update is None
def test_update_book_cost_for_commission_for_no_commission(): """ Tests that 'update_book_cost_for_commission' returns None when zero or None commission is provided. """ asset = Equity('Apple, Inc.', 'AAPL') position = Position(asset, quantity=100, book_cost_pu=950.0, current_price=950.0) assert position.update_book_cost_for_commission(asset, 0.0) is None assert position.update_book_cost_for_commission(asset, None) is None
def test_update_for_incorrect_asset(): """ Tests that the 'update' method, when provided with a transaction with an asset that does not match the position's asset, raises an Exception. """ asset1 = Equity('Apple, Inc.', 'AAPL') asset2 = Equity('Amazon, Inc.', 'AMZN') position = Position(asset1, quantity=100, book_cost_pu=950.0, current_price=950.0) dt = pd.Timestamp('2015-05-06') transaction = Transaction(asset2, quantity=50, dt=dt, price=960.0, order_id=123, commission=None) with pytest.raises(Exception): position.update(transaction)
def test_position_representation(): """ Tests that the Position representation correctly recreates the object. """ asset = Equity('Apple, Inc.', 'AAPL') position = Position(asset, quantity=153, book_cost_pu=950.0, current_price=950.0) exp_repr = ( "Position(asset=Equity(name='Apple, Inc.', symbol='AAPL', tax_exempt=True), " "quantity=153, book_cost_pu=950.0, current_price=950.0)") assert repr(position) == exp_repr
def test_update_commission(): """ Tests the 'update_commission' method to ensure commission is correctly set on the Position entities. """ ph = PositionHandler() # Asset 1 asset1 = Equity('Amazon, Inc.', 'AMZN') dt1 = pd.Timestamp('2015-05-06') trans_pos_1 = Transaction(asset1.symbol, quantity=75, dt=dt1, price=483.45, order_id=1, commission=0.0) ph.transact_position(trans_pos_1) ph.update_commission(asset1.symbol, 15.97) # Asset 2 asset2 = Equity('Microsoft, Inc.', 'MSFT') dt2 = pd.Timestamp('2015-05-07') trans_pos_2 = Transaction(asset2.symbol, quantity=250, dt=dt2, price=142.58, order_id=2, commission=0.0) ph.transact_position(trans_pos_2) ph.update_commission(asset2.symbol, 8.35) # Check all total values assert ph.total_book_cost() == 71928.07 assert ph.total_market_value() == 71903.75 assert ph.total_unrealised_gain() == -24.31999999999971 assert ph.total_unrealised_percentage_gain() == -0.03381155646190282
def test_update_book_cost_for_commission_some_commission(): """ Tests that 'update_book_cost_for_commission' calculates book cost correctly for the position when a positive commission is supplied. """ asset = Equity('Apple, Inc.', 'AAPL') position = Position(asset, quantity=100, book_cost_pu=50.0, current_price=50.0) position.update_book_cost_for_commission(asset, 15.0) assert position.book_cost_pu == 50.15 assert position.book_cost == 5015.0
def test_transaction_representation(): """ Tests that the Transaction representation correctly recreates the object. """ dt = pd.Timestamp('2015-05-06') asset = Equity('Apple, Inc.', 'AAPL') transaction = Transaction(asset, quantity=168, dt=dt, price=56.18, order_id=153) exp_repr = ( "Transaction(asset=Equity(name='Apple, Inc.', symbol='AAPL', tax_exempt=True), " "quantity=168, dt=2015-05-06 00:00:00, price=56.18, order_id=153)") assert repr(transaction) == exp_repr
def test_update_market_value_of_asset_negative_price(): """ Test update_market_value_of_asset for asset with negative price. """ start_dt = pd.Timestamp('2017-10-05 08:00:00', tz=pytz.UTC) later_dt = pd.Timestamp('2017-10-06 08:00:00', tz=pytz.UTC) port = Portfolio(start_dt) asset = Equity("Acme Inc.", "AAA", tax_exempt=False) port.subscribe_funds(later_dt, 100000.0) tn_asset = Transaction(asset=asset.symbol, quantity=100, dt=later_dt, price=567.0, order_id=1, commission=15.78) port.transact_asset(tn_asset) with pytest.raises(ValueError): port.update_market_value_of_asset(asset.symbol, -54.34, later_dt)
def test_update_market_value_of_asset_earlier_date(): """ Test update_market_value_of_asset for asset with current_trade_date in past """ start_dt = pd.Timestamp('2017-10-05 08:00:00', tz=pytz.UTC) earlier_dt = pd.Timestamp('2017-10-04 08:00:00', tz=pytz.UTC) later_dt = pd.Timestamp('2017-10-06 08:00:00', tz=pytz.UTC) port = Portfolio(start_dt, portfolio_id='1234') asset = Equity("Acme Inc.", "EQ:AAA", tax_exempt=False) port.subscribe_funds(later_dt, 100000.0) tn_asset = Transaction(asset=asset.symbol, quantity=100, dt=later_dt, price=567.0, order_id=1, commission=15.78) port.transact_asset(tn_asset) with pytest.raises(ValueError): port.update_market_value_of_asset(asset.symbol, 50.23, earlier_dt)
def test_position_short_goes_to_zero(): """ Tests that the quantity and book cost are correctly calculated for an initial short position, where the share value goes to zero. This should be a percentage gain of 100%. """ asset = Equity('Apple, Inc.', 'AAPL') position = Position(asset, quantity=-100, book_cost_pu=50.0, current_price=50.0) dt = pd.Timestamp('2015-05-06') position.current_price = 0.0 position.current_trade_date = dt assert position.quantity == -100 assert position.book_cost_pu == 50.0 assert position.direction == -1.0 assert position.current_price == 0.0 assert position.market_value == 0.0 assert position.unrealised_gain == 5000.0 assert position.unrealised_percentage_gain == 100.0
def test_transact_asset_behaviour(): """ Test transact_asset raises for incorrect time Test transact_asset raises for transaction total cost exceeding total cash Test correct total_cash and total_securities_value for correct transaction (commission etc), correct portfolio event and correct time update """ start_dt = pd.Timestamp('2017-10-05 08:00:00', tz=pytz.UTC) earlier_dt = pd.Timestamp('2017-10-04 08:00:00', tz=pytz.UTC) later_dt = pd.Timestamp('2017-10-06 08:00:00', tz=pytz.UTC) even_later_dt = pd.Timestamp('2017-10-07 08:00:00', tz=pytz.UTC) port = Portfolio(start_dt) asset = Equity("Acme Inc.", "EQ:AAA", tax_exempt=False) # Test transact_asset raises for incorrect time tn_early = Transaction(asset=asset.symbol, quantity=100, dt=earlier_dt, price=567.0, order_id=1, commission=0.0) with pytest.raises(ValueError): port.transact_asset(tn_early) # Test transact_asset raises for transaction total # cost exceeding total cash port.subscribe_funds(later_dt, 1000.0) assert port.total_cash == 1000.0 assert port.total_non_cash_equity == 0.0 assert port.total_equity == 1000.0 pe_sub1 = PortfolioEvent(dt=later_dt, type='subscription', description="SUBSCRIPTION", debit=0.0, credit=1000.0, balance=1000.0) tn_large = Transaction(asset=asset.symbol, quantity=100, dt=later_dt, price=567.0, order_id=1, commission=15.78) with pytest.raises(ValueError): port.transact_asset(tn_large) # Test correct total_cash and total_securities_value # for correct transaction (commission etc), correct # portfolio event and correct time update port.subscribe_funds(even_later_dt, 99000.0) assert port.total_cash == 100000.0 assert port.total_non_cash_equity == 0.0 assert port.total_equity == 100000.0 pe_sub2 = PortfolioEvent(dt=even_later_dt, type='subscription', description="SUBSCRIPTION", debit=0.0, credit=99000.0, balance=100000.0) tn_even_later = Transaction(asset=asset.symbol, quantity=100, dt=even_later_dt, price=567.0, order_id=1, commission=15.78) port.transact_asset(tn_even_later) assert port.total_cash == 43284.22 assert port.total_non_cash_equity == 56700.00 assert port.total_equity == 99984.22 description = "LONG 100 EQ:AAA 567.00 07/10/2017" pe_tn = PortfolioEvent(dt=even_later_dt, type="asset_transaction", description=description, debit=56715.78, credit=0.0, balance=43284.22) assert port.history == [pe_sub1, pe_sub2, pe_tn] assert port.current_dt == even_later_dt
def test_position_three_longs_one_short_one_long(): """ Tests that the quantity and book cost are correctly calculated for three long transactions, followed by a partial closing position, followed by a new long position, all in the same asset. Buy 100 qty at £1.00 -> £100 Buy 100 qty at £2.00 -> £200 Buy 200 qty at £3.00 -> £600 Total qty after 3 longs is 400, with book cost £900 (£2.25 p/u) Sell 100 qty -> Book cost now £675 (25% holdings reduced), still at £2.25 p/u Buy 100 at £4.00 -> 400 Final qty is 400, but book cost is now £1,075 (£2.6875 p/u). """ asset = Equity('Apple, Inc.', 'AAPL') # Initial long position = Position(asset, quantity=100, book_cost_pu=1.0, current_price=1.0) # Second long dt = pd.Timestamp('2015-05-06') transaction = Transaction(asset, quantity=100, dt=dt, price=2.0, order_id=123, commission=None) position.update(transaction) assert position.quantity == 200 assert position.book_cost_pu == 1.5 # Third long dt = pd.Timestamp('2015-05-07') transaction = Transaction(asset, quantity=200, dt=dt, price=3.0, order_id=123, commission=None) position.update(transaction) assert position.quantity == 400 assert position.book_cost_pu == 2.25 # First short dt = pd.Timestamp('2015-05-08') transaction = Transaction(asset, quantity=-100, dt=dt, price=3.5, order_id=123, commission=None) position.update(transaction) assert position.quantity == 300 assert position.book_cost_pu == 2.25 # Final long dt = pd.Timestamp('2015-05-09') transaction = Transaction(asset, quantity=100, dt=dt, price=4.0, order_id=123, commission=None) position.update(transaction) assert position.quantity == 400 assert position.book_cost_pu == 2.6875 assert position.direction == 1.0 assert position.current_price == 4.0 assert position.market_value == 1600.0 assert position.unrealised_gain == 525.0 assert position.unrealised_percentage_gain == 48.837209302325576