Repository: liampauling/flumine
Pull request #238 (2020-07-20 08:38:53)

Dev


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index c18535c..aa4eb4c 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,15 @@
 Release History
 ---------------
 
+1.10.0 (2020-07-20)
++++++++++++++++++++
+
+**Improvements**
+
+- #180 client paper trade now implemented
+- #193 initial work on multi client implementation
+- #192 simulation improvements with handling on runner removal
+
 1.9.3 (2020-07-17)
 +++++++++++++++++++
 
diff --git a/README.md b/README.md
index 5e13f78..c29a21a 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Currently tested on Python 3.6, 3.7 and 3.8.
 $ pip install flumine
 ```
 
-flumine requires Python 3.5+
+flumine requires Python 3.6+
 
 ## setup
 
@@ -112,7 +112,7 @@ framework.run()
 - Streaming
 - Multiple strategies
 - Order execution
-- Paper trading (in development)
+- Paper trading
 - Back testing
 - Analytics (in development)
 - Middleware and background workers to enable Scores / RaceCard / InPlayService
diff --git a/docs/advanced.md b/docs/advanced.md
index a5241ef..622523c 100644
--- a/docs/advanced.md
+++ b/docs/advanced.md
@@ -37,6 +37,7 @@ The Flumine class can be adapted by overriding the following functions:
 - `context` Dictionary object where any extra data can be stored here such as triggers
 - `max_selection_exposure` Max exposure per selection (including new order), note this does __not__ handle reduction in exposure due to laying another runner
 - `max_order_exposure` Max exposure per order
+- `client` Strategy client, half implemented when flumine will be migrated to multi clients
 
 ### Functions
 
diff --git a/docs/index.md b/docs/index.md
index 18b66e6..eb98fba 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -38,7 +38,7 @@ Currently tested on Python 3.6, 3.7 and 3.8.
 $ pip install flumine
 ```
 
-flumine requires Python 3.5+
+flumine requires Python 3.6+
 
 ## setup
 
@@ -129,7 +129,7 @@ framework.run()
 - Streaming
 - Multiple strategies
 - Order execution
-- Paper trading (in development)
+- Paper trading
 - Back testing
 - Analytics (in development)
 - Middleware and background workers to enable Scores / RaceCard / InPlayService
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 1a1155f..03ec30e 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -14,9 +14,7 @@ from flumine import Flumine, clients
 trading = betfairlightweight.APIClient("username")
 client = clients.BetfairClient(trading)
 
-framework = Flumine(
-    client=client,
-)
+framework = Flumine(client=client)
 ```
 
 !!! note
@@ -95,6 +93,31 @@ strategy = ExampleDataStrategy(
 flumine.add_strategy(strategy)
 ```
 
+## Paper Trading
+
+Flumine can be used to paper trade strategies live using the following code:
+
+```python
+import betfairlightweight
+from flumine import Flumine, clients
+
+trading = betfairlightweight.APIClient("username")
+client = clients.BetfairClient(trading, paper_trade=True)
+framework = Flumine(client=client)
+
+strategy = ExampleStrategy(
+    market_filter={"markets": ["/tmp/marketdata/1.170212754"]}
+)
+framework.add_strategy(strategy)
+
+framework.run()
+```
+
+Market data will be recieved as per live but any orders will use Simulated execution and Simulated order polling to replicate live trading.
+
+!!! tip
+    This can be handy when testing strategies as the betfair website can be used to validate the market.
+
 ## Backtesting
 
 Flumine can be used to backtest strategies using the following code:
diff --git a/flumine/__version__.py b/flumine/__version__.py
index 8e7caac..66bae86 100644
--- a/flumine/__version__.py
+++ b/flumine/__version__.py
@@ -1,6 +1,6 @@
 __title__ = "flumine"
 __description__ = "Betfair trading framework"
 __url__ = "https://github.com/liampauling/flumine"
-__version__ = "1.9.3"
+__version__ = "1.10.0"
 __author__ = "Liam Pauling"
 __license__ = "MIT"
diff --git a/flumine/baseflumine.py b/flumine/baseflumine.py
index f9be4b2..06a5582 100644
--- a/flumine/baseflumine.py
+++ b/flumine/baseflumine.py
@@ -11,7 +11,7 @@ from .worker import BackgroundWorker
 from .clients.baseclient import BaseClient
 from .markets.markets import Markets
 from .markets.market import Market
-from .markets.middleware import Middleware
+from .markets.middleware import Middleware, SimulatedMiddleware
 from .execution.betfairexecution import BetfairExecution
 from .execution.simulatedexecution import SimulatedExecution
 from .order.process import process_current_orders
@@ -73,10 +73,11 @@ class BaseFlumine:
     def run(self) -> None:
         raise NotImplementedError
 
-    def add_strategy(self, strategy: BaseStrategy) -> None:
+    def add_strategy(self, strategy: BaseStrategy, client: BaseClient = None) -> None:
         logger.info("Adding strategy {0}".format(strategy))
+        _client = client or self.client
         self.streams(strategy)  # create required streams
-        self.strategies(strategy)  # store in strategies
+        self.strategies(strategy, _client)  # store in strategies
         self.log_control(events.StrategyEvent(strategy))
 
     def add_worker(self, worker: BackgroundWorker) -> None:
@@ -295,6 +296,9 @@ class BaseFlumine:
             config.simulated = True
         else:
             config.simulated = False
+        # middleware
+        if self.BACKTEST or self.client.paper_trade:
+            self.add_market_middleware(SimulatedMiddleware())
         # login
         self.client.login()
         self.client.update_account_details()
diff --git a/flumine/clients/baseclient.py b/flumine/clients/baseclient.py
index 713c176..21fa30b 100644
--- a/flumine/clients/baseclient.py
+++ b/flumine/clients/baseclient.py
@@ -25,6 +25,7 @@ class BaseClient:
         id_: str = None,
         order_stream: bool = True,
         best_price_execution: bool = True,
+        paper_trade: bool = False,
     ):
         self.id = id_ or create_short_uuid()
         self.betting_client = betting_client
@@ -34,6 +35,7 @@ class BaseClient:
         self.interactive_login = interactive_login
         self.order_stream = order_stream
         self.best_price_execution = best_price_execution
+        self.paper_trade = paper_trade
 
         self.account_details = None
         self.account_funds = None
@@ -56,7 +58,7 @@ class BaseClient:
         raise NotImplementedError
 
     def add_execution(self, flumine) -> None:
-        if self.EXCHANGE == ExchangeType.SIMULATED:
+        if self.EXCHANGE == ExchangeType.SIMULATED or self.paper_trade:
             self.execution = flumine.simulated_execution
         elif self.EXCHANGE == ExchangeType.BETFAIR:
             self.execution = flumine.betfair_execution
@@ -83,4 +85,5 @@ class BaseClient:
             "trading_controls": self.trading_controls,
             "order_stream": self.order_stream,
             "best_price_execution": self.best_price_execution,
+            "paper_trade": self.paper_trade,
         }
diff --git a/flumine/execution/simulatedexecution.py b/flumine/execution/simulatedexecution.py
index b4fb519..eb478c5 100644
--- a/flumine/execution/simulatedexecution.py
+++ b/flumine/execution/simulatedexecution.py
@@ -1,3 +1,4 @@
+import time
 import requests
 from typing import Optional
 
@@ -28,16 +29,16 @@ class SimulatedExecution(BaseExecution):
         else:
             raise NotImplementedError()
 
-        # todo if order_package.client.paper_trade:
-        #     self._thread_pool.submit(func, order_package, None)
-        # else:
-        func(order_package, http_session=None)
+        if order_package.client.paper_trade:
+            self._thread_pool.submit(func, order_package, None)
+        else:
+            func(order_package, http_session=None)
 
     def execute_place(
         self, order_package, http_session: Optional[requests.Session]
     ) -> None:
-        # todo if order_package.client.paper_trade:
-        #     time.sleep(order_package.bet_delay + self.PLACE_LATENCY)
+        if order_package.client.paper_trade:
+            time.sleep(order_package.bet_delay + self.PLACE_LATENCY)
         market_book = order_package.market.market_book
         for order, instruction in zip(order_package, order_package.place_instructions):
             with order.trade:
@@ -56,8 +57,8 @@ class SimulatedExecution(BaseExecution):
     def execute_cancel(
         self, order_package, http_session: Optional[requests.Session]
     ) -> None:
-        # todo if order_package.client.paper_trade:
-        #     time.sleep(self.CANCEL_LATENCY)
+        if order_package.client.paper_trade:
+            time.sleep(self.CANCEL_LATENCY)
         for order in order_package:
             with order.trade:
                 simulated_response = order.simulated.cancel()
@@ -72,8 +73,8 @@ class SimulatedExecution(BaseExecution):
     def execute_update(
         self, order_package, http_session: Optional[requests.Session]
     ) -> None:
-        # todo if order_package.client.paper_trade:
-        #     time.sleep(self.UPDATE_LATENCY)
+        if order_package.client.paper_trade:
+            time.sleep(self.UPDATE_LATENCY)
         for order, instruction in zip(order_package, order_package.update_instructions):
             with order.trade:
                 simulated_response = order.simulated.update(instruction)
@@ -88,8 +89,10 @@ class SimulatedExecution(BaseExecution):
     def execute_replace(
         self, order_package, http_session: Optional[requests.Session]
     ) -> None:
-        # todo if order_package.client.paper_trade:
-        #     time.sleep(self.REPLACE_LATENCY)
+        if (
+            order_package.client.paper_trade
+        ):  # todo should the cancel happen without a delay?
+            time.sleep(order_package.bet_delay + self.REPLACE_LATENCY)
         market_book = order_package.market.market_book
         for order, instruction in zip(
             order_package, order_package.replace_instructions
diff --git a/flumine/markets/middleware.py b/flumine/markets/middleware.py
index 5e27c41..f1ffe9f 100644
--- a/flumine/markets/middleware.py
+++ b/flumine/markets/middleware.py
@@ -3,10 +3,13 @@ from collections import defaultdict
 from betfairlightweight.resources.bettingresources import RunnerBook
 
 from ..order.order import OrderStatus
-from ..utils import get_price
+from ..utils import get_price, wap
 
 logger = logging.getLogger(__name__)
 
+WIN_MINIMUM_ADJUSTMENT_FACTOR = 2.5
+PLACE_MINIMUM_ADJUSTMENT_FACTOR = 0  # todo implement correctly (https://en-betfair.custhelp.com/app/answers/detail/a_id/406)
+
 
 class Middleware:
     def __call__(self, market) -> None:
@@ -23,19 +26,38 @@ class SimulatedMiddleware(Middleware):
     """
     Calculates matched amounts per runner
     to be used in simulated matching.
-    # todo runner removal fucks everything
     # todo currency fluctuations fucks everything
     """
 
     def __init__(self):
         # {marketId: {(selectionId, handicap): RunnerAnalytics}}
         self.markets = defaultdict(dict)
+        self._runner_removals = []
 
     def __call__(self, market) -> None:
         market_analytics = self.markets[market.market_id]
+        runner_removals = []  # [(selectionId, handicap, adjustmentFactor)..]
         for runner in market.market_book.runners:
             if runner.status == "ACTIVE":
                 self._process_runner(market_analytics, runner)
+            elif runner.status == "REMOVED":
+                _removal = (
+                    runner.selection_id,
+                    runner.handicap,
+                    runner.adjustment_factor,
+                )
+                if _removal not in self._runner_removals:
+                    logger.warning(
+                        "Runner {0} ({2}) removed from market {3}".format(
+                            *_removal, market.market_id
+                        )
+                    )
+                    self._runner_removals.append(_removal)
+                    runner_removals.append(_removal)
+
+        for _removal in runner_removals:
+            self._process_runner_removal(market, *_removal)
+
         market.context["simulated"] = market_analytics
         # process simulated orders
         self._process_simulated_orders(market, market_analytics)
@@ -46,6 +68,50 @@ class SimulatedMiddleware(Middleware):
         except KeyError:
             pass
 
+    def _process_runner_removal(
+        self,
+        market,
+        removal_selection_id: int,
+        removal_handicap: int,
+        removal_adjustment_factor: float,
+    ) -> None:
+        for order in market.blotter:
+            if order.simulated:
+                if order.lookup == (
+                    market.market_id,
+                    removal_selection_id,
+                    removal_handicap,
+                ):
+                    # cancel and void order
+                    order.simulated.size_matched = 0
+                    order.simulated.average_price_matched = 0
+                    order.simulated.matched = []
+                    order.simulated.size_voided = order.order_type.size
+                    logger.warning(
+                        "Order voided on non runner {0}".format(order.selection_id),
+                        extra=order.info,
+                    )
+                else:
+                    if order.status == OrderStatus.EXECUTABLE:
+                        # todo cancel if not PERSIST
+                        # todo does a market version bump occur if withdrawal is below the limit?
+                        pass
+                    if removal_adjustment_factor >= WIN_MINIMUM_ADJUSTMENT_FACTOR:
+                        # todo place market
+                        for match in order.simulated.matched:
+                            match[1] = round(
+                                match[1] * (1 - (removal_adjustment_factor / 100)), 2
+                            )
+                        _, order.simulated.average_price_matched = wap(
+                            order.simulated.matched
+                        )
+                        logger.warning(
+                            "Order adjusted due to non runner {0}".format(
+                                order.selection_id
+                            ).format(order.selection_id),
+                            extra=order.info,
+                        )
+
     @staticmethod
     def _process_simulated_orders(market, market_analytics: dict) -> None:
         for order in market.blotter.live_orders:
diff --git a/flumine/order/trade.py b/flumine/order/trade.py
index 4fcb151..b86f025 100644
--- a/flumine/order/trade.py
+++ b/flumine/order/trade.py
@@ -134,6 +134,11 @@ class Trade:
         self.orders.append(order)
         return order
 
+    @property
+    def client(self):
+        # 193 todo trade.client
+        return self.strategy.client
+
     @property
     def notes_str(self) -> str:
         return ",".join(str(x) for x in self.notes.values())
diff --git a/flumine/strategy/strategy.py b/flumine/strategy/strategy.py
index d38166d..199bbeb 100644
--- a/flumine/strategy/strategy.py
+++ b/flumine/strategy/strategy.py
@@ -6,6 +6,7 @@ from ..streams.marketstream import BaseStream, MarketStream
 from ..markets.market import Market
 from .runnercontext import RunnerContext
 from ..utils import create_cheap_hash
+from ..clients import BaseClient
 
 DEFAULT_MARKET_DATA_FILTER = filters.streaming_market_data_filter(
     fields=[
@@ -32,6 +33,7 @@ class BaseStrategy:
         context: dict = None,
         max_selection_exposure: float = 100,
         max_order_exposure: float = 10,
+        client: BaseClient = None,
     ):
         """
         Processes data from streams.
@@ -55,6 +57,7 @@ class BaseStrategy:
         self.context = context or {}
         self.max_selection_exposure = max_selection_exposure
         self.max_order_exposure = max_order_exposure
+        self.client = client
 
         self._invested = {}  # {(marketId, selectionId, handicap): RunnerContext}
         self.streams = []  # list of streams strategy is subscribed
@@ -172,7 +175,8 @@ class Strategies:
     def __init__(self):
         self._strategies = []
 
-    def __call__(self, strategy: BaseStrategy) -> None:
+    def __call__(self, strategy: BaseStrategy, client: BaseClient) -> None:
+        strategy.client = client
         self._strategies.append(strategy)
         strategy.add()
 
diff --git a/flumine/streams/simulatedorderstream.py b/flumine/streams/simulatedorderstream.py
new file mode 100644
index 0000000..3ed6798
--- /dev/null
+++ b/flumine/streams/simulatedorderstream.py
@@ -0,0 +1,37 @@
+import time
+import logging
+
+from .basestream import BaseStream
+from ..events.events import CurrentOrdersEvent
+
+logger = logging.getLogger(__name__)
+
+
+class CurrentOrders:
+    def __init__(self, orders):
+        self.orders = orders
+        self.more_available = False
+
+
+class SimulatedOrderStream(BaseStream):
+    def run(self) -> None:
+        logger.info("Starting SimulatedOrderStream")
+
+        while self.is_alive():
+            if self.flumine.markets.live_orders:
+                current_orders = self._get_current_orders()
+                if current_orders:
+                    self.flumine.handler_queue.put(
+                        CurrentOrdersEvent([CurrentOrders(current_orders)])
+                    )
+            time.sleep(self.streaming_timeout)
+        logger.info("Stopped SimulatedOrderStream {0}".format(self.stream_id))
+
+    def _get_current_orders(self) -> list:
+        current_orders = []
+        for market in self.flumine.markets:
+            if market.closed is False:
+                for order in market.blotter:
+                    if order.simulated and order.trade.client == self.client:
+                        current_orders.append(order)
+        return current_orders
diff --git a/flumine/streams/streams.py b/flumine/streams/streams.py
index c538c63..5f0f20c 100644
--- a/flumine/streams/streams.py
+++ b/flumine/streams/streams.py
@@ -6,6 +6,7 @@ from .marketstream import MarketStream
 from .datastream import DataStream
 from .historicalstream import HistoricalStream
 from .orderstream import OrderStream
+from .simulatedorderstream import SimulatedOrderStream
 from ..clients import ExchangeType, BaseClient
 
 logger = logging.getLogger(__name__)
@@ -35,8 +36,9 @@ class Streams:
 
     def add_client(self, client: BaseClient) -> None:
         if client.order_stream:
-            # todo if paper_trade: add_simulated_order_stream()
-            if client.EXCHANGE == ExchangeType.BETFAIR:
+            if client.paper_trade:
+                self.add_simulated_order_stream(client)
+            elif client.EXCHANGE == ExchangeType.BETFAIR:
                 self.add_order_stream(client)
 
     """ market data """
@@ -119,8 +121,25 @@ class Streams:
         self._streams.append(stream)
         return stream
 
-    def add_simulated_order_stream(self):  # todo
-        return
+    def add_simulated_order_stream(
+        self,
+        client: BaseClient,
+        conflate_ms: int = None,
+        streaming_timeout: float = 0.25,
+    ) -> SimulatedOrderStream:
+        logger.warning(
+            "Client {0} now paper trading".format(client.betting_client.username)
+        )
+        stream_id = self._increment_stream_id()
+        stream = SimulatedOrderStream(
+            flumine=self.flumine,
+            stream_id=stream_id,
+            conflate_ms=conflate_ms,
+            streaming_timeout=streaming_timeout,
+            client=client,
+        )
+        self._streams.append(stream)
+        return stream
 
     def start(self) -> None:
         if not self.flumine.BACKTEST:


Test patch:
diff --git a/flumine/backtest/backtest.py b/flumine/backtest/backtest.py
index 5aa592f..1a8e4f7 100644
--- a/flumine/backtest/backtest.py
+++ b/flumine/backtest/backtest.py
@@ -9,7 +9,6 @@ from ..exceptions import RunError
 from .utils import PendingPackages
 from ..order.orderpackage import OrderPackageType
 from ..order import process
-from ..markets.middleware import SimulatedMiddleware
 
 logger = logging.getLogger(__name__)
 
@@ -26,7 +25,6 @@ class FlumineBacktest(BaseFlumine):
     def __init__(self, client):
         super(FlumineBacktest, self).__init__(client)
         self._pending_packages = PendingPackages()
-        self._market_middleware.append(SimulatedMiddleware())
 
     def run(self) -> None:
         if self.client.EXCHANGE != ExchangeType.SIMULATED:
diff --git a/flumine/backtest/simulated.py b/flumine/backtest/simulated.py
index f5e84c5..3d66803 100644
--- a/flumine/backtest/simulated.py
+++ b/flumine/backtest/simulated.py
@@ -1,5 +1,6 @@
+import logging
 import datetime
-from typing import Tuple
+from typing import List
 from betfairlightweight.resources.bettingresources import MarketBook, RunnerBook
 
 from .utils import (
@@ -11,6 +12,8 @@ from ..utils import get_price, wap
 from ..order.ordertype import OrderTypes
 from .. import config
 
+logger = logging.getLogger(__name__)
+
 
 class Simulated:
     """
@@ -22,7 +25,7 @@ class Simulated:
         self.order = order
         self.size_matched = 0
         self.average_price_matched = 0
-        self.matched = []  # [(publishTime, price, size)..]
+        self.matched = []  # [[publishTime, price, size]..]
         self.size_cancelled = 0.0
         self.size_lapsed = 0.0
         self.size_voided = 0.0
@@ -96,6 +99,9 @@ class Simulated:
                     self._piq = avail["size"]
                     break
 
+            logger.debug(
+                "Simulated order {0} PIQ: {1}".format(self.order.id, self._piq)
+            )
             return self._create_place_response(bet_id)
         else:
             return self._create_place_response(bet_id)
@@ -166,7 +172,7 @@ class Simulated:
                     _size_matched = _size_remaining
                 else:
                     _size_matched = avail["size"]
-                _matched = (publish_time, avail["price"], round(_size_matched, 2))
+                _matched = [publish_time, avail["price"], round(_size_matched, 2)]
                 self._update_matched(_matched)
             else:
                 break
@@ -195,12 +201,17 @@ class Simulated:
                     size = round(_order_type.liability / (actual_sp - 1), 2)
             else:
                 raise NotImplementedError()
-            self._update_matched((publish_time, actual_sp, size))
+            self._update_matched([publish_time, actual_sp, size])
 
     def _process_traded(self, publish_time: int, traded: dict) -> None:
         # calculate matched on MarketBook update
         price = self.order.order_type.price
         for traded_price, traded_size in traded.items():
+            logger.debug(
+                "Simulated order {0} traded: {1} - {2}".format(
+                    self.order.id, traded_price, traded_size
+                )
+            )
             if self.side == "BACK" and traded_price >= price:
                 self._calculate_process_traded(publish_time, traded_size)
             elif self.side == "LAY" and traded_price <= price:
@@ -213,15 +224,18 @@ class Simulated:
             size = round(min(self.size_remaining, size), 2)
             if size:
                 self._update_matched(
-                    (
+                    [
                         publish_time,
                         self.order.order_type.price,
                         size,
-                    )  # todo takes the worst price, i.e what was asked
+                    ]  # todo takes the worst price, i.e what was asked
                 )
             self._piq = 0
         else:
             self._piq -= traded_size
+            logger.debug(
+                "Simulated order {0} PIQ: {1}".format(self.order.id, self._piq)
+            )
 
     @property
     def take_sp(self) -> bool:
@@ -236,7 +250,8 @@ class Simulated:
     def side(self) -> str:
         return self.order.side
 
-    def _update_matched(self, data: Tuple[int, float, float]) -> None:
+    def _update_matched(self, data: List) -> None:
+        logger.debug("Simulated order {0} matched: {1}".format(self.order.id, data))
         self.matched.append(data)
         self.size_matched, self.average_price_matched = wap(self.matched)
 
@@ -270,4 +285,4 @@ class Simulated:
             return 0.0
 
     def __bool__(self):
-        return config.simulated
+        return config.simulated or self.order.trade.client.paper_trade
diff --git a/tests/test_baseflumine.py b/tests/test_baseflumine.py
index 0bc16e2..c875fe5 100644
--- a/tests/test_baseflumine.py
+++ b/tests/test_baseflumine.py
@@ -6,7 +6,7 @@ from flumine.baseflumine import BaseFlumine
 
 class BaseFlumineTest(unittest.TestCase):
     def setUp(self):
-        self.mock_client = mock.Mock()
+        self.mock_client = mock.Mock(paper_trade=False)
         self.base_flumine = BaseFlumine(self.mock_client)
 
     def test_init(self):
@@ -26,7 +26,8 @@ class BaseFlumineTest(unittest.TestCase):
     @mock.patch("flumine.baseflumine.BaseFlumine.log_control")
     def test_add_strategy(self, mock_log_control, mock_events):
         mock_strategy = mock.Mock()
-        self.base_flumine.add_strategy(mock_strategy)
+        mock_client = mock.Mock()
+        self.base_flumine.add_strategy(mock_strategy, mock_client)
         self.assertEqual(len(self.base_flumine.strategies), 1)
         self.assertEqual(len(self.base_flumine.streams), 1)
         mock_log_control.assert_called_with(mock_events.StrategyEvent(mock_strategy))
diff --git a/tests/test_clients.py b/tests/test_clients.py
index c0adbfd..059efe6 100644
--- a/tests/test_clients.py
+++ b/tests/test_clients.py
@@ -29,6 +29,7 @@ class BaseClientTest(unittest.TestCase):
         self.assertEqual(self.base_client.trading_controls, [])
         self.assertTrue(self.base_client.order_stream)
         self.assertTrue(self.base_client.best_price_execution)
+        self.assertFalse(self.base_client.paper_trade)
 
     def test_login(self):
         with self.assertRaises(NotImplementedError):
@@ -55,6 +56,13 @@ class BaseClientTest(unittest.TestCase):
         self.base_client.add_execution(mock_flumine)
         self.assertEqual(self.base_client.execution, mock_flumine.betfair_execution)
 
+    def test_add_execution_paper(self):
+        self.base_client.paper_trade = True
+        self.base_client.EXCHANGE = ExchangeType.BETFAIR
+        mock_flumine = mock.Mock()
+        self.base_client.add_execution(mock_flumine)
+        self.assertEqual(self.base_client.execution, mock_flumine.simulated_execution)
+
     def test_min_bet_size(self):
         with self.assertRaises(NotImplementedError):
             assert self.base_client.min_bet_size
diff --git a/tests/test_execution.py b/tests/test_execution.py
index 32ed8bb..d956b5a 100644
--- a/tests/test_execution.py
+++ b/tests/test_execution.py
@@ -726,9 +726,22 @@ class SimulatedExecutionTest(unittest.TestCase):
     def test_init(self):
         self.assertEqual(self.execution.EXCHANGE, ExchangeType.SIMULATED)
 
+    @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution.execute_place")
+    def test_handler_paper_trade(self, mock_execute_place):
+        mock_thread_pool = mock.Mock()
+        self.execution._thread_pool = mock_thread_pool
+        mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = True
+        mock_order_package.package_type = OrderPackageType.PLACE
+        self.execution.handler(mock_order_package)
+        mock_thread_pool.submit.assert_called_with(
+            mock_execute_place, mock_order_package, None
+        )
+
     @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution.execute_place")
     def test_handler_place(self, mock_execute_place):
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.package_type = OrderPackageType.PLACE
         self.execution.handler(mock_order_package)
         mock_execute_place.assert_called_with(mock_order_package, http_session=None)
@@ -738,6 +751,7 @@ class SimulatedExecutionTest(unittest.TestCase):
     )
     def test_handler_cancel(self, mock_execute_cancel):
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.package_type = OrderPackageType.PLACE.CANCEL
         self.execution.handler(mock_order_package)
         mock_execute_cancel.assert_called_with(mock_order_package, http_session=None)
@@ -747,6 +761,7 @@ class SimulatedExecutionTest(unittest.TestCase):
     )
     def test_handler_update(self, mock_execute_update):
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.package_type = OrderPackageType.UPDATE
         self.execution.handler(mock_order_package)
         mock_execute_update.assert_called_with(mock_order_package, http_session=None)
@@ -756,6 +771,7 @@ class SimulatedExecutionTest(unittest.TestCase):
     )
     def test_handler_replace(self, mock_execute_replace):
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.package_type = OrderPackageType.REPLACE
         self.execution.handler(mock_order_package)
         mock_execute_replace.assert_called_with(mock_order_package, http_session=None)
@@ -766,6 +782,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_order_package.place_instructions = [1]
         mock_order_package.info = {}
@@ -792,6 +809,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_order_package.place_instructions = [1]
         mock_order_package.info = {}
@@ -812,12 +830,21 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order.trade.__enter__.assert_called_with()
         mock_order.trade.__exit__.assert_called_with(None, None, None)
 
+    @mock.patch("flumine.execution.simulatedexecution.time")
+    def test_execute_place_paper_trade(self, mock_time):
+        mock_order_package = mock.Mock(place_instructions=[], bet_delay=1)
+        mock_order_package.__iter__ = mock.Mock(return_value=iter([]))
+        mock_order_package.client.paper_trade = True
+        self.execution.execute_place(mock_order_package, None)
+        mock_time.sleep.assert_called_with(self.execution.PLACE_LATENCY + 1)
+
     @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution._order_logger")
     def test_execute_cancel(self, mock__order_logger):
         mock_order = mock.Mock()
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_sim_resp = mock.Mock()
         mock_sim_resp.status = "SUCCESS"
@@ -837,6 +864,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_sim_resp = mock.Mock()
         mock_sim_resp.status = "FAILURE"
@@ -850,12 +878,21 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order.trade.__enter__.assert_called_with()
         mock_order.trade.__exit__.assert_called_with(None, None, None)
 
+    @mock.patch("flumine.execution.simulatedexecution.time")
+    def test_execute_cancel_paper_trade(self, mock_time):
+        mock_order_package = mock.Mock(bet_delay=1)
+        mock_order_package.__iter__ = mock.Mock(return_value=iter([]))
+        mock_order_package.client.paper_trade = True
+        self.execution.execute_cancel(mock_order_package, None)
+        mock_time.sleep.assert_called_with(self.execution.CANCEL_LATENCY)
+
     @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution._order_logger")
     def test_execute_update(self, mock__order_logger):
         mock_order = mock.Mock()
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_order_package.update_instructions = ["PERSIST"]
         mock_order_package.info = {}
@@ -877,6 +914,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_order_package.update_instructions = ["PERSIST"]
         mock_order_package.info = {}
@@ -892,6 +930,14 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order.trade.__enter__.assert_called_with()
         mock_order.trade.__exit__.assert_called_with(None, None, None)
 
+    @mock.patch("flumine.execution.simulatedexecution.time")
+    def test_execute_update_paper_trade(self, mock_time):
+        mock_order_package = mock.Mock(update_instructions=[], bet_delay=1)
+        mock_order_package.__iter__ = mock.Mock(return_value=iter([]))
+        mock_order_package.client.paper_trade = True
+        self.execution.execute_update(mock_order_package, None)
+        mock_time.sleep.assert_called_with(self.execution.UPDATE_LATENCY)
+
     @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution._order_logger")
     def test_execute_replace(self, mock__order_logger):
         mock_market = mock.Mock()
@@ -901,6 +947,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order.trade.__exit__ = mock.Mock()
         mock_order.market_id = "1.234"
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_order_package.replace_instructions = [{"newPrice": 2.03}]
         mock_replacement_order = mock.Mock()
@@ -942,6 +989,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
         mock_order_package = mock.Mock()
+        mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_order_package.replace_instructions = [{"newPrice": 2.54}]
         mock_replacement_order = mock.Mock()
@@ -969,3 +1017,11 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_replacement_order.executable.assert_not_called()
         mock_order.trade.__enter__.assert_called_with()
         mock_order.trade.__exit__.assert_called_with(None, None, None)
+
+    @mock.patch("flumine.execution.simulatedexecution.time")
+    def test_execute_replace_paper_trade(self, mock_time):
+        mock_order_package = mock.Mock(replace_instructions=[], bet_delay=1)
+        mock_order_package.__iter__ = mock.Mock(return_value=iter([]))
+        mock_order_package.client.paper_trade = True
+        self.execution.execute_replace(mock_order_package, None)
+        mock_time.sleep.assert_called_with(self.execution.REPLACE_LATENCY + 1)
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index 1efb229..4dece69 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -6,6 +6,8 @@ from flumine.markets.middleware import (
     SimulatedMiddleware,
     RunnerAnalytics,
     OrderStatus,
+    WIN_MINIMUM_ADJUSTMENT_FACTOR,
+    PLACE_MINIMUM_ADJUSTMENT_FACTOR,
 )
 
 
@@ -32,6 +34,9 @@ class SimulatedMiddlewareTest(unittest.TestCase):
 
     def test_init(self):
         self.assertEqual(self.middleware.markets, {})
+        self.assertEqual(self.middleware._runner_removals, [])
+        self.assertEqual(WIN_MINIMUM_ADJUSTMENT_FACTOR, 2.5)
+        self.assertEqual(PLACE_MINIMUM_ADJUSTMENT_FACTOR, 0)
 
     @mock.patch(
         "flumine.markets.middleware.SimulatedMiddleware._process_simulated_orders"
@@ -40,8 +45,7 @@ class SimulatedMiddlewareTest(unittest.TestCase):
     def test_call(self, mock__process_runner, mock__process_simulated_orders):
         mock_market = mock.Mock(context={})
         mock_market_book = mock.Mock()
-        mock_runner = mock.Mock()
-        mock_runner.status = "ACTIVE"
+        mock_runner = mock.Mock(status="ACTIVE")
         mock_market_book.runners = [mock_runner]
         mock_market.market_book = mock_market_book
         self.middleware(mock_market)
@@ -49,6 +53,38 @@ class SimulatedMiddlewareTest(unittest.TestCase):
         self.assertEqual(mock_market.context, {"simulated": {}})
         mock__process_simulated_orders.assert_called_with(mock_market, {})
 
+    @mock.patch(
+        "flumine.markets.middleware.SimulatedMiddleware._process_simulated_orders"
+    )
+    @mock.patch(
+        "flumine.markets.middleware.SimulatedMiddleware._process_runner_removal"
+    )
+    def test_call_non_runner(
+        self, mock__process_runner_removal, mock__process_simulated_orders
+    ):
+        mock_market = mock.Mock(context={})
+        mock_market_book = mock.Mock()
+        mock_runner = mock.Mock(status="REMOVED")
+        mock_market_book.runners = [mock_runner]
+        mock_market.market_book = mock_market_book
+        self.middleware(mock_market)
+        self.assertEqual(
+            self.middleware._runner_removals,
+            [
+                (
+                    mock_runner.selection_id,
+                    mock_runner.handicap,
+                    mock_runner.adjustment_factor,
+                )
+            ],
+        )
+        mock__process_runner_removal.assert_called_with(
+            mock_market,
+            mock_runner.selection_id,
+            mock_runner.handicap,
+            mock_runner.adjustment_factor,
+        )
+
     def test_remove_market(self):
         mock_market = mock.Mock(market_id="1.23")
         self.middleware.markets = {mock_market.market_id: []}
@@ -56,6 +92,41 @@ class SimulatedMiddlewareTest(unittest.TestCase):
         self.middleware.remove_market(mock_market)
         self.assertEqual(self.middleware.markets, {})
 
+    def test__process_runner_removal(self):
+        mock_simulated = mock.MagicMock(matched=[[123, 8.6, 10]])
+        mock_simulated.__bool__.return_value = True
+        mock_order = mock.Mock(simulated=mock_simulated, info={})
+        mock_simulated_two = mock.MagicMock(matched=[[123, 8.6, 10]])
+        mock_simulated_two.__bool__.return_value = False
+        mock_order_two = mock.Mock(simulated=mock_simulated_two, info={})
+        mock_market = mock.Mock(blotter=[mock_order, mock_order_two])
+        self.middleware._process_runner_removal(mock_market, 12345, 0, 16.2)
+        self.assertEqual(mock_order.simulated.matched, [[123, 7.21, 10]])
+        self.assertEqual(mock_order.simulated.average_price_matched, 7.21)
+        self.assertEqual(mock_order_two.simulated.matched, [[123, 8.6, 10]])
+
+    def test__process_runner_removal_under_limit(self):
+        mock_simulated = mock.MagicMock(matched=[[123, 8.6, 10]])
+        mock_simulated.__bool__.return_value = True
+        mock_order = mock.Mock(simulated=mock_simulated)
+        mock_market = mock.Mock(blotter=[mock_order])
+        self.middleware._process_runner_removal(mock_market, 12345, 0, 2.4)
+        self.assertEqual(mock_order.simulated.matched, [[123, 8.6, 10]])
+
+    def test__process_runner_removal_void(self):
+        mock_simulated = mock.MagicMock(matched=[[123, 8.6, 10]])
+        mock_simulated.__bool__.return_value = True
+        mock_order = mock.Mock(
+            lookup=("1.23", 12345, 0), simulated=mock_simulated, info={}
+        )
+        mock_order.order_type.size = 10
+        mock_market = mock.Mock(market_id="1.23", blotter=[mock_order])
+        self.middleware._process_runner_removal(mock_market, 12345, 0, 16.2)
+        self.assertEqual(mock_order.simulated.size_matched, 0)
+        self.assertEqual(mock_order.simulated.average_price_matched, 0)
+        self.assertEqual(mock_order.simulated.matched, [])
+        self.assertEqual(mock_order.simulated.size_voided, 10)
+
     def test__process_simulated_orders(self):
         mock_market_book = mock.Mock()
         mock_order = mock.Mock()
diff --git a/tests/test_order.py b/tests/test_order.py
index 0b3e181..17b2977 100644
--- a/tests/test_order.py
+++ b/tests/test_order.py
@@ -14,7 +14,8 @@ from flumine.exceptions import OrderUpdateError
 
 class BaseOrderTest(unittest.TestCase):
     def setUp(self) -> None:
-        self.mock_trade = mock.Mock()
+        mock_client = mock.Mock(paper_trade=False)
+        self.mock_trade = mock.Mock(client=mock_client)
         self.mock_order_type = mock.Mock()
         self.order = BaseOrder(self.mock_trade, "BACK", self.mock_order_type, 1)
 
@@ -230,7 +231,8 @@ class BaseOrderTest(unittest.TestCase):
 
 class BetfairOrderTest(unittest.TestCase):
     def setUp(self) -> None:
-        self.mock_trade = mock.Mock()
+        mock_client = mock.Mock(paper_trade=False)
+        self.mock_trade = mock.Mock(client=mock_client)
         self.mock_status = mock.Mock()
         self.mock_order_type = mock.Mock()
         self.order = BetfairOrder(self.mock_trade, "BACK", self.mock_order_type)
diff --git a/tests/test_simulated.py b/tests/test_simulated.py
index 464ac6b..0d6c7cb 100644
--- a/tests/test_simulated.py
+++ b/tests/test_simulated.py
@@ -10,8 +10,14 @@ class SimulatedTest(unittest.TestCase):
         self.mock_order_type = mock.Mock(
             price=12, size=2.00, ORDER_TYPE=OrderTypes.LIMIT
         )
+        mock_client = mock.Mock(paper_trade=False)
+        mock_trade = mock.Mock(client=mock_client)
         self.mock_order = mock.Mock(
-            selection_id=1234, handicap=1, side="BACK", order_type=self.mock_order_type
+            selection_id=1234,
+            handicap=1,
+            side="BACK",
+            order_type=self.mock_order_type,
+            trade=mock_trade,
         )
         self.simulated = simulated.Simulated(self.mock_order)
 
@@ -88,7 +94,7 @@ class SimulatedTest(unittest.TestCase):
         self.assertEqual(resp.average_price_matched, 12)
         self.assertEqual(resp.size_matched, 2)
         self.assertEqual(
-            self.simulated.matched, [(mock_market_book.publish_time_epoch, 12, 2)]
+            self.simulated.matched, [[mock_market_book.publish_time_epoch, 12, 2]]
         )
 
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
@@ -136,7 +142,7 @@ class SimulatedTest(unittest.TestCase):
         self.assertEqual(resp.average_price_matched, 12)
         self.assertEqual(resp.size_matched, 2)
         self.assertEqual(
-            self.simulated.matched, [(mock_market_book.publish_time_epoch, 12, 2)]
+            self.simulated.matched, [[mock_market_book.publish_time_epoch, 12, 2]]
         )
 
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
@@ -230,22 +236,22 @@ class SimulatedTest(unittest.TestCase):
         self.simulated._process_price_matched(
             1234567, 12.0, 2.00, [{"price": 15, "size": 120}]
         )
-        self.assertEqual(self.simulated.matched, [(1234567, 15, 2)])
+        self.assertEqual(self.simulated.matched, [[1234567, 15, 2]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234568, 12.0, 2.00, [{"price": 15, "size": 1}, {"price": 12, "size": 1}]
         )
-        self.assertEqual(self.simulated.matched, [(1234568, 15, 1), (1234568, 12, 1)])
+        self.assertEqual(self.simulated.matched, [[1234568, 15, 1], [1234568, 12, 1]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234569, 12.0, 2.00, [{"price": 15, "size": 1}, {"price": 12, "size": 0.5}]
         )
-        self.assertEqual(self.simulated.matched, [(1234569, 15, 1), (1234569, 12, 0.5)])
+        self.assertEqual(self.simulated.matched, [[1234569, 15, 1], [1234569, 12, 0.5]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234570, 12.0, 2.00, [{"price": 15, "size": 1}, {"price": 11, "size": 0.5}]
         )
-        self.assertEqual(self.simulated.matched, [(1234570, 15, 1)])
+        self.assertEqual(self.simulated.matched, [[1234570, 15, 1]])
 
     @mock.patch(
         "flumine.backtest.simulated.Simulated.side",
@@ -256,12 +262,12 @@ class SimulatedTest(unittest.TestCase):
         self.simulated._process_price_matched(
             1234571, 3.0, 20.00, [{"price": 2.02, "size": 120}]
         )
-        self.assertEqual(self.simulated.matched, [(1234571, 2.02, 20)])
+        self.assertEqual(self.simulated.matched, [[1234571, 2.02, 20]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234571, 3.0, 20.00, [{"price": 2.02, "size": 1}, {"price": 3, "size": 20}]
         )
-        self.assertEqual(self.simulated.matched, [(1234571, 2.02, 1), (1234571, 3, 19)])
+        self.assertEqual(self.simulated.matched, [[1234571, 2.02, 1], [1234571, 3, 19]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234571,
@@ -270,19 +276,19 @@ class SimulatedTest(unittest.TestCase):
             [{"price": 2.02, "size": 1}, {"price": 2.9, "size": 0.5}],
         )
         self.assertEqual(
-            self.simulated.matched, [(1234571, 2.02, 1), (1234571, 2.9, 0.5)]
+            self.simulated.matched, [[1234571, 2.02, 1], [1234571, 2.9, 0.5]]
         )
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234571, 3.0, 20.00, [{"price": 3, "size": 1}, {"price": 11, "size": 0.5}]
         )
-        self.assertEqual(self.simulated.matched, [(1234571, 3, 1)])
+        self.assertEqual(self.simulated.matched, [[1234571, 3, 1]])
 
     def test__process_sp(self):
         mock_runner = mock.Mock()
         mock_runner.sp.actual_sp = 12.20
         self.simulated._process_sp(1234571, mock_runner)
-        self.assertEqual(self.simulated.matched, [(1234571, 12.2, 2.00)])
+        self.assertEqual(self.simulated.matched, [[1234571, 12.2, 2.00]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_none(self):
@@ -295,12 +301,12 @@ class SimulatedTest(unittest.TestCase):
     def test__process_sp_processed_semi(self):
         mock_runner = mock.Mock()
         mock_runner.sp.actual_sp = 12.20
-        self.simulated.matched = [(1234571, 10.0, 1)]
+        self.simulated.matched = [[1234571, 10.0, 1]]
         self.simulated.size_matched = 1
         self.simulated.average_price_matched = 10.0
         self.simulated._process_sp(1234572, mock_runner)
         self.assertEqual(
-            self.simulated.matched, [(1234571, 10.0, 1), (1234572, 12.2, 1)]
+            self.simulated.matched, [[1234571, 10.0, 1], [1234572, 12.2, 1]]
         )
         self.assertTrue(self.simulated._bsp_reconciled)
 
@@ -311,7 +317,7 @@ class SimulatedTest(unittest.TestCase):
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
         self.simulated._process_sp(1234573, mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(1234573, 69, 2.00)])
+        self.assertEqual(self.simulated.matched, [[1234573, 69, 2.00]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_limit_on_close_back_no_match(self):
@@ -332,7 +338,7 @@ class SimulatedTest(unittest.TestCase):
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
         self.simulated._process_sp(1234575, mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(1234575, 69, 0.03)])
+        self.assertEqual(self.simulated.matched, [[1234575, 69, 0.03]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_limit_on_close_lay_no_match(self):
@@ -353,7 +359,7 @@ class SimulatedTest(unittest.TestCase):
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
         self.simulated._process_sp(1234577, mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(1234577, 69, 2.00)])
+        self.assertEqual(self.simulated.matched, [[1234577, 69, 2.00]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_market_on_close_lay(self):
@@ -364,7 +370,7 @@ class SimulatedTest(unittest.TestCase):
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
         self.simulated._process_sp(1234578, mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(1234578, 69, 0.03)])
+        self.assertEqual(self.simulated.matched, [[1234578, 69, 0.03]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     @mock.patch("flumine.backtest.simulated.Simulated._calculate_process_traded")
@@ -394,7 +400,7 @@ class SimulatedTest(unittest.TestCase):
         self.simulated._calculate_process_traded(1234583, 2.00)
         self.simulated._calculate_process_traded(1234584, 2.00)
         self.assertEqual(
-            self.simulated.matched, [(1234582, 12, 1.00), (1234583, 12, 1.00)]
+            self.simulated.matched, [[1234582, 12, 1.00], [1234583, 12, 1.00]]
         )
         self.assertEqual(self.simulated._piq, 0)
 
@@ -404,7 +410,7 @@ class SimulatedTest(unittest.TestCase):
         self.assertEqual(self.simulated.matched, [])
         self.assertEqual(self.simulated._piq, 0)
         self.simulated._calculate_process_traded(1234586, 4.00)
-        self.assertEqual(self.simulated.matched, [(1234586, 12, 2.00)])
+        self.assertEqual(self.simulated.matched, [[1234586, 12, 2.00]])
         self.assertEqual(self.simulated._piq, 0)
 
     def test_take_sp(self):
@@ -422,27 +428,27 @@ class SimulatedTest(unittest.TestCase):
 
     def test_average_price_matched(self):
         self.assertEqual(self.simulated.average_price_matched, 0)
-        self.simulated._update_matched((1234, 1, 2))
+        self.simulated._update_matched([1234, 1, 2])
         self.assertEqual(self.simulated.average_price_matched, 1)
 
     def test_size_matched(self):
         self.assertEqual(self.simulated.size_matched, 0)
-        self.simulated._update_matched((4321, 1, 2))
+        self.simulated._update_matched([4321, 1, 2])
         self.assertEqual(self.simulated.size_matched, 2)
 
     def test__update_matched(self):
-        self.simulated._update_matched((12345, 10.0, 2.64))
-        self.assertEqual(self.simulated.matched, [(12345, 10.0, 2.64)])
+        self.simulated._update_matched([12345, 10.0, 2.64])
+        self.assertEqual(self.simulated.matched, [[12345, 10.0, 2.64]])
         self.assertEqual(self.simulated.size_matched, 2.64)
         self.assertEqual(self.simulated.average_price_matched, 10.0)
 
     def test_size_remaining(self):
         self.assertEqual(self.simulated.size_remaining, 2)
-        self.simulated._update_matched((1234, 1, 1))
+        self.simulated._update_matched([1234, 1, 1])
         self.assertEqual(self.simulated.size_remaining, 1)
 
     def test_size_remaining_multi(self):
-        self.simulated._update_matched((1234, 1, 0.1))
+        self.simulated._update_matched([1234, 1, 0.1])
         self.simulated.size_cancelled = 0.2
         self.simulated.size_lapsed = 0.3
         self.simulated.size_voided = 0.4
@@ -456,7 +462,7 @@ class SimulatedTest(unittest.TestCase):
     def test_profit_back(self):
         self.assertEqual(self.simulated.profit, 0)
         self.simulated.order.runner_status = "WINNER"
-        self.simulated._update_matched((1234, 10.0, 2.0))
+        self.simulated._update_matched([1234, 10.0, 2.0])
         self.assertEqual(self.simulated.profit, 18.0)
         self.simulated.order.runner_status = "LOSER"
         self.assertEqual(self.simulated.profit, -2.0)
@@ -465,7 +471,7 @@ class SimulatedTest(unittest.TestCase):
         self.simulated.order.side = "LAY"
         self.assertEqual(self.simulated.profit, 0)
         self.simulated.order.runner_status = "WINNER"
-        self.simulated._update_matched((1234, 10.0, 2.0))
+        self.simulated._update_matched([1234, 10.0, 2.0])
         self.assertEqual(self.simulated.profit, -18.0)
         self.simulated.order.runner_status = "LOSER"
         self.assertEqual(self.simulated.profit, 2.0)
@@ -476,3 +482,9 @@ class SimulatedTest(unittest.TestCase):
 
         config.simulated = True
         self.assertTrue(self.simulated)
+        config.simulated = False
+
+    def test_bool_paper_trade(self):
+        self.assertFalse(self.simulated)
+        self.simulated.order.trade.client.paper_trade = True
+        self.assertTrue(self.simulated)
diff --git a/tests/test_strategy.py b/tests/test_strategy.py
index 906315f..f271bf1 100644
--- a/tests/test_strategy.py
+++ b/tests/test_strategy.py
@@ -13,9 +13,11 @@ class StrategiesTest(unittest.TestCase):
 
     def test_call(self):
         mock_strategy = mock.Mock()
-        self.strategies(mock_strategy)
+        mock_client = mock.Mock()
+        self.strategies(mock_strategy, mock_client)
         self.assertEqual(self.strategies._strategies, [mock_strategy])
         mock_strategy.add.assert_called_with()
+        mock_strategy.client = mock_client
 
     def test_start(self):
         mock_strategy = mock.Mock()
@@ -37,6 +39,7 @@ class BaseStrategyTest(unittest.TestCase):
         self.mock_market_data_filter = mock.Mock()
         self.streaming_timeout = 2
         self.conflate_ms = 100
+        self.mock_client = mock.Mock()
         self.strategy = strategy.BaseStrategy(
             market_filter=self.mock_market_filter,
             market_data_filter=self.mock_market_data_filter,
@@ -47,6 +50,7 @@ class BaseStrategyTest(unittest.TestCase):
             context={"trigger": 0.123},
             max_selection_exposure=1,
             max_order_exposure=2,
+            client=self.mock_client,
         )
 
     def test_init(self):
@@ -59,6 +63,7 @@ class BaseStrategyTest(unittest.TestCase):
         self.assertEqual(self.strategy.context, {"trigger": 0.123})
         self.assertEqual(self.strategy.max_selection_exposure, 1)
         self.assertEqual(self.strategy.max_order_exposure, 2)
+        self.assertEqual(self.strategy.client, self.mock_client)
 
     def test_check_market_no_subscribed(self):
         mock_market = mock.Mock()
diff --git a/tests/test_streams.py b/tests/test_streams.py
index 04f48b7..84ef9d4 100644
--- a/tests/test_streams.py
+++ b/tests/test_streams.py
@@ -1,10 +1,10 @@
 import unittest
 from unittest import mock
 
-from flumine.streams import streams, datastream
+from flumine.streams import streams, datastream, historicalstream
 from flumine.streams.basestream import BaseStream
+from flumine.streams.simulatedorderstream import CurrentOrders
 from flumine.exceptions import ListenerError
-from flumine.streams import historicalstream
 
 
 class StreamsTest(unittest.TestCase):
@@ -64,11 +64,18 @@ class StreamsTest(unittest.TestCase):
 
     @mock.patch("flumine.streams.streams.Streams.add_order_stream")
     def test_add_client_betfair(self, mock_add_order_stream):
-        mock_client = mock.Mock(order_stream=True)
+        mock_client = mock.Mock(order_stream=True, paper_trade=False)
         mock_client.EXCHANGE = streams.ExchangeType.BETFAIR
         self.streams.add_client(mock_client)
         mock_add_order_stream.assert_called_with(mock_client)
 
+    @mock.patch("flumine.streams.streams.Streams.add_simulated_order_stream")
+    def test_add_client_paper_trade(self, mock_add_simulated_order_stream):
+        mock_client = mock.Mock(order_stream=True, paper_trade=True)
+        mock_client.EXCHANGE = streams.ExchangeType.BETFAIR
+        self.streams.add_client(mock_client)
+        mock_add_simulated_order_stream.assert_called_with(mock_client)
+
     @mock.patch("flumine.streams.streams.Streams.add_order_stream")
     def test_add_client_no_order_stream(self, mock_add_order_stream):
         mock_client = mock.Mock(order_stream=False)
@@ -171,6 +178,25 @@ class StreamsTest(unittest.TestCase):
             client=mock_client,
         )
 
+    @mock.patch("flumine.streams.streams.SimulatedOrderStream")
+    @mock.patch("flumine.streams.streams.Streams._increment_stream_id")
+    def test_add_simulated_order_stream(self, mock_increment, mock_order_stream_class):
+        conflate_ms = 500
+        streaming_timeout = 0.5
+        mock_client = mock.Mock()
+        self.streams.add_simulated_order_stream(
+            mock_client, conflate_ms, streaming_timeout
+        )
+        self.assertEqual(len(self.streams), 1)
+        mock_increment.assert_called_with()
+        mock_order_stream_class.assert_called_with(
+            flumine=self.mock_flumine,
+            stream_id=mock_increment(),
+            streaming_timeout=streaming_timeout,
+            conflate_ms=conflate_ms,
+            client=mock_client,
+        )
+
     def test_start(self):
         mock_stream = mock.Mock()
         self.streams._streams = [mock_stream]
@@ -527,3 +553,36 @@ class TestOrderStream(unittest.TestCase):
     #
     # def test_handle_output(self):
     #     pass
+
+
+class TestSimulatedOrderStream(unittest.TestCase):
+    def setUp(self) -> None:
+        self.mock_flumine = mock.Mock()
+        self.stream = streams.SimulatedOrderStream(self.mock_flumine, 123, 0.01, 100)
+
+    def test_init(self):
+        self.assertEqual(self.stream.flumine, self.mock_flumine)
+        self.assertEqual(self.stream.stream_id, 123)
+        self.assertIsNone(self.stream.market_filter)
+        self.assertIsNone(self.stream.market_data_filter)
+        self.assertEqual(self.stream.streaming_timeout, 0.01)
+        self.assertEqual(self.stream.conflate_ms, 100)
+        self.assertIsNone(self.stream._stream)
+
+    def test_current_orders(self):
+        current_orders = CurrentOrders([1])
+        self.assertEqual(current_orders.orders, [1])
+        self.assertFalse(current_orders.more_available)
+
+    # def test_run(self):
+    #     pass
+
+    def test__get_current_orders(self):
+        mock_market = mock.Mock(closed=False)
+        order_one = mock.Mock(simulated=True)
+        order_one.trade.client = self.stream.client
+        order_two = mock.Mock(simulated=True)
+        order_three = mock.Mock(simulated=True)
+        mock_market.blotter = [order_one, order_two, order_three]
+        self.stream.flumine.markets = [mock_market, mock.Mock(closed=True)]
+        self.assertEqual(self.stream._get_current_orders(), [order_one])
diff --git a/tests/test_trade.py b/tests/test_trade.py
index 284b881..074f90e 100644
--- a/tests/test_trade.py
+++ b/tests/test_trade.py
@@ -9,7 +9,8 @@ from flumine.order.trade import Trade, OrderError, TradeStatus
 class TradeTest(unittest.TestCase):
     def setUp(self) -> None:
         logging.disable(logging.CRITICAL)
-        self.mock_strategy = mock.Mock()
+        mock_client = mock.Mock(paper_trade=False)
+        self.mock_strategy = mock.Mock(client=mock_client)
         self.mock_fill_kill = mock.Mock()
         self.mock_offset = mock.Mock()
         self.mock_green = mock.Mock()
@@ -122,6 +123,9 @@ class TradeTest(unittest.TestCase):
         with self.assertRaises(NotImplementedError):
             self.trade.create_order_from_current(mock_current_order, "12345")
 
+    def test_client(self):
+        self.assertEqual(self.trade.client, self.mock_strategy.client)
+
     def test_notes_str(self):
         self.trade.notes = collections.OrderedDict({"1": 1, 2: "2", 3: 3, 4: "four"})
         # self.assertEqual(self.trade.notes_str, "1,2,3,four")