How to Code a Crypto Trading Bot with JavaScript & Coygo

If you’ve ever wanted to code your own trading bot you’re in luck! Today we’ll be coding a crypto trading bot with JavaScript & Coygo Forge that implements a basic market maker strategy.

Coygo Forge is a framework for coding custom crypto trading bots using a simple JavaScript API with a focus on real-time order book data & day trading strategies like arbitrage and scalping. Forge is the underlying engine powering the Coygo Bots crypto trading bot platform which includes a number of pre-built trading bot strategies that were created with Forge, including triangular arbitrage and grid trading.

What we’re building: Simple market maker strategy

We will be creating a simple market making strategy that attempts to profit off of the bid-ask spread:

This exact strategy is available within Coygo Bots here: Simple market maker crypto trading bot strategy.

 

Benefits of Coygo Forge

Coygo Forge is an incredibly powerful tool for automating your crypto day trading. Some benefits include:


First we’ll create a new Strategy

The first thing we need to do is create a new Strategy on the “My Strategies” screen of Coygo Bots. Strategies define the behavior of trading bots within Coygo Bots and this new Strategy will contain our custom JavaScript code.

We’ll name it “Simple market maker” since that’s what we’re building today.

Once the Strategy is created we can click the “Forge — Edit this Strategy” button to launch Coygo Forge.

Coygo Forge overview

We’ve landed in Coygo Forge, the tool that we’ll be using to code our custom crypto trading bot today.

If you want to learn more please see our other blog post: Coygo Forge — Code Crypto Trading Bots With Javascript.

Coding intro: Access a real-time order book

When we create a new Strategy we’re given a basic starter template code that creates two inputs on the “Configure” screen to select an exchange and a trade pair to use, then accesses a real-time order book for that trade pair.

 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
// configure basic information about your bot strategy
coygo.configure({
    // which types of orders this strategy supports
    orderTypes: [
        coygo.orderTypes.market, // this indicates your strategy uses market orders
        coygo.orderTypes.limit // this indicates your strategy uses limit orders
    ]
});
// Create configurable input values for this Strategy. On the "Configure" screen you can use
// an interface to set these to whatever you'd like. This allows you to re-use the same Strategy
// code while using different exchanges, trade pairs, etc.
const exchangeInput = coygo.input({
    type: coygo.inputTypes.exchange,
    label: 'The exchange to use',
    description: 'The exchange that this bot will trade on.'
});
const tradePairInput = coygo.input({
    type: coygo.inputTypes.tradePair,
    label: 'The trade pair to use',
    description: 'The trade pair that this bot will trade on.'
});
// set up a Market to interact with an exchange
const marketToUse = coygo.market({
    exchange: exchangeInput,
    baseSymbol: tradePairInput.baseSymbol,
    quoteSymbol: tradePairInput.quoteSymbol
});
// access this trade pair's order book
const orderBook = coygo.orderBook({
    market: marketToUse
});

// log the order book to the Activity Log
coygo.log('This is the order book!', orderBook);

See the two times that coygo.input(options) was called? Those are used to automatically generate a user interface to configure your bot on the “Configure” screen. Here is what that looks like:

Notice that the last line of code uses coygo.log(messages,…). This lets us log any information to the Activity Log so that we can see it when the Bot is running.

Coding intro: Run your new Bot

With that basic starter template code we can try to run our new Bot on the “Run” screen.

Once the Bot has started and is running we will see the “Activity Log” on the left side of the screen. See this line in our code?

coygo.log(‘This is the order book!’, orderBook);

Now we can see that message in the Activity Log!

This allows us to see what our Bot is doing and debug when issues might occur.

Coding Part 1: Inputs & persisted data

Now we can begin coding our market maker strategy that we described above. First we’ll set up all of the configuration inputs and persisted data that we’ll need. (Remember you can see all of this in the video above as well!)

 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// configure basic information about your bot strategy
coygo.configure({
    // which types of orders this strategy supports
    orderTypes: [
        coygo.orderTypes.limit   // this indicates your strategy uses limit orders
    ]
});

// Create configurable input values for this Strategy. On the "Configure" screen you can use
// an interface to set these to whatever you'd like. This allows you to re-use the same Strategy
// code while using different exchanges, trade pairs, etc.
const exchangeInput = coygo.input({
    type: coygo.inputTypes.exchange,
    label: 'The exchange to use',
    description: 'The exchange that this bot will trade on.'
});
const tradePairInput = coygo.input({
    type: coygo.inputTypes.tradePair,
    label: 'The trade pair to use',
    description: 'The trade pair that this bot will trade on.'
});
const orderAmountInput = coygo.input({
    type: coygo.inputTypes.number,
    label: 'Order amount',
    description: 'The amount for each order submitted',
    min: 0
});
// buy orders use the "ask" rate that a seller is asking for
const buyRatePercentBelowAskInput = coygo.input({
    type: coygo.inputTypes.number,
    min: 0,
    label: 'Buy order rate % below ask',
    description: 'This determines the rate at which each buy order will be submitted. Buy orders will use a rate at a certain % below the current ask. **For example,** if this is set to zero buy orders will use the current ask rate. If this is set to 0.5, buy orders will use a rate at 0.5% below the ask rate.'
});
// sell orders use the "bid" rate that a buyer is bidding to buy
const sellRatePercentAboveBidInput = coygo.input({
    type: coygo.inputTypes.number,
    min: 0,
    label: 'Sell rate % above bid',
    description: 'This determines the rate at which each sell order will be submitted. Sell orders will use a rate at a certain % above the current bid. **For example,** if this is set to zero sell orders will use the current bid rate. If this is set to 0.5, sell orders will use a rate at 0.5% above the bid rate.'
});
const cancelOrderThresholdInput = coygo.input({
    type: coygo.inputTypes.number,
    label: 'Cancel order threshold %',
    min: 0.1,
    description: "If the difference between the current ask/bid rate and any open order's rate is greater than this percent, that order will be cancelledand re-submitted at a new price. **For example**, if this is set to 3 then any open orders with a rate > 3% away from the current ask/bid will be cancelled and re-submitted."
});

// set up a Market to interact with an exchange
const marketToUse = coygo.market({
    exchange: exchangeInput,
    baseSymbol: tradePairInput.baseSymbol,
    quoteSymbol: tradePairInput.quoteSymbol
});

// access this trade pair's order book
const orderBook = coygo.orderBook({
    market: marketToUse
});


let orderAmount = orderAmountInput;
if (marketToUse && marketToUse.baseSymbolDecimalCount) {
    // set decimal count accordingly for this exchange's precision limits
    orderAmount = parseFloat(orderAmount.toFixed(marketToUse.baseSymbolDecimalCount));
}

/*
  Create persisted data to keep track of any orders or waiting status
*/
// there will always be one buy and one sell order open at any time. record them so we can access them in later intervals
const buyOrder = coygo.persistedData();
const sellOrder = coygo.persistedData();
// canceling an order can take a variable amount of time so we will record when we're waiting for a buy
// or sell order to be finish being canceled
const isWaitingForBuyToCancel = coygo.persistedData(false);
const isWaitingForSellToCancel = coygo.persistedData(false);

Coding Part 2: Determining when to buy, sell, or cancel

With all of our inputs and data now set up we’re ready to determine what the Strategy should do. The Strategy may need to do one of a few things:

  1. Submit a new buy or sell order if there is not currently one open

  2. Cancel a buy or sell order if the current ask/bid rate has moved too far away from our open order’s rate

  3. Wait for a cancelled order to finish being canceled

  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
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
  Determine if orders should be submitted or cancelled
*/
let shouldSubmitBuyOrder = false;
let shouldCancelBuyOrder = false;
let shouldSubmitSellOrder = false;
let shouldCancelSellOrder = false;


// determine if it's time to submit or cancel the buy order
if (!buyOrder.get()) {
    // if no current buy order exists it's time to submit one
    shouldSubmitBuyOrder = true;
    coygo.log('## No current buy order found. Signalling to submit a new buy order');
} else {
    coygo.log('## Current buy order:');
    const buyOrderObj = buyOrder.get();
    let buyOrderTableLog =
        `| Property | Value |
| ---- | ---- |
| side | ${buyOrderObj.side} |
| status | ${buyOrderObj.status} |
| rate | ${buyOrderObj.rate} |
| amount | ${buyOrderObj.amount} |
| filled | ${buyOrderObj.filled} |
  `;
    coygo.log(buyOrderTableLog);

    // if a current buy order exists we need to check its status and rate to determine if we need
    // to submit a new order or cancel it
    if (isWaitingForBuyToCancel.get() === true) {
        coygo.log('Waiting for the buy order to be canceled. Buy order=', buyOrder.get());
        if (buyOrder.get().status === coygo.orderStatuses.canceled) {
            // we were waiting for the buy order to confirm being canceled and now it's done. time to submit new order
            shouldSubmitBuyOrder = true;
            isWaitingForBuyToCancel.set(false);
            coygo.log('Buy order finished being canceled. Signaling to submit a new buy order');
        }
    } else if (buyOrder.get().isOpen === false) {
        // the existing buy order is no longer open, time to re-submit a new one
        shouldSubmitBuyOrder = true;
        coygo.log('Current buy order is no longer open. New order status: ' + buyOrder.get().status + '. Signalling to submit a new buy order.');
        buyOrder.set(null);
    } else if (buyOrder.get().isOpen === true) {
        // current buy order found and is open. check if it needs to be cancelled by calculating difference between this order's rate and current ask
        // example: "ask" is 100, order "rate" is 95. difference = ((100 - 95) / 100) * 100 = 5. "rate" of 95 is 5% from "ask" of 100
        const differenceBetweenRateAndAsk = ((orderBook.ask - buyOrder.get().rate) / orderBook.ask) * 100;
        coygo.log('An existing buy order was found, determining if it should be cancelled');
        if (differenceBetweenRateAndAsk > cancelOrderThresholdInput) {
            shouldCancelBuyOrder = true;
            coygo.log('The buy order was found to be too far from current ask. Signaling to be cancelled', {
                ask: orderBook.ask,
                'order rate': buyOrder.get().rate,
                'difference between rate and ask': differenceBetweenRateAndAsk.toFixed(4) + ' %',
                'cancel order threshold': cancelOrderThresholdInput + ' %'
            });
        } else {
            coygo.log('The buy order was found to be within the proper threshold and will not be cancelled', {
                ask: orderBook.ask,
                'order rate': buyOrder.get().rate,
                'difference between rate and ask': differenceBetweenRateAndAsk.toFixed(4) + ' %',
                'cancel order threshold': cancelOrderThresholdInput + ' %'
            })
        }
    }
}
coygo.log('Finished determining next actions for buy order', {
    'is waiting for buy to cancel?': isWaitingForBuyToCancel.get(),
    'should submit buy order?': shouldSubmitBuyOrder,
    'should cancel buy order?': shouldCancelBuyOrder
});


// determine if it's time to submit or cancel the sell order
if (!sellOrder.get()) {
    // if no current sell order exists it's time to submit one
    shouldSubmitSellOrder = true;
    coygo.log('## No current sell order found. Signalling to submit a new sell order');
} else {
    coygo.log('## Current sell order:');
    const sellOrderObj = sellOrder.get();
    let sellOrderLog =
        `| Property | Value |
| ---- | ---- |
| side | ${sellOrderObj.side} |
| status | ${sellOrderObj.status} |
| rate | ${sellOrderObj.rate} |
| amount | ${sellOrderObj.amount} |
| filled | ${sellOrderObj.filled} |
  `;
    coygo.log(sellOrderLog);

    // if a current sell order exists we need to check its status and rate to determine if we need
    // to submit a new order or cancel it
    if (isWaitingForSellToCancel.get() === true) {
        coygo.log('Waiting for the sell order to be canceled. Sell order=', sellOrder.get());
        if (sellOrder.get().status === coygo.orderStatuses.canceled) {
            // we were waiting for the ellrder to confirm being canceled and now it's done. time to submit new order
            shouldSubmitSellOrder = true;
            isWaitingForSellToCancel.set(false);
            coygo.log('Sell order finished being canceled. Signaling to submit a new sell order');
        }
    } else if (sellOrder.get().isOpen === false) {
        // the existing sell order is no longer open, time to re-submit a new one
        shouldSubmitSellOrder = true;
        coygo.log('Current sell order is no longer open. New order status: ' + sellOrder.get().status + '. Signalling to submit a new sell order.');
        sellOrder.set(null);
    } else if (sellOrder.get().isOpen === true) {
        // current sell order found and is open. check if it needs to be cancelled by calculating difference between this order's rate and current bid
        // example: "bid" is 100, order "rate" is 105. difference = ((105 - 100) / 100) * 100 = 5. "rate" of 105 is 5% from "bid" of 100
        const differenceBetweenRateAndBid = ((sellOrder.get().rate - orderBook.bid) / orderBook.bid) * 100;
        coygo.log('An existing sell order was found, determining if it should be cancelled');
        if (differenceBetweenRateAndBid > cancelOrderThresholdInput) {
            shouldCancelSellOrder = true;
            coygo.log('The sell order was found to be too far from current bid. Signaling to be cancelled', {
                bid: orderBook.bid,
                'order rate': sellOrder.get().rate,
                'difference between rate and bid': differenceBetweenRateAndBid.toFixed(4) + ' %',
                'cancel order threshold': cancelOrderThresholdInput + ' %'
            });
        } else {
            coygo.log('The sell order was found to be within the proper threshold and will not be cancelled', {
                bid: orderBook.bid,
                'order rate': sellOrder.get().rate,
                'difference between rate and bid': differenceBetweenRateAndBid.toFixed(4) + ' %',
                'cancel order threshold': cancelOrderThresholdInput + ' %'
            });
        }
    }
}

coygo.log('Finished determining next actions for sell order', {
    'is waiting for sell to cancel?': isWaitingForSellToCancel.get(),
    'should submit sell order?': shouldSubmitSellOrder,
    'should cancel sell order?': shouldCancelSellOrder
});

Coding Part 3: Submitting and canceling orders

Our Strategy has determined what to do next, now it’s time to actually submit or cancel orders when appropriate. We will calculate the order’s rate depending on the current ask/bid rate and we’ll make sure our order rate and amounts adhere to the exchanges rules for how many decimal places are allowed.

 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*
  Submit or cancel orders when appropriate
*/

// handle submiting or canceling the buy order when instructed
if (shouldSubmitBuyOrder) {
    // calculate the buy order rate using configured inputs
    // example: if "ask" is 100 and "buyRatePercentBelowAskInput" is 2, buyOrderRate = 100 * (1 - (2 / 100) = 98
    let buyOrderRate = orderBook.ask * (1 - (buyRatePercentBelowAskInput / 100));
    if (marketToUse && marketToUse.quoteSymbolDecimalCount) {
        // apply decimal count limit for this exchange
        buyOrderRate = parseFloat(buyOrderRate.toFixed(marketToUse.quoteSymbolDecimalCount));
    }
    // there is no buy order, submit one
    const buyOrderConfig = {
        market: marketToUse,
        type: coygo.orderTypes.limit,
        side: coygo.orderSides.buy,
        amount: orderAmount,
        rate: buyOrderRate
    };
    coygo.log('## Attempting to submit the following buy order:', {
        ask: orderBook.ask,
        'buy rate % below ask input': buyRatePercentBelowAskInput,
        'buy order rate': buyOrderRate,
        'order config': buyOrderConfig
    })
    const canSubmitCheck = coygo.canSubmitOrder(buyOrderConfig);
    if (canSubmitCheck.canSubmit === true) {
        // order can be submitted
        const submittedBuyOrder = coygo.submitOrder(buyOrderConfig);
        coygo.log('Submitted buy order!');
        // record the submitted buy order so it can be accessed later to check on its status
        buyOrder.set(submittedBuyOrder);
    } else if (canSubmitCheck.canSubmit === false) {
        // order cannot be submitted. see 'canSubmitCheck.reason' for more details
        coygo.log('## Unable to submit buy order', canSubmitCheck);
    }
} else if (shouldCancelBuyOrder) {
    coygo.log('## Canceling buy order');
    // handle canceling the buy order
    coygo.cancelOpenOrder({
        order: buyOrder.get()
    });
    // record that we're now waiting for the buy order to cancel
    // this takes a variable amount of time so we don't know exactly when it will be complete
    isWaitingForBuyToCancel.set(true);
}


// handle submiting or canceling the sell order when instructed
if (shouldSubmitSellOrder) {
    // calculate the buy order rate using configured inputs
    // example: if "bid" is 100 and "sellRatePercentAboveBidInput" is 2, sellOrderRate = 100 * (1 + (2 / 100) = 102
    let sellOrderRate = orderBook.bid * (1 + (sellRatePercentAboveBidInput / 100));
    if (marketToUse && marketToUse.quoteSymbolDecimalCount) {
        // apply decimal count limit for this exchange
        sellOrderRate = parseFloat(sellOrderRate.toFixed(marketToUse.quoteSymbolDecimalCount));
    }
    // there is no buy order, submit one
    const sellOrderConfig = {
        market: marketToUse,
        type: coygo.orderTypes.limit,
        side: coygo.orderSides.sell,
        amount: orderAmount,
        rate: sellOrderRate
    };
    coygo.log('## Attempting to submit the following sell order:', {
        bid: orderBook.bid,
        'sell rate % above bid input': sellRatePercentAboveBidInput,
        'sell order rate': sellOrderRate,
        'order config': sellOrderConfig
    })
    const canSubmitCheck = coygo.canSubmitOrder(sellOrderConfig);
    if (canSubmitCheck.canSubmit === true) {
        // order can be submitted
        const submittedSellOrder = coygo.submitOrder(sellOrderConfig);
        coygo.log('Submitted sell order!');
        // record the submitted sell order so it can be accessed later to check on its status
        sellOrder.set(submittedSellOrder);
    } else if (canSubmitCheck.canSubmit === false) {
        // order cannot be submitted. see 'canSubmitCheck.reason' for more details
        coygo.log('## Unable to submit sell order', canSubmitCheck);
    }
} else if (shouldCancelSellOrder) {
    coygo.log('## Canceling sell order');
    // handle canceling the sell order
    coygo.cancelOpenOrder({
        order: sellOrder.get()
    });
    // record that we're now waiting for the sell order to cancel
    // this takes a variable amount of time so we don't know exactly when it will be complete
    isWaitingForSellToCancel.set(true);
}

Remember, if you want to see the full source code this exact strategy is available within Coygo Bots here: Simple market maker crypto trading bot strategy.

Run your new market maker Bot!

It’s time to try out our new Strategy! We can run the Bot using this Strategy on the “Run” screen. We’ll see it submit a buy and sell order as soon as the bot starts, and on the left side Activity Log we’ll see detailed information about what the Bot is doing.


Try Coygo for free today

If you’d like to try creating your own crypto trading bot you can try Coygofor free today! All subscription plans include a free trial. You can sign up on our website at coygo.app.

Follow us on social media and subscribe to our blog to keep up to date with Coygo’s progress as we continue to develop the best tools for cryptocurrency and digital asset traders.

Facebook: https://www.facebook.com/CoygoHQ/

LinkedIn: https://www.linkedin.com/company/coygo

Twitter: https://twitter.com/CoygoHQ