thigal_test
This commit is contained in:
parent
465a66fd53
commit
aa789d739c
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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",
|
||||
});
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user