Building a Crypto Trading Bot in Laravel using PHP and Binance

What we are doing

We will be building a crypto currency trading bot using PHP, Laravel and Binance. This trading bot will buy and sell for you using an algorithm of your making. Even if you aren't using Laravel you can get the basics and use a cron job to run your php file. You can find the project here

Laravel

Start by creating a new project:
laravel new blog

Tables & Model

php artisan make:Model CryptoTrading -m
php artisan make:Model CryptoTradingBot -m
Now lets create a table to save our data in:
Schema::create('crypto_tradings', function (Blueprint $table) {
$table->increments('id');
$table->string('ticker');
$table->string('amount');
$table->string('price');
$table->string('old_price');
$table->string('buy_sell');
$table->integer('order_id');
$table->string('status')->nullable();
$table->integer('checks');
$table->timestamps();
});


Schema::create('crypto_trading_bots', function (Blueprint $table) {
$table->increments('id');
$table->string('ticker');
$table->string('price');
$table->timestamps();
});
Now Run:
php artisan migrate

API

Install PHP Binance API and follow all the instructions on this page. You will need the details from your Binance Account. You will also need some BTC in your account. Go to Coinbase (use this link for $10 free) and buy some then send them to your Binance Account.

Controller

Create a new controller
php artisan make:controller ToolsController
Now add some properties to the controller.

//Top of the page
use App\CryptoTrading;
use App\CryptoTradingBot;
use Carbon\Carbon;
.....
//Above the construct
protected $api;
//These are the cryptos you want to trade with
protected $allowedCrypto = array('RDNBTC', 'SCBTC', 'LTCBTC', 'BNBBTC', 'NEOBTC', 'GASBTC', 'MCOBTC', 'WTCBTC', 'SALTBTC', 'EVXBTC', 'REQBTC', 'ICXBTC');
public function __construct()
{
$this->api = new \Binance\API([ADD YOU OWN HERE]);
}

Lets Create the Bot Code

public function checkTradingBids()
{
//Get all prices via the API
$ticker = $this->api->prices();
$trades = array();

$cryptoTrading = new CryptoTrading;
//Get all the trades actually sent
$allTradesMade = $cryptoTrading->orderBy('created_at', 'DESC')->get();
$trades['allTrades'] = $allTradesMade;
//Go though each ticker and save price
foreach ($ticker as $name => $value) {
//For testing buy/sell
if ($name == 'ICXBTC') {
//$value = '0.87999180';
}
//Check if we are working with the Ticker
if (in_array($name, $this->allowedCrypto)) {
$traded = false;
$percentChange = 0;
$oldPrice = 0;
$percentChangeTrade = 0;

//Get last trade made
$lastTradeMade = $cryptoTrading->orderBy('created_at', 'DESC')->first();
$tradeArray = array();
$cryptoTradingBotGet = new CryptoTradingBot;
$oldTrades = $cryptoTradingBotGet->where('ticker', $name)->whereBetween('created_at', [ Carbon::now()->subMinutes(1)->toDateTimeString(), Carbon::now()])->orderBy('id')->first();

//Check we have some trades in the system
if ($oldTrades) {
//Get the percentage change between now and 10 mins ago
$oldPrice = $oldTrades->price;
$decreaseValue = $value - $oldTrades->price;
$percentChange = round(($decreaseValue / $oldTrades->price) * 100, 2);
}

//Here we will check if last was a buy and if so show percentage increase
$lastTradeType = '';
if (isset($trades['allTrades'][0])) {
$lastTradeType = $trades['allTrades'][0]['buy_sell'];
}

if($lastTradeType == 'buy') {
//Get the name
$lastTradeTicker = $trades['allTrades'][0]['ticker'];

//check if this iteration is the same ticker
if ($lastTradeTicker == $name) {
//Update the live trade for buys to show how much up/down
$decreaseValue = $value - $trades['allTrades'][0]['price'];
$percentChangeTrade = round(($decreaseValue / $trades['allTrades'][0]['price']) * 100, 2);
$trades['allTrades'][0]['current'] = $percentChangeTrade;
}
}

if ($lastTradeMade) {
if ($this->checkIfLastOrderProcessed($lastTradeMade, $percentChangeTrade)) {
//Check latest prices and make decision to sell
$traded = $this->checkLastTradeAndProcessToSell($lastTradeMade, $name, $value);

//Check if trade has happened adn buy
if (!$traded) {
$traded = $this->checkLastTradeAndProcessToBuy($lastTradeMade, $percentChange, $name, $value);
}

//Create something to force sell if one is over 5% in 10 minutes
//Force Sell and buy immediately
}

}

//Create Ticker
$tradeArray['ticker'] = $name;
$tradeArray['gain'] = $percentChange . '%';
$tradeArray['oldValue'] = $oldPrice;
$tradeArray['latest'] = $value;
if (!$traded) {
$tradeArray['traded'] = 'No';
} else {
$tradeArray['traded'] = 'Yes';
}

//Push to array
array_push($trades, $tradeArray);

//Save the latest Trade
$cryptoTradingBot = new CryptoTradingBot;
$cryptoTradingBot->ticker = $name;
$cryptoTradingBot->price = $value;
$cryptoTradingBot->save();

}
}

return $trades;
}

private function returnReverseTickerName($lastTradeMade, $justTickerName = false)
{
$ticker = 'SCBTC';
if (isset($lastTradeMade->ticker)) {
$ticker = $lastTradeMade->ticker;
} else if ($justTickerName) {
$ticker = $lastTradeMade;
}

//Chec if ticket is 2,3 or 4 lenght ticker
if (substr($ticker, 3, 4) == 'BTC') {
//Switch around Name to get back to BTC from 3 char ticker
$firstTicker = substr($ticker, 0, 3);
$secondTicker = substr($ticker, 3, 4);
} else if (substr($ticker, 2, 4) == 'BTC') {
//Switch around Name to get back to BTC from 2 char ticker
$firstTicker = substr($ticker, 0, 2);
$secondTicker = substr($ticker, 2, 4);
} else {
//Switch around Name SALTBTC to get back to BTC 4 char ticker
$firstTicker = substr($ticker, 0, 4);
$secondTicker = substr($ticker, 4, 6);
}

//Trade Name
$tradeName = $secondTicker . $firstTicker;

if ($justTickerName) {
return $firstTicker;
}

return $tradeName;
}

private function checkIfLastOrderProcessed($lastTradeMade, $percentChangeTrade)
{
//Check if the status is filled of the last job
if ($lastTradeMade->status == 'FILLED') {
return true;
}
//If not filled check the system
$orderstatus = $this->api->orderStatus($lastTradeMade->ticker, $lastTradeMade->order_id);

//If filled then we can carry on
if ($orderstatus['status'] == 'FILLED') {
//If filled mark as filled and save
$lastTradeMade->status = 'FILLED';
$lastTradeMade->save();
return true;
} else {
//If checks over 500 then delete the old order and carry on
if (((Carbon::now()->subMinutes(10)->toDateTimeString() > $lastTradeMade->created_at) && ($percentChangeTrade < -5.00)) || ($percentChangeTrade > 5.00) || $lastTradeMade->checks > 20000) {
$response = $this->api->cancel($lastTradeMade->ticker, $lastTradeMade->order_id);
$lastTradeMade->delete();
return true;
}
$lastTradeMade->status = 'PROCESSING';
$lastTradeMade->save();
//If not add one more check for filled
$lastTradeMade->checks = (int)$lastTradeMade->checks + 1;
$lastTradeMade->save();
}
//Return false as we still have an order
return false;
}

public function checkLastTradeAndProcessToSell( $lastTradeMade, $name, $value)
{
if ($lastTradeMade->ticker == $name && $lastTradeMade->buy_sell == 'buy') {
// //Get last trade to make sure its still a buy to sell to avoid duops
DB::transaction(function() use($lastTradeMade, $name, $value)
{
$cryptoTrading = new CryptoTrading;
//Get the percentage change between now and 10 mins ago
$decreaseValue = $value - (float)$lastTradeMade->price;
$percentChange = round(($decreaseValue / $value) * 100, 2);

if ($percentChange >= 1.00 || $percentChange < -5.00) {
$tradeName = $this->returnReverseTickerName($lastTradeMade);

// //Get last trade to make sure its still a buy to sell to avoid duops
$cryptoTradingLive = new CryptoTrading;
$lastTradeMadeLive = $cryptoTradingLive->orderBy('created_at', 'DESC')->first();
if ($lastTradeMadeLive->buy_sell == 'buy') {
$amount = $this->getSellBalance($name);
$quantity = ($amount - (0.050*$amount)/100);

$convertToString = (string)$quantity;
$decimalLocation = strpos($convertToString, '.');
$quantityStart = substr($quantity,0 ,$decimalLocation);
$quantityEnd = substr($quantity,$decimalLocation ,3);

$quantity = $quantityStart .$quantityEnd;

//Sell Now

if (env('APP_ENV') != 'local') {
if ($percentChange < -5.00 || $percentChange > 5.00) {
$order = $this->api->marketSell($lastTradeMade->ticker, $quantity);
} else {
$order = $this->api->sell($lastTradeMade->ticker, $quantity, $value);
}

//$order = $this->api->marketSell($lastTradeMade->ticker, $quantity);
//signedRequest error: {"code":-1013,"msg":"Filter failure: LOT_SIZE"}
//check for lot size error
//and put it floor
if (isset($order['code']) && $order['code'] == '-1013') {
$quantity = floor($quantity);
if ($percentChange < -5.00 || $percentChange > 5.00) {
$order = $this->api->marketSell($lastTradeMade->ticker, $quantity);
} else {
$order = $this->api->sell($lastTradeMade->ticker, $quantity, $value);
}
//$order = $this->api->marketSell($lastTradeMade->ticker, $quantity);
}

if (!isset($order['code'])) {
if (!isset($order['orderId'])) {
$orderId = '';
} else {
$orderId = $order['orderId'];
}
}

} else {
$orderId = '123456';
}

if (isset($orderId)) {
$cryptoTrading->ticker = $name;
$cryptoTrading->price = $value;
$cryptoTrading->old_price = $lastTradeMade->price;
$cryptoTrading->buy_sell = 'sell';
$cryptoTrading->amount = $quantity;
$cryptoTrading->order_id = $orderId;
$cryptoTrading->save();

$cryptoTradingBot = new CryptoTradingBot;
$cryptoTradingBot->where('ticker', $name)->delete();

return false;
}
}
}
});
}
}

public function checkLastTradeAndProcessToBuy($lastTradeMade, $percentageIncrease, $name, $value)
{
if ($lastTradeMade->buy_sell == 'sell' && $percentageIncrease <= -1.00) {

// //Get last trade to make sure its still a buy to sell to avoid duops
DB::transaction(function() use($lastTradeMade, $percentageIncrease, $name, $value)
{
$cryptoTrading = new CryptoTrading;
$btcBalance = $this->getBTCBalance();
$cryptoTradingLive = new CryptoTrading;
$lastTradeMadeLive = $cryptoTradingLive->orderBy('created_at', 'DESC')->first();

if ($lastTradeMadeLive->buy_sell == 'sell') {
//Buy Now
$quantity = (float)$btcBalance/(float)$value;
$quantity = floor($quantity);
if ($quantity != 0) {
if (env('APP_ENV') != 'local') {
$order = $this->api->buy($name, $quantity, $value);
//$order = $this->api->marketBuy($name, $quantity);
} else {
$order = array();
$order['orderId'] = '123456';
}

if (isset($order['orderId'])) {
$cryptoTrading->ticker = $name;
$cryptoTrading->price = $value;
$cryptoTrading->old_price = '';
$cryptoTrading->buy_sell = 'buy';
$cryptoTrading->amount = $quantity;
$cryptoTrading->order_id = $order['orderId'];

$cryptoTrading->save();

return true;
}
}

}
});
}
}

Job

Create a new job called CryptoBot
php artisan make:job CryptoBot
and paste this code
<?php

namespace App\Jobs;

use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\CryptoTrading;
use App\CryptoTradingBot;

class CryptoBot implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;

/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{

}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{

app('App\Http\Controllers\ToolsController')->checkTradingBids();
sleep(18);
app('App\Http\Controllers\ToolsController')->checkTradingBids();
sleep(18);
app('App\Http\Controllers\ToolsController')->checkTradingBids();
}
}

Automate it

Go to your Kernel and in the schedule add:
$schedule->job(new CryptoBot())->everyMinute()->withoutOverlapping();
At the top of the page add
use App\Jobs\CryptoBot;

How to Debug

Add this to Rotes.web.php
Route::get('/checkTradingBot', [
'uses' => 'ToolsController@checkTradingBids'
]);
This will show you the errors in the file if there are any so you can debug.

Finished

Thats it!! If you are local run
php artisan queue:work
and you should see the trades going through. This PHP trading bot will check Binance for a crypto and check if its gone up by 1% and put a buy order in. Once this is placed it will check to see if its gone up by another 1% and put a sell order. So every-time you should make 1%. Obviously there is a chance it will go down and if it goes below 5% then it sells.
There are other exchanges and I do not have a preference. This is just the first one I found.

NOTE: Use at your own risk. I take no responsibility for losses of your money. The market fluctuates and you have the real possibility of losing all your money. I recommend starting with a small amount if you are going to do it. This is just example code for learning.

Once you've made your millions ;) I recommend looking at a Trezor as a hardware wallet to keep your coins secure.

Not many guides or a lot of issues with jaggedsoft/php-binance-api but hopefully in the code you will see some fixes. That code base is updated so may be better to download from there but to keep this tutorial correct I used the forked code in the example. Normal one is here.

Thank you for reading my guide on PHP Trading Bot for Crypto Currencies using Binance and Laravel. Any issues feel free to leave a comment.