Guest User

Untitled

a guest
Nov 5th, 2023
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.47 KB | None | 0 0
  1. import type { AppRouter } from '@server/routers';
  2. import type { Backtest } from '@server/routers/backtest';
  3. import type { Execution } from '@server/routers/execution';
  4. import type { TRPCClientErrorLike } from '@trpc/client';
  5. import type { UseTRPCQueryResult } from '@trpc/react-query/shared';
  6. import { subDays } from 'date-fns';
  7. import type Decimal from 'decimal.js';
  8. import { useTheme } from 'next-themes';
  9. import { useEffect, useMemo, useRef, useState } from 'react';
  10. import { Line, LineChart, ReferenceLine, ResponsiveContainer, YAxis } from 'recharts';
  11.  
  12. import { BacktestStatus, ExecutionStatus, ExecutionType } from '@cybotrade/core';
  13.  
  14. import { calculatePerformance } from '@/lib/calculation';
  15. import { Skeleton } from '@/ui';
  16. import { trpcVanilla } from '@/utils/trpc';
  17.  
  18. type EquityCurveRendererProps = {
  19. backtests: UseTRPCQueryResult<Backtest[], TRPCClientErrorLike<AppRouter>>;
  20. executions: UseTRPCQueryResult<Execution[], TRPCClientErrorLike<AppRouter>>;
  21. };
  22.  
  23. export const EquityCurveRenderer = ({ backtests, executions }: EquityCurveRendererProps) => {
  24. const { theme } = useTheme();
  25. const isDarkTheme = theme === 'dark';
  26.  
  27. const [backtestHistories, setBacktestHistories] = useState<
  28. {
  29. id: string;
  30. equity: { name: number; equity: Decimal }[];
  31. createdAt: Date;
  32. }[]
  33. >([]);
  34. const [executionHistories, setExecutionHistories] = useState<
  35. {
  36. id: string;
  37. equity: { name: number; equity: Decimal }[];
  38. createdAt: Date;
  39. }[]
  40. >([]);
  41. // const [chartData, setChartData] = useState<
  42. // {
  43. // name: number;
  44. // equity: Decimal;
  45. // }[]
  46. // >([]);
  47. const [isLoading, setLoading] = useState(true);
  48. const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
  49.  
  50. useEffect(() => {
  51. async function computeBacktests() {
  52. if (!backtests.data) return;
  53. if (backtests.data.length === 0) return;
  54.  
  55. const succeed = (
  56. await Promise.all(
  57. backtests.data
  58. .filter(({ status }) => status === BacktestStatus.Completed)
  59. .map(
  60. async ({
  61. id,
  62. parameters: { comission, initialCapital, endDate },
  63. closedTrades,
  64. openedTrades,
  65. createdAt,
  66. }) => {
  67. const equities = (
  68. await trpcVanilla.statistics.equity.list.query({
  69. correlationId: id,
  70. timestamp: {
  71. start: subDays(new Date(endDate), 7),
  72. end: new Date(endDate),
  73. },
  74. })
  75. ).Items.map((item) => ({
  76. value: item.value,
  77. timestamp: new Date(item.timestamp.div(10 ** 6).toNumber()),
  78. }));
  79.  
  80. return {
  81. id,
  82. createdAt,
  83. ...calculatePerformance({
  84. history: {
  85. closedTrades,
  86. openedTrades,
  87. equities,
  88. drawdowns: equities.map((equity) => ({
  89. ...equity,
  90. value: equity.value.sub(initialCapital),
  91. })),
  92. },
  93. parameters: { comission, initialCapital, riskFreeRate: 0.02 },
  94. }),
  95. };
  96. },
  97. ),
  98. )
  99. ).map(({ id, equities, createdAt }) => ({
  100. id,
  101. createdAt,
  102. equity: equities.map((equity, i) => ({
  103. name: i,
  104. equity: equity.value.div(equities[0].value),
  105. })),
  106. }));
  107.  
  108. const failed = backtests.data
  109. .filter(({ status }) => status === BacktestStatus.Failed)
  110. .map(({ id, createdAt }) => ({
  111. id,
  112. equity: [],
  113. createdAt,
  114. }));
  115.  
  116. const pending = backtests.data
  117. .filter(({ status }) => [BacktestStatus.Pending, BacktestStatus.Running].includes(status))
  118. .map(({ id, createdAt }) => ({
  119. id,
  120. equity: [],
  121. createdAt,
  122. }));
  123. setBacktestHistories([...succeed, ...failed, ...pending]);
  124. }
  125. computeBacktests();
  126. }, [backtests.data]);
  127.  
  128. useEffect(() => {
  129. async function computeExecutions() {
  130. if (!executions.data) return;
  131. if (executions.data.length === 0) return;
  132.  
  133. const succeed = (
  134. await Promise.all(
  135. executions.data
  136. .filter(({ type }) => type === ExecutionType.Live)
  137. .filter(({ status }) => status === ExecutionStatus.Running)
  138. .map(
  139. async ({
  140. id,
  141. parameters: { comission, initialCapital },
  142. closedTrades,
  143. openedTrades,
  144. createdAt,
  145. }) => {
  146. const equities = (
  147. await trpcVanilla.statistics.equity.list.query({
  148. correlationId: id,
  149. timestamp: {
  150. start: subDays(new Date(), 7),
  151. end: new Date(),
  152. },
  153. })
  154. ).Items.map((item) => ({
  155. value: item.value,
  156. timestamp: new Date(item.timestamp.div(10 ** 6).toNumber()),
  157. }));
  158.  
  159. return {
  160. id,
  161. createdAt,
  162. ...calculatePerformance({
  163. history: {
  164. closedTrades,
  165. openedTrades,
  166. equities: equities,
  167. drawdowns: equities.map((equity) => ({
  168. ...equity,
  169. value: equity.value.sub(initialCapital),
  170. })),
  171. },
  172. parameters: {
  173. comission,
  174. initialCapital:
  175. equities && equities.length > 0 ? equities[0].value.toNumber() : 0,
  176. riskFreeRate: 0.02,
  177. },
  178. }),
  179. };
  180. },
  181. ),
  182. )
  183. ).map(({ id, equities, createdAt }) => ({
  184. id,
  185. equity: equities.map((equity, i) => ({
  186. name: i,
  187. equity: equity.value.div(equities[0].value),
  188. })),
  189. createdAt,
  190. }));
  191.  
  192. const failed = executions.data
  193. .filter(({ status }) => status === ExecutionStatus.Failed)
  194. .map(({ id, createdAt }) => ({
  195. id,
  196. equity: [],
  197. createdAt,
  198. }));
  199.  
  200. const pending = executions.data
  201. .filter(({ status }) => [ExecutionStatus.Pending].includes(status))
  202. .map(({ id, createdAt }) => ({
  203. id,
  204. equity: [],
  205. createdAt,
  206. }));
  207.  
  208. setExecutionHistories([...succeed, ...failed, ...pending]);
  209. }
  210. computeExecutions();
  211. }, [executions.data]);
  212. // not working as i expected
  213. // useEffect(() => {
  214. // timer.current && clearTimeout(timer.current);
  215. // const computeChartData = [...backtestHistories, ...executionHistories]
  216. // .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
  217. // .flatMap(({ equity }) => equity);
  218. // timer.current = setTimeout(() => {
  219. // setChartData(computeChartData);
  220. // setLoading(false);
  221. // }, 5000);
  222. // }, [backtestHistories, executionHistories]);
  223.  
  224. const chartData = useMemo(
  225. () =>
  226. [...backtestHistories, ...executionHistories]
  227. .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
  228. .flatMap(({ equity }) => equity),
  229. [backtestHistories, executionHistories],
  230. );
  231. // loading skeleton
  232. // if (isLoading) {
  233. // return (
  234. // <div className="flex mx-auto max-w-[150px] h-[80px]">
  235. // <Skeleton className="w-full h-full" />
  236. // </div>
  237. // );
  238. // }
  239.  
  240. return (
  241. <div className="flex mx-auto max-w-[150px] h-[80px]">
  242. <ResponsiveContainer width="100%" height="100%">
  243. <LineChart data={chartData}>
  244. <ReferenceLine y={chartData?.[0]?.equity.toNumber() || 0} stroke="black" />
  245.  
  246. <YAxis domain={['dataMin', 'dataMax']} hide={true} />
  247. <Line
  248. type="monotone"
  249. dataKey="equity"
  250. dot={false}
  251. stroke={isDarkTheme ? '#00FC65' : '#FC9E26'}
  252. // stroke="#FC9E26"
  253. />
  254. </LineChart>
  255. </ResponsiveContainer>
  256. </div>
  257. );
  258. };
  259.  
Advertisement
Add Comment
Please, Sign In to add comment