From 522425d90bfb4a20af61d8799b5f67747cbe6554 Mon Sep 17 00:00:00 2001 From: saml33 <30796577+saml33@users.noreply.github.com> Date: Mon, 19 Jul 2021 19:51:40 +1000 Subject: [PATCH] Pnl leaderboard (#43) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * leaderboard layout * hook up pnl endpoint * api limits, offset and start_date * pnl history charts * support account names * ui feedback updates * play with leaderboard 🙈 * fix chart bug Co-authored-by: Maximilian Schneider --- @types/index.d.ts | 8 + components/LeaderboardTable.tsx | 242 ++++++++++++++++++++++ components/TopBar.tsx | 1 + components/account-page/AccountAssets.tsx | 2 +- components/icons.jsx | 29 +++ pages/leaderboard.tsx | 47 +++++ public/assets/icons/step.png | Bin 0 -> 9561 bytes stores/useMangoStore.tsx | 96 ++++++++- yarn.lock | 4 +- 9 files changed, 425 insertions(+), 4 deletions(-) create mode 100644 components/LeaderboardTable.tsx create mode 100644 pages/leaderboard.tsx create mode 100644 public/assets/icons/step.png diff --git a/@types/index.d.ts b/@types/index.d.ts index 9b9471d..b630a55 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -1,4 +1,12 @@ +import 'dayjs' + declare module '*.svg' { const content: any export default content } + +declare module 'dayjs' { + interface Dayjs { + utc() + } +} diff --git a/components/LeaderboardTable.tsx b/components/LeaderboardTable.tsx new file mode 100644 index 0000000..fea3447 --- /dev/null +++ b/components/LeaderboardTable.tsx @@ -0,0 +1,242 @@ +import { useEffect, useState } from 'react' +import styled from '@emotion/styled' +import dayjs from 'dayjs' +import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table' +import { AreaChart, Area, ReferenceLine, XAxis, YAxis, Tooltip } from 'recharts' +import { ExternalLinkIcon } from '@heroicons/react/outline' +import { usdFormatter } from '../utils' +import { AwardIcon, TrophyIcon } from './icons' +import useMangoStore from '../stores/useMangoStore' + +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const StyledTooltipWrapper = styled.div` + min-width: 180px; +` + +const LeaderboardTable = () => { + const [pnlHistory, setPnlHistory] = useState([]) + const [loading, setLoading] = useState(false) + const pnlLeaderboard = useMangoStore((s) => s.pnlLeaderboard) + + /* API Returns: + * [ { cumulative_pnl: -3.687498 + date: "2021-06-10" + margin_account: "J8XtwLVyZjeH1PG1Nnk9cWbLn3zEemS1rCbn4x6AjtXM" + name: "" + owner: "APLKzSqJQw79q4U4ipBWnLdqkVzijSPNpDCNKwL8mW3B" + }, ... ] + */ + useEffect(() => { + const getPnlHistory = async () => { + setLoading(true) + const start = dayjs().utc().subtract(31, 'day').format('YYYY-MM-DD') + console.log(start) + const results = await Promise.all( + pnlLeaderboard.slice(pnlHistory.length).map(async (acc) => { + const response = await fetch( + `https://mango-transaction-log.herokuapp.com/stats/pnl_history/${acc.margin_account}?start_date=${start}` + ) + const parsedResponse = await response.json() + return parsedResponse ? parsedResponse.reverse() : [] + }) + ) + setPnlHistory(pnlHistory.concat(results)) + setLoading(false) + } + getPnlHistory() + }, [pnlLeaderboard]) + + const formatPnlHistoryData = (data) => { + const start = new Date( + dayjs().utc().hour(0).minute(0).subtract(31, 'day') + ).getTime() + + return data.filter((d) => new Date(d.date).getTime() > start) + } + + const tooltipContent = (tooltipProps) => { + if (tooltipProps.payload.length > 0) { + return ( + +
+
Date
+
+ {tooltipProps.payload[0].payload.date} +
+
+
+
PNL
+
+ {usdFormatter.format( + tooltipProps.payload[0].payload.cumulative_pnl + )} +
+
+
+ ) + } + return null + } + + return ( +
+
+
+ {pnlLeaderboard.length > 0 ? ( +
+ + + + + + + + + + + + {pnlLeaderboard.map((acc, index) => ( + + + + + + + + ))} + +
+ Rank + + Account + + PNL + + PNL / Time + +
+ View on Step + +
+
+
+ {acc.rank} + {acc.rank === 1 ? ( + + ) : null} + {acc.rank === 2 || acc.rank === 3 ? ( + + ) : null} +
+
+ {acc.name + ? acc.name + : `${acc.margin_account.slice( + 0, + 5 + )}...${acc.margin_account.slice(-5)}`} + +
+ {usdFormatter.format(acc.pnl)} +
+
+ {loading && !pnlHistory[index] ? ( +
+ ) : ( + + + + + + + + )} +
+ + View + + +
+
+ ) : ( +
+
+
+
+
+
+
+ )} +
+
+
+ ) +} + +export default LeaderboardTable diff --git a/components/TopBar.tsx b/components/TopBar.tsx index 83d9651..5c84026 100644 --- a/components/TopBar.tsx +++ b/components/TopBar.tsx @@ -31,6 +31,7 @@ const TopBar = () => { Account Borrow Alerts + Leaderboard Stats Learn
diff --git a/components/account-page/AccountAssets.tsx b/components/account-page/AccountAssets.tsx index 8105937..936085b 100644 --- a/components/account-page/AccountAssets.tsx +++ b/components/account-page/AccountAssets.tsx @@ -147,7 +147,7 @@ export default function AccountAssets() { scope="col" className={`px-6 py-3 text-left font-normal`} > - Available + Deposits { ) } + +export const AwardIcon = ({ className }) => { + return ( + + + + + ) +} + +export const TrophyIcon = ({ className }) => { + return ( + + + + ) +} diff --git a/pages/leaderboard.tsx b/pages/leaderboard.tsx new file mode 100644 index 0000000..f43676c --- /dev/null +++ b/pages/leaderboard.tsx @@ -0,0 +1,47 @@ +import { useEffect, useState } from 'react' +import useMangoStore from '../stores/useMangoStore' +import PageBodyContainer from '../components/PageBodyContainer' +import TopBar from '../components/TopBar' +import LeaderboardTable from '../components/LeaderboardTable' +import { LinkButton } from '../components/Button' + +export default function Leaderboard() { + const [offsetResults, setOffsetResults] = useState(0) + const actions = useMangoStore((s) => s.actions) + const pnlLeaderboard = useMangoStore((s) => s.pnlLeaderboard) + + useEffect(() => { + actions.fetchPnlLeaderboard(offsetResults, 29) + }, []) + + const handleShowMore = async () => { + const offset = offsetResults + 25 + await actions.fetchPnlLeaderboard(offset, 29) + setOffsetResults(offset) + } + + return ( +
+ + +
+

+ Leaderboard +

+
+
+

Top 100 accounts by PNL over the last 30 days

+ + {pnlLeaderboard.length < 100 ? ( + handleShowMore()} + > + Show More + + ) : null} +
+
+
+ ) +} diff --git a/public/assets/icons/step.png b/public/assets/icons/step.png new file mode 100644 index 0000000000000000000000000000000000000000..a7364f57a538e02b67ddd945947d0a6a0d8a2dfb GIT binary patch literal 9561 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 } +interface AccountPnl { + margin_account: string + name: string + owner: string + pnl: number + rank: number +} + interface MangoStore extends State { notifications: Array<{ type: string @@ -119,9 +131,12 @@ interface MangoStore extends State { liquidationHistory: any[] withdrawalHistory: any[] tradeHistory: any[] + pnlHistory: any[] + pnlLeaderboard: any[] + accountPnl: AccountPnl set: (x: any) => void actions: { - [key: string]: () => void + [key: string]: (...args: any[]) => void } } @@ -177,6 +192,9 @@ const useMangoStore = create((set, get) => ({ liquidationHistory: [], withdrawalHistory: [], tradeHistory: [], + pnlHistory: [], + pnlLeaderboard: [], + accountPnl: null, set: (fn) => set(produce(fn)), actions: { async fetchWalletBalances() { @@ -428,6 +446,82 @@ const useMangoStore = create((set, get) => ({ state.withdrawalHistory = results }) }, + async fetchPnlHistory(marginAccount = null) { + const selectedMarginAccount = + marginAccount || + get().selectedMarginAccount.current.publicKey.toString() + const set = get().set + + if (!selectedMarginAccount) return + + const response = await fetch( + `https://mango-transaction-log.herokuapp.com/stats/pnl_history/${selectedMarginAccount}` + ) + const parsedResponse = await response.json() + const results = parsedResponse.length ? parsedResponse.reverse() : [] + + set((state) => { + state.pnlHistory = results + }) + }, + async fetchPnlLeaderboard(offset = 0, start?: number) { + const baseUrl = + 'https://mango-transaction-log.herokuapp.com/stats/pnl_leaderboard' + + const startAt = start + ? dayjs().utc().subtract(start, 'day').format('YYYY-MM-DD') + : null + + const url = startAt + ? `${baseUrl}?start_date=${startAt}&limit=25&offset=${offset}` + : `${baseUrl}?limit=25&offset=${offset}` + + const response = await fetch(url) + const parsedResponse = await response.json() + const results = parsedResponse ? parsedResponse : [] + + const currentLeaderboard = get().pnlLeaderboard + + if (currentLeaderboard.length > 0 && offset > 0) { + const updatedLeaderboard = currentLeaderboard.concat(results) + set((state) => { + state.pnlLeaderboard = updatedLeaderboard + }) + } else { + set((state) => { + state.pnlLeaderboard = results + }) + } + }, + async fetchPnlByAccount(marginAccount = null, start?: number) { + const selectedMarginAccount = + marginAccount || get().selectedMarginAccount.current + const set = get().set + + if (!selectedMarginAccount) return + + const startAt = start + ? new Date(Date.now() - start * 24 * 60 * 60 * 1000).toLocaleDateString( + 'en-ZA' + ) + : null + + const baseUrl = + 'https://mango-transaction-log.herokuapp.com/stats/pnl_leaderboard_rank' + + const url = startAt + ? `${baseUrl}/${selectedMarginAccount.publicKey.toString()}?start_date=${startAt}` + : `${baseUrl}/${selectedMarginAccount.publicKey.toString()}` + + const response = await fetch(url) + const parsedResponse = + response.status === 200 ? await response.json() : null + const results = parsedResponse ? parsedResponse : null + + set((state) => { + state.accountPnl = results + }) + }, }, })) diff --git a/yarn.lock b/yarn.lock index 57336e8..74f9f14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2515,9 +2515,9 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -"borsh@git+https://github.com/defactojob/borsh-js.git#field-mapper": +"borsh@https://github.com/defactojob/borsh-js#field-mapper": version "0.3.1" - resolved "git+https://github.com/defactojob/borsh-js.git#33a0d24af281112c0a48efb3fa503f3212443de9" + resolved "https://github.com/defactojob/borsh-js#33a0d24af281112c0a48efb3fa503f3212443de9" dependencies: "@types/bn.js" "^4.11.5" bn.js "^5.0.0"