thigal_test

This commit is contained in:
Thigazhezhilan J 2026-03-22 14:37:23 +05:30
parent 465a66fd53
commit aa789d739c
6 changed files with 103 additions and 54 deletions

View File

@ -1,26 +1,16 @@
const API_BASE = "/api";
import { apiRequest } from "@/lib/queryClient";
export async function startStrategy(data) {
const res = await fetch(`${API_BASE}/strategy/start`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(data),
});
const res = await apiRequest("POST", "/strategy/start", data);
return res.json();
}
export async function stopStrategy() {
const res = await fetch(`${API_BASE}/strategy/stop`, {
method: "POST",
credentials: "include",
});
const res = await apiRequest("POST", "/strategy/stop");
return res.json();
}
export async function getStrategyStatus() {
const res = await fetch(`${API_BASE}/strategy/status`, {
credentials: "include",
});
const res = await apiRequest("GET", "/strategy/status");
return res.json();
}

View File

@ -1,4 +1,5 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { apiRequest } from "@/lib/queryClient";
type StrategyEvent = {
seq?: number;
@ -110,9 +111,7 @@ export default function StrategyTimeline() {
useEffect(() => {
const fetchLogs = async () => {
try {
const res = await fetch(`/api/logs?since_seq=${latestSeqRef.current}`, {
credentials: "include",
});
const res = await apiRequest("GET", `/logs?since_seq=${latestSeqRef.current}`);
const data = await res.json();
const events = Array.isArray(data?.events) ? data.events : [];
const normalized = events.map(normalizeLog);

View File

@ -250,7 +250,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
data-testid="button-learn-more"
onClick={() => navigate("/learn-more")}
>
Learn More
Learn More Test
</Button>
</div>
</div>

View File

@ -5,11 +5,12 @@ const API_BASE_URL =
((typeof window !== "undefined" &&
(window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1")) as any
? "http://localhost:8000"
? "http://localhost:8000/api"
: undefined);
const NORMALIZED_API_BASE_URL = API_BASE_URL
? API_BASE_URL.replace(/\/+$/, "")
: "";
const REQUEST_TIMEOUT_MS = 12000;
function resolveApiUrl(url: string) {
if (url.startsWith("http://") || url.startsWith("https://")) {
@ -18,7 +19,8 @@ function resolveApiUrl(url: string) {
if (!NORMALIZED_API_BASE_URL) {
return url;
}
return `${NORMALIZED_API_BASE_URL}${url}`;
const normalizedPath = url.startsWith("/") ? url : `/${url}`;
return `${NORMALIZED_API_BASE_URL}${normalizedPath}`;
}
async function throwIfResNotOk(res: Response) {
@ -28,12 +30,31 @@ async function throwIfResNotOk(res: Response) {
}
}
async function fetchWithTimeout(input: RequestInfo | URL, init?: RequestInit) {
const controller = new AbortController();
const timeoutId = globalThis.setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
try {
return await fetch(input, {
...init,
signal: controller.signal,
});
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") {
throw new Error("Request timed out. Please try again.");
}
throw error;
} finally {
globalThis.clearTimeout(timeoutId);
}
}
export async function apiRequest(
method: string,
url: string,
data?: unknown | undefined,
): Promise<Response> {
const res = await fetch(resolveApiUrl(url), {
const res = await fetchWithTimeout(resolveApiUrl(url), {
method,
headers: data ? { "Content-Type": "application/json" } : {},
body: data ? JSON.stringify(data) : undefined,
@ -50,7 +71,7 @@ export const getQueryFn: <T>(options: {
}) => QueryFunction<T> =
({ on401: unauthorizedBehavior }) =>
async ({ queryKey }) => {
const res = await fetch(resolveApiUrl(queryKey.join("/") as string), {
const res = await fetchWithTimeout(resolveApiUrl(queryKey.join("/") as string), {
credentials: "include",
});

View File

@ -246,12 +246,7 @@ function PaperTradingPortfolio() {
let timer: number;
const poll = async () => {
try {
const res = await fetch("/paper/mtm", {
credentials: "include",
});
if (!res.ok) {
return;
}
const res = await apiRequest("GET", "/paper/mtm");
const data: PaperMtmResponse = await res.json();
setMtmPositions(Array.isArray(data.positions) ? data.positions : []);
if (typeof data.equity === "number") {
@ -307,12 +302,7 @@ function PaperTradingPortfolio() {
useEffect(() => {
const fetchMarketStatus = async () => {
try {
const res = await fetch("/market/status", {
credentials: "include",
});
if (!res.ok) {
return;
}
const res = await apiRequest("GET", "/market/status");
const data: MarketStatusResponse = await res.json();
setMarketStatus(data);
} catch {
@ -340,10 +330,7 @@ function PaperTradingPortfolio() {
setIsResetting(true);
try {
const res = await fetch("/paper/reset", { method: "POST" });
if (!res.ok) {
throw new Error("Reset failed");
}
await apiRequest("POST", "/paper/reset");
setMtmPositions([]);
setMtmEquity(null);
setMtmInitialCash(null);
@ -353,16 +340,21 @@ function PaperTradingPortfolio() {
setMtmPnlPoints([]);
skipFirstPnlPointRef.current = true;
setInitialCash("");
fundsQuery.refetch();
positionsQuery.refetch();
ordersQuery.refetch();
await refreshStatus();
await Promise.all([
fundsQuery.refetch(),
positionsQuery.refetch(),
ordersQuery.refetch(),
refreshStatus(),
]);
setTimelineKey((value) => value + 1);
setIsResetting(false);
} catch (error) {
setIsResetting(false);
alert("Reset failed. Check backend logs.");
toast({
title: "Reset failed",
description: error instanceof Error ? error.message : "Please try again.",
});
console.error(error);
} finally {
setIsResetting(false);
}
};
@ -395,9 +387,26 @@ function PaperTradingPortfolio() {
description: "Paper trading is the only available mode right now.",
});
}
if (result?.status === "started" || result?.status === "restarted") {
toast({
title: "Paper strategy started",
description: "The simulator is now running.",
});
}
} catch (error) {
toast({
title: "Start failed",
description: error instanceof Error ? error.message : "Please try again.",
});
console.error(error);
} finally {
setIsStarting(false);
await refreshStatus();
await Promise.all([
refreshStatus(),
fundsQuery.refetch(),
positionsQuery.refetch(),
ordersQuery.refetch(),
]);
}
};
@ -405,9 +414,24 @@ function PaperTradingPortfolio() {
setIsStopping(true);
try {
await stopStrategy();
toast({
title: "Paper strategy stopped",
description: "The simulator has been stopped.",
});
} catch (error) {
toast({
title: "Stop failed",
description: error instanceof Error ? error.message : "Please try again.",
});
console.error(error);
} finally {
setIsStopping(false);
await refreshStatus();
await Promise.all([
refreshStatus(),
fundsQuery.refetch(),
positionsQuery.refetch(),
ordersQuery.refetch(),
]);
}
};
@ -418,8 +442,12 @@ function PaperTradingPortfolio() {
? "WAITING"
: "STOPPED";
const canStart = typeof initialCash === "number" && initialCash >= 10000;
const canStop =
normalizedStrategyStatus === "RUNNING" ||
normalizedStrategyStatus === "WAITING";
const strategyLocked = canStop || isStarting || isStopping || isResetting;
const canAddCash =
addCashEnabled && typeof addCashAmount === "number" && addCashAmount > 0;
canStop && addCashEnabled && typeof addCashAmount === "number" && addCashAmount > 0;
const strategyBadgeClass =
normalizedStrategyStatus === "RUNNING"
? "bg-green-500 text-white"
@ -568,7 +596,7 @@ function PaperTradingPortfolio() {
type="number"
min={10000}
value={initialCash}
disabled={normalizedStrategyStatus === "RUNNING"}
disabled={strategyLocked}
onChange={(event) => {
const raw = event.target.value;
if (raw === "") {
@ -588,6 +616,7 @@ function PaperTradingPortfolio() {
<Checkbox
id="paper-add-cash-toggle"
checked={addCashEnabled}
disabled={!canStop || isAddingCash || isStarting || isStopping || isResetting}
onCheckedChange={(value) => setAddCashEnabled(value === true)}
/>
<Label htmlFor="paper-add-cash-toggle">Add cash during run</Label>
@ -601,7 +630,7 @@ function PaperTradingPortfolio() {
min={1}
step={100}
value={addCashAmount}
disabled={!addCashEnabled || isAddingCash}
disabled={!canStop || !addCashEnabled || isAddingCash || isStarting || isStopping || isResetting}
onChange={(event) => {
const raw = event.target.value;
if (raw === "") {
@ -634,6 +663,7 @@ function PaperTradingPortfolio() {
min={0}
step={100}
value={sipAmount}
disabled={strategyLocked}
onChange={(event) => {
const value = Number(event.target.value);
setSipAmount(Number.isNaN(value) ? 0 : value);
@ -650,6 +680,7 @@ function PaperTradingPortfolio() {
min={1}
step={1}
value={frequencyValue}
disabled={strategyLocked}
onChange={(event) => {
const value = Number(event.target.value);
setFrequencyValue(Number.isNaN(value) ? 1 : value);
@ -661,6 +692,7 @@ function PaperTradingPortfolio() {
<select
id="paper-frequency-unit"
value={frequencyUnit}
disabled={strategyLocked}
onChange={(event) =>
setFrequencyUnit(event.target.value as "minutes" | "days")
}
@ -680,18 +712,22 @@ function PaperTradingPortfolio() {
<MotionButton
{...ctaMotionProps}
onClick={handleStart}
disabled={!canStart || isStarting || isResetting || normalizedStrategyStatus === "RUNNING"}
disabled={!canStart || isStarting || isStopping || isResetting || canStop}
className="shimmer"
>
{isStarting ? "Starting..." : "Start Paper Strategy"}
</MotionButton>
<Button variant="outline" onClick={handleStop} disabled={isStopping}>
<Button
variant="outline"
onClick={handleStop}
disabled={!canStop || isStarting || isStopping || isResetting}
>
{isStopping ? "Stopping..." : "Stop Paper Strategy"}
</Button>
<Button
variant="destructive"
onClick={handleReset}
disabled={isResetting}
disabled={isStarting || isStopping || isResetting}
>
{isResetting ? "Resetting..." : "Reset Paper Account"}
</Button>

View File

@ -3,6 +3,9 @@ import react from "@vitejs/plugin-react";
import path from "path";
import runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal";
const proxyTarget =
process.env.VITE_PROXY_TARGET?.trim() || "http://localhost:8000";
export default defineConfig({
plugins: [
react(),
@ -32,7 +35,7 @@ export default defineConfig({
allowedHosts: ["*","app.quantfortune.com"],
proxy: {
"/api": {
target: "https://api.quantfortune.com/",
target: proxyTarget,
changeOrigin: true,
secure: false,
},