-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New Implementation Best Practices
If you'd like to add a new exchange, broker, or market data provider to XChange, you've come to the right page. Here we outline the steps for creating a new implementation from the ground up. It's actually not too hard and there are already lots of examples to copy and paste from as you go.
Familiarize yourself with the data provider's API. What data is provided? What API do they have? In which format is the data returned? Do you need a login or key? Are there limitations on how often you can query the data? Create a file called api-specification.txt and take some notes.
Create a new Maven module for the data provider. Update the pom.xml
file using existing pom.xml
files as a template.
Get actual data and store it in a file in the project under test/resources. For example, see example-ticker-data.json. You can use JSON Formatter to format it nicely.
To get the raw JSON returned from an API endpoint through XChange you first need a logback.xml
file on your classpath. You can find an example logback.xml
file in the xchange-examples
module here. Make sure the following line is UNCOMMENTED:
<logger name="si.mazi.rescu" level="TRACE" />
You will then find the returned raw JSON logged to the console on the line beginning like this:
si.mazi.rescu.HttpTemplate - Response body:
For each data file, create a corresponding Java object (DTO) that will contain all the data in the file. You can use JSON Schema 2 Pojo or json2csharp to speed up the process. Oh, and make it immutable (see BitstampTicker.java
example). There are examples of how to create DTOs that Jackson can unmarshall JSON into of varying complexity throughout the XChange project, so just look around for examples.
Create JUnit tests to verify that the raw JSON to unmarshalled DTO works. See TickerJSONTest.java
. The project uses AssertJ, so please use these as well. Here is a link which shows lots of AssertJ assertion examples.
Name all test classes with the following pattern: *"DTO class name"*Test.java, e.g. CoinfloorOrderbookTest.java.
Then you create an adapter class to take the provider-specific DTO (a raw DTO) and convert it into an XChange DTO. See BitstampAdapters.java
.
And unit test that too (see BitstampAdapterTest.java
)!
Put it all together in two exchange service classes, such as BinanceMarketDataService.java
and BinanceMarketDataServiceRaw.java
. The *Raw class should fetch the JSON and deserialize it into exchange-specific DTOs. The non-*Raw class should use the *Raw class to get the exchange-specific DTOs and convert them to XChange-specific DTOs using the *Adapter
class created in an earlier step.
As for exception handling you should catch exchange-specific Exceptions in the non-*Raw class and re-throw them after adapting to an ExchangeException class or subclass using an *ErrorAdapter
class. Exchange-specific Exceptions should NOT be leaked out of the generic interface methods. An example of creating exchange-specific exceptions can be found in the xchange-binance
module where you have:
- a
BinanceException
that accepts JSON properties (you could also make it aware of the HTTP status using byHttpStatusExceptionSupport
) -
Binance
andBinanceAuthenticated
client interfaces that throw it - a
BinanceMarketDataServiceRaw
raw service which does not and should not try to catch thoseBinanceException
exceptions - a
BinanceMarketDataService
that implements generic methods of theMarketDataService
interface and catchesBinanceException
exceptions to adapt them using aBinanceErrorAdapter
toExchangeException
classes A full explanation on how exception handling works in our HTTP client can be found on Rescu's Wiki.
In the module xchange-examples
, add example classes to demo the new exchange API capabilities. See TickerDemo.java.
Create a single integration test class with a minimum of one test method that polls for a ticker at the exchange. This acts as an integration test for the exchange and catches any problems with the exchange itself. When creating the test class, put Integration
at the end of the class name (i.e. AccountInfoFetchIntegration.java
). This is how to designate it as an integration test class. To run all unit tests WITHOUT running the integration test use mvn clean test
. To run all unit tests WITH the integration test use mvn clean verify -DskipIntegrationTests=false
.
Name all integration test classes with the following pattern: *"DTO class name"*Integration.java, e.g. CoinfloorOrderbookIntegration.java.
Format the Code with the provided XChange code style format profiles.
Submit a pull request against the develop branch.
Update the Exchange Support page.
Now you have a completed and documented polling API implementation, if the exchange has a websocket or other similar "push" data API, you may wish to consider implementing some or more of the StreamingExchange
API.
It is strongly recommended that you do this after creating the core polling implementation. Streaming implementations are fully dependent on a functioning polling implementation and can usually share a great deal of code (particularly where exchanges use the same data structures on their polling and websocket APIs).
For this, follow a similar process, creating a new xchange-stream-XYZ
module and referring to existing examples. Binance and Bitfinex have particularly complete and well-tested implementations.
Some important things to note:
- Standards may differ between the streaming code and polling code examples you may look at. This is because they are the results of two different merged projects. The streaming code is being slowly changed to match the polling code's standards. If in doubt, use the approach in the polling code.
- The streaming API is intended to be highly concurrent. You should have a good understanding of concurrency; in particular the risks of using non-threadsafe objects such as
HashMap
s to maintain state that may be viewed by multiple threads. - Spend some time familiarizing yourself with the ReactiveX documentation. It may also help to read up on non-blocking code in Java in general:
CompletableFuture
and the principle of promise chaining it borrows from languages like Javascript.
The REST client that XChange uses (ResCU) provides a nice mechanism for handling returned JSON that has a different format than the expected valid JSON. See this Wiki page for more information.
- Never set a
Date
field in a DTO tonew Date()
. The timestamp fields represent "server" timestamps and not a client computer's system time. - Please include exchange name (or specific component name) in the commit message, if it is not common. Like this:
[btce] Removed unnecessary code
div
{
color: white;
background-color: 009900;
margin: 2px;
font-size: 25px;
}
span
{
color: black;
background-color: gray;
margin: 5px;
font-size: 25px;
}
</style>
<div> div tag </div>
<div> div tag </div>
<div> div tag </div>
<div> div tag </div>
<span>span-tag</span>
<span>span-tag</span>
<span>span-tag</span>
<span>span-tag</span>