Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Market Manipulations Widget #426

Merged
merged 3 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/hugo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.108.0
HUGO_RAPID_HOST: ${{ secrets.HUGO_RAPID_HOST }}
HUGO_RAPID_KEY: ${{ secrets.HUGO_RAPID_KEY }}
steps:
- name: Install Hugo CLI
run: |
Expand Down
2 changes: 2 additions & 0 deletions assets/js/axios.min.js

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions assets/js/chart.js

Large diffs are not rendered by default.

268 changes: 268 additions & 0 deletions assets/js/widget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
const options = {
method: 'GET',
url: 'https://crypto-market-health.p.rapidapi.com/metrics',
params: {
limit: 50,
sort: 'asc'
},
headers: {
'X-RapidAPI-Key': RAPID_KEY,
'X-RapidAPI-Host': RAPID_HOST
}
};

// Utility function to format date and time
function formatDateAndTime(timestamp) {
const date = new Date(timestamp);
const options = { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' };
return date.toLocaleString('en-US', options);
}

const displayData = {};

// Initialize chart
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
let chart = new Chart(context, {
options: {
plugins: {
title: {
display: true,
text: 'Market Manipulations Metrics'
}
}
}
});

function parseDataFromSelects() {
const exchange = document.getElementById('exchange').value;
const pair = document.getElementById('pair').value;
parseData(exchange, pair);
}

function parseData(market, pair) {
Object.assign(options.params, { marketvenueid: market, pairid: pair });

axios.request(options).then(response => {
const metricsData = response.data;
processData(metricsData);
paintChart({value: document.getElementById('metric').value});
});
}

function processData(metricsData) {
displayData.buySellRatio = createBuySellRatioData(metricsData);
displayData.vwap = createVwapData(metricsData);
displayData.timesOfTrade = createTimesOfTradeData(metricsData);
displayData.volumeDistribution = createVolumeDistributionData(metricsData);
displayData.firstDigitDistribution = createFirstDigitDistributionData(metricsData);
}

function paintChart(metricKey) {
if (chart) chart.destroy();
chart = new Chart(context, displayData[metricKey.value]);
chart.options.plugins.title = {
display: true,
text: 'Market Manipulations Metrics'
};
chart.update();
}

function createBuySellRatioData(metricsData) {
return {
type: "line",
options: {
elements: {
point: {
radius: 0
}
}
},
data: {
labels: metricsData.map(item => formatDateAndTime(item.timestamp)),
datasets: [
{
label: "Buy/Sell Ratio",
data: metricsData.map(item => item.buysellratio),
borderColor: 'rgb(86, 171, 86)',
borderWidth: 2,
tension: 0.1
},
{
label: "Buy/Sell Ratio Absolute",
data: metricsData.map(item => item.buysellratioabs),
borderColor: 'rgb(77, 77, 255)',
borderWidth: 2,
tension: 0.1
}
]
}
};
}

function createVwapData(metricsData) {
return {
options: {
elements: {
point: {
radius: 0
}
},
scales: {
y: {
stack: 'vwapStack',
stackWeight: 2
},
y2: {
offset: true,
stack: 'vwapStack',
stackWeight: 1
}
}
},
data: {
labels: metricsData.map(item => formatDateAndTime(item.timestamp)),
datasets: [
{
label: "VWAP",
type: "line",
data: metricsData.map(item => item.vwap),
borderColor: 'rgb(127, 0, 127)',
tension: 0.1,
yAxisID: 'y2'
},
{
label: "Trade Count",
type: "bar",
data: metricsData.map(item => item.tradecount),
backgroundColor: 'rgb(16, 163, 127)',
}
]
}
};
}

function createTimesOfTradeData(metricsData) {
let timesOfTradeData = new Array(60).fill(0);
let totalTradesCount = 0;
for (let i = 0; i < timesOfTradeData.length; i++) {
for (let j = 0; j < metricsData.length; j++) {
timesOfTradeData[i] += metricsData[j].timeoftrade.seconds[i];
totalTradesCount += metricsData[j].timeoftrade.seconds[i];
}
}
let averageTrades = new Array(60).fill(totalTradesCount / 60);

return {
options: {
elements: {
point: {
radius: 0
}
}
},
data: {
labels: Array.from(Array(60).keys()),
datasets: [
{
label: "Average Trades Per Second",
type: "line",
data: averageTrades,
borderColor: 'rgb(255, 11, 11)',
borderDash: [5, 5],
tention: 0.1
},
{
label: "Trade Frequencies per Second Aggregated Over the Last 7 Days",
type: "bar",
data: timesOfTradeData,
backgroundColor: 'rgb(0, 128, 0)',
}
]
}
};
}

function createVolumeDistributionData(metricsData) {
let volumeBins = new Array(100).fill(0);
for (let i = 0; i < volumeBins.length; i++) {
for (let j = 0; j < metricsData.length; j++) {
volumeBins[i] += metricsData[j].volumedist[i][1];
}
}

return {
data: {
labels: Array.from(Array(100).keys()),
datasets: [
{
label: "Histogram of Trading Volumes Aggregated Over the Last 7 Days",
type: "bar",
data: volumeBins,
backgroundColor: 'rgb(0, 0, 255)',
}
]
}
};
}

function createFirstDigitDistributionData(metricsData) {
let fddData = new Array(9).fill(0);
let totalFDD = 0;
for (let i = 0; i < metricsData.length; i++) {
for (let j = 0; j < 10; j++) {
let currentData = metricsData[i].firstdigitdist[String(j + 1)];
if (currentData === undefined) { currentData = 0; }
fddData[j] += currentData;
totalFDD += currentData;
}
}

// Expected FDD data calculation
let expectedFddPercentage = [301, 176, 125, 97, 79, 67, 58, 51, 46];
let expectedFdd = [];
for (let i = 0; i < 10; i++) {
expectedFdd.push((totalFDD * expectedFddPercentage[i]) / 1000);
}

return {
options: {
elements: {
point: {
radius: 0
}
},
scales: {
y: {
stacked: true
}
}
},
data: {
labels: Array.from({ length: 9 }, (_, i) => i + 1),
datasets: [
{
label: "First Digit Distribution",
data: fddData,
type: "bar",
backgroundColor: 'rgb(255, 165, 0)',
stack: "Stack 1"
},
{
label: "Expected First Digit Distribution",
data: expectedFdd,
type: "bar",
backgroundColor: 'rgb(68, 147, 245)',
stack: "Stack 2"
}
]
}
};
}

// Initial data load
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => parseData('ascendex', 'btc-usdt'), { passive: true });
} else {
parseData('ascendex', 'btc-usdt');
}
2 changes: 2 additions & 0 deletions content/market-health/docs/market-health-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ weight: 10

We're dedicated to enhancing the clarity and integrity within the cryptocurrency landscape. This documentation provides a guide on how to use and interpret market surveillance statistical metrics, made accessible through DNI's [free API](https://rapidapi.com/DNInstitute/api/crypto-market-health), aiding in the identification of possible market manipulation.

{{< market_widget >}}

Your insight is essential. We encourage submissions of analytical articles that utilize data from our API, offering a fresh perspective on the multifaceted dimensions of the cryptocurrency markets. All submissions are compensated, and noteworthy contributions could potentially open doors to a position as a Market Surveillance Analyst. Submit your articles via a pull request to our GitHub [repository](https://github.com/1712n/dn-institute/tree/main/content/market-health/posts). Join us in fostering a more transparent and healthy cryptocurrency ecosystem.

| Indicator | API metric | Description |
Expand Down
42 changes: 42 additions & 0 deletions layouts/shortcodes/market_widget.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<canvas id='{{ .Get "id" }}' width='{{ .Get "width" }}' height='{{ .Get "height" }}'></canvas>
<!--
Implement environment variables fetching
-->
<script>
const RAPID_HOST = {{ getenv "HUGO_RAPID_HOST" }}
const RAPID_KEY = {{ getenv "HUGO_RAPID_KEY" }}
</script>
<div class="center">
<label for="exchange">Exchange:</label>
<select id="exchange" onchange="parseDataFromSelects()">
<option value="ascendex">AscendEX</option>
<option value="binance">Binance</option>
<option value="coinbase">Coinbase</option>
<option value="bitstamp">Bitstamp</option>
</select>

<label for="pair">Pair:</label>
<select id="pair" onchange="parseDataFromSelects()">
<option value="btc-usdt">BTC-USDT</option>
<option value="eth-usdt">ETH-USDT</option>
<option value="xrp-usdt">XRP-USDT</option>
</select>

<label for="metric">Metric:</label>
<select id="metric" onchange="paintChart(this)">
<option value="timesOfTrade">Times Of Trade</option>
<option value="firstDigitDistribution">First Digit Distribution</option>
<option value="volumeDistribution">Volume Distribution</option>
<option value="vwap">VWAP & Trade Count</option>
<option value="buySellRatio">Buy/Sell Ratio</option>
</select>
</div>

{{ $axios := resources.Get "/js/axios.min.js" }}
<script src="{{ $axios.Permalink }}"></script>

{{ $chart := resources.Get "/js/chart.js" }}
<script src="{{ $chart.Permalink }}"></script>

{{ $widget := resources.Get "/js/widget.js" }}
<script src="{{ $widget.Permalink }}"></script>