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

added realtime depth and Trade #5

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"use client";
import { useEffect, useState } from "react";
import { SignalingManager } from "@/app/utils/SignalingManager";
import { Depth, Trade } from "@/app/utils/types";
import { DepthComponent } from "./depth/Depth";
import Trades from "./Trades";

export function DepthAndTrade({ market }: {market: string}) {
const [bids, setBids] = useState<[string, string][]>([]);
const [asks, setAsks] = useState<[string, string][]>([]);
const [price, setPrice] = useState<string>("");
const [trades, setTrades] = useState<Partial<Trade>[]>([]);
const [type, setType] = useState<'books' | 'trades'>('books');

useEffect(() => {

SignalingManager.getInstance().registerCallback("depth", (data: any) => {
setBids((previousBids)=>{
let newBids = [...previousBids];
for(let i=0; i<data.bids.length; i++){
let found = false;
for(let j=0; j<previousBids.length; j++){
if(data.bids[i][0] === previousBids[j][0]){
newBids[j][1] = data.bids[i][1];
found = true;
break;
}
}
if(!found){
newBids.push(data.bids[i]);
}
}
// remove 0 quantity data
newBids = newBids.filter(bid => bid[1] !== "0.00");
//reverse sort by pppu and take the first 15
newBids = newBids.sort((a, b) => Number(b[0]) - Number(a[0])).slice(0,50);
return newBids;
})
setAsks((previousAsks)=>{
let newAsks = previousAsks;
for(let i=0; i<data.asks.length; i++){
let found = false;
for(let j=0; j<previousAsks.length; j++){
if(data.asks[i][0] === previousAsks[j][0]){

newAsks[j][1] = data.asks[i][1];
found = true;
break;
}
}
if(!found){
newAsks.push(data.asks[i]);
}
}
// remove 0 quantity data
newAsks = newAsks.filter(ask => ask[1] !== "0.00");
// sort by ppu
newAsks = newAsks.sort((a, b) => Number(a[0]) - Number(b[0])).slice(0,50);
return newAsks;
})

}, `Depth-${market}`);
SignalingManager.getInstance().registerCallback("trade", (data: Partial<Trade>) => {

setTrades((previousTrades)=>{
let newTrades = [...previousTrades];
newTrades = newTrades.reverse();
newTrades.push(data);
newTrades = newTrades.reverse();
newTrades = newTrades.slice(0,50);
return newTrades;
})
}, `Trade-${market}`);


SignalingManager.getInstance().sendMessage({"method":"SUBSCRIBE","params":[`depth.200ms.${market}`]});
SignalingManager.getInstance().sendMessage({"method":"SUBSCRIBE","params":[`trade.${market}`]});
return () => {
SignalingManager.getInstance().deRegisterCallback("depth", `Depth-${market}`);
SignalingManager.getInstance().sendMessage({"method":"UNSUBSCRIBE","params":[`depth.200ms.${market}`]} );
SignalingManager.getInstance().sendMessage({"method":"UNSUBSCRIBE","params":[`trade.${market}`]} );
}
}, [market])

return <div className="relative">
<TableHeader type={type} setType={setType} />
{/* <div className="h-[500px] relative overflow-y-scroll no-scrollbar">
{asks && <AskTable asks={asks} />}
{price && <div>{price}</div>}
{bids && <BidTable bids={bids} />}
</div> */}
{type==="trades" && <Trades trades={trades} />}
{type==="books" && <DepthComponent asks={asks} bids={bids} price={price} />}
</div>
}

function TableHeader({type, setType}: {type: string, setType: any}) {
return <div className="absolute top-0 left-0 w-full h-5 flex gap-5 text-xs bg-black z-10">
<BookButton type={type} setType={setType} />
<TradesButton type={type} setType={setType} />
</div>
}

function BookButton({ type, setType }: { type: string, setType: any }) {
return <div className="flex flex-col cursor-pointer justify-center py-2" onClick={() => setType('books')}>
<div
className={`text-sm font-medium py-1 border-b-2 ${type === 'books' ? "border-accentBlue text-baseTextHighEmphasis" : "border-transparent text-baseTextMedEmphasis hover:border-baseTextHighEmphasis hover:text-baseTextHighEmphasis"}`}
>
Books
</div>
</div>
}

function TradesButton({ type, setType }: { type: string, setType: any }) {
return <div className="flex flex-col cursor-pointer justify-center py-2" onClick={() => setType('trades')}>
<div
className={`text-sm font-medium py-1 border-b-2 ${type === 'trades' ? "border-accentBlue text-baseTextHighEmphasis" : "border-b-2 border-transparent text-baseTextMedEmphasis hover:border-baseTextHighEmphasis hover:text-baseTextHighEmphasis"} `}
>
Trades
</div>
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const MarketBar = ({market}: {market: string}) => {
const [ticker, setTicker] = useState<Ticker | null>(null);

useEffect(() => {
getTicker(market).then(setTicker);

SignalingManager.getInstance().registerCallback("ticker", (data: Partial<Ticker>) => setTicker(prevTicker => ({
firstPrice: data?.firstPrice ?? prevTicker?.firstPrice ?? '',
high: data?.high ?? prevTicker?.high ?? '',
Expand All @@ -20,11 +20,16 @@ export const MarketBar = ({market}: {market: string}) => {
symbol: data?.symbol ?? prevTicker?.symbol ?? '',
trades: data?.trades ?? prevTicker?.trades ?? '',
volume: data?.volume ?? prevTicker?.volume ?? '',
})), `TICKER-${market}`);

})), `Ticker-${market}`);



SignalingManager.getInstance().sendMessage({"method":"SUBSCRIBE","params":[`ticker.${market}`]} );

return () => {
SignalingManager.getInstance().deRegisterCallback("ticker", `TICKER-${market}`);

SignalingManager.getInstance().sendMessage({"method":"UNSUBSCRIBE","params":[`ticker.${market}`]} );
}
}, [market])
Expand All @@ -33,7 +38,7 @@ export const MarketBar = ({market}: {market: string}) => {
return <div>
<div className="flex items-center flex-row relative w-full overflow-hidden border-b border-slate-800">
<div className="flex items-center justify-between flex-row no-scrollbar overflow-auto pr-4">
<Ticker market={market} />
<TickerComponent market={market} />
<div className="flex items-center flex-row space-x-8 pl-4">
<div className="flex flex-col h-full justify-center">
<p className={`font-medium tabular-nums text-greenText text-md text-green-500`}>${ticker?.lastPrice}</p>
Expand Down Expand Up @@ -63,7 +68,7 @@ export const MarketBar = ({market}: {market: string}) => {

}

function Ticker({market}: {market: string}) {
function TickerComponent({market}: {market: string}) {
return <div className="flex h-[60px] shrink-0 space-x-4">
<div className="flex flex-row relative ml-2 -mr-4">
<img alt="SOL Logo" loading="lazy" decoding="async" data-nimg="1" className="z-10 rounded-full h-6 w-6 mt-4 outline-baseBackgroundL1" src="/sol.webp" />
Expand Down
37 changes: 19 additions & 18 deletions week-1/day-2/binance-frontend-realtime/app/components/TradeView.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { ChartManager } from "../utils/ChartManager";
import { getKlines } from "../utils/httpClient";
import { KLine } from "../utils/types";
import { SignalingManager } from "../utils/SignalingManager";

export function TradeView({
market,
}: {
market: string;
}) {

const chartRef = useRef<HTMLDivElement>(null);
const chartManagerRef = useRef<ChartManager>(null);

useEffect(() => {
const init = async () => {
let klineData: KLine[] = [];
try {
klineData = await getKlines(market, "1h", Math.floor((new Date().getTime() - 1000 * 60 * 60 * 24 * 7) / 1000), Math.floor(new Date().getTime() / 1000));
} catch (e) { }


if (chartRef) {
if (chartManagerRef.current) {
chartManagerRef.current.destroy();
}

const chartManager = new ChartManager(
chartRef.current,
[
...klineData?.map((x) => ({
close: parseFloat(x.close),
high: parseFloat(x.high),
low: parseFloat(x.low),
open: parseFloat(x.open),
timestamp: new Date(x.end),
})),
].sort((x, y) => (x.timestamp < y.timestamp ? -1 : 1)) || [],
[],
{
background: "#0e0f14",
color: "white",
Expand All @@ -42,8 +31,20 @@ export function TradeView({
//@ts-ignore
chartManagerRef.current = chartManager;
}
};
init();


SignalingManager.getInstance().registerCallback('kline', (data:any)=>{
chartManagerRef.current?.update(data);

}, `Kline-${market}`);

SignalingManager.getInstance().sendMessage({"method":"SUBSCRIBE","params":[`kline.5m.${market}`]});

return ()=>{
SignalingManager.getInstance().deRegisterCallback("kline", `Kline-${market}`);
SignalingManager.getInstance().sendMessage({"method":"UNSUBSCRIBE","params":[`kline.5m.${market}`]} );
}

}, [market, chartRef]);

return (
Expand Down
59 changes: 59 additions & 0 deletions week-1/day-2/binance-frontend-realtime/app/components/Trades.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client";

import { Trade } from "../utils/types";



export default function Trades({
trades,
}:{
trades: Partial<Trade>[],
}){
return <div className="relative mt-6">
<TableHeader />
<div className="h-[500px] relative overflow-y-scroll no-scrollbar">
{ trades && trades.map((trade,index)=>{
let timestamp = trade.timestamp!;
let date = new Date(timestamp);
// Extract hours, minutes, and seconds
let hours = date.getUTCHours();
// Get minutes part from the timestamp
let minutes = date.getUTCMinutes();
// Get seconds part from the timestamp
let seconds = date.getUTCSeconds();

// Pad hours, minutes, and seconds to ensure two digits
let formattedHours = String(hours).padStart(2, '0');
let formattedMinutes = String(minutes).padStart(2, '0');
let formattedSeconds = String(seconds).padStart(2, '0');

// Format the time as HH.MM.SS
let timeStr = `${formattedHours}.${formattedMinutes}.${formattedSeconds}`;
return (
<>
<div key={index}className="flex justify-between text-xs w-full">
<div>
{trade.price}
</div>
<div>
{trade.quantity}
</div>
<div>
{timeStr}
</div>
</div>
</>
)
})
}
</div>
</div>
}

function TableHeader() {
return <div className="absolute top-0 left-0 w-full h-5 flex justify-between text-xs bg-black z-10">
<div className="text-white">Price</div>
<div className="text-slate-500">Qunatity</div>
<div className="text-slate-500">Time</div>
</div>
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@

export const AskTable = ({ asks }: { asks: [string, string][] }) => {
let currentTotal = 0;
const relevantAsks = asks.slice(0, 15);
relevantAsks.reverse();
const asksWithTotal: [string, string, number][] = relevantAsks.map(([price, quantity]) => [price, quantity, currentTotal += Number(quantity)]);
const maxTotal = relevantAsks.reduce((acc, [_, quantity]) => acc + Number(quantity), 0);
asks.reverse();
const asksWithTotal: [string, string, number][] = asks.map(([price, quantity]) => [price, quantity, currentTotal += Number(quantity)]);
const maxTotal = asks.reduce((acc, [_, quantity]) => acc + Number(quantity), 0);
asksWithTotal.reverse();

return <div>
return <div className="mt-5">
{asksWithTotal.map(([price, quantity, total]) => <Ask maxTotal={maxTotal} key={price} price={price} quantity={quantity} total={total} />)}
</div>
}
Expand All @@ -16,17 +15,17 @@ function Ask({price, quantity, total, maxTotal}: {price: string, quantity: strin
style={{
display: "flex",
position: "relative",
marginTop:4,
width: "100%",
backgroundColor: "transparent",
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: `${(100 * total) / maxTotal}%`,
right: 0,
width: `${(100 * (total)) / maxTotal}%`,
height: "100%",
background: "rgba(228, 75, 68, 0.325)",
transition: "width 0.3s ease-in-out",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@

export const BidTable = ({ bids }: {bids: [string, string][]}) => {
let currentTotal = 0;
const relevantBids = bids.slice(0, 15);
const bidsWithTotal: [string, string, number][] = relevantBids.map(([price, quantity]) => [price, quantity, currentTotal += Number(quantity)]);
const maxTotal = relevantBids.reduce((acc, [_, quantity]) => acc + Number(quantity), 0);
const bidsWithTotal: [string, string, number][] = bids.map(([price, quantity]) => [price, quantity, currentTotal += Number(quantity)]);
const maxTotal = bids.reduce((acc, [_, quantity]) => acc + Number(quantity), 0);

return <div>
{bidsWithTotal?.map(([price, quantity, total]) => <Bid maxTotal={maxTotal} total={total} key={price} price={price} quantity={quantity} />)}
Expand All @@ -16,16 +15,17 @@ function Bid({ price, quantity, total, maxTotal }: { price: string, quantity: st
style={{
display: "flex",
position: "relative",
marginTop:4,
width: "100%",
backgroundColor: "transparent",
overflow: "hidden",
// overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
width: `${(100 * total) / maxTotal}%`,
height: "100%",
background: "rgba(1, 167, 129, 0.325)",
Expand Down
Loading