Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ public interface SimpleAlgoState {
public List<ChildOrder> getActiveChildOrders();

public long getInstrumentId();


}
43 changes: 43 additions & 0 deletions algo-exercise/getting-started/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Coding Black Females - My Trading Algorithm Application

### Overview
This project is a Java- based trading algorithm designed to automate decision making in the financial markets. The algorithm does this by analysing the order and executing buy and sell actions based on market conditions. The project aims to simplify trading operations by automatically managing orders, this helps users to make timely trading decisions without manual intervention. The main problem it addresses is the need for efficient and rule-based order management in fast-moving markets. It reduces human error by automating repetitive tasks like matching buy and sell orders, creating new orders based on specific conditions, and cancelling orders that no longer meet the criteria. This type of automation is particularly valuable for traders who want to capitalise on market opportunities quickly while adhering to a pre-defined strategy.

This project is useful because it enhances trading efficiency by using predefined thresholds and actions to guide decision-making. The algorithm’s logging system also provides real-time insights into its operations, which helps in monitoring performance and refining trading strategies. My project offers an extendable framework starting point for developers and traders interested in creating or optimising algorithmic trading solutions.

### How to Get Started

#### Pre-requisites

1. The project requires Java version 17 or higher

##### Note
This project is configured for Java 17. If you have a later version installed, it will compile and run successfully, but you may see warnings in the log like this, which you can safely ignore:

```sh
[WARNING] system modules path not set in conjunction with -source 17
```

#### Opening the project

1. Fork this repo in GitHub and clone it to your local machine
2. Open the project as a Maven project in your IDE (normally by opening the top level pom.xml file)
3. Click to expand the "getting-started" module

##### Note
You will first need to run the Maven `install` task to make sure the binary encoders and decoders are installed and available for use. You can use the provided Maven wrapper or an installed instance of Maven, either in the command line or from the IDE integration.

To get started, run the following command from the project root: `./mvnw clean install`. Once you've done this, you can compile or test specific projects using the `--projects` flag, e.g.:

- Clean all projects: `./mvnw clean`
- Test all `algo-exercise` projects: `./mvnw test --projects algo-exercise`
- Compile the `getting-started` project only: `./mvnw compile --projects algo-exercise/getting-started`
- Then run the Algotest/ AlgoBacktest to see the output of my code

☺️ 💻






Original file line number Diff line number Diff line change
@@ -1,30 +1,175 @@
package codingblackfemales.gettingstarted;

import codingblackfemales.action.Action;
import codingblackfemales.action.CancelChildOrder;
import codingblackfemales.action.CreateChildOrder;
import codingblackfemales.action.NoAction;
import codingblackfemales.algo.AlgoLogic;
import codingblackfemales.sotw.ChildOrder;
import codingblackfemales.sotw.SimpleAlgoState;
import codingblackfemales.sotw.marketdata.AskLevel;
import codingblackfemales.sotw.marketdata.BidLevel;
import codingblackfemales.util.Util;
import messages.order.Side;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Iterator;

public class MyAlgoLogic implements AlgoLogic {

private static final Logger logger = LoggerFactory.getLogger(MyAlgoLogic.class);
// Thresholds for buy and sell actions
private static long buyThreshold = 90; // Minimum acceptable bid price for a buy order
private static long sellThreshold = 115; // Minimum acceptable ask price for a sell order
private static long spreadThreshold = -3; // Minimum spread threshold for action



@Override
public Action evaluate(SimpleAlgoState state) {

var orderBookAsString = Util.orderBookToString(state);

//shows current state of order book and current state of active orders
logger.info("[MYALGO] The state of the order book is:\n" + orderBookAsString);
logger.info("Active Orders:" + state.getActiveChildOrders().toString());

var totalOrderCount = state.getChildOrders().size();

//make sure we have an exit condition...
if (totalOrderCount > 10) {
return NoAction.NoAction;
}



final BidLevel bidlevel = state.getBidAt(0);
final long bestBidPrice = bidlevel.price;
final long bidQuantity = bidlevel.quantity;



final AskLevel asklevel = state.getAskAt(0);
final long bestAskPrice = asklevel.price;
final long askQuantity = asklevel.quantity;


//calculate the spread
final long spread = bestAskPrice - bestBidPrice;
logger.info("[MYALGO] Spread between ask and bid " + spread);

//calculate the midPrice
final long midPrice =(bestBidPrice + bestBidPrice)/2;
logger.info("[MYALGO] Mid-price calculated:" + midPrice);

//Calculate the spread as a percentage of the mid-price
final double spreadPercentage = (double) spread / midPrice * 100;
logger.info("[MYALGO] Spread percentage: " + spreadPercentage + "%");

// Check for matching orders
matchOrders(state, bestBidPrice, bestAskPrice);



// not to create or cancel orders because spread is small
if (spread < spreadThreshold) {
logger.info("[MYALGO] The spread is small " + spread + " No Action");
return NoAction.NoAction;

}


//count the buy and sell orders
long buyOrdersCount = state.getChildOrders().stream().filter(ChildOrder -> ChildOrder.getSide() == Side.BUY).count();
long sellOrdersCount = state.getChildOrders().stream().filter(ChildOrder -> ChildOrder.getSide() == Side.SELL).count();



//create new buy orders if there are less than 5 orders
if (buyOrdersCount < 5 ) {
return createBuyOrder(state, buyOrdersCount, bidQuantity, bestBidPrice);
}

/********
*
* Add your logic here....
*
*/
//create new sell orders if there are less than 5 order
if (sellOrdersCount < 5 ) {
return createSellOrder(state, sellOrdersCount, askQuantity, bestAskPrice);
}

//cancel orders that don't match the best price

return cancelOrders(state, bestBidPrice, bestAskPrice);
}

private void matchOrders(SimpleAlgoState state, long bestBidPrice, long bestAskPrice) {
Iterator<ChildOrder> iterator = state.getActiveChildOrders().iterator();

while (iterator.hasNext()) {
ChildOrder order = iterator.next();
if (order.getSide() == Side.BUY && order.getPrice() >= bestAskPrice) {
logger.info("[MYALGO] Matched BUY order: Order ID: #" + order.getOrderId() +", Side: " + order.getSide() + ", Price: " + order.getPrice() + ", Quantity: " + order.getQuantity());
iterator.remove(); // Remove matched order
} else if (order.getSide() == Side.SELL && order.getPrice() <= bestBidPrice) {
logger.info("[MYALGO] Matched SELL order: Order ID: #" + order.getOrderId() +", Side: " + order.getSide() + ", Price: " + order.getPrice() + ", Quantity: " + order.getQuantity());
iterator.remove(); // Remove matched order
}
}
}





//create method to create new buy order
public Action createBuyOrder(SimpleAlgoState state, long buyOrdersCount, long bidQuantity, long bestBidPrice) {
logger.info("[MYALGO] Creating new buy order: " + state.getChildOrders().size() + " orders and add to new buy order " + bidQuantity + " @ " + bestBidPrice);
logger.info(state.getActiveChildOrders().toString());
logger.info("Buy order count is:" + buyOrdersCount);
//creates a new child order
return new CreateChildOrder(Side.BUY, bidQuantity, bestBidPrice);

}

// create a method to create a new sell order
public Action createSellOrder(SimpleAlgoState state, long sellOrdersCount, long askQuantity, long bestAskPrice) {
logger.info("[MYALGO] Creating new sell order:" + state.getChildOrders().size() + " orders and add to new sell order " + askQuantity + " @ " + bestAskPrice);
logger.info("Sell order count is:" + sellOrdersCount);
//creates a new child order
return new CreateChildOrder(Side.SELL, askQuantity, bestAskPrice);
}

//create a method to cancel orders that don't match best price or fall below thresholds
public Action cancelOrders(SimpleAlgoState state, long bestBidPrice, long bestAskPrice){
for (ChildOrder order : state.getActiveChildOrders()){
logger.info("Order ID: #" + order.getOrderId() + ", Side: " + order.getSide() + ", Price: " + order.getPrice() + ", Quantity: " + order.getQuantity());

boolean buyOrder = order.getSide() ==Side.BUY;
boolean notBuyThreshold = order.getPrice() != buyThreshold;
boolean lessThanBuyThreshold = order.getPrice() < buyThreshold;

// Cancel buy orders not matching the best price or below the buy threshold
if (buyOrder && (notBuyThreshold || lessThanBuyThreshold)) {
logger.info(String.format("The buy Threshold is %d", + buyThreshold ));
logger.info(String.format("[MYALGO] Cancel BUY order %d with price %d and quantity %d. The current best bid price is %d." ,+ order.getOrderId(), + order.getPrice(), + order.getQuantity(), + bestBidPrice));
return new CancelChildOrder(order);
}
boolean sellTheOrder = order.getSide() ==Side.SELL;
boolean notSellThreshold = order.getPrice() != sellThreshold;
boolean lessThanSellThreshold = order.getPrice() < sellThreshold;


// Cancel sell orders not matching the best price or below the sell threshold
if (sellTheOrder && (notSellThreshold || lessThanSellThreshold)) {
logger.info(String.format("The sell Threshold is %d", + sellThreshold ));
logger.info(String.format("[MYALGO] Cancel SELL order %d with price %d and quantity %d. The current best ask price is %d." ,+ order.getOrderId(), + order.getPrice(), + order.getQuantity(), + bestAskPrice));
return new CancelChildOrder(order);
}
}

return NoAction.NoAction;
}
}




Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,73 @@ protected UnsafeBuffer createTick(){
return directBuffer;
}

protected UnsafeBuffer createTickWithHighThreshold() {
final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();
final BookUpdateEncoder encoder = new BookUpdateEncoder();
final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
final UnsafeBuffer directBuffer = new UnsafeBuffer(byteBuffer);

// Encode the message
encoder.wrapAndApplyHeader(directBuffer, 0, headerEncoder);

// Set a high spread by configuring ask and bid levels
encoder.venue(Venue.XLON);
encoder.instrumentId(123L);

// Here, we’ll set a very high ask and a much lower bid.
encoder.askBookCount(1).next().price(150L).size(300L); // High ask price
encoder.bidBookCount(1).next().price(100L).size(300L); // Low bid price

encoder.instrumentStatus(InstrumentStatus.CONTINUOUS);
encoder.source(Source.STREAM);

return directBuffer;
}

protected UnsafeBuffer createTickWithLowThreshold() {
final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();
final BookUpdateEncoder encoder = new BookUpdateEncoder();
final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
final UnsafeBuffer directBuffer = new UnsafeBuffer(byteBuffer);

encoder.wrapAndApplyHeader(directBuffer, 0, headerEncoder);
encoder.venue(Venue.XLON);
encoder.instrumentId(123L);

encoder.askBookCount(1)
.next().price(100L).size(200L); // Ask price close to bid

encoder.bidBookCount(1)
.next().price(90L).size(200L); // Bid price close to ask

encoder.instrumentStatus(InstrumentStatus.CONTINUOUS);
encoder.source(Source.STREAM);

return directBuffer;
}

protected UnsafeBuffer createTickWithIncreasingVolume() {
final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();
final BookUpdateEncoder encoder = new BookUpdateEncoder();
final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
final UnsafeBuffer directBuffer = new UnsafeBuffer(byteBuffer);

encoder.wrapAndApplyHeader(directBuffer, 0, headerEncoder);
encoder.venue(Venue.XLON);
encoder.instrumentId(123L);

encoder.askBookCount(1)
.next().price(110L).size(1000L); // Higher ask quantity

encoder.bidBookCount(1)
.next().price(108L).size(900L); // Higher bid quantity

encoder.instrumentStatus(InstrumentStatus.CONTINUOUS);
encoder.source(Source.STREAM);

return directBuffer;
}



}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package codingblackfemales.gettingstarted;

import codingblackfemales.algo.AlgoLogic;
import codingblackfemales.sotw.ChildOrder;
import messages.order.Side;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

/**
* This test plugs together all of the infrastructure, including the order book (which you can trade against)
* and the market data feed.
Expand All @@ -22,6 +27,14 @@ public AlgoLogic createAlgoLogic() {
return new MyAlgoLogic();
}


@Before

public void setup() {
container.getState().getChildOrders().clear();

}

@Test
public void testExampleBackTest() throws Exception {
//create a sample market data tick....
Expand All @@ -42,4 +55,25 @@ public void testExampleBackTest() throws Exception {
//assertEquals(225, filledQuantity);
}

@Test public void testForOrderManagement () throws Exception{
send(createTick());
send(createTick2());

//check algo has a max order limit of 10
assertTrue("There should be at least 10 child orders", container.getState().getChildOrders().size() <=10 );
}

// @Test
//
// public void testCancelOrders () throws Exception{
// for (int i = 0; i< 5; i++){
// container.getState().getChildOrders().add(new ChildOrder(Side.BUY, 500L + i, 110, 115, i + 1));
// send(createTick2());
// assertEquals(10, container.getState().getChildOrders().size());
// ChildOrder cancelOrders = container.getState().getChildOrders().stream().filter(order -> order.getState() == 2).findFirst().orElse(null);
// System.out.println("Check if there are cancelled orders. Found: " + (cancelOrders != null));
// assertEquals(true, cancelOrders != null);
//
// }
// }
}
Loading