Creating a Crypto Stats Page: Part 4: MACD

In my last post, Creating a Crypto Stats Page: Part3: SMA and EMA, I showed you how to calculate the SMA and EMA and add them to the stats page. In this post, we will calculate and add the MACD to our stats page.

Step 1: What is the MACD?

The MACD is the Moving Average Convergence Divergence. It is a measure of the momentum of the trading prices of an asset. If the MACD decreases, it implies that the trading momentum is slowing. If it increases, it implies that the trading momentum is increasing. It can be used to detect changes in trends before they occur. For example, if the price of an asset is increasing but the MACD is decreasing, it can indicate that the price of the asset is about to drop.

Step 2: How is the MACD calculated?

The MACD is calculated by taking the 12-period EMA (we learned about the EMA in Part 3) and subtracting the 26-period EMA. The ‘period’ in this calculation is the interval between taking values for prices to use for the SMA and EMA calculations. So for example, if we were taking daily prices for our averages, we would use the 12-day EMA and 26-day EMA for the MACD calculation.

The formula for the MACD is:

macd = ema_12_period - ema_26_period

Step 3: Add MACD function

We can add a function that calculates the MACD to our existing tracker.php script. In Part 3, we updated get_rolling_mean_data to be able to calculate SMAs and EMAs for any interval and number of periods. This means we can re-use this function in a new function called get_macd to calculate the MACD:

function get_macd($pair, $interval) {
    # Get 12-period EMA
    $data = get_rolling_mean_data($pair, $interval, 12);
    $ema_12 = $data['ema'];
    
    # Get 26-period EMA
    $data = get_rolling_mean_data($pair, $interval, 26);
    $ema_26 = $data['ema'];
    
    # Subtract to get MACD
    $macd = $ema_12 - $ema_26;
    
    return $macd;  
}

Step 4: Update the Stats Page Display

Now we just need to update the stats page display to retrieve and include the MACD. This code slots in after the line that starts:
$ema = $mean_array['ema'];:

$macd = get_macd($pair, $interval);    

$current_price = get_current_price($pair);
?>
<p>7-Day <?=$pairs[$pair];?> Stats:</p>
<p>Current Price: <?=$current_price;?> GBP</p>
<p>Mean Price (SMA): <?=$mean;?> GBP</p>
<p>EMA: <?=$ema;?> GBP</p>
<p>MACD: <?=$macd;?></p>
<p>Mean High Price: <?=$mean_high;?> GBP</p>
<p>Mean Low Price: <?=$mean_low;?> GBP</p>

Step 5: Upload and Test

We should now be ready to upload and test the code. The complete script should now look like this:

<?php

function get_current_price($pair) {
    $url = 'https://api.kraken.com/0/public/Ticker?pair=' . $pair;
    $data = file_get_contents($url);
    $json_array = json_decode($data, true);
    $current_price = $json_array['result'][$pair]['c'][0];
    return $current_price;
}

function get_rolling_mean_data($pair, $interval = null, $num_periods = null) {
    if ($interval == null) {
        $interval = 1440;  // daily (num minutes in day)
    }
    if ($num_periods == null) {
        $num_periods = 7; // 7 days of data
    }
    $now = time();
    // 14 days ago timestamp by default (get double for the ema)
    $since = $now - ($interval * 60 * $num_periods * 2); 
	
    $url = 'https://api.kraken.com/0/public/OHLC?pair=' . $pair . '&interval=' . $interval . '&since=' . $since;
    $data = file_get_contents($url);
    // Example data (headings not present in real data):
    // '{"error": [], "result": {"ADAGPB": 
    // [
    // timestamp	open 	   high       low        close      vwap       volume             count
    // [1624147200, "1.00520", "1.05875", "0.95188", "1.03663", "0.99311", "865404.81347312", 1285], 
    // [1624233600, "1.04041", "1.04212", "0.84270", "0.84904", "0.90723", "1829249.14853250", 1702],
    // ], "last": 1624579200}}';
    $json_array = json_decode($data, true);
    $historic_data = $json_array['result'][$pair];
    $count = 0;
    $results = array();
    $historic_prices = array();
    foreach($historic_data as $x => $daily_data) {
        $count++;
        $closing_price = $daily_data[4];
	$high = $daily_data[2];
        $low = $daily_data[3];
        $historic_prices[$count]['closing_price'] = $closing_price;
        $historic_prices[$count]['high'] = $high;
        $historic_prices[$count]['low'] = $low;
    }
    
    $smas = array();
    
    $num = count($historic_prices);
    for ($i=$num_periods;$i<=$num;$i++) {
        // Start at nth period, to get n-period smas
        $total = 0;
        $total_high = 0;
        $total_low = 0;
        for ($j=$i; $j>$i-$num_periods; $j--) {
            // Average n-periods of data, working backwards from starting point
            $closing_price = $historic_prices[$j]['closing_price'];
            $high = $historic_prices[$j]['high'];
            $low = $historic_prices[$j]['low'];
            $total += $closing_price;
            $total_high += $high;
            $total_low += $low;
        }
        $rolling_mean = round($total / $num_periods, 6);
	$rolling_mean_high = round($total_high / $num_periods, 6);
	$rolling_mean_low = round($total_low / $num_periods, 6);
        $smas[$i]['mean'] = $rolling_mean;
        $smas[$i]['mean_high'] = $rolling_mean_high;
        $smas[$i]['mean_low'] = $rolling_mean_low;
    }
    
    $emas = array();
    
    for ($i=$num_periods+1; $i<=$num; $i++) {
        // Start at n+1th period, to get n-period emas
        $closing_price = $historic_prices[$i]['closing_price'];
        if ($i==$num_periods+1) {
            $last_ema = $smas[$num_periods]['mean'];
        } else {
            $last_ema = $emas[$i-1];   
        }
        
        $ema = get_exponential_moving_average($pair, $last_ema, $interval, $num_periods, $closing_price);
        $emas[$i] = $ema;
    }
    
    $results['mean'] = $smas[$num]['mean'];
    $results['mean_high'] = $smas[$num]['mean_high'];
    $results['mean_low'] = $smas[$num]['mean_low'];
    $results['ema'] = $emas[$num];
    
    return $results;
}

function get_exponential_moving_average($pair, $last_ema, $interval, $num_periods, $last_closing_price) {

    $smoothing_factor = 2;
    $weighting_factor = ($smoothing_factor / (1 + $num_periods));
	
    $ema = ($last_closing_price * $weighting_factor) + ($last_ema * (1 - $weighting_factor));
    return round($ema, 6);
}

function get_macd($pair, $interval) {
    # Get 12-period EMA
    $data = get_rolling_mean_data($pair, $interval, 12);
    $ema_12 = $data['ema'];
    
    # Get 26-period EMA
    $data = get_rolling_mean_data($pair, $interval, 26);
    $ema_26 = $data['ema'];
    
    # Subtract to get MACD
    $macd = $ema_12 - $ema_26;
    
    return $macd;  
}

function sanitise($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    return $data;
}

$pairs = array(
    "ADAGBP" => "ADAGBP",
    "DOTGBP" => "DOTGBP",
    "XETHZGBP" => "ETHGBP",
    "XXBTZGBP" => "BTCGBP"
);
?>
<html>
<head>
<title>Crypto Stats</title>
<meta name="robots" content="noindex">
<style type="text/css">
p,label,select,option,input {
	font-size: 1.25em; 
	font-family: Verdana, sans-serif;
}
</style>
</head>
<body>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>" method="post" id="pairform">
<label for="pair">Asset pair:</label>
<select name="pair" id="pair" form="pairform">
	<option value="">--- Select an assert pair ---</option>
	<?
	foreach ($pairs as $pair_id => $pair_name) {
	?>
	<option value="<?=$pair_id;?>"$gt;<?=$pair_name;?></option>
	<?
	}
	?>
</select>
<input type="submit">
</form>
<?
if ($_SERVER["REQUEST_METHOD"] == "POST") {
	$pair = sanitise($_POST["pair"]);
	if ($pair == "") {
?>
		<p>Please select an asset pair from the list</p>
<?
	} else {

$num_periods = 7;
$interval = 1440;
$mean_array = get_rolling_mean_data($pair, $interval, $num_periods);

$mean = $mean_array['mean'];
$mean_high = $mean_array['mean_high'];
$mean_low = $mean_array['mean_low'];
$ema = $mean_array['ema'];
$macd = get_macd($pair, $interval); 

$current_price = get_current_price($pair);
?>
<p>7-Day <?=$pairs[$pair];?> Stats:</p>
<p>Current Price: <?=$current_price;?> GBP</p>
<p>Mean Price (SMA): <?=$mean;?> GBP</p>
<p>EMA: <?=$ema;?> GBP</p>
<p>MACD: <?=$macd;?></p>
<p>Mean High Price: <?=$mean_high;?> GBP</p>
<p>Mean Low Price: <?=$mean_low;?> GBP</p>
	<?}
}?>
</body>
</html>

If you select an asset pair and submit the form, you should see the MACD value added to the stats page.

Conclusion

You should now have an updated crypto stats page that shows you the current MACD using a period of one day for the calculation. Thanks for reading, see you next time!