Experimental features are early versions for users to test before final release. We work hard to ensure that every available Simudyne SDK feature is thoroughly tested, but these experimental features may have minor bugs we still need to work on.
If you have any comments or find any bugs, please share with support@simudyne.com.
Introduction
The simudyne.core.abm.finkit.market.simulator provides an API, that sits on top of the core of the SDK, which affords us the tools to create market simulations tersely. This enables us to generate market simulations quickly, leaving you free to develop and deploy trading algorithms in a controlled environment. All this is done while speaking the semantics of markets.
Market simulation models comprise of two distinct types of Agents, Traders and an AuctionMarket. Traders allow us to submit bids and asks to an AuctionMarket which process the requests and send responses. For this functionality to execute, we must run our models in a MarketSimulator.
Trader
Traders interact with a central market by sending requests. These requests can take on one of three forms.
NewOrder requests allow traders to make bids or asks on the market.
Cancel requests enable traders to cancel bids that are currently on the market.
Amend requests allow traders to amend orders currently on the market.
Traders also have internal state and expose methods for dealing with current bids on the market and getting the current market information.
To create an agent with this functionality simply extend Trader.
Requests are the way Traders communicate with the market. Traders expose three methods for sending each of the requests.
New Request - sendNewRequest()
A Trader can send requests to the market by using specific methods:
Sending a new bid request
// sending an order to the market to buy 12 units at a price of 2 per unit or less
buyer.sendNewRequest(buyer.bid().forPrice(2).forQuantity(12));
Sending a new ask request
// sending an order to the market to sell 10 units at a price of 3 per unit or more
seller.sendNewRequest(seller.ask().forPrice(3).forQuantity(10));
Amend Request - sendAmendRequest()
Once a trader has orders on the market, it can send requests to amend them. Orders currently on the market are accessed via Trader#getOrdersOnMarket() method, which will return a List containing orders. The orders can, in turn, be passed to the amend request like so.
// we begin by getting the order we want to amend
ImmutableOrder order =getOrdersOnMarket().get(0);// here, we are amending this order with a new quantity, but the price per unit remains the same
trader.sendAmendRequest(trader.amend(order).samePrice().newQuantity(9));
Cancel Request - sendCancelRequest()
Cancelling orders is similar to amending them, except you simply need to pass the order to cancel via Trader#sendCancelRequest
// we begin by getting the order we want to cancel
ImmutableOrder order =getOrdersOnMarket().get(0);// here, we are cancelling the order
trader.sendCancelRequest(order);
Price must be a multiple of the market's tick
You can access the tick of the market with the method `getMarketConfig().getTick()`. If the specified price does not conform to the tick, the price will be floored (if bid order) or ceiled (if ask order). You should avoid entering bids/asks with prices many orders of magnitude larger than tick size to avoid rounding errors.
Internal state
Each trader has its own wealth and quantity of shares.
Every time a trader sells or buys on the market, its wealth and quantity of shares will automatically update.
Traders have access to some limited information about the market. This information will be automatically updated at each step().
A trader will know about its own orders on the market.
Accessing its own orders can be done using the method Trader#getOrdersOnMarket(). This method will return an immutable List containing the orders.
A trader is also able to access the 10 best bids and asks on the market. These can be found inside the MarketInfo using Trader#getMarketInfo.
Custom handles
Once the orders have been sent to the market, the trader will receive its response during the next phase. There are several types of responses, and the trader will automatically update its knowledge of the market, wealth and quantity.
Moreover, a trader can also have user defined behaviours when receiving a certain type of response from the market. These behaviours can be defined in the following methods.
publicclassBasicTraderextendsTrader<GlobalState>{@OverridepublicvoidhandleCancels(Cancelled response){//define the behaviour when receiving a confirmation that an order has been cancelled.}@OverridepublicvoidhandleAcknowledgements(Acknowledge response){//define the behaviour when receiving a confirmation that an order has been amended.}@OverridepublicvoidhandleCreation(OrderCreation response){//define the behaviour when receiving a confirmation that an order has been created.}@OverridepublicvoidhandleReject(Reject response){//define the behaviour when receiving a confirmation that an order has been rejected.}@OverridepublicvoidhandleFilledOrder(Filled response){//define the behaviour when receiving an information that one of the trader's order has been filled.}@OverridepublicvoidhandlePartialFill(PartiallyFilled response){//define the behaviour when receiving an information that one of the trader's order has been filled.}}
Continuous Auction Market
The auction market has one primary function. It takes requests from the Traders and matches bids to asks. As well as this, the market allows you to implement custom handles whenever a new request enters the market, has its own configurable parameters and distributes market information at the end of each round. Currently the tool kit supports a continuous auction market. In order to implement your own continuous auction market simply extend ContinuousAuctionMarket like so.
The ContinuousAuctionMarket implements a continuous trading matching engine. Continuous trading involves the immediate execution of orders upon their receipt by the market. On entering the market new orders are exhaustively matched against all possible orders and it is only if they do not full match that they are placed on the orderbook. Cancel requests are also processed at the point they are received, removing the order from the order book before any other requests are processed. Amend requests are a combination of the two, firstly, the previous order is canceled and the new augmented order is added to the order book, attempting to match instantly.
This entire process is handled for you by the tool kit. All you need to do is create an Action and call ContinuousAuctionMarket#processRequests when you wish to process pending Trader requests.
If a market receives orders in different runs but you want the market to process them as if they arrived at the same time, you can shuffle the orders before processing them. To do this call `ContinuousAuctionMarket#shuffleAndProcessRequests`.
Market configuration
Each market has an associated MarketConfig. This allows you to set the configurable parameters of your market. Firstly, you can set the tick size of the market, the minimum price movement of the trading instrument, which will be used as the resolution of which bids and asks are rounded to. Secondly, you can set the pricing policy of the market, this allows you to choose between pricing by the mean of the order prices or the earliest order price.
Market information
At the end of each round the market sends MarketInfo to Traders. This contains information of the best bid and ask orders left on the market at the end of bidding organised by price level.
Custom handles
As with traders you have the option of implementing custom handles which are called automatically whenever a market receives a certain request.
Market simulator models must run inside a MarketSimulator. This provides you with familiar setup and step methods. It also provides a way of easily connecting your traders to the market.
publicclassDummyModelextendsMarketSimulator<GlobalState>{publicvoidstep(){super.step();//the actions to run at each step}publicvoidsetup(){//creation of the different agents and of the topologysuper.setup();}}
In order to connect traders to your market, simply execute the following at setup. Note that you can add as many trader groups to the market as you wish before calling build().
The method MarketSimulator#generateMarket will generate a market of the Type passed as argument, then link it to the various traders which are added using the MarketSimulator#withTrader method. This will create a fully connected topology, with all the traders being linked to the market and vice-versa. Once the desired traders have been added, the topology can be created by calling the MarketSimulator#build() method.
This will return a MarketTopology object containing the market's group (which only has one agent) and the trader's groups.
You can access those group using the methods MarketTopology#getMarket() and MarketTopology#getTraders() if you want to create a more complex topology above the one created by the MarketTopology#generateMarket.
Let's start by creating all of the classes we need for this model. We will need two different Trader groups; one for the buyers and one for the sellers. We will also need a ContinousAuctionMarket to act as our market maker. Finally, our model will run in a MarketSimulator.
Lets create skeleton classes and fill in the blanks as we go.
We will need to implement two actions for our Buyer. Firstly, each step the buyer will make trading decisions. If they have no currently placed bids, they have some probability of making a new bid request dependant on their activity. We can also call Trader#getOrdersOnMarket to make decisions on adjusting the price of any current bids on the market.
Secondly, following Gode and Sunder, our traders will stop once their order has been filled. We can implement a custom handle for the filled response by calling Trader#handleFilledOrder and reducing the activity of the trader to zero.
The logic for the seller is much the same as the buyer. It has a trading decision to make each round and can update bids. It also stops participating in the market once its has been matched with a bid.
Seller.java
double quantity;double limitPrice;double activity =0.1;publicstatic Action<Seller>step(){return Action.create(
Seller.class,
s ->{double p = s.getPrng().uniform(s.limitPrice,400).sample();if(s.getPrng().uniform(0,1).sample()< s.activity){if(s.getOrdersOnMarket().isEmpty()){
s.sendNewRequest(s.ask().forPrice(p).forQuantity(s.quantity));}else{
ImmutableOrder order = s.getOrdersOnMarket().get(0);if(p < order.getPrice()){
s.sendAmendRequest(s.amend(order).newPrice(p).sameQuantity());}}}});}@OverridepublicvoidhandleFilledOrder(Filled filled){
activity =0;}
Implement the Market
For the market we simply need to create a single action that will shuffle our orders before processing them.
We can generate our market/trader connections in setup. This is where where we call generateMarket, withTraders and build to construct the makret.
@Overridepublicvoidsetup(){generateMarket(Market.class).withTrader(
Seller.class,500,
s ->{
s.quantity =1;
s.limitPrice = s.getPrng().uniform(100,300).sample();}).withTrader(
Buyer.class,500,
b ->{
b.quantity =1;
b.limitPrice = b.getPrng().uniform(100,300).sample();}).build();super.setup();}
In order to get our actions to execute we need to add them to separate runs in the model step First, our sellers and buyers will make their trading decision and at the end of each time step the market will clear.
Let's go ahead and report the best bids and asks left on the market at the end of a time step. The Market exposes a getMarketInformation method which returns an object that has the best bids and best asks left on it, these are simply java treeMaps of price quantity, key value pairs. At each time step we can set the top values to a @Variable and report to the console.