Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,11 @@ python -m quant_research_starter.cli compute-factors -d data_sample/sample_price
# run a backtest
python -m quant_research_starter.cli backtest -d data_sample/sample_prices.csv -s output/factors.csv -o output/backtest_results.json

# optional: start the Streamlit dashboard
# DISCLAIMER: OLD VERSION
# optional: start the Streamlit dashboard, if on main stream
streamlit run src/quant_research_starter/dashboard/streamlit_app.py
# NEW VERSION: if streamlit is in legacy folder
streamlit run legacy/streamlit/streamlit_app.py
```

---
Expand Down
32 changes: 32 additions & 0 deletions src/quant_research_starter/frontend/cauweb/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@qrs/cauweb",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint src --ext .ts,.tsx"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.17",
"chart.js": "^4.5.1",
"lucide-react": "^0.263.1",
"react": "^18.2.0",
"react-chartjs-2": "^5.3.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0"
},
"devDependencies": {
"@tailwindcss/cli": "^4.1.17",
"@tailwindcss/postcss": "^4.1.17",
"@types/react": "^18.3.26",
"@types/react-dom": "^18.3.7",
"@vitejs/plugin-react": "^4.1.0",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"vite": "^5.0.0"
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

208 changes: 208 additions & 0 deletions src/quant_research_starter/frontend/cauweb/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { useState, useRef, useEffect } from 'react';
import { Bell, Search, User, Settings, LogOut, ChevronDown, Menu, X } from 'lucide-react';

export const Header = () => {
const [isProfileOpen, setIsProfileOpen] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [showSuggestions, setShowSuggestions] = useState(false);
const [searchQuery, setSearchQuery] = useState('');

const profileRef = useRef<HTMLDivElement>(null);
const searchRef = useRef<HTMLDivElement>(null);

// Close dropdowns when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (profileRef.current && !profileRef.current.contains(event.target as Node)) {
setIsProfileOpen(false);
}
if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
setShowSuggestions(false);
}
};

document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

// Mock search suggestions
const searchSuggestions = [
"Mean Reversion Strategy",
"BTC/USD Correlation",
"Portfolio Optimization",
"Risk Metrics Analysis",
"Volatility Forecasting"
];

const filteredSuggestions = searchSuggestions.filter(suggestion =>
suggestion.toLowerCase().includes(searchQuery.toLowerCase())
);

// Handle mobile search
const handleMobileSearch = () => {
// Implement mobile search modal or expandable search
console.log("Mobile search activated");
};

// Handle profile actions
const handleProfileAction = (action: string) => {
setIsProfileOpen(false);
console.log(`${action} clicked`);
// Add your navigation or action logic here
};

return (
<header className="bg-white dark:bg-gray-900 shadow-sm border-b border-gray-200 dark:border-gray-700 sticky top-0 z-50">
<div className="flex items-center justify-between px-4 lg:px-6 py-4">
{/* Logo and Mobile Menu Button */}
<div className="flex items-center space-x-4">
{/* Mobile Menu Button */}
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="lg:hidden p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
aria-label="Toggle menu"
aria-expanded={isMobileMenuOpen}
>
{isMobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>

{/* Logo */}
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
QuantResearch
</h1>
</div>

{/* Search Bar - Hidden on mobile, visible on desktop */}
<div ref={searchRef} className="hidden lg:block flex-1 max-w-2xl mx-8">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
setShowSuggestions(true);
}}
onFocus={() => setShowSuggestions(true)}
placeholder="Search strategies, assets, or metrics..."
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-gray-50 dark:bg-gray-800 hover:bg-white dark:hover:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
/>

{/* Search Suggestions Dropdown */}
{showSuggestions && searchQuery && (
<div className="absolute top-full left-0 right-0 mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50 max-h-60 overflow-y-auto">
<div className="py-2">
{filteredSuggestions.length > 0 ? (
filteredSuggestions.map((suggestion, index) => (
<button
key={`suggestion-${index}`}
onClick={() => {
setSearchQuery(suggestion);
setShowSuggestions(false);
}}
className="w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
{suggestion}
</button>
))
) : (
<div className="px-4 py-2 text-sm text-gray-500 dark:text-gray-400">
No results found
</div>
)}
</div>
</div>
)}
</div>
</div>

{/* Mobile Search Button */}
<button
onClick={handleMobileSearch}
className="lg:hidden p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
aria-label="Search"
>
<Search className="w-5 h-5" />
</button>

{/* Right Section - Notifications & Profile */}
<div className="flex items-center space-x-4">
{/* Notifications - Hidden on mobile */}
<button
className="hidden sm:flex relative p-2 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
aria-label="Notifications"
>
<Bell className="w-5 h-5" />
<span className="absolute -top-1 -right-1 w-4 h-4 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
3
</span>
</button>

{/* Profile Dropdown */}
<div ref={profileRef} className="relative">
<button
onClick={() => setIsProfileOpen(!isProfileOpen)}
className="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
aria-label="User menu"
aria-expanded={isProfileOpen}
>
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center shadow-sm">
<User className="w-4 h-4 text-white" />
</div>
<div className="hidden sm:flex items-center space-x-1">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
Sarah Wilson
</span>
<ChevronDown
className={`w-4 h-4 text-gray-500 dark:text-gray-400 transition-transform duration-200 ${
isProfileOpen ? 'rotate-180' : ''
}`}
/>
</div>
</button>

{/* Dropdown Menu */}
{isProfileOpen && (
<div className="absolute right-0 top-full mt-2 w-48 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-50">
<div className="py-1">
{/* Mobile-only user name */}
<div className="px-4 py-2 border-b border-gray-200 dark:border-gray-700 sm:hidden">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
Sarah Wilson
</span>
</div>

<button
onClick={() => handleProfileAction('Profile')}
className="flex items-center w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150"
>
<User className="w-4 h-4 mr-3" />
Profile
</button>

<button
onClick={() => handleProfileAction('Settings')}
className="flex items-center w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150"
>
<Settings className="w-4 h-4 mr-3" />
Settings
</button>

<div className="border-t border-gray-200 dark:border-gray-700 my-1" />

<button
onClick={() => handleProfileAction('Sign Out')}
className="flex items-center w-full px-4 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors duration-150"
>
<LogOut className="w-4 h-4 mr-3" />
Sign Out
</button>
</div>
</div>
)}
</div>
</div>
</div>
</header>
);
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import {
LayoutDashboard,
PlayCircle,
Beaker,
PieChart,
Settings,
TrendingUp
} from 'lucide-react';

export const Navigation: React.FC = () => {
const navItems = [
{ path: '/', icon: LayoutDashboard, label: 'Dashboard' },
{ path: '/backtest', icon: PlayCircle, label: 'Backtest Studio' },
{ path: '/research', icon: Beaker, label: 'Research Lab' },
{ path: '/portfolio', icon: PieChart, label: 'Portfolio Analytics' },
{ path: '/settings', icon: Settings, label: 'Settings' }
];

return (
<nav className="navigation">
{/* Logo */}
<div className="nav-header">
<div className="nav-logo">
<div className="logo-icon">
<TrendingUp size={20} />
</div>
<div className="logo-text">
<h1>CAUQuant</h1>
<p>Research Platform</p>
</div>
</div>
</div>

{/* Navigation Items */}
<div className="nav-items">
{navItems.map((item) => {
const Icon = item.icon;
return (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
`nav-item ${isActive ? 'active' : ''}`
}
>
<Icon className="icon" size={20} />
<span>{item.label}</span>
</NavLink>
);
})}
</div>

{/* Quick Stats */}
<div className="nav-stats">
<div className="stat-item">
<span>Strategies</span>
<span>12</span>
</div>
<div className="stat-item">
<span>Assets</span>
<span>248</span>
</div>
<div className="stat-item">
<span>Backtests</span>
<span>1.2K</span>
</div>
</div>
</nav>
);
};
Loading
Loading