diff --git a/src/quant_research_starter/frontend/cauweb/package-lock.json b/src/quant_research_starter/frontend/cauweb/package-lock.json index b659d3d4..aef91449 100644 --- a/src/quant_research_starter/frontend/cauweb/package-lock.json +++ b/src/quant_research_starter/frontend/cauweb/package-lock.json @@ -29,227 +29,6 @@ "vite": "^5.0.0" } }, - "../node_modules/.pnpm/autoprefixer@10.4.21_postcss@8.5.6/node_modules/autoprefixer": { - "version": "10.4.21", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss": { - "version": "8.5.6", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom": { - "version": "18.3.1", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "../node_modules/.pnpm/react-router-dom@6.30.1_nnrd3gsncyragczmpvfhocinkq/node_modules/react-router-dom": { - "version": "6.30.1", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.0", - "react-router": "6.30.1" - }, - "devDependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "../node_modules/.pnpm/react@18.3.1/node_modules/react": { - "version": "18.3.1", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../node_modules/.pnpm/vite@5.4.21/node_modules/vite": { - "version": "5.4.21", - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "devDependencies": { - "@ampproject/remapping": "^2.3.0", - "@babel/parser": "^7.25.6", - "@jridgewell/trace-mapping": "^0.3.25", - "@polka/compression": "^1.0.0-next.25", - "@rollup/plugin-alias": "^5.1.0", - "@rollup/plugin-commonjs": "^26.0.1", - "@rollup/plugin-dynamic-import-vars": "^2.1.2", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "15.2.3", - "@rollup/pluginutils": "^5.1.0", - "@types/escape-html": "^1.0.4", - "@types/pnpapi": "^0.0.5", - "artichokie": "^0.2.1", - "cac": "^6.7.14", - "chokidar": "^3.6.0", - "connect": "^3.7.0", - "convert-source-map": "^2.0.0", - "cors": "^2.8.5", - "cross-spawn": "^7.0.3", - "debug": "^4.3.6", - "dep-types": "link:./src/types", - "dotenv": "^16.4.5", - "dotenv-expand": "^11.0.6", - "es-module-lexer": "^1.5.4", - "escape-html": "^1.0.3", - "estree-walker": "^3.0.3", - "etag": "^1.8.1", - "fast-glob": "^3.3.2", - "http-proxy": "^1.18.1", - "launch-editor-middleware": "^2.9.1", - "lightningcss": "^1.26.0", - "magic-string": "^0.30.11", - "micromatch": "^4.0.8", - "mlly": "^1.7.1", - "mrmime": "^2.0.0", - "open": "^8.4.2", - "parse5": "^7.1.2", - "pathe": "^1.1.2", - "periscopic": "^4.0.2", - "picocolors": "^1.0.1", - "picomatch": "^2.3.1", - "postcss-import": "^16.1.0", - "postcss-load-config": "^4.0.2", - "postcss-modules": "^6.0.0", - "resolve.exports": "^2.0.2", - "rollup-plugin-dts": "^6.1.1", - "rollup-plugin-esbuild": "^6.1.1", - "rollup-plugin-license": "^3.5.2", - "sass": "^1.77.8", - "sass-embedded": "^1.77.8", - "sirv": "^2.0.4", - "source-map-support": "^0.5.21", - "strip-ansi": "^7.1.0", - "strip-literal": "^2.1.0", - "tsconfck": "^3.1.4", - "tslib": "^2.7.0", - "types": "link:./types", - "ufo": "^1.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -465,64 +244,409 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=12" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, "node_modules/@jridgewell/gen-mapping": { @@ -852,36 +976,296 @@ "cpu": [ "x64" ], - "dev": true, + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz", + "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz", + "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz", + "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz", + "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz", + "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz", + "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz", + "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz", + "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz", + "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz", + "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz", + "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz", + "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz", + "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz", + "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz", + "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz", + "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz", + "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz", + "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz", + "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz", + "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz", + "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==", + "cpu": [ + "x64" + ], "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } + ] }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz", + "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@tailwindcss/cli": { "version": "4.1.17", @@ -1115,7 +1499,7 @@ "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "node_modules/@tailwindcss/oxide/node_modules/@tailwindcss/oxide-win32-x64-msvc": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", @@ -1197,6 +1581,11 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -1243,13 +1632,46 @@ } }, "node_modules/autoprefixer": { - "resolved": "../node_modules/.pnpm/autoprefixer@10.4.21_postcss@8.5.6/node_modules/autoprefixer", - "link": true + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } }, "node_modules/baseline-browser-mapping": { - "version": "2.8.26", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.26.tgz", - "integrity": "sha512-73lC1ugzwoaWCLJ1LvOgrR5xsMLTqSKIEoMHVtL9E/HNk0PXtTM76ZIm84856/SF7Nv8mPZxKoBsgpm0tR1u1Q==", + "version": "2.8.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.27.tgz", + "integrity": "sha512-2CXFpkjVnY2FT+B6GrSYxzYf65BJWEqz5tIRHCvNsZZ2F3CmsCB37h8SpYgKG7y9C4YAeTipIPWG7EmFmhAeXA==", "dev": true, "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -1361,11 +1783,15 @@ } }, "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, "engines": { - "node": ">=8" + "node": ">=0.10" } }, "node_modules/electron-to-chromium": { @@ -1386,6 +1812,43 @@ "node": ">=10.13.0" } }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1407,6 +1870,32 @@ "node": ">=8" } }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1462,8 +1951,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/jsesc": { "version": "3.1.0", @@ -1726,6 +2214,25 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -1779,6 +2286,23 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -1791,11 +2315,19 @@ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -1810,12 +2342,48 @@ } }, "node_modules/postcss": { - "resolved": "../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss", - "link": true + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true }, "node_modules/react": { - "resolved": "../node_modules/.pnpm/react@18.3.1/node_modules/react", - "link": true + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/react-chartjs-2": { "version": "5.3.1", @@ -1827,8 +2395,16 @@ } }, "node_modules/react-dom": { - "resolved": "../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom", - "link": true + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } }, "node_modules/react-refresh": { "version": "0.17.0", @@ -1839,9 +2415,83 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/react-router-dom": { - "resolved": "../node_modules/.pnpm/react-router-dom@6.30.1_nnrd3gsncyragczmpvfhocinkq/node_modules/react-router-dom", - "link": true + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/rollup": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", + "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.2", + "@rollup/rollup-android-arm64": "4.53.2", + "@rollup/rollup-darwin-arm64": "4.53.2", + "@rollup/rollup-darwin-x64": "4.53.2", + "@rollup/rollup-freebsd-arm64": "4.53.2", + "@rollup/rollup-freebsd-x64": "4.53.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", + "@rollup/rollup-linux-arm-musleabihf": "4.53.2", + "@rollup/rollup-linux-arm64-gnu": "4.53.2", + "@rollup/rollup-linux-arm64-musl": "4.53.2", + "@rollup/rollup-linux-loong64-gnu": "4.53.2", + "@rollup/rollup-linux-ppc64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-musl": "4.53.2", + "@rollup/rollup-linux-s390x-gnu": "4.53.2", + "@rollup/rollup-linux-x64-gnu": "4.53.2", + "@rollup/rollup-linux-x64-musl": "4.53.2", + "@rollup/rollup-openharmony-arm64": "4.53.2", + "@rollup/rollup-win32-arm64-msvc": "4.53.2", + "@rollup/rollup-win32-ia32-msvc": "4.53.2", + "@rollup/rollup-win32-x64-gnu": "4.53.2", + "@rollup/rollup-win32-x64-msvc": "4.53.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } }, "node_modules/semver": { "version": "6.3.1", @@ -1933,8 +2583,62 @@ } }, "node_modules/vite": { - "resolved": "../node_modules/.pnpm/vite@5.4.21/node_modules/vite", - "link": true + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } }, "node_modules/yallist": { "version": "3.1.1", diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.js b/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.js deleted file mode 100644 index 7859640c..00000000 --- a/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.js +++ /dev/null @@ -1,47 +0,0 @@ -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { useState } from 'react'; -import { Play } from 'lucide-react'; -import { useQuantData } from '../hooks/useQuantData'; -export const BacktestStudio = () => { - const { loading, runBacktest, backtestResults } = useQuantData(); - const [config, setConfig] = useState({ - initialCapital: 100000, - startDate: '2020-01-01', - endDate: '2023-01-01', - rebalanceFrequency: 'monthly', - symbols: ['AAPL', 'MSFT', 'GOOGL', 'AMZN'] - }); - const handleRunBacktest = () => { - runBacktest(config); - }; - // Format metric value for display with proper typing - const formatMetricValue = (value) => { - if (typeof value === 'number') { - // For ratios (like sharpe), don't add percentage - if (value > 1 || value < -1) { - return value.toFixed(3); - } - return (value * 100).toFixed(2) + '%'; - } - return String(value); - }; - // Safely get metrics with proper typing - const getMetrics = () => { - if (!backtestResults?.metrics) - return []; - const metrics = backtestResults.metrics; - return [ - ['Total Return', formatMetricValue(metrics.totalReturn)], - ['Annualized Return', formatMetricValue(metrics.annualizedReturn)], - ['Volatility', formatMetricValue(metrics.volatility)], - ['Sharpe Ratio', formatMetricValue(metrics.sharpeRatio)], - ['Max Drawdown', formatMetricValue(metrics.maxDrawdown)], - ['Win Rate', formatMetricValue(metrics.winRate)], - ['Turnover', formatMetricValue(metrics.turnover)] - ]; - }; - return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Backtest Studio" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Test and optimize your trading strategies" })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-8", children: [_jsx("div", { className: "lg:col-span-1", children: _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Strategy Configuration" }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Initial Capital ($)" }), _jsx("input", { type: "number", value: config.initialCapital, onChange: (e) => setConfig({ ...config, initialCapital: Number(e.target.value) }), className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Start Date" }), _jsx("input", { type: "date", value: config.startDate, onChange: (e) => setConfig({ ...config, startDate: e.target.value }), className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "End Date" }), _jsx("input", { type: "date", value: config.endDate, onChange: (e) => setConfig({ ...config, endDate: e.target.value }), className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Rebalance Frequency" }), _jsxs("select", { value: config.rebalanceFrequency, onChange: (e) => setConfig({ - ...config, - rebalanceFrequency: e.target.value - }), className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent", children: [_jsx("option", { value: "daily", children: "Daily" }), _jsx("option", { value: "weekly", children: "Weekly" }), _jsx("option", { value: "monthly", children: "Monthly" })] })] }), _jsxs("button", { onClick: handleRunBacktest, disabled: loading, className: "w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center space-x-2", children: [_jsx(Play, { className: "w-4 h-4" }), _jsx("span", { children: loading ? 'Running...' : 'Run Backtest' })] })] })] }) }), _jsx("div", { className: "lg:col-span-2", children: _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Backtest Results" }), loading ? (_jsxs("div", { className: "text-center py-12", children: [_jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4" }), _jsx("p", { className: "text-gray-600", children: "Running backtest analysis..." })] })) : backtestResults ? (_jsxs("div", { className: "space-y-6", children: [_jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: getMetrics().map(([key, value]) => (_jsxs("div", { className: "text-center p-4 bg-gray-50 rounded-lg", children: [_jsx("div", { className: "text-sm text-gray-600 capitalize", children: key }), _jsx("div", { className: "text-xl font-bold text-gray-900 mt-1", children: value })] }, key))) }), _jsxs("div", { className: "border-t pt-6", children: [_jsx("h4", { className: "font-semibold text-gray-900 mb-3", children: "Performance Chart" }), _jsx("div", { className: "h-64 bg-gray-100 rounded-lg flex items-center justify-center text-gray-500", children: "Performance chart will be displayed here" })] })] })) : (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Play, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), _jsx("p", { children: "Configure and run a backtest to see results" })] }))] }) })] })] })); -}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.jsx b/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.jsx new file mode 100644 index 00000000..674db562 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.jsx @@ -0,0 +1,1020 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { + Play, + Save, + Download, + Upload, + Settings, + BarChart3, + TrendingUp, + TrendingDown, + Clock, + DollarSign, + Target, + Activity, + Shield, + RefreshCw, + Plus, + Trash2, + Copy, + History, + BookOpen, + Zap, + Cpu, + LineChart, + PieChart +} from 'lucide-react'; + +// Chart.js components +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + BarElement, + Title, + Tooltip, + Legend, + Filler +} from 'chart.js'; +import { Line, Bar } from 'react-chartjs-2'; + +// Register ChartJS components +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + BarElement, + Title, + Tooltip, + Legend, + Filler +); + +// Mock hook with enhanced data +const useQuantData = () => { + const [loading, setLoading] = useState(false); + const [progress, setProgress] = useState(0); + const [backtestResults, setBacktestResults] = useState(null); + + const runBacktest = async (config) => { + setLoading(true); + setProgress(0); + + // Simulate progress updates + const progressInterval = setInterval(() => { + setProgress(prev => { + if (prev >= 95) { + clearInterval(progressInterval); + return 95; + } + return prev + 5; + }); + }, 150); + + // Simulate API call + await new Promise(resolve => setTimeout(resolve, 3000)); + + clearInterval(progressInterval); + setProgress(100); + + // Enhanced mock results with realistic data + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + const years = [2020, 2021, 2022, 2023]; + + const equityData = []; + let currentValue = config.initialCapital; + + for (let year of years) { + for (let month of months) { + // Realistic market simulation with trends and noise + const monthlyReturn = (Math.random() * 0.08 - 0.02) + 0.005; // -2% to +6% with slight positive bias + currentValue = currentValue * (1 + monthlyReturn); + equityData.push({ + date: `${month} ${year}`, + value: Math.round(currentValue) + }); + } + } + + const drawdownData = equityData.map((point, index) => { + const peak = Math.max(...equityData.slice(0, index + 1).map(p => p.value)); + const drawdown = ((point.value - peak) / peak) * 100; + return { date: point.date, value: drawdown }; + }); + + // Generate realistic trades + const trades = Array.from({ length: 25 }, (_, i) => { + const symbols = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA', 'JPM', 'JNJ', 'V']; + const action = Math.random() > 0.4 ? 'BUY' : 'SELL'; + const basePrices = { + 'AAPL': 150, 'MSFT': 300, 'GOOGL': 2800, 'AMZN': 3300, + 'TSLA': 200, 'META': 350, 'NVDA': 800, 'JPM': 170, 'JNJ': 160, 'V': 230 + }; + + const symbol = symbols[Math.floor(Math.random() * symbols.length)]; + const price = basePrices[symbol] * (0.9 + Math.random() * 0.2); + + return { + id: i, + symbol, + action, + quantity: Math.floor(Math.random() * 100) + 10, + price: Math.round(price * 100) / 100, + timestamp: new Date(2020 + Math.floor(i/6), (i % 12), (i % 28) + 1), + pnl: (Math.random() - 0.3) * 5000 + }; + }); + + setBacktestResults({ + metrics: { + totalReturn: 0.2345, + annualizedReturn: 0.156, + volatility: 0.182, + sharpeRatio: 1.234, + maxDrawdown: -0.1234, + winRate: 0.645, + turnover: 0.89, + alpha: 0.0234, + beta: 1.12, + sortinoRatio: 1.89, + calmarRatio: 1.45, + informationRatio: 0.78 + }, + equityCurve: equityData, + drawdownCurve: drawdownData, + trades: trades.sort((a, b) => b.timestamp - a.timestamp), + performance: { + monthlyReturns: Array.from({ length: 36 }, () => (Math.random() - 0.5) * 0.1), + benchmarkReturns: Array.from({ length: 36 }, () => (Math.random() - 0.5) * 0.08) + } + }); + + setTimeout(() => setProgress(0), 1000); + setLoading(false); + }; + + return { loading, progress, runBacktest, backtestResults }; +}; + +export const BacktestStudio = () => { + const { loading, progress, runBacktest, backtestResults } = useQuantData(); + const [activeTab, setActiveTab] = useState('configuration'); + const [savedStrategies, setSavedStrategies] = useState([]); + const [strategyName, setStrategyName] = useState(''); + const [newSymbol, setNewSymbol] = useState(''); + + const [config, setConfig] = useState({ + strategyName: 'Momentum Strategy v1', + initialCapital: 100000, + startDate: '2020-01-01', + endDate: '2023-12-31', + rebalanceFrequency: 'monthly', + strategyType: 'momentum', + universe: 'large_cap', + riskModel: 'min_variance', + transactionCosts: 0.001, + maxPositionSize: 0.1, + stopLoss: 0.05, + takeProfit: 0.15, + symbols: ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA', 'JPM', 'JNJ', 'V'] + }); + + // Chart options and data + const equityChartOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + title: { + display: false, + }, + tooltip: { + mode: 'index', + intersect: false, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: '#fff', + bodyColor: '#fff', + borderColor: 'rgba(255, 255, 255, 0.1)', + borderWidth: 1, + }, + }, + scales: { + x: { + grid: { + color: 'rgba(0, 0, 0, 0.1)', + }, + ticks: { + color: '#6B7280', + }, + }, + y: { + grid: { + color: 'rgba(0, 0, 0, 0.1)', + }, + ticks: { + color: '#6B7280', + callback: function(value) { + return '$' + value.toLocaleString(); + }, + }, + }, + }, + }; + + const drawdownChartOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + }, + scales: { + x: { + grid: { + color: 'rgba(0, 0, 0, 0.1)', + }, + ticks: { + color: '#6B7280', + }, + }, + y: { + grid: { + color: 'rgba(0, 0, 0, 0.1)', + }, + ticks: { + color: '#6B7280', + callback: function(value) { + return value + '%'; + }, + }, + }, + }, + }; + + const getEquityChartData = () => { + if (!backtestResults?.equityCurve) return { labels: [], datasets: [] }; + + return { + labels: backtestResults.equityCurve.map(point => point.date), + datasets: [ + { + label: 'Portfolio Value', + data: backtestResults.equityCurve.map(point => point.value), + borderColor: 'rgb(34, 197, 94)', + backgroundColor: 'rgba(34, 197, 94, 0.1)', + fill: true, + tension: 0.4, + }, + ], + }; + }; + + const getDrawdownChartData = () => { + if (!backtestResults?.drawdownCurve) return { labels: [], datasets: [] }; + + return { + labels: backtestResults.drawdownCurve.map(point => point.date), + datasets: [ + { + label: 'Drawdown', + data: backtestResults.drawdownCurve.map(point => point.value), + borderColor: 'rgb(239, 68, 68)', + backgroundColor: 'rgba(239, 68, 68, 0.1)', + fill: true, + tension: 0.4, + }, + ], + }; + }; + + const strategyTypes = [ + { value: 'momentum', label: 'Momentum', description: 'Follow trending assets', icon: TrendingUp }, + { value: 'mean_reversion', label: 'Mean Reversion', description: 'Bet on price normalization', icon: Activity }, + { value: 'factor', label: 'Factor Investing', description: 'Use quantitative factors', icon: Target }, + { value: 'ml', label: 'Machine Learning', description: 'AI-powered predictions', icon: Cpu } + ]; + + const universes = [ + { value: 'large_cap', label: 'Large Cap (S&P 500)' }, + { value: 'small_cap', label: 'Small Cap (Russell 2000)' }, + { value: 'technology', label: 'Technology Sector' }, + { value: 'all', label: 'All Available Assets' } + ]; + + const riskModels = [ + { value: 'min_variance', label: 'Minimum Variance' }, + { value: 'equal_weight', label: 'Equal Weight' }, + { value: 'risk_parity', label: 'Risk Parity' }, + { value: 'black_litterman', label: 'Black-Litterman' } + ]; + + const handleRunBacktest = () => { + runBacktest(config); + setActiveTab('results'); + }; + + const handleSaveStrategy = () => { + if (strategyName.trim()) { + const newStrategy = { + id: Date.now(), + name: strategyName, + config: { ...config }, + createdAt: new Date().toISOString() + }; + setSavedStrategies(prev => [newStrategy, ...prev]); + setStrategyName(''); + } + }; + + const handleLoadStrategy = (strategy) => { + setConfig(strategy.config); + setActiveTab('configuration'); + }; + + const addSymbol = () => { + if (newSymbol.trim()) { + const symbol = newSymbol.trim().toUpperCase(); + if (!config.symbols.includes(symbol)) { + setConfig({ + ...config, + symbols: [...config.symbols, symbol] + }); + } + setNewSymbol(''); + } + }; + + const removeSymbol = (index) => { + const newSymbols = config.symbols.filter((_, i) => i !== index); + setConfig({ ...config, symbols: newSymbols }); + }; + + const formatMetricValue = (value) => { + if (typeof value === 'number') { + if (Math.abs(value) > 10 || (value >= -1 && value <= 1 && Math.abs(value) < 0.01)) { + return value.toFixed(3); + } + return (value * 100).toFixed(2) + '%'; + } + return String(value); + }; + + const getMetrics = () => { + if (!backtestResults?.metrics) return []; + + const metrics = backtestResults.metrics; + return [ + { key: 'totalReturn', label: 'Total Return', value: metrics.totalReturn, icon: TrendingUp, trend: metrics.totalReturn >= 0 ? 'up' : 'down' }, + { key: 'annualizedReturn', label: 'Annualized Return', value: metrics.annualizedReturn, icon: DollarSign, trend: metrics.annualizedReturn >= 0 ? 'up' : 'down' }, + { key: 'volatility', label: 'Volatility', value: metrics.volatility, icon: Activity, trend: 'neutral' }, + { key: 'sharpeRatio', label: 'Sharpe Ratio', value: metrics.sharpeRatio, icon: Target, trend: metrics.sharpeRatio >= 1 ? 'up' : 'down' }, + { key: 'maxDrawdown', label: 'Max Drawdown', value: metrics.maxDrawdown, icon: TrendingDown, trend: 'down' }, + { key: 'winRate', label: 'Win Rate', value: metrics.winRate, icon: Target, trend: metrics.winRate >= 0.5 ? 'up' : 'down' }, + { key: 'alpha', label: 'Alpha', value: metrics.alpha, icon: TrendingUp, trend: metrics.alpha >= 0 ? 'up' : 'down' }, + { key: 'beta', label: 'Beta', value: metrics.beta, icon: Activity, trend: 'neutral' } + ]; + }; + + // Enhanced loading component with progress + if (loading) { + return ( +
+
+
+ + +
+

Running Backtest

+

Analyzing strategy performance across historical data

+ +
+
+
+

{progress}% Complete

+
+
+ ); + } + + return ( +
+ {/* Header Section */} +
+
+

+ Backtest Studio +

+

+ + Test, optimize, and validate your trading strategies with precision +

+
+
+ + + +
+
+ + {/* Tab Navigation */} +
+ {[ + { id: 'configuration', label: 'Configuration', icon: Settings }, + { id: 'results', label: 'Results', icon: BarChart3 }, + { id: 'saved', label: 'Saved Strategies', icon: Save } + ].map((tab) => { + const Icon = tab.icon; + return ( + + ); + })} +
+ + {/* Main Content */} +
+ {/* Configuration Panel */} + {activeTab === 'configuration' && ( + <> +
+
+
+
+

+ Strategy Configuration +

+

Define your trading strategy parameters

+
+
+ + +
+
+ +
+ {/* Basic Settings */} +
+
+

+ + Basic Settings +

+ +
+
+ + setConfig({ ...config, strategyName: e.target.value })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + placeholder="Enter strategy name" + /> +
+ +
+ + setConfig({ ...config, initialCapital: Number(e.target.value) })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+ +
+
+ + setConfig({ ...config, startDate: e.target.value })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+
+ + setConfig({ ...config, endDate: e.target.value })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+
+
+
+ + {/* Risk Management */} +
+

+ + Risk Management +

+ +
+
+ + setConfig({ ...config, transactionCosts: Number(e.target.value) })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+ +
+ + setConfig({ ...config, maxPositionSize: Number(e.target.value) / 100 })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+ +
+
+ + setConfig({ ...config, stopLoss: Number(e.target.value) / 100 })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+
+ + setConfig({ ...config, takeProfit: Number(e.target.value) / 100 })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+
+
+
+
+ + {/* Strategy Parameters & Asset Selection */} +
+
+

+ + Strategy Parameters +

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + {/* Asset Selection */} +
+

+ + Asset Selection +

+ +
+ +
+
+ {config.symbols.map((symbol, index) => ( + + {symbol} + + + ))} +
+
+ setNewSymbol(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && addSymbol()} + placeholder="Add symbol (e.g., AAPL)" + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200" + /> + +
+
+
+
+
+
+ + {/* Run Button */} +
+ +
+
+
+ + {/* Strategy Templates */} +
+
+

+ + Quick Templates +

+
+ {[ + { name: 'Momentum Strategy', type: 'momentum', description: 'Follow trending assets with momentum signals', color: 'blue' }, + { name: 'Mean Reversion', type: 'mean_reversion', description: 'Bet on price normalization in oversold assets', color: 'green' }, + { name: 'Quality Factor', type: 'factor', description: 'Invest in high-quality companies with strong fundamentals', color: 'purple' } + ].map((template, index) => ( + + ))} +
+
+
+ + )} + + {/* Results Panel */} + {activeTab === 'results' && backtestResults && ( +
+ {/* Key Metrics */} +
+

+ Performance Summary +

+
+ {getMetrics().map((metric) => { + const Icon = metric.icon; + return ( +
+ +
{metric.label}
+
+ {formatMetricValue(metric.value)} +
+
+ ); + })} +
+
+ + {/* Charts and Detailed Analysis */} +
+ {/* Equity Curve */} +
+

Equity Curve

+
+ +
+
+ + {/* Drawdown Chart */} +
+

Drawdown Analysis

+
+ +
+
+ + {/* Recent Trades */} +
+
+

Recent Trades

+ {backtestResults.trades.length} trades +
+
+ {backtestResults.trades.slice(0, 10).map((trade) => ( +
+
+
+ {trade.action === 'BUY' ? 'B' : 'S'} +
+
+
{trade.symbol}
+
+ {trade.quantity} shares @ ${trade.price.toFixed(2)} +
+
+
+
+
= 0 ? 'text-green-600' : 'text-red-600' + }`}> + {trade.pnl >= 0 ? '+' : ''}${trade.pnl.toFixed(2)} +
+
+ {trade.timestamp.toLocaleDateString()} +
+
+
+ ))} +
+
+ + {/* Risk Analysis */} +
+

Risk Metrics

+
+ {[ + { label: 'Value at Risk (95%)', value: '-5.2%', color: 'red' }, + { label: 'Conditional VaR', value: '-7.8%', color: 'red' }, + { label: 'Tail Ratio', value: '0.89', color: 'green' }, + { label: 'Skewness', value: '-0.23', color: 'yellow' }, + { label: 'Kurtosis', value: '3.45', color: 'yellow' }, + { label: 'Information Ratio', value: '0.78', color: 'green' } + ].map((metric, index) => ( +
+ {metric.label} + + {metric.value} + +
+ ))} +
+
+
+
+ )} + + {/* Saved Strategies */} + {activeTab === 'saved' && ( +
+
+
+
+

+ Saved Strategies +

+

Manage your strategy configurations

+
+
+ setStrategyName(e.target.value)} + placeholder="Strategy name" + className="px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> + +
+
+ + {savedStrategies.length === 0 ? ( +
+ +

No saved strategies yet

+

Save your current configuration to get started

+
+ ) : ( +
+ {savedStrategies.map((strategy) => ( +
+
+

{strategy.name}

+ +
+
+
+ Capital: + ${strategy.config.initialCapital.toLocaleString()} +
+
+ Period: + {strategy.config.startDate} to {strategy.config.endDate} +
+
+ Assets: + {strategy.config.symbols.length} symbols +
+
+
+ Created: {new Date(strategy.createdAt).toLocaleDateString()} +
+
+
+
+ ))} +
+ )} +
+
+ )} + + {/* Empty State for Results */} + {activeTab === 'results' && !backtestResults && ( +
+
+
+ +

No Results Yet

+

+ Configure and run a backtest to see detailed performance results and analytics +

+ +
+
+
+ )} +
+
+ ); +}; + +export default BacktestStudio; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/Dashboard.js b/src/quant_research_starter/frontend/cauweb/src/pages/Dashboard.js deleted file mode 100644 index f2feb894..00000000 --- a/src/quant_research_starter/frontend/cauweb/src/pages/Dashboard.js +++ /dev/null @@ -1,20 +0,0 @@ -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { TrendingUp, TrendingDown, Target } from 'lucide-react'; -export const Dashboard = () => { - const metrics = [ - { title: 'Total Return', value: '+23.45%', change: '+2.1%', icon: TrendingUp, trend: 'up' }, - { title: 'Sharpe Ratio', value: '1.234', change: '+0.12', icon: TrendingUp, trend: 'up' }, - { title: 'Max Drawdown', value: '-12.34%', change: '-1.2%', icon: TrendingDown, trend: 'down' }, - { title: 'Win Rate', value: '64.50%', change: '+3.2%', icon: Target, trend: 'up' } - ]; - return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Dashboard" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Welcome to your quantitative research workspace" })] }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8", children: metrics.map((metric, index) => { - const Icon = metric.icon; - return (_jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsx("div", { className: "p-2 bg-blue-50 rounded-lg", children: _jsx(Icon, { className: `w-6 h-6 ${metric.trend === 'up' ? 'text-green-600' : 'text-red-600'}` }) }), _jsx("span", { className: `text-sm font-medium ${metric.trend === 'up' ? 'text-green-600' : 'text-red-600'}`, children: metric.change })] }), _jsx("h3", { className: "text-gray-600 text-sm font-medium mb-1", children: metric.title }), _jsx("div", { className: `text-2xl font-bold ${metric.trend === 'up' ? 'text-green-600' : 'text-red-600'}`, children: metric.value })] }, index)); - }) }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8", children: [_jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Recent Backtests" }), _jsx("div", { className: "space-y-4", children: [ - { name: 'Momentum Strategy', date: '2 hours ago', status: 'Completed' }, - { name: 'Mean Reversion', date: '5 hours ago', status: 'Completed' }, - { name: 'Sector Rotation', date: '1 day ago', status: 'Running' } - ].map((test, index) => (_jsxs("div", { className: "flex items-center justify-between p-3 bg-gray-50 rounded-lg", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium text-gray-900", children: test.name }), _jsx("div", { className: "text-sm text-gray-500", children: test.date })] }), _jsx("span", { className: `px-2 py-1 rounded-full text-xs font-medium ${test.status === 'Completed' - ? 'bg-green-100 text-green-800' - : 'bg-blue-100 text-blue-800'}`, children: test.status })] }, index))) })] }), _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Quick Actions" }), _jsxs("div", { className: "space-y-3", children: [_jsxs("button", { className: "w-full text-left p-4 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition-colors", children: [_jsx("div", { className: "font-medium text-blue-900", children: "Run New Backtest" }), _jsx("div", { className: "text-sm text-blue-700", children: "Test a new trading strategy" })] }), _jsxs("button", { className: "w-full text-left p-4 bg-green-50 border border-green-200 rounded-lg hover:bg-green-100 transition-colors", children: [_jsx("div", { className: "font-medium text-green-900", children: "Analyze Portfolio" }), _jsx("div", { className: "text-sm text-green-700", children: "Deep dive into performance metrics" })] }), _jsxs("button", { className: "w-full text-left p-4 bg-purple-50 border border-purple-200 rounded-lg hover:bg-purple-100 transition-colors", children: [_jsx("div", { className: "font-medium text-purple-900", children: "Research Factors" }), _jsx("div", { className: "text-sm text-purple-700", children: "Explore alpha factors" })] })] })] })] })] })); -}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/PortfolioAnalytics.js b/src/quant_research_starter/frontend/cauweb/src/pages/PortfolioAnalytics.js deleted file mode 100644 index 0e96033b..00000000 --- a/src/quant_research_starter/frontend/cauweb/src/pages/PortfolioAnalytics.js +++ /dev/null @@ -1,19 +0,0 @@ -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { PieChart, BarChart } from 'lucide-react'; -export const PortfolioAnalytics = () => { - return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Portfolio Analytics" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Deep dive into portfolio performance and risk" })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8", children: [_jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Asset Allocation" }), _jsxs("div", { className: "h-64 flex items-center justify-center text-gray-500", children: [_jsx(PieChart, { className: "w-12 h-12 opacity-50 mr-4" }), _jsx("span", { children: "Allocation chart will be displayed here" })] })] }), _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Sector Exposure" }), _jsxs("div", { className: "h-64 flex items-center justify-center text-gray-500", children: [_jsx(BarChart, { className: "w-12 h-12 opacity-50 mr-4" }), _jsx("span", { children: "Sector exposure chart will be displayed here" })] })] }), _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Risk Analysis" }), _jsx("div", { className: "space-y-4", children: [ - { metric: 'Value at Risk (95%)', value: '-5.2%' }, - { metric: 'Expected Shortfall', value: '-7.8%' }, - { metric: 'Beta to Market', value: '1.12' }, - { metric: 'Tracking Error', value: '4.5%' } - ].map((item, index) => (_jsxs("div", { className: "flex justify-between items-center p-3 bg-gray-50 rounded-lg", children: [_jsx("span", { className: "text-gray-700", children: item.metric }), _jsx("span", { className: "font-semibold text-gray-900", children: item.value })] }, index))) })] }), _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Performance Attribution" }), _jsx("div", { className: "space-y-3", children: [ - { factor: 'Stock Selection', contribution: '+3.2%' }, - { factor: 'Sector Allocation', contribution: '+1.8%' }, - { factor: 'Currency Effects', contribution: '-0.4%' }, - { factor: 'Transaction Costs', contribution: '-0.9%' } - ].map((item, index) => (_jsxs("div", { className: "flex justify-between items-center", children: [_jsx("span", { className: "text-gray-600", children: item.factor }), _jsx("span", { className: `font-medium ${item.contribution.startsWith('+') - ? 'text-green-600' - : item.contribution.startsWith('-') - ? 'text-red-600' - : 'text-gray-600'}`, children: item.contribution })] }, index))) })] })] })] })); -}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.js b/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.js deleted file mode 100644 index 4ca3eb6c..00000000 --- a/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.js +++ /dev/null @@ -1,20 +0,0 @@ -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { LineChart, BarChart3, Activity } from 'lucide-react'; -export const ResearchLab = () => { - const factors = [ - { name: 'Momentum', description: 'Price momentum factors', icon: Activity, color: 'blue' }, - { name: 'Value', description: 'Valuation metrics', icon: BarChart3, color: 'green' }, - { name: 'Size', description: 'Market capitalization', icon: LineChart, color: 'purple' }, - { name: 'Volatility', description: 'Price volatility measures', icon: Activity, color: 'red' } - ]; - return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Research Lab" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Explore and analyze alpha factors" })] }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8", children: factors.map((factor, index) => { - const Icon = factor.icon; - const colorClasses = { - blue: 'bg-blue-100 text-blue-600', - green: 'bg-green-100 text-green-600', - purple: 'bg-purple-100 text-purple-600', - red: 'bg-red-100 text-red-600' - }; - return (_jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow", children: [_jsx("div", { className: `w-12 h-12 ${colorClasses[factor.color]} rounded-lg flex items-center justify-center mb-4`, children: _jsx(Icon, { className: "w-6 h-6" }) }), _jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-2", children: factor.name }), _jsx("p", { className: "text-gray-600 text-sm", children: factor.description }), _jsx("button", { className: "mt-4 w-full bg-gray-100 text-gray-700 py-2 rounded-lg hover:bg-gray-200 transition-colors text-sm", children: "Analyze Factor" })] }, index)); - }) }), _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Factor Performance" }), _jsx("div", { className: "h-96 bg-gray-100 rounded-lg flex items-center justify-center text-gray-500", children: "Factor performance charts will be displayed here" })] })] })); -}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.jsx b/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.jsx new file mode 100644 index 00000000..f2de40ed --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.jsx @@ -0,0 +1,364 @@ +import React, { useState } from 'react'; +import { + LineChart, + BarChart3, + Activity, + TrendingUp, + DollarSign, + Shield, + Zap, + Search, + Filter, + Download, + Play, + Plus, + Clock, + ArrowRight, + Brain, + PieChart, + BarChart, +} from 'lucide-react'; + +// ✅ Chart.js imports +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + BarElement, + ArcElement, // ✅ Required for Doughnut charts + Title, + Tooltip, + Legend, + Filler, +} from 'chart.js'; +import { Line, Bar, Doughnut } from 'react-chartjs-2'; + +// ✅ Register ChartJS components +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + BarElement, + ArcElement, // ✅ Add this line + Title, + Tooltip, + Legend, + Filler +); + +export const ResearchLab = () => { + const [activeFactor, setActiveFactor] = useState('momentum'); + const [timeRange, setTimeRange] = useState('1Y'); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedFactors, setSelectedFactors] = useState(['momentum']); + + // ✅ Factor data + const factors = [ + { + id: 'momentum', + name: 'Momentum', + description: 'Price momentum and trend following factors', + icon: TrendingUp, + color: 'blue', + performance: 0.234, + volatility: 0.182, + sharpe: 1.284, + correlation: 0.12, + lastUpdated: '2 hours ago', + status: 'active', + }, + { + id: 'value', + name: 'Value', + description: 'Valuation metrics and fundamental analysis', + icon: DollarSign, + color: 'green', + performance: 0.156, + volatility: 0.154, + sharpe: 1.012, + correlation: -0.08, + lastUpdated: '1 day ago', + status: 'active', + }, + { + id: 'size', + name: 'Size', + description: 'Market capitalization and small-cap premium', + icon: BarChart, + color: 'purple', + performance: 0.089, + volatility: 0.198, + sharpe: 0.449, + correlation: 0.23, + lastUpdated: '3 days ago', + status: 'active', + }, + { + id: 'volatility', + name: 'Volatility', + description: 'Price volatility and risk measures', + icon: Activity, + color: 'red', + performance: -0.045, + volatility: 0.267, + sharpe: -0.168, + correlation: 0.31, + lastUpdated: '5 hours ago', + status: 'active', + }, + { + id: 'quality', + name: 'Quality', + description: 'Profitability and financial health metrics', + icon: Shield, + color: 'orange', + performance: 0.187, + volatility: 0.142, + sharpe: 1.316, + correlation: 0.05, + lastUpdated: '1 week ago', + status: 'active', + }, + { + id: 'liquidity', + name: 'Liquidity', + description: 'Trading volume and market liquidity factors', + icon: BarChart3, + color: 'indigo', + performance: 0.112, + volatility: 0.173, + sharpe: 0.647, + correlation: 0.18, + lastUpdated: '2 days ago', + status: 'active', + }, + ]; + + const researchProjects = [ + { + id: 1, + title: 'Momentum Crash Protection', + description: 'Developing strategies to protect against momentum factor crashes', + status: 'in-progress', + progress: 65, + contributors: 3, + lastActivity: '2 hours ago', + }, + { + id: 2, + title: 'Multi-Factor Optimization', + description: 'Optimizing factor weights using machine learning', + status: 'completed', + progress: 100, + contributors: 5, + lastActivity: '1 week ago', + }, + { + id: 3, + title: 'Factor Timing Model', + description: 'Building predictive models for factor timing', + status: 'in-progress', + progress: 30, + contributors: 2, + lastActivity: '1 day ago', + }, + ]; + + // ✅ Chart options + const performanceChartOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { legend: { position: 'top' } }, + scales: { + y: { + ticks: { + callback: (value) => (value * 100).toFixed(1) + '%', + }, + }, + }, + }; + + const correlationChartOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { legend: { position: 'top' } }, + }; + + // ✅ Helper functions + const getColorClass = (color) => { + const colors = { + blue: { background: 'rgba(59,130,246,0.1)', border: 'rgb(59,130,246)', text: 'text-blue-600', light: 'bg-blue-100' }, + green: { background: 'rgba(16,185,129,0.1)', border: 'rgb(16,185,129)', text: 'text-green-600', light: 'bg-green-100' }, + purple: { background: 'rgba(139,92,246,0.1)', border: 'rgb(139,92,246)', text: 'text-purple-600', light: 'bg-purple-100' }, + red: { background: 'rgba(239,68,68,0.1)', border: 'rgb(239,68,68)', text: 'text-red-600', light: 'bg-red-100' }, + orange: { background: 'rgba(245,158,11,0.1)', border: 'rgb(245,158,11)', text: 'text-orange-600', light: 'bg-orange-100' }, + indigo: { background: 'rgba(99,102,241,0.1)', border: 'rgb(99,102,241)', text: 'text-indigo-600', light: 'bg-indigo-100' }, + }; + return colors[color] || colors.blue; + }; + + const getPerformanceData = () => { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + const selectedFactor = factors.find((f) => f.id === activeFactor); + const color = getColorClass(selectedFactor?.color || 'blue'); + + return { + labels: months, + datasets: [ + { + label: `${selectedFactor?.name} Factor`, + data: months.map(() => Math.random() * 0.1 - 0.02), + borderColor: color.border, + backgroundColor: color.background, + fill: true, + tension: 0.4, + }, + { + label: 'Benchmark', + data: months.map(() => Math.random() * 0.08 - 0.03), + borderColor: 'rgb(75,85,99)', + backgroundColor: 'rgba(75,85,99,0.1)', + borderDash: [5, 5], + fill: true, + tension: 0.4, + }, + ], + }; + }; + + const getCorrelationData = () => { + const selectedFactorsData = factors.filter((f) => selectedFactors.includes(f.id)); + return { + labels: selectedFactorsData.map((f) => f.name), + datasets: [ + { + label: 'Factor Correlation', + data: selectedFactorsData.map(() => Math.random() * 2 - 1), + backgroundColor: selectedFactorsData.map((f) => getColorClass(f.color).background), + borderColor: selectedFactorsData.map((f) => getColorClass(f.color).border), + borderWidth: 2, + }, + ], + }; + }; + + const getExposureData = () => ({ + labels: ['Technology', 'Healthcare', 'Financials', 'Consumer', 'Energy', 'Industrial'], + datasets: [ + { + label: 'Sector Exposure', + data: [25, 18, 15, 12, 8, 22], + backgroundColor: [ + 'rgba(59,130,246,0.8)', + 'rgba(16,185,129,0.8)', + 'rgba(139,92,246,0.8)', + 'rgba(245,158,11,0.8)', + 'rgba(239,68,68,0.8)', + 'rgba(99,102,241,0.8)', + ], + borderColor: [ + 'rgb(59,130,246)', + 'rgb(16,185,129)', + 'rgb(139,92,246)', + 'rgb(245,158,11)', + 'rgb(239,68,68)', + 'rgb(99,102,241)', + ], + borderWidth: 2, + }, + ], + }); + + const formatPercent = (value) => (value * 100).toFixed(2) + '%'; + + const toggleFactorSelection = (factorId) => { + setSelectedFactors((prev) => + prev.includes(factorId) ? prev.filter((id) => id !== factorId) : [...prev, factorId] + ); + }; + + // ✅ Main return + return ( +
+

Research Lab

+ +
+ {/* Sidebar */} +
+
+

Alpha Factors

+
+ {factors.map((factor) => { + const Icon = factor.icon; + const colorClass = getColorClass(factor.color); + const isSelected = selectedFactors.includes(factor.id); + return ( +
setActiveFactor(factor.id)} + > +
+
+
+ +
+
+

{factor.name}

+

{factor.description}

+
+
+ { + e.stopPropagation(); + toggleFactorSelection(factor.id); + }} + className="w-4 h-4 text-blue-600" + /> +
+
+ ); + })} +
+
+
+ + {/* Charts */} +
+ {/* Performance */} +
+

Factor Performance

+
+ +
+
+ + {/* Sector Exposure */} +
+

Sector Exposure

+
+ +
+
+ + {/* Correlation */} +
+

Factor Correlation

+
+ +
+
+
+
+
+ ); +}; + +export default ResearchLab; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/Settings.js b/src/quant_research_starter/frontend/cauweb/src/pages/Settings.js deleted file mode 100644 index 6c3c4838..00000000 --- a/src/quant_research_starter/frontend/cauweb/src/pages/Settings.js +++ /dev/null @@ -1,5 +0,0 @@ -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { Save } from 'lucide-react'; -export const Settings = () => { - return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Settings" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Configure your research environment" })] }), _jsx("div", { className: "max-w-2xl", children: _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-6", children: "Preferences" }), _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Default Timezone" }), _jsxs("select", { className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent", children: [_jsx("option", { children: "UTC" }), _jsx("option", { children: "EST" }), _jsx("option", { children: "PST" })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Currency" }), _jsxs("select", { className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent", children: [_jsx("option", { children: "USD ($)" }), _jsx("option", { children: "EUR (\u20AC)" }), _jsx("option", { children: "GBP (\u00A3)" })] })] }), _jsx("div", { children: _jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "checkbox", className: "rounded border-gray-300 text-blue-600 focus:ring-blue-500" }), _jsx("span", { className: "ml-2 text-sm text-gray-700", children: "Enable real-time data updates" })] }) }), _jsx("div", { children: _jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "checkbox", className: "rounded border-gray-300 text-blue-600 focus:ring-blue-500", defaultChecked: true }), _jsx("span", { className: "ml-2 text-sm text-gray-700", children: "Send performance reports via email" })] }) }), _jsxs("button", { className: "bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors flex items-center space-x-2", children: [_jsx(Save, { className: "w-4 h-4" }), _jsx("span", { children: "Save Preferences" })] })] })] }) })] })); -}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/Settings.jsx b/src/quant_research_starter/frontend/cauweb/src/pages/Settings.jsx new file mode 100644 index 00000000..399551b3 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/Settings.jsx @@ -0,0 +1,281 @@ +import { useState } from 'react'; +import { Save, Bell, Database, Cpu, Shield, BarChart3, TrendingUp } from 'lucide-react'; + +export const Settings = () => { + const [settings, setSettings] = useState({ + timezone: 'UTC', + currency: 'USD ($)', + realTimeUpdates: false, + emailReports: true, + riskTolerance: 'medium', + chartTheme: 'dark', + dataRetention: '12', + apiRateLimit: '1000' + }); + + const handleSettingChange = (key, value) => { + setSettings(prev => ({ ...prev, [key]: value })); + }; + + const handleSave = () => { + // Save settings logic here + console.log('Saving settings:', settings); + }; + + return ( +
+
+ {/* Header */} +
+
+
+ +
+

+ Research Settings +

+
+

+ Configure your quantitative research environment, data preferences, and notification settings +

+
+ +
+ {/* Main Settings Column */} +
+ {/* General Preferences */} +
+
+
+ +
+

General Preferences

+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + {/* Data & API Settings */} +
+
+
+ +
+

Data & API Settings

+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + + +
+
+
+ + {/* Sidebar */} +
+ {/* Performance Preview */} +
+
+ +

Performance Preview

+
+ + {/* Mini Chart Placeholder */} +
+
+ {[30, 45, 60, 75, 90, 75, 60, 45, 60, 75, 85, 70].map((height, index) => ( +
+ ))} +
+
+ +
+
+

Current ROI

+

+12.4%

+
+
+

Sharpe Ratio

+

1.8

+
+
+
+ + {/* Security & Actions */} +
+
+
+ +
+

Security

+
+ +
+ + + +
+ + {/* Save Button */} + +
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/package.json b/src/quant_research_starter/frontend/metrics/package.json new file mode 100644 index 00000000..366df2e2 --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/package.json @@ -0,0 +1,33 @@ +{ + "name": "metrics-dashboard", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "recharts": "^2.8.0", + "lucide-react": "^0.294.0" + }, + "devDependencies": { + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "@vitejs/plugin-react": "^4.1.1", + "autoprefixer": "^10.4.16", + "eslint": "^8.53.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.4", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.5", + "typescript": "^5.2.2", + "vite": "^4.5.0" + } +} \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/postcss.config.js b/src/quant_research_starter/frontend/metrics/postcss.config.js new file mode 100644 index 00000000..e99ebc2c --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/src/App.tsx b/src/quant_research_starter/frontend/metrics/src/App.tsx new file mode 100644 index 00000000..4b003b76 --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/src/App.tsx @@ -0,0 +1,130 @@ +//import React from 'react'; +import { useBacktestData } from './hooks/useBacktestData'; +import MetricCard from './components/MetricCard'; +import PerformanceChart from './components/PerformanceChart'; +import DrawdownChart from './components/DrawdownChart'; +//import { RefreshCw, AlertCircle, Database } from 'lucide-react'; + +function App() { + const { data, loading, error, refreshData } = useBacktestData(); + + if (error) { + return ( +
+
+ +

Error Loading Data

+

{error}

+ +
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+
+ +

Metrics Dashboard

+
+ +
+
+
+ + {/* Main Content */} +
+ {/* Metrics Grid */} +
+ + + + + + +
+ + {/* Charts */} +
+ + +
+ + {/* Loading State */} + {loading && !data && ( +
+
+ +

Loading metrics data...

+
+
+ )} +
+ + {/* Footer */} +
+
+

+ Metrics Dashboard • Built with React, TypeScript, and Recharts +

+
+
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/src/components/DrawdownChart.tsx b/src/quant_research_starter/frontend/metrics/src/components/DrawdownChart.tsx new file mode 100644 index 00000000..2db2ab7b --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/src/components/DrawdownChart.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer +} from 'recharts'; +import { DrawdownDataPoint } from '../types'; +import { formatNumber, formatDate } from '../utils/formatters'; + +interface DrawdownChartProps { + data: DrawdownDataPoint[]; + height?: number; + loading?: boolean; +} + +const DrawdownChart: React.FC = ({ + data, + height = 300, + loading = false +}) => { + if (loading) { + return ( +
+
+
+ ); + } + + const chartData = data.map(item => ({ + ...item, + date: formatDate(item.date), + drawdown: item.drawdown * 100 // Convert to percentage + })); + + const CustomTooltip = ({ active, payload, label }: any) => { + if (active && payload && payload.length) { + return ( +
+

{label}

+

+ Drawdown: {formatNumber(payload[0].value, 'percentage')} +

+
+ ); + } + return null; + }; + + return ( +
+

Drawdown Chart

+ + + + + formatNumber(value, 'percentage')} + /> + } /> + + + +
+ ); +}; + +export default DrawdownChart; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/src/components/MetricCard.tsx b/src/quant_research_starter/frontend/metrics/src/components/MetricCard.tsx new file mode 100644 index 00000000..82ef0bca --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/src/components/MetricCard.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { MetricCardProps } from '../types'; +import { formatNumber, formatChange, getColorForValue } from '../utils/formatters'; +import { TrendingUp, TrendingDown, HelpCircle } from 'lucide-react'; + +const MetricCard: React.FC = ({ + title, + value, + change, + format = 'number', + description +}) => { + const formattedValue = typeof value === 'number' ? formatNumber(value, format) : value; + const changeColor = change && change >= 0 ? 'text-green-600' : 'text-red-600'; + const valueColor = getColorForValue(typeof value === 'number' ? value : 0, title.toLowerCase()); + + return ( +
+
+

+ {title} + {description && ( + + )} +

+ {change !== undefined && ( +
+ {change >= 0 ? ( + + ) : ( + + )} + {formatChange(change)} +
+ )} +
+ +
+ {formattedValue} +
+ + {description && ( +

+ {description} +

+ )} +
+ ); +}; + +export default MetricCard; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/src/components/PerformanceChart.tsx b/src/quant_research_starter/frontend/metrics/src/components/PerformanceChart.tsx new file mode 100644 index 00000000..2d532941 --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/src/components/PerformanceChart.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer +} from 'recharts'; +import { PerformanceDataPoint } from '../types'; +import { formatNumber, formatDate } from '../utils/formatters'; + +interface PerformanceChartProps { + data: PerformanceDataPoint[]; + height?: number; + loading?: boolean; +} + +const PerformanceChart: React.FC = ({ + data, + height = 300, + loading = false +}) => { + if (loading) { + return ( +
+
+
+ ); + } + + const chartData = data.map(item => ({ + ...item, + date: formatDate(item.date), + portfolio: item.value, + benchmark: item.benchmark + })); + + const CustomTooltip = ({ active, payload, label }: any) => { + if (active && payload && payload.length) { + return ( +
+

{label}

+ {payload.map((entry: any, index: number) => ( +

+ {entry.name}: {formatNumber(entry.value, 'currency')} +

+ ))} +
+ ); + } + return null; + }; + + return ( +
+

Performance Chart

+ + + + + formatNumber(value, 'currency')} + /> + } /> + + + + + +
+ ); +}; + +export default PerformanceChart; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/src/hooks/useBacktestData.ts b/src/quant_research_starter/frontend/metrics/src/hooks/useBacktestData.ts new file mode 100644 index 00000000..c301a16a --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/src/hooks/useBacktestData.ts @@ -0,0 +1,95 @@ +import { useState, useEffect } from 'react'; +import { BacktestData } from '../types'; + +// Mock data generator - replace with actual API calls +const generateMockData = (): BacktestData => { + const baseValue = 10000; + const performance: any[] = []; + const drawdown: any[] = []; + + let currentValue = baseValue; + let peak = baseValue; + + for (let i = 0; i < 100; i++) { + const date = new Date(); + date.setDate(date.getDate() - (99 - i)); + + // Generate random return between -2% and +3% + const dailyReturn = (Math.random() * 0.05) - 0.02; + currentValue = currentValue * (1 + dailyReturn); + peak = Math.max(peak, currentValue); + const currentDrawdown = (currentValue - peak) / peak; + + performance.push({ + date: date.toISOString().split('T')[0], + value: currentValue, + benchmark: baseValue * (1 + (i * 0.0005)) // Simple benchmark + }); + + drawdown.push({ + date: date.toISOString().split('T')[0], + drawdown: currentDrawdown + }); + } + + const totalReturn = (currentValue - baseValue) / baseValue; + + return { + id: '1', + timestamp: new Date().toISOString(), + metrics: { + totalReturn, + sharpeRatio: 1.2 + Math.random() * 0.8, + maxDrawdown: Math.min(...drawdown.map(d => d.drawdown)), + volatility: 0.15 + Math.random() * 0.1, + winRate: 0.55 + Math.random() * 0.15, + profitFactor: 1.5 + Math.random() * 0.8 + }, + performance, + drawdown + }; +}; + +export const useBacktestData = () => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + // Simulate API call delay + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Replace this with actual API call + const mockData = generateMockData(); + setData(mockData); + setError(null); + } catch (err) { + setError('Failed to load backtest data'); + console.error('Error fetching backtest data:', err); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + const refreshData = async () => { + try { + setLoading(true); + await new Promise(resolve => setTimeout(resolve, 800)); + const mockData = generateMockData(); + setData(mockData); + setError(null); + } catch (err) { + setError('Failed to refresh data'); + } finally { + setLoading(false); + } + }; + + return { data, loading, error, refreshData }; +}; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/src/types/index.ts b/src/quant_research_starter/frontend/metrics/src/types/index.ts new file mode 100644 index 00000000..5df2dd89 --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/src/types/index.ts @@ -0,0 +1,39 @@ +export interface BacktestData { + id: string; + timestamp: string; + metrics: { + totalReturn: number; + sharpeRatio: number; + maxDrawdown: number; + volatility: number; + winRate: number; + profitFactor: number; + }; + performance: PerformanceDataPoint[]; + drawdown: DrawdownDataPoint[]; +} + +export interface PerformanceDataPoint { + date: string; + value: number; + benchmark?: number; +} + +export interface DrawdownDataPoint { + date: string; + drawdown: number; +} + +export interface MetricCardProps { + title: string; + value: number | string; + change?: number; + format?: 'percentage' | 'currency' | 'number' | 'ratio'; + description?: string; +} + +export interface ChartProps { + data: PerformanceDataPoint[] | DrawdownDataPoint[]; + height?: number; + loading?: boolean; +} \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/src/utils/formatters.ts b/src/quant_research_starter/frontend/metrics/src/utils/formatters.ts new file mode 100644 index 00000000..c2ef2b45 --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/src/utils/formatters.ts @@ -0,0 +1,47 @@ +export const formatNumber = (value: number, type: 'percentage' | 'currency' | 'number' | 'ratio' = 'number'): string => { + switch (type) { + case 'percentage': + return `${(value * 100).toFixed(2)}%`; + case 'currency': + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }).format(value); + case 'ratio': + return value.toFixed(3); + default: + return new Intl.NumberFormat('en-US', { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }).format(value); + } +}; + +export const formatChange = (change: number): string => { + const sign = change >= 0 ? '+' : ''; + return `${sign}${formatNumber(change, 'percentage')}`; +}; + +export const formatDate = (date: string): string => { + return new Date(date).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); +}; + +export const getColorForValue = (value: number, type: string): string => { + switch (type) { + case 'return': + case 'winRate': + case 'profitFactor': + return value >= 0 ? 'text-green-600' : 'text-red-600'; + case 'drawdown': + case 'volatility': + return value >= 0 ? 'text-red-600' : 'text-green-600'; + default: + return 'text-gray-900'; + } +}; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/tailwind.config.js b/src/quant_research_starter/frontend/metrics/tailwind.config.js new file mode 100644 index 00000000..5c2e7b00 --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/tailwind.config.js @@ -0,0 +1,20 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + primary: { + 50: '#eff6ff', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + } + } + }, + }, + plugins: [], +} \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/tsconfig.json b/src/quant_research_starter/frontend/metrics/tsconfig.json new file mode 100644 index 00000000..d0104edb --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/tsconfig.node.json b/src/quant_research_starter/frontend/metrics/tsconfig.node.json new file mode 100644 index 00000000..099658cf --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/src/quant_research_starter/frontend/metrics/vite.config.ts b/src/quant_research_starter/frontend/metrics/vite.config.ts new file mode 100644 index 00000000..013977cc --- /dev/null +++ b/src/quant_research_starter/frontend/metrics/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + open: true + }, + build: { + outDir: 'dist', + sourcemap: true + } +}) \ No newline at end of file