├── .checkstyle
├── .classpath
├── .github
└── workflows
│ ├── checkstyle.yml
│ └── maven.yml
├── .gitignore
├── .mvn
└── wrapper
│ ├── MavenWrapperDownloader.java
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── .project
├── .settings
├── org.eclipse.jdt.core.prefs
└── org.eclipse.m2e.core.prefs
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
└── java
│ └── net
│ └── laffyco
│ └── javamatchingengine
│ ├── core
│ ├── CoreApplication.java
│ ├── api
│ │ ├── IOrderInterface.java
│ │ ├── ISpreadInterface.java
│ │ ├── OrderInterface.java
│ │ ├── SpreadInterface.java
│ │ └── package-info.java
│ ├── engine
│ │ ├── Order.java
│ │ ├── OrderBook.java
│ │ ├── Side.java
│ │ ├── Trade.java
│ │ └── package-info.java
│ ├── events
│ │ ├── OrderAddedEvent.java
│ │ ├── OrderMatchedEvent.java
│ │ └── package-info.java
│ └── package-info.java
│ └── package-info.java
└── test
├── java
├── net
│ └── laffyco
│ │ └── javamatchingengine
│ │ ├── core
│ │ ├── api
│ │ │ ├── OrderInterfaceTests.java
│ │ │ ├── SpreadInterfaceTests.java
│ │ │ └── package-info.java
│ │ ├── engine
│ │ │ ├── CancelOrderTests.java
│ │ │ ├── MatchingEngineTest.java
│ │ │ ├── OrderBookTests.java
│ │ │ ├── OrderTests.java
│ │ │ ├── SpreadTests.java
│ │ │ └── package-info.java
│ │ └── package-info.java
│ │ ├── package-info.java
│ │ └── scenarios
│ │ ├── CucumberRunner.java
│ │ ├── orders
│ │ ├── Orders.feature
│ │ ├── OrdersSteps.java
│ │ └── package-info.java
│ │ └── package-info.java
└── test
│ └── utils
│ ├── AbstractTest.java
│ └── package-info.java
└── resources
└── cucumber.properties
/.checkstyle:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.github/workflows/checkstyle.yml:
--------------------------------------------------------------------------------
1 | on: pull_request
2 | jobs:
3 | checkstyle_job:
4 | runs-on: ubuntu-latest
5 | name: Checkstyle job
6 | steps:
7 | - name: Checkout
8 | uses: actions/checkout@v2
9 | - name: Run check style
10 | uses: nikitasavinov/checkstyle-action@master
11 | with:
12 | github_token: ${{ secrets.GITHUB_TOKEN }}
13 | reporter: 'github-pr-check'
14 | tool_name: 'testtool'
15 | checkstyle_config: sun_checks.xml
--------------------------------------------------------------------------------
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI with Maven
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | pull_request:
10 | branches: [ main ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up JDK 17
20 | uses: actions/setup-java@v1
21 | with:
22 | java-version: 17
23 | - name: Build with Maven
24 | run: mvn -B package --file pom.xml
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2007-present the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import java.net.*;
17 | import java.io.*;
18 | import java.nio.channels.*;
19 | import java.util.Properties;
20 |
21 | public class MavenWrapperDownloader {
22 |
23 | private static final String WRAPPER_VERSION = "0.5.6";
24 | /**
25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
26 | */
27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
29 |
30 | /**
31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
32 | * use instead of the default one.
33 | */
34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
35 | ".mvn/wrapper/maven-wrapper.properties";
36 |
37 | /**
38 | * Path where the maven-wrapper.jar will be saved to.
39 | */
40 | private static final String MAVEN_WRAPPER_JAR_PATH =
41 | ".mvn/wrapper/maven-wrapper.jar";
42 |
43 | /**
44 | * Name of the property which should be used to override the default download url for the wrapper.
45 | */
46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
47 |
48 | public static void main(String args[]) {
49 | System.out.println("- Downloader started");
50 | File baseDirectory = new File(args[0]);
51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
52 |
53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom
54 | // wrapperUrl parameter.
55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
56 | String url = DEFAULT_DOWNLOAD_URL;
57 | if(mavenWrapperPropertyFile.exists()) {
58 | FileInputStream mavenWrapperPropertyFileInputStream = null;
59 | try {
60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
61 | Properties mavenWrapperProperties = new Properties();
62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
64 | } catch (IOException e) {
65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
66 | } finally {
67 | try {
68 | if(mavenWrapperPropertyFileInputStream != null) {
69 | mavenWrapperPropertyFileInputStream.close();
70 | }
71 | } catch (IOException e) {
72 | // Ignore ...
73 | }
74 | }
75 | }
76 | System.out.println("- Downloading from: " + url);
77 |
78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
79 | if(!outputFile.getParentFile().exists()) {
80 | if(!outputFile.getParentFile().mkdirs()) {
81 | System.out.println(
82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
83 | }
84 | }
85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
86 | try {
87 | downloadFileFromURL(url, outputFile);
88 | System.out.println("Done");
89 | System.exit(0);
90 | } catch (Throwable e) {
91 | System.out.println("- Error downloading");
92 | e.printStackTrace();
93 | System.exit(1);
94 | }
95 | }
96 |
97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception {
98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
99 | String username = System.getenv("MVNW_USERNAME");
100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
101 | Authenticator.setDefault(new Authenticator() {
102 | @Override
103 | protected PasswordAuthentication getPasswordAuthentication() {
104 | return new PasswordAuthentication(username, password);
105 | }
106 | });
107 | }
108 | URL website = new URL(urlString);
109 | ReadableByteChannel rbc;
110 | rbc = Channels.newChannel(website.openStream());
111 | FileOutputStream fos = new FileOutputStream(destination);
112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
113 | fos.close();
114 | rbc.close();
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Laffini/Java-Matching-Engine-Core/20d5e162f2c605773f7f1bf37d5a0287b8b24c8c/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
3 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Java-Matching-Engine
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | net.sf.eclipsecs.core.CheckstyleBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.m2e.core.maven2Builder
20 |
21 |
22 |
23 |
24 |
25 | org.eclipse.jdt.core.javanature
26 | org.eclipse.m2e.core.maven2Nature
27 | net.sf.eclipsecs.core.CheckstyleNature
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
3 | org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
4 | org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
5 | org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
6 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
7 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
8 | org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
9 | org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
10 | org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
11 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
12 | org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
13 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
14 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
15 | org.eclipse.jdt.core.compiler.compliance=1.8
16 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
17 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
18 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
19 | org.eclipse.jdt.core.compiler.problem.APILeak=warning
20 | org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
21 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
22 | org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
23 | org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
24 | org.eclipse.jdt.core.compiler.problem.deadCode=warning
25 | org.eclipse.jdt.core.compiler.problem.deprecation=warning
26 | org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
27 | org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
28 | org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
29 | org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
30 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
31 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
32 | org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
33 | org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
34 | org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
35 | org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
36 | org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
37 | org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
38 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
39 | org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
40 | org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
41 | org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
42 | org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
43 | org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
44 | org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
45 | org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
46 | org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
47 | org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
48 | org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
49 | org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
50 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
51 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
52 | org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
53 | org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
54 | org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
55 | org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
56 | org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
57 | org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
58 | org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
59 | org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
60 | org.eclipse.jdt.core.compiler.problem.nullReference=warning
61 | org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
62 | org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
63 | org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
64 | org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
65 | org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
66 | org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
67 | org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
68 | org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
69 | org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
70 | org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
71 | org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
72 | org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
73 | org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
74 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
75 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
76 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
77 | org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
78 | org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
79 | org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
80 | org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
81 | org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
82 | org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
83 | org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
84 | org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
85 | org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
86 | org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
87 | org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
88 | org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
89 | org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
90 | org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
91 | org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
92 | org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
93 | org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
94 | org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
95 | org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
96 | org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
97 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
98 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
99 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
100 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
101 | org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
102 | org.eclipse.jdt.core.compiler.problem.unusedImport=warning
103 | org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
104 | org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
105 | org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
106 | org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
107 | org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
108 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
109 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
110 | org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
111 | org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
112 | org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
113 | org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
114 | org.eclipse.jdt.core.compiler.release=disabled
115 | org.eclipse.jdt.core.compiler.source=1.8
116 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.m2e.core.prefs:
--------------------------------------------------------------------------------
1 | activeProfiles=
2 | eclipse.preferences.version=1
3 | resolveWorkspaceProjects=true
4 | version=1
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Java-Matching-Engine-Core
2 |
3 | 
4 |
5 | A matching engine written in Java.
6 |
7 | ## What is a matching engine?
8 | A matching engine matches buy and sell orders in a market.
9 |
10 | ## Matching Algorithm
11 | The matching engine uses a price-time-priority algorithm.
12 | The matching priority is firstly price, then time.
13 | Market participants are rewarded for offering the best price and coming early.
14 |
15 | ## Usage
16 | The Java-Matching-Engine is broken up into multiple projects.
17 | This is the 'core' project.
18 | This project only contains the engine (no REST API, persistence, etc).
19 |
20 | To use this project, you will need to include the 'core' package as part of your Spring configuration scanning (as seen below).
21 | ```
22 | @SpringBootApplication(scanBasePackages = "net.laffyco.javamatchingengine.core")
23 | public class ExampleApp {
24 |
25 | @Autowired
26 | private IOrderInterface orderInterface
27 |
28 | public static void main(final String[] args) {
29 | SpringApplication.run(ExampleApp.class, args);
30 | System.out.println(this.orderInterface.getOrders());
31 | }
32 |
33 | }
34 |
35 | ```
36 |
37 | In the example above, we are interacting with the order book through the OrderInterface.
38 | The example starts and prints the orders to the console (it will be empty as no orders have been added).
39 | In most cases you will want to use some of the other Java-Matching-Engine projects as these add additional functionality (such as a REST API) that may suit your needs.
40 |
41 | It is recommended that every user of this service audits and verifies all underlying code for its validity and suitability. Mistakes and bugs happen.
42 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Mingw, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | fi
118 |
119 | if [ -z "$JAVA_HOME" ]; then
120 | javaExecutable="`which javac`"
121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
122 | # readlink(1) is not available as standard on Solaris 10.
123 | readLink=`which readlink`
124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
125 | if $darwin ; then
126 | javaHome="`dirname \"$javaExecutable\"`"
127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
128 | else
129 | javaExecutable="`readlink -f \"$javaExecutable\"`"
130 | fi
131 | javaHome="`dirname \"$javaExecutable\"`"
132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
133 | JAVA_HOME="$javaHome"
134 | export JAVA_HOME
135 | fi
136 | fi
137 | fi
138 |
139 | if [ -z "$JAVACMD" ] ; then
140 | if [ -n "$JAVA_HOME" ] ; then
141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
142 | # IBM's JDK on AIX uses strange locations for the executables
143 | JAVACMD="$JAVA_HOME/jre/sh/java"
144 | else
145 | JAVACMD="$JAVA_HOME/bin/java"
146 | fi
147 | else
148 | JAVACMD="`which java`"
149 | fi
150 | fi
151 |
152 | if [ ! -x "$JAVACMD" ] ; then
153 | echo "Error: JAVA_HOME is not defined correctly." >&2
154 | echo " We cannot execute $JAVACMD" >&2
155 | exit 1
156 | fi
157 |
158 | if [ -z "$JAVA_HOME" ] ; then
159 | echo "Warning: JAVA_HOME environment variable is not set."
160 | fi
161 |
162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
163 |
164 | # traverses directory structure from process work directory to filesystem root
165 | # first directory with .mvn subdirectory is considered project base directory
166 | find_maven_basedir() {
167 |
168 | if [ -z "$1" ]
169 | then
170 | echo "Path not specified to find_maven_basedir"
171 | return 1
172 | fi
173 |
174 | basedir="$1"
175 | wdir="$1"
176 | while [ "$wdir" != '/' ] ; do
177 | if [ -d "$wdir"/.mvn ] ; then
178 | basedir=$wdir
179 | break
180 | fi
181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
182 | if [ -d "${wdir}" ]; then
183 | wdir=`cd "$wdir/.."; pwd`
184 | fi
185 | # end of workaround
186 | done
187 | echo "${basedir}"
188 | }
189 |
190 | # concatenates all lines of a file
191 | concat_lines() {
192 | if [ -f "$1" ]; then
193 | echo "$(tr -s '\n' ' ' < "$1")"
194 | fi
195 | }
196 |
197 | BASE_DIR=`find_maven_basedir "$(pwd)"`
198 | if [ -z "$BASE_DIR" ]; then
199 | exit 1;
200 | fi
201 |
202 | ##########################################################################################
203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
204 | # This allows using the maven wrapper in projects that prohibit checking in binary data.
205 | ##########################################################################################
206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
207 | if [ "$MVNW_VERBOSE" = true ]; then
208 | echo "Found .mvn/wrapper/maven-wrapper.jar"
209 | fi
210 | else
211 | if [ "$MVNW_VERBOSE" = true ]; then
212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
213 | fi
214 | if [ -n "$MVNW_REPOURL" ]; then
215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
216 | else
217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
218 | fi
219 | while IFS="=" read key value; do
220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
221 | esac
222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
223 | if [ "$MVNW_VERBOSE" = true ]; then
224 | echo "Downloading from: $jarUrl"
225 | fi
226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
227 | if $cygwin; then
228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
229 | fi
230 |
231 | if command -v wget > /dev/null; then
232 | if [ "$MVNW_VERBOSE" = true ]; then
233 | echo "Found wget ... using wget"
234 | fi
235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
236 | wget "$jarUrl" -O "$wrapperJarPath"
237 | else
238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
239 | fi
240 | elif command -v curl > /dev/null; then
241 | if [ "$MVNW_VERBOSE" = true ]; then
242 | echo "Found curl ... using curl"
243 | fi
244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
245 | curl -o "$wrapperJarPath" "$jarUrl" -f
246 | else
247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
248 | fi
249 |
250 | else
251 | if [ "$MVNW_VERBOSE" = true ]; then
252 | echo "Falling back to using Java to download"
253 | fi
254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
255 | # For Cygwin, switch paths to Windows format before running javac
256 | if $cygwin; then
257 | javaClass=`cygpath --path --windows "$javaClass"`
258 | fi
259 | if [ -e "$javaClass" ]; then
260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
261 | if [ "$MVNW_VERBOSE" = true ]; then
262 | echo " - Compiling MavenWrapperDownloader.java ..."
263 | fi
264 | # Compiling the Java class
265 | ("$JAVA_HOME/bin/javac" "$javaClass")
266 | fi
267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
268 | # Running the downloader
269 | if [ "$MVNW_VERBOSE" = true ]; then
270 | echo " - Running MavenWrapperDownloader.java ..."
271 | fi
272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
273 | fi
274 | fi
275 | fi
276 | fi
277 | ##########################################################################################
278 | # End of extension
279 | ##########################################################################################
280 |
281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
282 | if [ "$MVNW_VERBOSE" = true ]; then
283 | echo $MAVEN_PROJECTBASEDIR
284 | fi
285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
286 |
287 | # For Cygwin, switch paths to Windows format before running java
288 | if $cygwin; then
289 | [ -n "$M2_HOME" ] &&
290 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
291 | [ -n "$JAVA_HOME" ] &&
292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
293 | [ -n "$CLASSPATH" ] &&
294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
295 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
297 | fi
298 |
299 | # Provide a "standardized" way to retrieve the CLI args that will
300 | # work with both Windows and non-Windows executions.
301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
302 | export MAVEN_CMD_LINE_ARGS
303 |
304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
305 |
306 | exec "$JAVACMD" \
307 | $MAVEN_OPTS \
308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
311 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM https://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM set title of command window
39 | title %0
40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
42 |
43 | @REM set %HOME% to equivalent of $HOME
44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
45 |
46 | @REM Execute a user defined script before this one
47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
51 | :skipRcPre
52 |
53 | @setlocal
54 |
55 | set ERROR_CODE=0
56 |
57 | @REM To isolate internal variables from possible post scripts, we use another setlocal
58 | @setlocal
59 |
60 | @REM ==== START VALIDATION ====
61 | if not "%JAVA_HOME%" == "" goto OkJHome
62 |
63 | echo.
64 | echo Error: JAVA_HOME not found in your environment. >&2
65 | echo Please set the JAVA_HOME variable in your environment to match the >&2
66 | echo location of your Java installation. >&2
67 | echo.
68 | goto error
69 |
70 | :OkJHome
71 | if exist "%JAVA_HOME%\bin\java.exe" goto init
72 |
73 | echo.
74 | echo Error: JAVA_HOME is set to an invalid directory. >&2
75 | echo JAVA_HOME = "%JAVA_HOME%" >&2
76 | echo Please set the JAVA_HOME variable in your environment to match the >&2
77 | echo location of your Java installation. >&2
78 | echo.
79 | goto error
80 |
81 | @REM ==== END VALIDATION ====
82 |
83 | :init
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
122 |
123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
124 |
125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
127 | )
128 |
129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data.
131 | if exist %WRAPPER_JAR% (
132 | if "%MVNW_VERBOSE%" == "true" (
133 | echo Found %WRAPPER_JAR%
134 | )
135 | ) else (
136 | if not "%MVNW_REPOURL%" == "" (
137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
138 | )
139 | if "%MVNW_VERBOSE%" == "true" (
140 | echo Couldn't find %WRAPPER_JAR%, downloading it ...
141 | echo Downloading from: %DOWNLOAD_URL%
142 | )
143 |
144 | powershell -Command "&{"^
145 | "$webclient = new-object System.Net.WebClient;"^
146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
148 | "}"^
149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
150 | "}"
151 | if "%MVNW_VERBOSE%" == "true" (
152 | echo Finished downloading %WRAPPER_JAR%
153 | )
154 | )
155 | @REM End of extension
156 |
157 | @REM Provide a "standardized" way to retrieve the CLI args that will
158 | @REM work with both Windows and non-Windows executions.
159 | set MAVEN_CMD_LINE_ARGS=%*
160 |
161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
162 | if ERRORLEVEL 1 goto error
163 | goto end
164 |
165 | :error
166 | set ERROR_CODE=1
167 |
168 | :end
169 | @endlocal & set ERROR_CODE=%ERROR_CODE%
170 |
171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
175 | :skipRcPost
176 |
177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
179 |
180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
181 |
182 | exit /B %ERROR_CODE%
183 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.springframework.boot
8 | spring-boot-starter-parent
9 | 2.5.0
10 |
11 |
12 | net.laffyco.java-matching-engine
13 | core
14 | 0.0.1-SNAPSHOT
15 | java-matching-engine-core
16 | A matching engine made in Java
17 |
18 | 17
19 |
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-web
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-devtools
30 | runtime
31 |
32 |
33 |
34 |
35 | io.cucumber
36 | cucumber-java
37 | 7.0.0
38 |
39 |
40 | io.cucumber
41 | cucumber-junit
42 | 7.0.0
43 | test
44 |
45 |
46 | org.junit.vintage
47 | junit-vintage-engine
48 | 5.8.1
49 | test
50 |
51 |
52 | org.springframework.boot
53 | spring-boot-starter-test
54 | test
55 |
56 |
57 |
58 |
59 |
60 |
61 | org.apache.maven.plugins
62 | maven-surefire-plugin
63 |
64 |
65 |
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-surefire-plugin
70 | 3.0.0-M5
71 |
72 |
73 | *
74 |
75 | methods
76 | 10
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | github
86 | GitHub Laffini Apache Maven Packages
87 | https://maven.pkg.github.com/Laffini/Java-Matching-Engine-Core
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/CoreApplication.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core;
2 |
3 | import org.springframework.boot.autoconfigure.SpringBootApplication;
4 |
5 | /**
6 | * Service bootstrap.
7 | *
8 | * @author Laffini
9 | *
10 | */
11 | @SpringBootApplication
12 | public class CoreApplication {
13 |
14 | /**
15 | * Start the service.
16 | *
17 | * @param args
18 | */
19 | public static void main(final String[] args) {
20 | /*
21 | * Intentionally left blank.
22 | */
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/api/IOrderInterface.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.api;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 | import java.util.Optional;
6 |
7 | import net.laffyco.javamatchingengine.core.engine.Order;
8 | import net.laffyco.javamatchingengine.core.engine.Side;
9 |
10 | /**
11 | * Interface definition for orders.
12 | *
13 | * @author Laffini
14 | *
15 | */
16 | public interface IOrderInterface {
17 |
18 | /**
19 | * Retrieve all orders.
20 | *
21 | * @return A map of orders.
22 | */
23 | Map> getOrders();
24 |
25 | /**
26 | * Retrieve an order by ID.
27 | *
28 | * @param id
29 | * @param side
30 | * @return order
31 | */
32 | Map getOrder(String id, Side side);
33 |
34 | /**
35 | * Add an order.
36 | *
37 | * @param side
38 | * @param amount
39 | * @param price
40 | * @return order id
41 | */
42 | Map addOrder(Side side, double amount, double price);
43 |
44 | /**
45 | * Delete an order.
46 | *
47 | * @param id
48 | * @param side
49 | * @return whether an order was deleted or not
50 | */
51 | Map deleteOrder(String id, Side side);
52 |
53 | /**
54 | * Update an order.
55 | *
56 | * @param id
57 | * @param side
58 | * @param newAmount
59 | * @param newPrice
60 | * @param newSide
61 | * @return whether an order has been updated or not
62 | */
63 | Map updateOrder(String id, Side side,
64 | Optional newAmount, Optional newPrice,
65 | Optional newSide);
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/api/ISpreadInterface.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.api;
2 |
3 | import java.util.Map;
4 |
5 | /**
6 | * Interface definition for spread.
7 | *
8 | * @author Laffini
9 | *
10 | */
11 | public interface ISpreadInterface {
12 |
13 | /**
14 | * Retrieve the order book spread.
15 | *
16 | * @return spread
17 | */
18 | Map getSpread();
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/api/OrderInterface.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.api;
2 |
3 | import java.util.HashMap;
4 | import java.util.List;
5 | import java.util.Map;
6 | import java.util.Optional;
7 |
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.context.ApplicationEventPublisher;
10 | import org.springframework.stereotype.Service;
11 |
12 | import net.laffyco.javamatchingengine.core.engine.Order;
13 | import net.laffyco.javamatchingengine.core.engine.OrderBook;
14 | import net.laffyco.javamatchingengine.core.engine.Side;
15 | import net.laffyco.javamatchingengine.core.engine.Trade;
16 | import net.laffyco.javamatchingengine.core.events.OrderAddedEvent;
17 | import net.laffyco.javamatchingengine.core.events.OrderMatchedEvent;
18 |
19 | /**
20 | * Controller for orders.
21 | *
22 | * @author Laffini
23 | *
24 | */
25 | @Service
26 | public class OrderInterface implements IOrderInterface {
27 |
28 | /**
29 | * Order book.
30 | */
31 | @Autowired
32 | private OrderBook orderBook;
33 |
34 | /**
35 | * Event publisher.
36 | */
37 | @Autowired
38 | private ApplicationEventPublisher applicationEventPublisher;
39 |
40 | @Override
41 | public final Map> getOrders() {
42 | final Map> response = new HashMap<>();
43 | response.put("buy", this.orderBook.getBuyOrders());
44 | response.put("sell", this.orderBook.getSellOrders());
45 | return response;
46 | }
47 |
48 | @Override
49 | public final Map getOrder(final String id, final Side side) {
50 | final Map response = new HashMap<>();
51 |
52 | response.put("order", this.orderBook.findOrder(id, side));
53 |
54 | return response;
55 | }
56 |
57 | @Override
58 | public final Map addOrder(final Side side,
59 | final double amount, final double price) {
60 | final Map response = new HashMap<>();
61 |
62 | final Order order = new Order.Builder(side).withAmount(amount)
63 | .atPrice(price).build();
64 |
65 | final List trades = this.orderBook.process(order);
66 |
67 | response.put("id", order.getId());
68 | response.put("trades", trades);
69 | this.applicationEventPublisher
70 | .publishEvent(new OrderAddedEvent(this, order));
71 |
72 | if (!trades.isEmpty()) {
73 | this.applicationEventPublisher
74 | .publishEvent(new OrderMatchedEvent(this, trades));
75 | }
76 |
77 | return response;
78 | }
79 |
80 | @Override
81 | public final Map deleteOrder(final String id,
82 | final Side side) {
83 | final Map response = new HashMap<>();
84 |
85 | final boolean result = this.orderBook.cancelOrder(id, side);
86 |
87 | response.put("order_deleted", result);
88 |
89 | return response;
90 | }
91 |
92 | @Override
93 | public final Map updateOrder(final String id,
94 | final Side side, final Optional newAmount,
95 | final Optional newPrice, final Optional newSide) {
96 |
97 | final Map response = new HashMap<>();
98 |
99 | boolean isUpdated = false;
100 |
101 | final Order toUpdate = this.orderBook.findOrder(id, side);
102 |
103 | if (newAmount.isPresent()) {
104 | // Update amount.
105 | toUpdate.setAmount(newAmount.get());
106 | isUpdated = true;
107 | }
108 |
109 | if (newPrice.isPresent()) {
110 | // Update price.
111 | toUpdate.setPrice(newPrice.get());
112 | isUpdated = true;
113 | }
114 |
115 | if (newSide.isPresent()) {
116 | // Update side.
117 | toUpdate.setSide(newSide.get());
118 | isUpdated = true;
119 | }
120 |
121 | response.put("updated", isUpdated);
122 |
123 | return response;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/api/SpreadInterface.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.api;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.stereotype.Service;
8 |
9 | import net.laffyco.javamatchingengine.core.engine.OrderBook;
10 |
11 | /**
12 | * Spread controller.
13 | *
14 | * @author Laffini
15 | *
16 | */
17 | @Service
18 | public class SpreadInterface implements ISpreadInterface {
19 |
20 | /**
21 | * Order book.
22 | */
23 | @Autowired
24 | private OrderBook orderBook;
25 |
26 | @Override
27 | public final Map getSpread() {
28 | final Map response = new HashMap<>();
29 | final double spread = this.orderBook.getSpread();
30 | response.put("spread", spread);
31 | return response;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/api/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine.core.api;
6 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/engine/Order.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.engine;
2 |
3 | import java.util.Date;
4 | import java.util.Optional;
5 | import java.util.UUID;
6 |
7 | /**
8 | * Orders.
9 | *
10 | * @author Laffini
11 | *
12 | */
13 | public class Order implements Comparable {
14 |
15 | /**
16 | * Size of the order.
17 | */
18 | private double amount;
19 |
20 | /**
21 | * Order price.
22 | */
23 | private double price;
24 |
25 | /**
26 | * ID.
27 | */
28 | private String id;
29 |
30 | /**
31 | * Side of the order.
32 | */
33 | private Side side;
34 |
35 | /**
36 | * Date & Time of order.
37 | */
38 | private final Date dateTimeOfOrder;
39 |
40 | /**
41 | * Class for building orders.
42 | *
43 | * @author Laffini
44 | *
45 | */
46 | public static class Builder {
47 |
48 | /**
49 | * Size of the order.
50 | */
51 | private double amount;
52 |
53 | /**
54 | * Order price.
55 | */
56 | private double price;
57 |
58 | /**
59 | * Side of the order.
60 | */
61 | private final Side side;
62 |
63 | /**
64 | * Date & Time of order.
65 | */
66 | private Date dateTimeOfOrder;
67 |
68 | /**
69 | * ID.
70 | */
71 | private String id;
72 |
73 | /**
74 | * Builder constructor.
75 | *
76 | * @param pSide
77 | */
78 | public Builder(final Side pSide) {
79 | this.side = pSide;
80 | }
81 |
82 | /**
83 | * The order's amount.
84 | *
85 | * @param amt
86 | * @return The builder.
87 | */
88 | public Builder withAmount(final double amt) {
89 | this.amount = amt;
90 | return this;
91 | }
92 |
93 | /**
94 | * The order's price.
95 | *
96 | * @param pPrice
97 | * @return The builder.
98 | */
99 | public Builder atPrice(final double pPrice) {
100 | this.price = pPrice;
101 | return this;
102 | }
103 |
104 | /**
105 | * The order's ID.
106 | *
107 | * @param pId
108 | * @return The builder.
109 | */
110 | public Builder withId(final String pId) {
111 | this.id = pId;
112 | return this;
113 | }
114 |
115 | /**
116 | * The order's date time stamp.
117 | *
118 | * @param dateTime
119 | * @return The builder.
120 | */
121 | public Builder withDateTime(final Date dateTime) {
122 | this.dateTimeOfOrder = dateTime;
123 | return this;
124 | }
125 |
126 | /**
127 | * Build an order.
128 | *
129 | * @return The new order.
130 | */
131 | public Order build() {
132 | return new Order(this.amount, this.price,
133 | Optional.ofNullable(this.id), this.side,
134 | Optional.ofNullable(this.dateTimeOfOrder));
135 | }
136 |
137 | }
138 |
139 | /**
140 | * Create an instance of Order. Private so that the builder is used.
141 | *
142 | * @param pAmount
143 | * @param pPrice
144 | * @param pId
145 | * @param pSide
146 | * @param pDateTimeOfOrder
147 | */
148 | private Order(final double pAmount, final double pPrice,
149 | final Optional pId, final Side pSide,
150 | final Optional pDateTimeOfOrder) {
151 |
152 | if (pPrice <= 0) {
153 | throw new IllegalArgumentException(
154 | "Order prices must be greater than zero");
155 | } else if (pAmount <= 0) {
156 | throw new IllegalArgumentException(
157 | "Order amounts must be greater than zero");
158 | }
159 |
160 | this.amount = pAmount;
161 | this.price = pPrice;
162 | this.id = pId.orElse(UUID.randomUUID().toString());
163 | this.side = pSide;
164 | this.dateTimeOfOrder = pDateTimeOfOrder.orElse(new Date());
165 | }
166 |
167 | /**
168 | * @return the amount
169 | */
170 | public double getAmount() {
171 | return this.amount;
172 | }
173 |
174 | /**
175 | * @param pAmount the amount to set
176 | */
177 | public void setAmount(final double pAmount) {
178 | this.amount = pAmount;
179 | }
180 |
181 | /**
182 | * @return the price
183 | */
184 | public double getPrice() {
185 | return this.price;
186 | }
187 |
188 | /**
189 | * @param pPrice the price to set
190 | */
191 | public void setPrice(final double pPrice) {
192 | this.price = pPrice;
193 | }
194 |
195 | /**
196 | * @return the id
197 | */
198 | public String getId() {
199 | return this.id;
200 | }
201 |
202 | /**
203 | * @param pId the id to set
204 | */
205 | public void setId(final String pId) {
206 | this.id = pId;
207 | }
208 |
209 | /**
210 | * @return the side
211 | */
212 | public Side getSide() {
213 | return this.side;
214 | }
215 |
216 | /**
217 | * @param pSide the side to set
218 | */
219 | public void setSide(final Side pSide) {
220 | this.side = pSide;
221 | }
222 |
223 | /**
224 | * @return the dateTimeOfOrder
225 | */
226 | public Date getDateTimeOfOrder() {
227 | return this.dateTimeOfOrder;
228 | }
229 |
230 | /**
231 | * Compare two orders by price & time.
232 | *
233 | * @param o
234 | */
235 | @Override
236 | public int compareTo(final Order o) {
237 |
238 | if (Double.compare(this.getPrice(), o.getPrice()) == 0) {
239 | // If equal, check date & time.
240 | if (this.getDateTimeOfOrder().before(o.getDateTimeOfOrder())) {
241 | // The current Order occurred before, therefore should appear
242 | // first.
243 | return -1;
244 | } else {
245 | return 1;
246 | }
247 |
248 | } else {
249 | return Double.compare(this.getPrice(), o.getPrice());
250 | }
251 | }
252 |
253 | }
254 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/engine/OrderBook.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.engine;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * The order book.
11 | *
12 | * @author Laffini
13 | *
14 | */
15 | @Component
16 | public class OrderBook {
17 |
18 | /**
19 | * List of asking buy orders.
20 | */
21 | private List buyOrders;
22 |
23 | /**
24 | * List of asking sell orders.
25 | */
26 | private List sellOrders;
27 |
28 | /**
29 | * Last sale price.
30 | */
31 | private double lastSalePrice;
32 |
33 | /**
34 | * Constructor.
35 | */
36 | public OrderBook() {
37 | this.buyOrders = new ArrayList<>();
38 | this.sellOrders = new ArrayList<>();
39 | }
40 |
41 | /**
42 | * Process an order and return the trades generated before adding the
43 | * remaining amount to the market.
44 | *
45 | * @param pOrder
46 | * @return trades
47 | */
48 | public synchronized List process(final Order pOrder) {
49 | if (pOrder.getSide() == Side.BUY) {
50 | return this.processLimitBuy(pOrder);
51 | } else {
52 | return this.processLimitSell(pOrder);
53 | }
54 | }
55 |
56 | /**
57 | * Process limit buy.
58 | *
59 | * @param order
60 | * @return trades
61 | */
62 | private synchronized List processLimitBuy(final Order order) {
63 | final ArrayList trades = new ArrayList<>();
64 |
65 | final int n = this.sellOrders.size();
66 |
67 | // Check if order book is empty.
68 | if (n != 0) {
69 | // Check if at least one matching order.
70 | if (this.sellOrders.get(n - 1).getPrice() <= order.getPrice()) {
71 |
72 | // Traverse matching orders
73 | while (true) {
74 | final Order sellOrder = this.sellOrders.get(0);
75 | if (sellOrder.getPrice() > order.getPrice()) {
76 | break;
77 | }
78 | // Fill entire order.
79 | if (sellOrder.getAmount() >= order.getAmount()) {
80 | trades.add(new Trade(order.getId(), sellOrder.getId(),
81 | order.getAmount(), sellOrder.getPrice()));
82 | sellOrder.setAmount(
83 | sellOrder.getAmount() - order.getAmount());
84 | if (sellOrder.getAmount() == 0) {
85 | this.removeSellOrder(0);
86 | }
87 | this.setLastSalePrice(sellOrder.getPrice());
88 | return trades;
89 | }
90 |
91 | // Fill partial order & continue.
92 | if (sellOrder.getAmount() < order.getAmount()) {
93 | trades.add(new Trade(order.getId(), sellOrder.getId(),
94 | sellOrder.getAmount(), sellOrder.getPrice()));
95 | order.setAmount(
96 | order.getAmount() - sellOrder.getAmount());
97 | this.removeSellOrder(0);
98 | this.setLastSalePrice(sellOrder.getPrice());
99 | continue;
100 | }
101 | }
102 | }
103 | }
104 |
105 | // Add remaining order to book.
106 | this.buyOrders.add(order);
107 |
108 | Collections.sort(this.buyOrders);
109 |
110 | return trades;
111 | }
112 |
113 | /**
114 | * Process a limit sell.
115 | *
116 | * @param order
117 | * @return Trades.
118 | */
119 | private synchronized List processLimitSell(final Order order) {
120 |
121 | final ArrayList trades = new ArrayList<>();
122 |
123 | final int n = this.buyOrders.size();
124 |
125 | // Check if order book is empty.
126 | double currentPrice;
127 | if (n == 0) {
128 | currentPrice = -1;
129 | } else {
130 | currentPrice = this.buyOrders.get(n - 1).getPrice();
131 | }
132 |
133 | // Check that there is at least one matching order.
134 | if (n != 0 || currentPrice >= order.getPrice()) {
135 | // Traverse all matching orders.
136 | for (int i = 0; i >= 0; i++) {
137 | final Order buyOrder = this.buyOrders.get(0);
138 |
139 | // Fill entire order.
140 | if (buyOrder.getAmount() >= order.getAmount()) {
141 | trades.add(new Trade(order.getId(), buyOrder.getId(),
142 | order.getAmount(), buyOrder.getPrice()));
143 | buyOrder.setAmount(
144 | buyOrder.getAmount() - order.getAmount());
145 | if (buyOrder.getAmount() == 0) {
146 | this.removeBuyOrder(0);
147 | }
148 | this.setLastSalePrice(buyOrder.getPrice());
149 | return trades;
150 | }
151 |
152 | // Fill partial order and continue.
153 | if (buyOrder.getAmount() < order.getAmount()) {
154 | trades.add(new Trade(order.getId(), buyOrder.getId(),
155 | buyOrder.getAmount(), buyOrder.getPrice()));
156 | order.setAmount(order.getAmount() - buyOrder.getAmount());
157 | this.removeBuyOrder(0);
158 | this.setLastSalePrice(buyOrder.getPrice());
159 | continue;
160 | }
161 | }
162 | }
163 | // Add remaining order to the list.
164 | this.sellOrders.add(order);
165 |
166 | Collections.sort(this.sellOrders);
167 |
168 | return trades;
169 | }
170 |
171 | /**
172 | * Calculate the spread.
173 | *
174 | * @return Difference in buy and sell books.
175 | */
176 | public double getSpread() {
177 |
178 | if (this.buyOrders.size() != 0 && this.sellOrders.size() != 0) {
179 | final double buyOrderPrice = this.buyOrders
180 | .get(this.buyOrders.size() - 1).getPrice();
181 |
182 | final double sellOrderPrice = this.sellOrders.get(0).getPrice();
183 |
184 | return sellOrderPrice - buyOrderPrice;
185 | }
186 | return 0;
187 | }
188 |
189 | /**
190 | * Find an order by ID.
191 | *
192 | * @param id
193 | * @param side
194 | * @return the order (or null if not found)
195 | */
196 | public synchronized Order findOrder(final String id, final Side side) {
197 | List toSearch;
198 | if (side == Side.BUY) {
199 | toSearch = this.buyOrders;
200 | } else {
201 | toSearch = this.sellOrders;
202 | }
203 | return toSearch.stream().filter(order -> order.getId().equals(id))
204 | .findFirst().orElse(null);
205 | }
206 |
207 | /**
208 | * Cancel an order when the side is known.
209 | *
210 | * @param orderId
211 | * @param side
212 | * @return is order cancelled.
213 | */
214 | public synchronized boolean cancelOrder(final String orderId,
215 | final Side side) {
216 | if (side == Side.BUY) {
217 | // Search buy orders.
218 | return this.cancel(orderId, this.buyOrders);
219 | } else if (side == Side.SELL) {
220 | // Search sell orders.
221 | return this.cancel(orderId, this.sellOrders);
222 | } else {
223 | return false;
224 | }
225 | }
226 |
227 | /**
228 | * Cancel an order from an order book.
229 | *
230 | * @param orderId
231 | * @param orderBook
232 | * @return whether an order has been cancelled
233 | */
234 | private synchronized boolean cancel(final String orderId,
235 | final List orderBook) {
236 | // Loop through order book to find order.
237 | for (int i = 0; i < orderBook.size(); i++) {
238 | final Order currentOrder = orderBook.get(i);
239 | if (currentOrder.getId().equals(orderId)) {
240 | orderBook.remove(i);
241 | return true; // Order cancelled.
242 | }
243 | }
244 | return false; // No order cancelled.
245 | }
246 |
247 | /**
248 | * Remove a buy order from the order book.
249 | *
250 | * @param index
251 | */
252 | private synchronized void removeBuyOrder(final int index) {
253 | this.buyOrders.remove(index);
254 | }
255 |
256 | /**
257 | * Remove a sell order from the order book.
258 | *
259 | * @param index
260 | */
261 | private synchronized void removeSellOrder(final int index) {
262 | this.sellOrders.remove(index);
263 | }
264 |
265 | /**
266 | * @return the buyOrders
267 | */
268 | public synchronized List getBuyOrders() {
269 | return this.buyOrders;
270 | }
271 |
272 | /**
273 | * @param pBuyOrders the buyOrders to set
274 | */
275 | public synchronized void setBuyOrders(final ArrayList pBuyOrders) {
276 | this.buyOrders = pBuyOrders;
277 | }
278 |
279 | /**
280 | * @return the sellOrders
281 | */
282 | public synchronized List getSellOrders() {
283 | return this.sellOrders;
284 | }
285 |
286 | /**
287 | * @param pSellOrders the sellOrders to set
288 | */
289 | public synchronized void setSellOrders(final ArrayList pSellOrders) {
290 | this.sellOrders = pSellOrders;
291 | }
292 |
293 | /**
294 | * @return the lastSalePrice
295 | */
296 | public synchronized double getLastSalePrice() {
297 | return this.lastSalePrice;
298 | }
299 |
300 | /**
301 | * @param pLastSalePrice the lastSalePrice to set
302 | */
303 | public synchronized void setLastSalePrice(final double pLastSalePrice) {
304 | this.lastSalePrice = pLastSalePrice;
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/engine/Side.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.engine;
2 |
3 | /**
4 | * Order sides.
5 | *
6 | * @author Laffini
7 | *
8 | */
9 | public enum Side {
10 |
11 | /**
12 | * Buy orders.
13 | */
14 | BUY,
15 |
16 | /**
17 | * Sell orders.
18 | */
19 | SELL
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/engine/Trade.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.engine;
2 |
3 | /**
4 | * Trades.
5 | *
6 | * @author Laffini
7 | *
8 | */
9 | public class Trade {
10 |
11 | /**
12 | * Taker's order ID.
13 | */
14 | private String takerOrderId;
15 |
16 | /**
17 | * Maker's order ID.
18 | */
19 | private String makerOrderId;
20 |
21 | /**
22 | * Trade amount.
23 | */
24 | private double amount;
25 |
26 | /**
27 | * Trade price.
28 | */
29 | private double price;
30 |
31 | /**
32 | * Create an instance of Trade.
33 | *
34 | * @param pTakerOrderId
35 | * @param pMakerOrderId
36 | * @param pAmount
37 | * @param pPrice
38 | */
39 | public Trade(final String pTakerOrderId, final String pMakerOrderId,
40 | final double pAmount, final double pPrice) {
41 | super();
42 | this.takerOrderId = pTakerOrderId;
43 | this.makerOrderId = pMakerOrderId;
44 | this.amount = pAmount;
45 | this.price = pPrice;
46 | }
47 |
48 | /**
49 | * @return the takerOrderId
50 | */
51 | public String getTakerOrderId() {
52 | return this.takerOrderId;
53 | }
54 |
55 | /**
56 | * @param pTakerOrderId the takerOrderId to set
57 | */
58 | public void setTakerOrderId(final String pTakerOrderId) {
59 | this.takerOrderId = pTakerOrderId;
60 | }
61 |
62 | /**
63 | * @return the makerOrderId
64 | */
65 | public String getMakerOrderId() {
66 | return this.makerOrderId;
67 | }
68 |
69 | /**
70 | * @param pMakerOrderId the makerOrderId to set
71 | */
72 | public void setMakerOrderId(final String pMakerOrderId) {
73 | this.makerOrderId = pMakerOrderId;
74 | }
75 |
76 | /**
77 | * @return the amount
78 | */
79 | public double getAmount() {
80 | return this.amount;
81 | }
82 |
83 | /**
84 | * @param pAmount the amount to set
85 | */
86 | public void setAmount(final double pAmount) {
87 | this.amount = pAmount;
88 | }
89 |
90 | /**
91 | * @return the price
92 | */
93 | public double getPrice() {
94 | return this.price;
95 | }
96 |
97 | /**
98 | * @param pPrice the price to set
99 | */
100 | public void setPrice(final double pPrice) {
101 | this.price = pPrice;
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/engine/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine.core.engine;
6 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/events/OrderAddedEvent.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.events;
2 |
3 | import org.springframework.context.ApplicationEvent;
4 |
5 | import net.laffyco.javamatchingengine.core.engine.Order;
6 |
7 | /**
8 | * Event for when an order is added to the engine.
9 | *
10 | * @author Laffini
11 | *
12 | */
13 | public class OrderAddedEvent extends ApplicationEvent {
14 |
15 | /**
16 | * The order added.
17 | */
18 | private final Order order;
19 |
20 | /**
21 | * Constructor.
22 | *
23 | * @param source
24 | * @param pOrder
25 | */
26 | public OrderAddedEvent(final Object source, final Order pOrder) {
27 | super(source);
28 | this.order = pOrder;
29 | }
30 |
31 | /**
32 | * Get order.
33 | *
34 | * @return the order
35 | */
36 | public Order getOrder() {
37 | return this.order;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/events/OrderMatchedEvent.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.events;
2 |
3 | import java.util.List;
4 |
5 | import org.springframework.context.ApplicationEvent;
6 |
7 | import net.laffyco.javamatchingengine.core.engine.Trade;
8 |
9 | /**
10 | * Order matched event.
11 | *
12 | * @author Laffini
13 | *
14 | */
15 | public class OrderMatchedEvent extends ApplicationEvent {
16 |
17 | /**
18 | * Completed trades.
19 | */
20 | private final List trades;
21 |
22 | /**
23 | * Constructor.
24 | *
25 | * @param source
26 | * @param pTrades
27 | */
28 | public OrderMatchedEvent(final Object source, final List pTrades) {
29 | super(source);
30 | this.trades = pTrades;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/events/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine.core.events;
6 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/core/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine.core;
6 |
--------------------------------------------------------------------------------
/src/main/java/net/laffyco/javamatchingengine/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine;
6 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/api/OrderInterfaceTests.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.api;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertTrue;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Optional;
10 |
11 | import javax.annotation.Resource;
12 |
13 | import org.junit.jupiter.api.DisplayName;
14 | import org.junit.jupiter.api.Test;
15 | import org.mockito.ArgumentCaptor;
16 | import org.mockito.Captor;
17 | import org.mockito.InjectMocks;
18 | import org.mockito.Mock;
19 | import org.mockito.Mockito;
20 | import org.springframework.context.ApplicationEvent;
21 | import org.springframework.context.ApplicationEventPublisher;
22 |
23 | import net.laffyco.javamatchingengine.core.engine.Order;
24 | import net.laffyco.javamatchingengine.core.engine.OrderBook;
25 | import net.laffyco.javamatchingengine.core.engine.Side;
26 | import net.laffyco.javamatchingengine.core.engine.Trade;
27 | import net.laffyco.javamatchingengine.core.events.OrderAddedEvent;
28 | import net.laffyco.javamatchingengine.core.events.OrderMatchedEvent;
29 | import test.utils.AbstractTest;
30 |
31 | /**
32 | * Tests for the OrderInterface class.
33 | *
34 | * @author Laffini
35 | *
36 | */
37 | public class OrderInterfaceTests extends AbstractTest {
38 |
39 | /**
40 | * Mock order book.
41 | */
42 | @Mock
43 | private OrderBook orderBook;
44 |
45 | /**
46 | * Event publisher.
47 | */
48 | @Mock
49 | private ApplicationEventPublisher applicationEventPublisher;
50 |
51 | /**
52 | * Controller under test.
53 | */
54 | @InjectMocks
55 | @Resource
56 | private OrderInterface controller;
57 |
58 | /**
59 | * Key captor.
60 | */
61 | @Captor
62 | private ArgumentCaptor captor;
63 |
64 | /**
65 | * Test data id.
66 | */
67 | private final String id = "id";
68 |
69 | /**
70 | * Test data side.
71 | */
72 | private final Side side = Side.BUY;
73 |
74 | /** Create a mock order to return. */
75 | private final Order mockOrder = Mockito.mock(Order.class);
76 |
77 | /**
78 | * Test data amount.
79 | */
80 | private final double amt = 25.0;
81 |
82 | /**
83 | * Test data price.
84 | */
85 | private final double price = 25;
86 |
87 | @Override
88 | public final void init() {
89 | Mockito.when(this.orderBook.findOrder(this.id, this.side))
90 | .thenReturn(this.mockOrder);
91 | }
92 |
93 | /**
94 | * Get orders test.
95 | */
96 | @Test
97 | @DisplayName("Get the orders")
98 | public void getOrders() {
99 | final Map> result = this.controller.getOrders();
100 |
101 | Mockito.verify(this.orderBook).getBuyOrders();
102 | Mockito.verify(this.orderBook).getSellOrders();
103 |
104 | assertTrue(result.containsKey("buy"));
105 | assertTrue(result.containsKey("sell"));
106 | }
107 |
108 | /**
109 | * Get order by ID test.
110 | */
111 | @Test
112 | @DisplayName("Get an order by ID")
113 | public void getOrderById() {
114 | final Map result = this.controller.getOrder(this.id,
115 | this.side);
116 | assertTrue(result.containsKey("order"));
117 | assertEquals(result.get("order"), this.mockOrder);
118 | }
119 |
120 | /**
121 | * Add order test.
122 | */
123 | @Test
124 | @DisplayName("Add an order")
125 | public void addOrder() {
126 | final List trades = new ArrayList();
127 | Mockito.when(this.orderBook.process(Mockito.any())).thenReturn(trades);
128 |
129 | final Map result = this.controller.addOrder(this.side,
130 | this.amt, this.price);
131 |
132 | assertTrue(result.containsKey(this.id));
133 | assertTrue(result.containsKey("trades"));
134 | assertEquals(result.get("trades"), trades);
135 | }
136 |
137 | /**
138 | * Checking that the correct events are published.
139 | */
140 | @Test
141 | @DisplayName("Check that appropriate events are published")
142 | public void events() {
143 | final List trades = new ArrayList() {
144 | {
145 | this.add(null);
146 | }
147 | };
148 | Mockito.when(this.orderBook.process(Mockito.any())).thenReturn(trades);
149 |
150 | this.controller.addOrder(this.side, this.amt, this.price);
151 |
152 | Mockito.verify(this.applicationEventPublisher, Mockito.times(2))
153 | .publishEvent(this.captor.capture());
154 |
155 | assertTrue(
156 | this.captor.getAllValues().get(0) instanceof OrderAddedEvent);
157 | assertTrue(
158 | this.captor.getAllValues().get(1) instanceof OrderMatchedEvent);
159 | }
160 |
161 | /**
162 | * Delete an order.
163 | */
164 | @Test
165 | @DisplayName("Delete an order")
166 | public void deleteOrder() {
167 |
168 | Mockito.when(this.orderBook.cancelOrder(this.id, this.side))
169 | .thenReturn(true);
170 |
171 | final Map result = this.controller.deleteOrder(this.id,
172 | this.side);
173 |
174 | assertTrue((boolean) result.get("order_deleted"));
175 |
176 | Mockito.verify(this.orderBook).cancelOrder(this.id, this.side);
177 | }
178 |
179 | /**
180 | * Update an order.
181 | */
182 | @Test
183 | @DisplayName("Update an order")
184 | public void updateOrder() {
185 | final Side newSide = Side.SELL;
186 |
187 | final Map result = this.controller.updateOrder(this.id,
188 | this.side, Optional.of(this.amt), Optional.of(this.price),
189 | Optional.of(newSide));
190 |
191 | assertTrue((boolean) result.get("updated"));
192 | Mockito.verify(this.mockOrder).setAmount(this.amt);
193 | Mockito.verify(this.mockOrder).setPrice(this.price);
194 | Mockito.verify(this.mockOrder).setSide(newSide);
195 | }
196 |
197 | /**
198 | * Update an order with one parameter.
199 | */
200 | @Test
201 | @DisplayName("Update an order with one parameter")
202 | public void updateOrder1Param() {
203 | final Map result = this.controller.updateOrder(this.id,
204 | this.side, Optional.of(this.amt), Optional.empty(),
205 | Optional.empty());
206 |
207 | assertTrue((boolean) result.get("updated"));
208 | Mockito.verify(this.mockOrder).setAmount(this.amt);
209 | Mockito.verify(this.mockOrder, Mockito.times(0))
210 | .setPrice(Mockito.anyDouble());
211 | Mockito.verify(this.mockOrder, Mockito.times(0))
212 | .setSide(Mockito.any(Side.class));
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/api/SpreadInterfaceTests.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.api;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import java.util.Map;
6 |
7 | import javax.annotation.Resource;
8 |
9 | import org.junit.jupiter.api.DisplayName;
10 | import org.junit.jupiter.api.Test;
11 | import org.mockito.InjectMocks;
12 | import org.mockito.Mock;
13 | import org.mockito.Mockito;
14 |
15 | import net.laffyco.javamatchingengine.core.engine.OrderBook;
16 | import test.utils.AbstractTest;
17 |
18 | /**
19 | * @author Laffini
20 | *
21 | */
22 | public class SpreadInterfaceTests extends AbstractTest {
23 |
24 | /**
25 | * Mock order book.
26 | */
27 | @Mock
28 | private OrderBook orderBook;
29 |
30 | /**
31 | * Controller under test.
32 | */
33 | @InjectMocks
34 | @Resource
35 | private SpreadInterface controller;
36 |
37 | @Override
38 | public void init() {
39 | // Left blank intentionally.
40 | }
41 |
42 | /**
43 | * Get spread.
44 | */
45 | @Test
46 | @DisplayName("Get the spread")
47 | public void getSpread() {
48 | final double spread = 3.0;
49 | Mockito.when(this.orderBook.getSpread()).thenReturn(spread);
50 | final Map result = this.controller.getSpread();
51 | assertEquals(spread, result.get("spread"));
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/api/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine.core.api;
6 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/engine/CancelOrderTests.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.engine;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import javax.annotation.Resource;
6 |
7 | import org.junit.jupiter.api.DisplayName;
8 | import org.junit.jupiter.api.Test;
9 | import org.mockito.InjectMocks;
10 |
11 | /**
12 | * Test the cancel order functionality.
13 | *
14 | * @author Laffini
15 | *
16 | */
17 | public class CancelOrderTests extends MatchingEngineTest {
18 |
19 | /**
20 | * Order book.
21 | */
22 | @InjectMocks
23 | @Resource
24 | private OrderBook orderBook;
25 |
26 | /**
27 | * Attempt to cancel an order that doesn't exist.
28 | *
29 | */
30 | @Test
31 | @DisplayName("Attempt to cancel an order that doesn't exist")
32 | public void cancelNoCancellation() {
33 | assertEquals(this.orderBook.cancelOrder("test", Side.BUY), false);
34 | assertEquals(this.orderBook.cancelOrder("test", Side.SELL), false);
35 | }
36 |
37 | /**
38 | * Cancel a buy order, with side not given.
39 | *
40 | * @throws InterruptedException
41 | */
42 | @Test
43 | @DisplayName("Cancel a buy order with the side not given")
44 | public void cancelBuyOrder() throws InterruptedException {
45 |
46 | final int amt = 1;
47 | final double price = 1;
48 | final Side side = Side.BUY;
49 | // Create order.
50 | final Order order = new Order.Builder(side).atPrice(price)
51 | .withAmount(amt).build();
52 |
53 | // Process order
54 | this.orderBook.process(order);
55 |
56 | // Assert only 1 order in book.
57 | assertEquals(this.orderBook.getBuyOrders().size(), 1);
58 |
59 | // Cancel order
60 | assertEquals(this.orderBook.cancelOrder(order.getId(), side), true);
61 | assertEquals(this.orderBook.getBuyOrders().size(), 0);
62 | }
63 |
64 | /**
65 | * Cancel a buy order, with side given.
66 | *
67 | */
68 | @Test
69 | @DisplayName("Cancel a buy order")
70 | public void cancelBuyOrderSideGiven() {
71 |
72 | final int amt = 1;
73 | final double price = 1;
74 | final Side side = Side.BUY;
75 | // Create order.
76 | final Order order = new Order.Builder(side).atPrice(price)
77 | .withAmount(amt).build();
78 |
79 | // Process order
80 | this.orderBook.process(order);
81 |
82 | // Assert only 1 order in book.
83 | assertEquals(this.orderBook.getBuyOrders().size(), 1);
84 |
85 | // Cancel order
86 | assertEquals(this.orderBook.cancelOrder(order.getId(), side), true);
87 | assertEquals(this.orderBook.getBuyOrders().size(), 0);
88 | }
89 |
90 | /**
91 | * Cancel a sell order, with side not given.
92 | *
93 | * @throws InterruptedException
94 | */
95 | @Test
96 | @DisplayName("Cancel a sell order with the side not given")
97 | public void cancelSellOrder() throws InterruptedException {
98 | final int amt = 1;
99 | final double price = 1;
100 | final Side side = Side.SELL;
101 | // Create order.
102 | final Order order = new Order.Builder(side).atPrice(price)
103 | .withAmount(amt).build();
104 |
105 | // Process order
106 | this.orderBook.process(order);
107 |
108 | // Assert only 1 order in book.
109 | assertEquals(this.orderBook.getSellOrders().size(), 1);
110 |
111 | // Cancel order
112 | assertEquals(this.orderBook.cancelOrder(order.getId(), side), true);
113 | assertEquals(this.orderBook.getSellOrders().size(), 0);
114 | }
115 |
116 | /**
117 | * Cancel a sell order.
118 | *
119 | */
120 | @Test
121 | @DisplayName("Cancel a sell order")
122 | public void cancelSellOrderSideGiven() {
123 | final int amt = 1;
124 | final double price = 1;
125 | final Side side = Side.SELL;
126 | // Create order.
127 | final Order order = new Order.Builder(side).atPrice(price)
128 | .withAmount(amt).build();
129 |
130 | // Process order
131 | this.orderBook.process(order);
132 |
133 | // Assert only 1 order in book.
134 | assertEquals(this.orderBook.getSellOrders().size(), 1);
135 |
136 | // Cancel order
137 | assertEquals(this.orderBook.cancelOrder(order.getId(), side), true);
138 | assertEquals(this.orderBook.getSellOrders().size(), 0);
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/engine/MatchingEngineTest.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.engine;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.mockito.MockitoAnnotations;
5 |
6 | /**
7 | * Common test functionality.
8 | *
9 | * @author Laffini
10 | *
11 | */
12 | public abstract class MatchingEngineTest {
13 |
14 | /**
15 | * Test setup.
16 | */
17 | @BeforeEach
18 | public void setup() {
19 | MockitoAnnotations.openMocks(this);
20 | }
21 |
22 | /**
23 | * Add an array of orders to the order book.
24 | *
25 | * @param orderBook
26 | * @param orders
27 | */
28 | public void addOrders(final OrderBook orderBook, final Order[] orders) {
29 | for (int i = 0; i < orders.length; i++) {
30 | orderBook.process(orders[i]);
31 | }
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/engine/OrderBookTests.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.engine;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertFalse;
5 | import static org.junit.jupiter.api.Assertions.assertTrue;
6 |
7 | import java.util.List;
8 |
9 | import javax.annotation.Resource;
10 |
11 | import org.junit.jupiter.api.DisplayName;
12 | import org.junit.jupiter.api.Test;
13 | import org.mockito.InjectMocks;
14 |
15 | /**
16 | * Order book tests.
17 | *
18 | * @author Laffini
19 | *
20 | */
21 | public class OrderBookTests extends MatchingEngineTest {
22 |
23 | /**
24 | * Test amount.
25 | */
26 | private final double amount = 3;
27 |
28 | /**
29 | * Test price.
30 | */
31 | private final double price = 2;
32 |
33 | /**
34 | * Array of orders.
35 | */
36 | private final Order[] orders = {
37 | new Order.Builder(Side.BUY).withAmount(this.amount)
38 | .atPrice(this.price).build(),
39 | new Order.Builder(Side.SELL).withAmount(this.amount)
40 | .atPrice(this.price).build() };
41 |
42 | /**
43 | * Test OrderBook.
44 | */
45 | @InjectMocks
46 | @Resource
47 | private OrderBook orderBook;
48 |
49 | /**
50 | * Add a buy order, then add a matching sell order.
51 | */
52 | @Test
53 | @DisplayName("Add a buy order, then add a matching sell order")
54 | public void buyThenSell() {
55 |
56 | // Add buy order
57 | this.orderBook.process(this.orders[0]);
58 |
59 | // There should only be 1 buy order.
60 | assertEquals(this.orderBook.getBuyOrders().size(), 1);
61 |
62 | // There should not be any sell orders
63 | assertEquals(this.orderBook.getSellOrders().size(), 0);
64 |
65 | // Add sell order
66 | final List trades = this.orderBook.process(this.orders[1]);
67 |
68 | // There should not be any buy or sell orders
69 | assertEquals(this.orderBook.getSellOrders().size(), 0);
70 | assertEquals(this.orderBook.getBuyOrders().size(), 0);
71 |
72 | // There should be only 1 trade
73 | assertEquals(trades.size(), 1);
74 |
75 | // Assert the trade details
76 | final Trade trade = trades.get(0);
77 | assertEquals(trade.getPrice(), this.orders[1].getPrice());
78 | assertEquals(trade.getAmount(), this.orders[1].getAmount());
79 |
80 | // Assert last trade price
81 | assertEquals(this.orderBook.getLastSalePrice(), 2);
82 | }
83 |
84 | /**
85 | * Add a sell order, then add a buy order.
86 | */
87 | @Test
88 | @DisplayName("Add a sell order, then add a matching buy order")
89 | public void sellThenBuy() {
90 |
91 | // Add sell order
92 | this.orderBook.process(this.orders[1]);
93 |
94 | // There should only be 1 sell order.
95 | assertEquals(this.orderBook.getSellOrders().size(), 1);
96 |
97 | // There should not be any buy orders
98 | assertEquals(this.orderBook.getBuyOrders().size(), 0);
99 |
100 | // Add buy order
101 | final List trades = this.orderBook.process(this.orders[0]);
102 |
103 | // There should not be any buy or sell orders
104 | assertEquals(this.orderBook.getSellOrders().size(), 0);
105 | assertEquals(this.orderBook.getBuyOrders().size(), 0);
106 |
107 | // There should be only 1 trade
108 | assertEquals(trades.size(), 1);
109 |
110 | // Assert the trade details
111 | final Trade trade = trades.get(0);
112 | assertEquals(trade.getPrice(), this.orders[0].getPrice());
113 | assertEquals(trade.getAmount(), this.orders[0].getAmount());
114 |
115 | // Assert last trade price
116 | assertEquals(this.orderBook.getLastSalePrice(), 2);
117 | }
118 |
119 | /**
120 | * Find order tests.
121 | */
122 | @Test
123 | @DisplayName("Find orders")
124 | public void findOrder() {
125 |
126 | // Can't find an order that hasn't been added to the book.
127 | assertEquals(this.orderBook.findOrder(this.orders[1].getId(),
128 | this.orders[1].getSide()), null);
129 | assertEquals(this.orderBook.findOrder(this.orders[0].getId(),
130 | this.orders[0].getSide()), null);
131 |
132 | // Add sell order
133 | this.orderBook.process(this.orders[1]);
134 |
135 | // Can find the order now.
136 | assertEquals(this.orderBook.findOrder(this.orders[1].getId(),
137 | this.orders[1].getSide()), this.orders[1]);
138 |
139 | // Add & find buy order.
140 | this.orderBook.process(this.orders[0]);
141 | // Add twice as first is matched with previous sell.
142 | this.orderBook.process(this.orders[0]);
143 | assertEquals(this.orderBook.findOrder(this.orders[0].getId(),
144 | this.orders[0].getSide()), this.orders[0]);
145 | }
146 |
147 | /**
148 | * Test that no orders are cancelled when an invalid side is provided.
149 | */
150 | @Test
151 | @DisplayName("No orders are cancelled when an invalid side is provided")
152 | public void cancelTestInvalidSide() {
153 |
154 | final String orderId = "";
155 |
156 | final boolean result = this.orderBook.cancelOrder(orderId, null);
157 | assertFalse(result);
158 | }
159 |
160 | /**
161 | * A buy order that fills two sell orders.
162 | */
163 | @Test
164 | @DisplayName("A buy order that fills two sell orders")
165 | public void buyPartialFill() {
166 |
167 | // Add two sell orders.
168 | this.orderBook.process(this.orders[1]);
169 | this.orderBook.process(this.orders[1]);
170 |
171 | // Modify buy order to be twice the amount.
172 | final Order buyOrder = this.orders[0];
173 | buyOrder.setAmount(buyOrder.getAmount() * 2);
174 |
175 | // Add the buy order.
176 | final List trades = this.orderBook.process(buyOrder);
177 |
178 | // Two trades should have taken place.
179 | assertTrue(trades.size() == 2);
180 |
181 | // Order book should be empty.
182 | assertTrue(this.orderBook.getBuyOrders().isEmpty());
183 | assertTrue(this.orderBook.getSellOrders().isEmpty());
184 |
185 | // The trades match the expected amt and price.
186 | for (final Trade trade : trades) {
187 | assertTrue(trade.getAmount() == buyOrder.getAmount());
188 | assertTrue(trade.getPrice() == buyOrder.getPrice());
189 | }
190 | }
191 |
192 | /**
193 | * A buy order that fills two sell orders.
194 | */
195 | @Test
196 | @DisplayName("A sell order that fills two buy orders")
197 | public void sellPartialFill() {
198 |
199 | // Add two buy orders.
200 | this.orderBook.process(this.orders[0]);
201 | this.orderBook.process(this.orders[0]);
202 |
203 | // Modify sell order to be twice the amount.
204 | final Order sellOrder = this.orders[1];
205 | sellOrder.setAmount(sellOrder.getAmount() * 2);
206 |
207 | // Add the buy order.
208 | final List trades = this.orderBook.process(sellOrder);
209 |
210 | // Two trades should have taken place.
211 | assertTrue(trades.size() == 2);
212 |
213 | // Order book should be empty.
214 | assertTrue(this.orderBook.getBuyOrders().isEmpty());
215 | assertTrue(this.orderBook.getSellOrders().isEmpty());
216 |
217 | // The trades match the expected amt and price.
218 | for (final Trade trade : trades) {
219 | assertTrue(trade.getAmount() == sellOrder.getAmount());
220 | assertTrue(trade.getPrice() == sellOrder.getPrice());
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/engine/OrderTests.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.engine;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertTrue;
5 | import static org.junit.jupiter.api.Assertions.fail;
6 |
7 | import java.util.Date;
8 |
9 | import org.junit.jupiter.api.DisplayName;
10 | import org.junit.jupiter.api.Test;
11 |
12 | /**
13 | * Order tests.
14 | *
15 | * @author Laffini
16 | *
17 | */
18 | public class OrderTests extends MatchingEngineTest {
19 |
20 | /**
21 | * Order amount.
22 | */
23 | private final int amt = 1;
24 |
25 | /**
26 | * Order price.
27 | */
28 | private final double price = 2;
29 |
30 | /**
31 | * Order side.
32 | */
33 | private final Side side = Side.BUY;
34 |
35 | /**
36 | * Compare two orders with different prices.
37 | */
38 | @Test
39 | @DisplayName("Compare two orders with different prices")
40 | public void comparePrice() {
41 | final double price2 = 3;
42 |
43 | final Order order1 = new Order.Builder(this.side).atPrice(this.price)
44 | .withAmount(this.amt).build();
45 | final Order order2 = new Order.Builder(this.side).atPrice(price2)
46 | .withAmount(this.amt).build();
47 |
48 | // Assert
49 | assertTrue(order1.compareTo(order2) < 0);
50 | assertTrue(order2.compareTo(order1) > 0);
51 | }
52 |
53 | /**
54 | * Compare orders with the same price, so therefore time is taken into
55 | * account.
56 | */
57 | @Test
58 | public void compareSamePrice() {
59 |
60 | final Date date1 = new Date(1626518162);
61 | final Date date2 = new Date(1626515150);
62 |
63 | final Order order1 = new Order.Builder(this.side).withAmount(this.amt)
64 | .atPrice(this.price).withDateTime(date1).build();
65 |
66 | final Order order2 = new Order.Builder(this.side).withAmount(this.amt)
67 | .atPrice(this.price).withDateTime(date2).build();
68 |
69 | // Assert
70 | assertTrue(order1.compareTo(order2) > 0);
71 | assertTrue(order2.compareTo(order1) < 0);
72 | }
73 |
74 | /**
75 | * Test exception is thrown when an invalid price is entered.
76 | */
77 | @Test
78 | @DisplayName("An exception is thrown when an invalid order price is entered")
79 | public void invalidPrice() {
80 | final double invalidPrice = -1;
81 |
82 | try {
83 | new Order.Builder(Side.BUY).atPrice(invalidPrice).build();
84 | fail("Should have thrown an exception due to invalid order price");
85 | } catch (final IllegalArgumentException e) {
86 | assertEquals(e.getMessage(),
87 | "Order prices must be greater than zero");
88 |
89 | }
90 | }
91 |
92 | /**
93 | * Test exception is thrown when an invalid amount is entered.
94 | */
95 | @Test
96 | @DisplayName("An exception is thrown when an invalid order amount is entered")
97 | public void invalidAmount() {
98 | final double invalidAmount = -1;
99 |
100 | try {
101 | new Order.Builder(Side.BUY).atPrice(this.price)
102 | .withAmount(invalidAmount).build();
103 | fail("Should have thrown an exception due to invalid order amount");
104 | } catch (final IllegalArgumentException e) {
105 | assertEquals(e.getMessage(),
106 | "Order amounts must be greater than zero");
107 |
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/engine/SpreadTests.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.core.engine;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import javax.annotation.Resource;
6 |
7 | import org.junit.jupiter.api.DisplayName;
8 | import org.junit.jupiter.api.Test;
9 | import org.mockito.InjectMocks;
10 |
11 | /**
12 | * Test the getSpread() functionality.
13 | *
14 | * @author Laffini
15 | */
16 | public class SpreadTests extends MatchingEngineTest {
17 |
18 | /**
19 | * Order book.
20 | */
21 | @InjectMocks
22 | @Resource
23 | private OrderBook orderBook;
24 |
25 | @Test
26 | @DisplayName("Get the spread when there are two orders")
27 | void spreadTwoOrders() {
28 | final Order[] orders = {
29 | new Order.Builder(Side.SELL).withAmount(2).atPrice(2.5)
30 | .withId("sellOrder").build(),
31 | new Order.Builder(Side.BUY).withAmount(2).atPrice(2)
32 | .withId("buyOrder").build() };
33 | this.addOrders(this.orderBook, orders);
34 |
35 | final double expectedSpread = 0.5;
36 |
37 | assertEquals(expectedSpread, this.orderBook.getSpread());
38 | }
39 |
40 | @Test
41 | @DisplayName("Get the spread when there are multiple orders")
42 | void spreadMultipleOrders() {
43 | final Order[] orders = {
44 | new Order.Builder(Side.SELL).withAmount(2).atPrice(2.5)
45 | .withId("sellOrder").build(),
46 | new Order.Builder(Side.SELL).withAmount(2).atPrice(2.75)
47 | .withId("secondSellOrder").build(),
48 | new Order.Builder(Side.BUY).withAmount(2).atPrice(2)
49 | .withId("buyOrder").build(),
50 | new Order.Builder(Side.BUY).withAmount(2).atPrice(1.5)
51 | .withId("secondBuyOrder").build() };
52 | this.addOrders(this.orderBook, orders);
53 |
54 | final double expectedSpread = 0.5;
55 |
56 | assertEquals(expectedSpread, this.orderBook.getSpread());
57 | }
58 |
59 | @Test
60 | @DisplayName("Attempt to get the spread when there are no orders")
61 | void spreadNoOrders() {
62 | assertEquals(this.orderBook.getSpread(), 0);
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/engine/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine.core.engine;
6 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/core/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine.core;
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine;
6 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/scenarios/CucumberRunner.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.scenarios;
2 |
3 | import static io.cucumber.junit.CucumberOptions.SnippetType.CAMELCASE;
4 |
5 | import org.junit.runner.RunWith;
6 |
7 | import io.cucumber.junit.Cucumber;
8 | import io.cucumber.junit.CucumberOptions;
9 |
10 | /**
11 | * Cucumber test runner.
12 | *
13 | * @author Laffini
14 | */
15 | @RunWith(Cucumber.class)
16 | @CucumberOptions(plugin = { "pretty",
17 | "summary" }, snippets = CAMELCASE,
18 | features = "src/test/java/net/laffyco/javamatchingengine/scenarios/")
19 | public class CucumberRunner {
20 | // Intentionally left blank.
21 | }
22 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/scenarios/orders/Orders.feature:
--------------------------------------------------------------------------------
1 | Feature: Orders
2 |
3 | Tests related to orders.
4 |
5 | Scenario: Adding an order
6 | Given There is an instance of the order book
7 | When I add an order
8 | Then It is added to the order book
9 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/scenarios/orders/OrdersSteps.java:
--------------------------------------------------------------------------------
1 | package net.laffyco.javamatchingengine.scenarios.orders;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertTrue;
5 |
6 | import java.util.List;
7 |
8 | import io.cucumber.java.en.Given;
9 | import io.cucumber.java.en.Then;
10 | import io.cucumber.java.en.When;
11 | import net.laffyco.javamatchingengine.core.engine.Order;
12 | import net.laffyco.javamatchingengine.core.engine.OrderBook;
13 | import net.laffyco.javamatchingengine.core.engine.Side;
14 |
15 | /**
16 | * Orders feature steps.
17 | *
18 | * @author Laffini
19 | */
20 | public class OrdersSteps {
21 |
22 | /**
23 | * Order book instance.
24 | */
25 | private OrderBook orderBook;
26 |
27 | /**
28 | * Order to add to order book.
29 | */
30 | private Order order;
31 |
32 | /**
33 | * Create the order book.
34 | */
35 | @Given("There is an instance of the order book")
36 | public void createOrderBook() {
37 | this.orderBook = new OrderBook();
38 | }
39 |
40 | /**
41 | * Add the order.
42 | */
43 | @When("I add an order")
44 | public void addOrder() {
45 | final double amount = 10;
46 | final double price = 25.59;
47 | final Side side = Side.BUY;
48 |
49 | this.order = new Order.Builder(side).withAmount(amount).atPrice(price)
50 | .build();
51 | this.orderBook.process(this.order);
52 | }
53 |
54 | /**
55 | * Confirm order is added.
56 | */
57 | @Then("It is added to the order book")
58 | public void orderIsAdded() {
59 | final List buyOrders = this.orderBook.getBuyOrders();
60 |
61 | // Assert there is only one order.
62 | final int oneOrder = 1;
63 | assertEquals(oneOrder, buyOrders.size());
64 | assertTrue(this.orderBook.getSellOrders().isEmpty());
65 |
66 | // Assert orders are equal match.
67 | final Order addedOrder = buyOrders.get(0);
68 | assertEquals(this.order, addedOrder);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/scenarios/orders/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine.scenarios.orders;
6 |
--------------------------------------------------------------------------------
/src/test/java/net/laffyco/javamatchingengine/scenarios/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package net.laffyco.javamatchingengine.scenarios;
6 |
--------------------------------------------------------------------------------
/src/test/java/test/utils/AbstractTest.java:
--------------------------------------------------------------------------------
1 | package test.utils;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.mockito.MockitoAnnotations;
5 |
6 | /**
7 | * An abstract test to extend.
8 | *
9 | * @author Laffini
10 | *
11 | */
12 | public abstract class AbstractTest {
13 |
14 | /**
15 | * Setup.
16 | */
17 | @BeforeEach
18 | public void setUp() {
19 | // Initialise mocks created above
20 | MockitoAnnotations.openMocks(this);
21 | this.init();
22 | }
23 |
24 | /**
25 | * Any additional initialisation.
26 | */
27 | public abstract void init();
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/test/utils/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Laffini
3 | *
4 | */
5 | package test.utils;
6 |
--------------------------------------------------------------------------------
/src/test/resources/cucumber.properties:
--------------------------------------------------------------------------------
1 | cucumber.publish.quiet=true
2 |
--------------------------------------------------------------------------------