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. 配置

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

# Exchange configuration
cassandre.trading.bot.exchange.name=kucoin
[email protected]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=

配置分为四类:

  • 交换配置 :我们为我们设置的交换凭据,用于连接到 Kucoin 上现有的沙箱帐户
  • 模式 :我们要使用的模式。在我们的例子中,我们要求 Cassandre 使用沙箱数据
  • 交易所 API 调用率 :指示我们希望从交易所检索数据(账户、订单、交易和股票代码)的速度。当心;所有交易所都有我们可以调用的最高费率
  • 数据库配置 :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需要知道其中哪个账户是交易账户。为此,我们必须实现 getTradeAccount() 方法,该方法将我们拥有的账户列表作为参数提供给我们,并且从该列表中,我们必须返回我们想要用于交易的账户。

在我们的示例中,我们在交易所的交易账户名为 “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 MyFirstStrategyUnitTest {
    @Autowired
    private MyFirstStrategy strategy;

    private final Logger logger = LoggerFactory.getLogger(MyFirstStrategyTest.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() 方法来确保从文件加载的所有代码都已发送到我们的策略。最后,我们显示已平仓头寸、仍开仓头寸以及全局收益。

9. 结论

本教程说明了如何创建与加密货币交易所交互的策略并根据历史数据对其进行测试。

当然,我们的算法很简单;在现实生活中,我们的目标是找到一种有前途的技术、一个好的算法和好的数据来知道我们什么时候可以创建一个职位。例如,我们可以使用技术分析,因为 Cassandre 集成了 ta4j。

本文的所有代码都可以在 GitHub 上找到。