Contents

Cassandre 创建交易机器人

1. 概述

交易机器人是一种无需人工干预即可自动向市场或交易所下订单的计算机程序。

在本教程中,我们将使用Cassandre 创建一个简单的加密交易机器人,它会在我们认为最佳时机生成仓位。

2. 机器人概述

交易的意思是“将一件物品换成另一件物品”。

在金融市场上,它是购买股票、期货、期权、掉期、债券,或者在我们的例子中,购买一定数量的加密货币。这里的想法是以特定价格购买加密货币并以更高的价格出售以赚取利润(即使如果价格下跌,我们仍然可以获利)空头头寸。

我们将使用沙盒交换;沙盒是一个虚拟系统,我们在其中拥有“假”资产,我们可以在其中下订单和接收代码。

首先,让我们看看我们会做什么:

  • 将 Cassandre spring boot starter 添加到我们的项目中
  • 添加所需的配置以连接到交易所
  • 创建策略:
    • 从交易所接收代码
    • 选择何时购买
    • 到了买的时候,检查我们是否有足够的资产并创建一个头寸
    • 显示日志以查看何时打开/关闭头寸以及我们获得了多少收益
  • 对历史数据进行测试,看看我们是否可以获利

3. Maven依赖

让我们开始向我们的pom.xml添加必要的依赖项,首先是Cassandre spring boot starter

<dependency>
    <groupId>tech.cassandre.trading.bot</groupId>
    <artifactId>cassandre-trading-bot-spring-boot-starter</artifactId>
    <version>4.2.1</version>
</dependency>

Cassandre 依靠XChange 连接到加密货币交易所。对于本教程,我们将使用Kucoin XChange 库

<dependency>
    <groupId>org.knowm.xchange</groupId>
    <artifactId>xchange-kucoin</artifactId>
    <version>5.0.8</version>
</dependency>

我们还使用*hsqld *来存储数据:

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.5.2</version>
</dependency>

为了根据历史数据测试我们的交易机器人,我们还添加了Cassandre spring boot starter 进行测试:

<dependency>
    <groupId>tech.cassandre.trading.bot</groupId>
    <artifactId>cassandre-trading-bot-spring-boot-starter-test</artifactId>
    <version>4.2.1</version>
    <scope>test</scope>
</dependency>

4.配置

让我们编辑application.properties来设置我们的配置:

# Exchange configuration
cassandre.trading.bot.exchange.name=kucoin
[[email protected]](/cdn_cgi/l/email_protection)ail.com
cassandre.trading.bot.exchange.passphrase=cassandre
cassandre.trading.bot.exchange.key=6054ad25365ac6000689a998
cassandre.trading.bot.exchange.secret=af080d55-afe3-47c9-8ec1-4b479fbcc5e7
# Modes
cassandre.trading.bot.exchange.modes.sandbox=true
cassandre.trading.bot.exchange.modes.dry=false
# Exchange API calls rates (ms or standard ISO 8601 duration like 'PT5S')
cassandre.trading.bot.exchange.rates.account=2000
cassandre.trading.bot.exchange.rates.ticker=2000
cassandre.trading.bot.exchange.rates.trade=2000
# Database configuration
cassandre.trading.bot.database.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
cassandre.trading.bot.database.datasource.url=jdbc:hsqldb:mem:cassandre
cassandre.trading.bot.database.datasource.username=sa
cassandre.trading.bot.database.datasource.password=

配置有四类:

  • Exchange configuration:我们为我们设置的交换凭证与 Kucoin 上现有沙盒帐户的连接
  • Modes:我们想要使用的模式。在我们的例子中,我们要求 Cassandre 使用沙盒数据
  • Exchange API calls rates:指示我们希望以何种速度从交易所检索数据(账户、订单、交易和代码)。当心; 所有交易所都有我们可以调用的最高汇率
  • Database configuration:Cassandre 使用数据库来存储头寸、订单和交易。对于本教程,我们将使用一个简单的hsqld内存数据库。当然,在生产中,我们应该使用持久化数据库

现在让我们在测试目录的application.properties中创建相同的文件,但是我们将cassandre.trading.bot.exchange.modes.dry更改为true,因为在测试期间,我们不想将真实订单发送到沙箱。我们只想模拟它们。

5. 策略

交易策略是旨在实现盈利回报的固定计划;我们可以通过添加一个带有*@CassandreStrategy注释的 Java 类并扩展BasicCassandreStrategy*来制作我们的。

让我们在MyFirstStrategy.java中创建我们的策略类:

@CassandreStrategy
public class MyFirstStrategy extends BasicCassandreStrategy {
    @Override
    public Set<CurrencyPairDTO> getRequestedCurrencyPairs() {
        return Set.of(new CurrencyPairDTO(BTC, USDT));
    }
    @Override
    public Optional<AccountDTO> getTradeAccount(Set<AccountDTO> accounts) {
        return accounts.stream()
          .filter(a -> "trade".equals(a.getName()))
          .findFirst();
    }
}

实现BasicCassandreStrategy迫使我们实现两个方法getRequestedCurrencyPairs()getTradeAccount()

在*getRequestedCurrencyPairs()*中,我们必须返回我们希望从交易所接收的货币对更新列表。货币对是两种不同货币的报价,其中一种货币的价值相对于另一种货币进行报价。在我们的示例中,我们希望使用 BTC/USDT。

为了更清楚,我们可以使用以下curl命令手动检索代码:

curl -s https://api.kucoin.com/api/v1/market/orderbook/level1?symbol=BTC-USDT

我们会得到类似的东西:

{
  "time": 1620227845003,
  "sequence": "1615922903162",
  "price": "57263.3",
  "size": "0.00306338",
  "bestBid": "57259.4",
  "bestBidSize": "0.00250335",
  "bestAsk": "57260.4",
  "bestAskSize": "0.01"
}

价格值表示 1 BTC 需要 57263.3 USDT。

我们必须实现的另一个方法是getTradeAccount()。在交易所,我们通常有几个账户,Cassandre 需要知道哪一个账户是交易账户。为此,e 必须实现*getTradeAccount()*方法,该方法将我们拥有的账户列表作为参数提供给 usw,并且从该列表中,我们必须返回我们想要用于交易的账户。

在我们的示例中,我们在交易所的交易账户名为*“trade”*,因此我们只需将其返回即可。

6. 创建仓位

要获得新数据的通知,我们可以覆盖BasicCassandreStrategy的以下方法:

  • *onAccountUpdate()*接收有关帐户的更新
  • *onTickerUpdate()*接收新的代码
  • *onOrderUpdate()*接收有关订单的更新
  • *onTradeUpdate()*接收有关交易的更新
  • *onPositionUpdate()*接收有关职位的更新
  • *onPositionStatusUpdate()*接收有关位置状态变化的更新

在本教程中,我们将实现一个愚蠢的算法:我们检查每个收到的新代码。如果 1 BTC 的价格低于 56 000 USDT,我们认为是时候买入了

为了让收益计算、订单、交易和平仓变得更容易,Cassandre 提供了一个自动管理头寸的类。

要使用它,第一步是通过PositionRulesDTO类为头寸创建规则,例如:

PositionRulesDTO rules = PositionRulesDTO.builder()
  .stopGainPercentage(4f)
  .stopLossPercentage(25f)
  .create();

然后,让我们使用该规则创建仓位:

createLongPosition(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"), rules);

此时,Cassandre 将创建一个 0.01 BTC 的买单。仓位状态为 OPENING,当对应的所有交易都到齐后,状态变为 OPENED。从现在开始,对于收到的每个代码,Cassandre 将使用新价格自动计算以该价格平仓是否会触发我们的两条规则之一(4% 止损或 25% 止损)。

如果触发了一条规则,Cassandre 将自动创建我们的 0.01 BTC 的卖单。仓位状态将移至CLOSING,当所有对应的交易都到达时,状态将移至CLOSED

这是我们将拥有的代码:

@Override
public void onTickerUpdate(TickerDTO ticker) {
    if (new BigDecimal("56000").compareTo(ticker.getLast()) == -1) {
        if (canBuy(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"))) {
            PositionRulesDTO rules = PositionRulesDTO.builder()
              .stopGainPercentage(4f)
              .stopLossPercentage(25f)
              .build();
            createLongPosition(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"), rules);
        }
    }
}

总结一下:

  • 对于每个新代码,我们检查价格是否低于 56000。
  • 如果我们的交易账户上有足够的 USDT,我们就开仓 0.01 BTC。
  • 从现在开始,对于每个股票:
    • 如果以新价格计算的收益超过 4% 收益或 25% 损失,Cassandre 将关闭我们通过卖出买入的 0.01 BTC 创建的头寸。

7. 跟踪日志中的仓位演变

我们最终将实现*onPositionStatusUpdate()*以查看何时打开/关闭仓位:

@Override
public void onPositionStatusUpdate(PositionDTO position) {
    if (position.getStatus() == OPENED) {
        logger.info("> New position opened : {}", position.getPositionId());
    }
    if (position.getStatus() == CLOSED) {
        logger.info("> Position closed : {}", position.getDescription());
    }
}

8. 回测

简单来说,回测策略是在前期测试交易策略的过程。Cassandre 交易机器人允许我们模拟机器人对历史数据的反应。

第一步是将我们的历史数据(CSV 或 TSV 文件)放在我们的src/test/resources文件夹中。

如果我们在 Linux 下,这里有一个简单的脚本来生成它们:

startDate=`date --date="3 months ago" +"%s"`
endDate=`date +"%s"`
curl -s "https://api.kucoin.com/api/v1/market/candles?type=1day&symbol=BTC-USDT&startAt=${startDate}&endAt=${endDate}" \
| jq -r -c ".data[] | @tsv" \
| tac $1 > tickers-btc-usdt.tsv

它将创建一个名为tickers-btc-usdt.tsv 的文件,其中包含从startDate(3 个月前)到endDate(现在)的BTC-USDT 历史汇率。

第二步是创建我们的虚拟账户余额,以模拟我们想要投资的确切资产数量。

在这些文件中,我们为每个账户设置了每种加密货币的余额。例如,这是模拟我们的交易账户资产的 user-trade.csv 的内容:

该文件还必须位于src/test/resources文件夹中。

BTC 1
USDT 10000
ETH 10

现在,我们可以添加一个测试:

@SpringBootTest
@Import(TickerFluxMock.class)
@DisplayName("Simple strategy test")
public class MyFirstStrategyLiveTest {
    @Autowired
    private MyFirstStrategy strategy;
    private final Logger logger = LoggerFactory.getLogger(MyFirstStrategyLiveTest.class);
    @Autowired
    private TickerFluxMock tickerFluxMock;
    @Test
    @DisplayName("Check gains")
    public void whenTickersArrives_thenCheckGains() {
        await().forever().until(() -> tickerFluxMock.isFluxDone());
        HashMap<CurrencyDTO, GainDTO> gains = strategy.getGains();
        logger.info("Cumulated gains:");
        gains.forEach((currency, gain) -> logger.info(currency + " : " + gain.getAmount()));
        logger.info("Position still opened :");
        strategy.getPositions()
          .values()
          .stream()
          .filter(p -> p.getStatus().equals(OPENED))
          .forEach(p -> logger.info(" - {} " + p.getDescription()));
        assertTrue(gains.get(USDT).getPercentage() > 0);
    }

}

TickerFluxMock的*@Import将从我们的src/test/resources文件夹加载历史数据并将它们发送到我们的策略。然后我们使用await()*方法来确保从文件加载的所有代码都已发送到我们的策略。最后,我们显示已平仓头寸、未平仓头寸和全局收益。