diff --git a/src/assets/icons/Binance.svg b/src/assets/icons/Binance.svg new file mode 100644 index 0000000..40b093c --- /dev/null +++ b/src/assets/icons/Binance.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/Calendar.svg b/src/assets/icons/Calendar.svg index 238bbc4..1ae2912 100644 --- a/src/assets/icons/Calendar.svg +++ b/src/assets/icons/Calendar.svg @@ -1,12 +1,12 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/src/assets/icons/Cube.svg b/src/assets/icons/Cube.svg new file mode 100644 index 0000000..f1ff594 --- /dev/null +++ b/src/assets/icons/Cube.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/Search24.svg b/src/assets/icons/Search24.svg new file mode 100644 index 0000000..3125ab4 --- /dev/null +++ b/src/assets/icons/Search24.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/Uniswap.svg b/src/assets/icons/Uniswap.svg new file mode 100644 index 0000000..4a24dfa --- /dev/null +++ b/src/assets/icons/Uniswap.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/icons/arrowDown2.svg b/src/assets/icons/arrowDown2.svg new file mode 100644 index 0000000..6053a16 --- /dev/null +++ b/src/assets/icons/arrowDown2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/arrowUp2.svg b/src/assets/icons/arrowUp2.svg new file mode 100644 index 0000000..917dd9e --- /dev/null +++ b/src/assets/icons/arrowUp2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/bot.svg b/src/assets/icons/bot.svg new file mode 100644 index 0000000..ba35fd2 --- /dev/null +++ b/src/assets/icons/bot.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index a8307b2..9955f9d 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -16,6 +16,7 @@ import { ReactComponent as FolderIcon } from '@assets/icons/folder.svg'; import { ReactComponent as BellIcon } from '@assets/icons/bell.svg'; import { ReactComponent as ExportIcon } from '@assets/icons/export.svg'; import { ReactComponent as SearchIcon } from '@assets/icons/search.svg'; +import { ReactComponent as Search24Icon } from '@assets/icons/Search24.svg'; import { ReactComponent as AttachIcon } from '@assets/icons/attach.svg'; import { ReactComponent as EditIcon } from '@assets/icons/edit.svg'; import { ReactComponent as HomeIcon } from '@assets/icons/home.svg'; @@ -38,8 +39,8 @@ import { ReactComponent as ChartminiIcon } from '@assets/icons/Chartmini.svg'; import { ReactComponent as WalletIcon } from '@assets/icons/Wallet.svg'; import { ReactComponent as UpIcon } from '@assets/icons/Up.svg'; import { ReactComponent as DownIcon } from '@assets/icons/Down.svg'; -import { ReactComponent as ArrowUp2Icon } from '@assets/icons/arrowUp.svg'; -import { ReactComponent as ArrowDown2Icon } from '@assets/icons/arrowDown.svg'; +import { ReactComponent as ArrowUp2Icon } from '@assets/icons/arrowUp2.svg'; +import { ReactComponent as ArrowDown2Icon } from '@assets/icons/arrowDown2.svg'; import { ReactComponent as BackIcon } from '@assets/icons/back.svg'; import { ReactComponent as ArrowLeftIcon } from '@assets/icons/arrow-left.svg'; import { ReactComponent as ArrowRightIcon } from '@assets/icons/arrow-right.svg'; @@ -48,7 +49,8 @@ import { ReactComponent as MetaMaskIcon } from '@assets/icons/Metamask.svg'; import { ReactComponent as PointerIcon } from '@assets/icons/pointer.svg'; import { ReactComponent as PointerBottomIcon } from '@assets/icons/pointer-bottom.svg'; import { ReactComponent as LinkIcon } from '@assets/icons/link.svg'; -import { ReactComponent as BackPackLogo } from '@assets/icons/Backpack.svg'; +import { ReactComponent as BotIcon } from '@assets/icons/bot.svg'; +import { ReactComponent as BackPackLogo } from '@assets/icons/backpack.svg'; import { ReactComponent as BONKLogo } from '@assets/icons/BONK.svg'; import { ReactComponent as BTCLogo } from '@assets/icons/BTC.svg'; import { ReactComponent as ETHLogo } from '@assets/icons/ETH.svg'; @@ -57,6 +59,9 @@ import { ReactComponent as SolanaLogo } from '@assets/icons/Solana.svg'; import { ReactComponent as SolflareLogo } from '@assets/icons/Solflare.svg'; import { ReactComponent as USDCLogo } from '@assets/icons/USDC.svg'; import { ReactComponent as USDTLogo } from '@assets/icons/USDT.svg'; +import { ReactComponent as CubeLogo } from '@assets/icons/cube.svg'; +import { ReactComponent as BinanceLogo } from '@assets/icons/binance.svg'; +import { ReactComponent as UniswapLogo } from '@assets/icons/Uniswap.svg'; export { AlarmClockIcon, @@ -77,6 +82,7 @@ export { BellIcon, ExportIcon, SearchIcon, + Search24Icon, AttachIcon, EditIcon, HomeIcon, @@ -109,6 +115,7 @@ export { PointerIcon, PointerBottomIcon, LinkIcon, + BotIcon, BONKLogo, BTCLogo, BackPackLogo, @@ -118,4 +125,7 @@ export { SolflareLogo, USDCLogo, USDTLogo, + BinanceLogo, + CubeLogo, + UniswapLogo }; diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 75151e9..4f410e4 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -3,6 +3,7 @@ @tailwind utilities; @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Ubuntu+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap'); @import './variables'; body { diff --git a/src/components/ui/CustomInput.tsx b/src/components/ui/CustomInput.tsx index 6f51d7e..7ad9480 100644 --- a/src/components/ui/CustomInput.tsx +++ b/src/components/ui/CustomInput.tsx @@ -2,18 +2,20 @@ import React, { InputHTMLAttributes, ReactNode } from 'react'; interface CustomInputProps extends InputHTMLAttributes { icon?: ReactNode | string; + postIcon?: ReactNode | string; } const CustomInput: React.FC = ({ icon, + postIcon, disabled, ...rest }) => { return (
{icon && {icon}} @@ -22,6 +24,7 @@ const CustomInput: React.FC = ({ disabled={disabled} className="w-full bg-transparent text-blue-400 outline-none placeholder:text-blue-200" /> + {postIcon && {postIcon}}
); }; diff --git a/src/pages/overview-bots/components/CardBot.tsx b/src/pages/overview-bots/components/CardBot.tsx index fcf9dab..a0bfe53 100644 --- a/src/pages/overview-bots/components/CardBot.tsx +++ b/src/pages/overview-bots/components/CardBot.tsx @@ -1,5 +1,4 @@ -import { UpIcon, DownIcon } from '@assets/icons'; -import CustomButton from '@components/ui/Button'; +import { UpIcon, DownIcon, BotIcon } from '@assets/icons'; import classNames from 'classnames'; import React from 'react'; import CustomBtn from '@components/ui/CustomBtn'; @@ -11,22 +10,36 @@ interface ICardBotProps { cardBotData: ICardBotData; xtraStyle?: string; isEmpty: boolean; + showSideColor?: boolean; } const CardBot: React.FC = ({ cardBotData, xtraStyle, isEmpty, + showSideColor, }) => { return (
- {!isEmpty && ( -
+ {isEmpty ? ( +
+
+ +

+ CREATE NEW BOT +

+
+
+ ) : ( +
{cardBotData.tableData.map((row, i) => (
@@ -50,19 +63,9 @@ const CardBot: React.FC = ({ }`}

- P&L last week + P&L, last 24h

- {cardBotData.lineChartData && ( -
- -
- )}
@@ -78,8 +81,8 @@ const CardBot: React.FC = ({ )}
-

- {row.labelA[0]}% +

+ {row.labelA[0]}

{row.labelA[1]} @@ -88,28 +91,14 @@ const CardBot: React.FC = ({

-

{row.labelB[0]}

+

{row.labelB[0]}

{row.labelB[1]}

- {row.percentage !== null && ( -
- {row.isProfit ? : } -

- {row.percentage}% -

-
- )}
{i === 0 && ( diff --git a/src/pages/overview-bots/components/CustomDatePicker.tsx b/src/pages/overview-bots/components/CustomDatePicker.tsx index 96312da..0faaea2 100644 --- a/src/pages/overview-bots/components/CustomDatePicker.tsx +++ b/src/pages/overview-bots/components/CustomDatePicker.tsx @@ -9,13 +9,14 @@ type ValuePiece = Date | null; type Value = ValuePiece | [ValuePiece, ValuePiece]; interface ICustomDatePickerProps { - getUnixTimeStamp: (unixTimeStamp: number) => void; + getUnixTimeStamp?: (unixTimeStamp: number) => void; direction?: 'right' | 'left' | 'bottom' | 'top'; - isEmpty?: boolean + getDate?: (date: Date) => void; + isEmpty?: boolean; } const CustomDatePicker = forwardRef( - ({ getUnixTimeStamp, direction = 'bottom', isEmpty }, ref) => { + ({ getUnixTimeStamp, getDate, direction = 'bottom', isEmpty }, ref) => { const [value, setValue] = useState(isEmpty ? null : new Date()); const [isCalendarOpen, setIsCalendarOpen] = useState(false); const [activeMonth, setActiveMonth] = useState(new Date()); @@ -59,7 +60,8 @@ const CustomDatePicker = forwardRef( if (date instanceof Date) { const unixTimestamp = Math.floor(date.getTime() / 1000); - getUnixTimeStamp(unixTimestamp); + getUnixTimeStamp && getUnixTimeStamp(unixTimestamp); + getDate && getDate(date); } setIsCalendarOpen(false); @@ -83,6 +85,15 @@ const CustomDatePicker = forwardRef( setActiveMonth(newDate); }; + // New: Handle next and previous year + const handleNextYear = () => { + setActiveMonth(new Date(activeMonth.setFullYear(activeMonth.getFullYear() + 1))); + }; + + const handlePrevYear = () => { + setActiveMonth(new Date(activeMonth.setFullYear(activeMonth.getFullYear() - 1))); + }; + // Close calendar when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -133,19 +144,21 @@ const CustomDatePicker = forwardRef(
setIsCalendarOpen(!isCalendarOpen)} - className={`flex items-center bg-light-200 text-dark-blue text-sm w-full h-[2.25rem] rounded-[100px] px-5 cursor-pointer border border-transparent hover:border-blue-300/50 outline outline-2 outline-transparent ${ - isCalendarOpen ? 'outline-blue-300 hover:border-white' : '' + className={`flex items-center text-dark-blue text-sm w-full h-[2.25rem] rounded-[10px] px-5 text-dark-300 cursor-pointer border-none hover:outline-blue-300/40 outline outline-1 outline-light-400 ${ + isCalendarOpen ? '!outline-blue-300 hover:border-white' : '' }`} > - - {value - ? (value as Date).toLocaleDateString('en-CA') - : 'YYYY-MM-DD'} + + {value ? (value as Date).toLocaleDateString('en-CA') : 'YYYY-MM-DD'}
setIsCalendarOpen(!isCalendarOpen)} /> @@ -216,8 +229,14 @@ const CustomDatePicker = forwardRef(
{/* Right section for years */} -
-
+
+ +
{[...Array(10)].map((_, i) => { const year = activeMonth.getFullYear() - 5 + i; return ( @@ -237,6 +256,12 @@ const CustomDatePicker = forwardRef( ); })}
+
)} diff --git a/src/pages/overview-bots/components/CustomDropdown.tsx b/src/pages/overview-bots/components/CustomDropdown.tsx index cea1b37..d13aebf 100644 --- a/src/pages/overview-bots/components/CustomDropdown.tsx +++ b/src/pages/overview-bots/components/CustomDropdown.tsx @@ -1,10 +1,19 @@ -import { ArrowDown2Icon, ArrowUp2Icon, PointerIcon } from '@assets/icons'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; +import { + ArrowDown2Icon, + ArrowUp2Icon, + PointerIcon, + Search24Icon, +} from '@assets/icons'; +import { useAppDispatch } from '@store/hooks'; +import { setDisabledRunBacktest } from '@slices/generalSlice'; -interface Option { +export interface Option { label: string; value: string; + tags?: string[]; + logo?: JSX.Element; } interface ICustomDropdownProps { @@ -13,6 +22,8 @@ interface ICustomDropdownProps { placeholder?: string; disabled?: boolean; showTooTip?: boolean; + isSearchable?: boolean; + searchableName?: string; } const CustomDropdown: React.FC = ({ @@ -21,6 +32,8 @@ const CustomDropdown: React.FC = ({ placeholder, disabled, showTooTip, + isSearchable, + searchableName }) => { const dropDownRef = useRef(null); const [isOpen, setIsOpen] = useState(false); @@ -29,19 +42,34 @@ const CustomDropdown: React.FC = ({ placeholder || (options.length > 0 ? options[0].label : 'Select Option') ); const [hoveredIndex, setHoveredIndex] = useState(null); + const [textValue, setTextValue] = useState({ [searchableName || ""]: '' }); + const [optionsObj] = useState(options); const [tooltipPos, setTooltipPos] = useState<{ top: number; left: number; } | null>(null); + const dispatch = useAppDispatch(); const handleOptionClick = (option: Option) => { setSelectedValue(option.label); + setTextValue({ [searchableName || ""]: option.label }); setIsOpen(false); setIsSelected(true); onSelect(option.value); setTooltipPos(null); }; + const filteredOpts = isSearchable + ? optionsObj.filter((option) => + option.label.toLowerCase().includes((textValue[searchableName || ""] || "").toLowerCase()) + ) + : optionsObj; + + const handleOnChange = (evt: ChangeEvent) => { + const {value, name} = evt.target; + setTextValue({ [name]: value }); + }; + const handleMouseEnter = (event: React.MouseEvent, idx: number) => { const rect = event.currentTarget.getBoundingClientRect(); setTooltipPos({ @@ -51,7 +79,47 @@ const CustomDropdown: React.FC = ({ setHoveredIndex(idx); }; + const tagSpan = (tag: string, idx: number) => { + let bgColor = ''; + switch (tag.toLowerCase()) { + case 'largest volume': + bgColor = '#E6F4FE'; + break; + case 'binance': + bgColor = '#EFB621'; + break; + case 'most frequent': + bgColor = '#E6F4FE'; + break; + case 'mango': + bgColor = '#A3E5C8'; + break; + case 'uniswap': + bgColor = '#E62788'; + break; + case 'cube': + bgColor = '#FF822E'; + break; + default: + bgColor = ''; + break; + } + return ( + + {tag} + + ); + }; + useEffect(() => { + dispatch(setDisabledRunBacktest(textValue[searchableName || ""] === "")); const handleClickOutside = (event: MouseEvent) => { if ( dropDownRef.current && @@ -64,37 +132,72 @@ const CustomDropdown: React.FC = ({ return () => { document.removeEventListener('mousedown', handleClickOutside); }; - }, []); + }, [textValue[searchableName || ""]]); return ( -
+
setIsOpen(!isOpen)} > - {selectedValue} - {isOpen && !disabled ? : } + {isSearchable ? ( + + {' '} + + + ) : ( + selectedValue + )} + {!disabled ? ( + isOpen && !disabled ? ( + + ) : ( + + ) + ) : null}
- {isOpen && !disabled && ( + {filteredOpts.length > 0 && isOpen && !disabled && (
- {options.map((option, idx) => ( + {filteredOpts.map((option, idx) => (
handleOptionClick(option)} onMouseEnter={(e) => handleMouseEnter(e, idx)} onMouseLeave={() => setHoveredIndex(null)} > - {option.label} + <>{option.label} + + {option.tags && ( + + {option.tags.map((tag, idx) => tagSpan(tag, idx))} + + )} + {option.logo && ( + + {option.logo} + + )} +
))}
diff --git a/src/pages/overview-bots/components/CustomText.tsx b/src/pages/overview-bots/components/CustomText.tsx index d5d382a..46d788e 100644 --- a/src/pages/overview-bots/components/CustomText.tsx +++ b/src/pages/overview-bots/components/CustomText.tsx @@ -8,10 +8,22 @@ interface ICustomTextProps { hasQuestionMark?: boolean; xtraStyle?: string; toolTipWidth?: string; + isEmpty?: boolean; } const CustomText = forwardRef( - ({ text, toolTipText, showOptText, hasQuestionMark = true, xtraStyle, toolTipWidth }, ref) => { + ( + { + text, + toolTipText, + showOptText, + hasQuestionMark = true, + xtraStyle, + toolTipWidth, + isEmpty, + }, + ref + ) => { const [showToolTip, setShowToolTip] = useState(false); const spanRef = useRef(null); @@ -34,20 +46,30 @@ const CustomText = forwardRef( }, []); return ( -

+

{text} - {showOptText && (Optional)} + {showOptText && ( + (Optional) + )} {hasQuestionMark && ( ? - {showToolTip && ( - + {showToolTip && !isEmpty && ( + )} )} diff --git a/src/pages/overview-bots/components/GoBack.tsx b/src/pages/overview-bots/components/GoBack.tsx index 3572ac0..27160f2 100644 --- a/src/pages/overview-bots/components/GoBack.tsx +++ b/src/pages/overview-bots/components/GoBack.tsx @@ -11,7 +11,7 @@ const GoBack: React.FC = ({ onClick, disabled }) => {

diff --git a/src/pages/overview-bots/components/GraphChart.tsx b/src/pages/overview-bots/components/GraphChart.tsx new file mode 100644 index 0000000..392003b --- /dev/null +++ b/src/pages/overview-bots/components/GraphChart.tsx @@ -0,0 +1,49 @@ +import { transformData } from '@utils/transformData'; +import { CandleDataResp } from '@store/market/types'; +import CandlestickChart from './CandlestickChart'; +import CustomDatePicker from './CustomDatePicker'; +import { FadeLoader } from 'react-spinners'; +import CustomText from './CustomText'; +import { forwardRef } from 'react'; + +interface IGraphChart { + isLoading: boolean; + className: string | undefined; + data: CandleDataResp | undefined; + startTimeUnix: (unix: number) => void; + endTimeUnix: (unix: number) => void; +} + +const GraphChart = forwardRef( + ({ isLoading, data, className, startTimeUnix, endTimeUnix }, ref) => { + return ( + + ); + } +); + +export default GraphChart; diff --git a/src/pages/overview-bots/components/GroupedConfig.tsx b/src/pages/overview-bots/components/GroupedConfig.tsx index 52d6970..4785e40 100644 --- a/src/pages/overview-bots/components/GroupedConfig.tsx +++ b/src/pages/overview-bots/components/GroupedConfig.tsx @@ -1,12 +1,13 @@ import { countConfigsPerGroup } from '../../..//utils/countConfigsPerGroup.util'; import { IStrategiesConfigData } from '../../../utils/strategyConfigData'; import { formatText } from '../../../utils/formatText.util'; +import CustomDropdown, { Option } from './CustomDropdown'; +import CustomInput from '@components/ui/CustomInput'; import { ChangeEvent, forwardRef } from 'react'; -import CustomDropdown from './CustomDropdown'; import ToggleButton from './ToggleButton'; import RangeSlider from './RangeSlider'; -import CustomText from './CustomText'; import NumberInput from './NumberInput'; +import CustomText from './CustomText'; interface IGroupedConfigProps { config: IStrategiesConfigData; @@ -14,6 +15,7 @@ interface IGroupedConfigProps { hasOtherGroup?: boolean; uniqueGroups: string[]; value: { [key: string]: number | string | boolean }; + tradingPairOpts: Option[]; handleOnInputChange: (evt: ChangeEvent) => void; handleOnRangeChange: (evt: ChangeEvent) => void; handleOnToggle: (isOn: boolean, key: string) => void; @@ -27,6 +29,7 @@ const GroupedConfig = forwardRef( value, hasOtherGroup, uniqueGroups, + tradingPairOpts, handleOnInputChange, handleOnRangeChange, handleOnToggle, @@ -86,19 +89,18 @@ const GroupedConfig = forwardRef( {cfg.display_type === 'input' || cfg.display_type === 'slider' ? ( typeof cfg.default === 'number' ? (
-
+
{cfg.is_percentage ? '+' : ''} {cfg.is_percentage ? '%' : ''}
-
( ) : typeof cfg.default === 'string' || cfg.type === 'str' ? ( cfg.default && cfg.default.toString().includes(',') ? ( + ) : formatText(key) === 'trading pair' ? ( + {}} + isSearchable + /> ) : ( - ( )}
{objectConfig(group)} diff --git a/src/pages/overview-bots/components/Header.tsx b/src/pages/overview-bots/components/Header.tsx index c7dce04..0dece51 100644 --- a/src/pages/overview-bots/components/Header.tsx +++ b/src/pages/overview-bots/components/Header.tsx @@ -20,12 +20,20 @@ import { IStratTab, ITab, ITabs, + ITimeBTab, ITimeTab, } from '../hooks/useProfile'; import Switcher from './Switcher'; export interface IHeaderProps { - query: ITab | ITimeTab | IDateTab | IStratTab | IChatTab | IPerfTab; + query: + | ITab + | ITimeTab + | ITimeBTab + | IDateTab + | IStratTab + | IChatTab + | IPerfTab; tabs: ITabs[]; searchParams: URLSearchParams; setSearchParams: SetURLSearchParams; @@ -90,16 +98,16 @@ const Header: React.FC = ({
- +

3

- + - + {address ? ( @@ -120,7 +128,8 @@ const Header: React.FC = ({
) : ( = ({ number, onRemove }) => ( -
+
{number} @@ -86,7 +86,7 @@ const NumberInput: React.FC = ({ data }) => { return (
@@ -106,7 +106,7 @@ const NumberInput: React.FC = ({ data }) => { placeholder={ numbers.length === 0 ? 'Input a value and press Enter' : '' } - className={`flex-none bg-transparent pl-1 border-none outline-none placeholder:text-blue-200 ${ + className={`flex-none bg-transparent pl-1 border-none text-sm font-normal outline-none placeholder:text-blue-200 ${ numbers.length === 0 ? 'w-full' : 'max-w-[10ch]' }`} /> diff --git a/src/pages/overview-bots/components/Overview.tsx b/src/pages/overview-bots/components/Overview.tsx index 912ea77..db7244e 100644 --- a/src/pages/overview-bots/components/Overview.tsx +++ b/src/pages/overview-bots/components/Overview.tsx @@ -16,6 +16,7 @@ import { ITabs, ITimeTab, IChatTab, + ITimeBTab, } from '../hooks/useProfile'; import Performance from './Performance'; import CryptoStats from './CryptoStats'; @@ -24,15 +25,16 @@ import Switcher from './Switcher'; import PnLChart from './PnLChart'; import LineTab from './LineTab'; import PnLineChart from './PnLineChart'; +import { truncateText } from '@utils/truncateText.util'; interface IOverviewProps { dateTabs: IDateTabs[]; dateQuery: string; - timeQuery: ITimeTab; + timeBQuery: ITimeBTab; chartTypeQuery: IChatTab; cryptoQuery: ICryptoTab; perfQuery: IPerfTab; - timeTabs: ITabs[]; + timeBTabs: ITabs[]; perfTabs: ITabs[]; cryptoTabs: ITabs[]; chartTypeTabsB: ITabs[]; @@ -47,14 +49,14 @@ interface IOverviewProps { const Overview: React.FC = ({ dateTabs, - timeTabs, + timeBTabs, perfTabs, cryptoTabs, dateQuery, cryptoQuery, chartTypeQuery, chartTypeTabsB, - timeQuery, + timeBQuery, perfQuery, statsData, statsDataOTN, @@ -69,7 +71,6 @@ const Overview: React.FC = ({ const { address } = useAppSelector((state) => state.auth); const { isOpen, handleOpen, handleClose } = useModal(); const getChartTypeQuery = searchParams.get('chart') || 'pnl'; - const navigate = useNavigate(); const handleCreateNewModel = useCallback(async () => { if (!address) { @@ -79,11 +80,11 @@ const Overview: React.FC = ({ } try { - await createInstance({ - strategy_name: 'test', - strategy_parameters: {}, - market: 'string', - }).unwrap(); + // await createInstance({ + // strategy_name: 'test', + // strategy_parameters: {}, + // market: 'string', + // }).unwrap(); setSearchParams({ tab: 'training' }); } catch (e) { console.error('Failed to create new model', e); @@ -109,14 +110,18 @@ const Overview: React.FC = ({ id="overview_header" className="flex flex-col md:flex-row gap-y-3 md:gap-y-3 justify-between items-center mt-5" > -

- Portfolio:{' '} - {isEmpty ? ( - $0 (0%) - ) : ( - $19 349 (+20%) - )} -

+
+

Overview

+

+ Portfolio:{' '} + {isEmpty ? ( + $0 (0%) + ) : ( + $19 349 (+20%) + )} +

+
+ = ({ />
- -
- - -
+
{isEmpty ? ( -
+
+ Portfolio +
) : ( cryptoStats.map((data, i) => { const total = cryptoStats.reduce( @@ -152,13 +149,39 @@ const Overview: React.FC = ({
+ className="h-full px-3 flex items-center" + > +
+

+ {`${ + data.percentage <= 5 + ? truncateText(data.tag, 3) + : data.tag + }`} +

+

{`${ + data.percentage <= 5 + ? truncateText( + `$${data.amount} (${data.percentage}%)`, + 3 + ) + : `$${data.amount} (${data.percentage}%)` + }`}

+
+
); }) )}
+ +
= ({ isEmpty={isEmpty} />
@@ -191,33 +213,33 @@ const Overview: React.FC = ({ )} - {isEmpty ? ( -
- ) : ( - + - )} +
-
-
+ ) : ( + -
-
+ )} +
@@ -225,7 +247,9 @@ const Overview: React.FC = ({
{isEmpty ? ( -
+
+ P&L CHART +
) : (
{getChartTypeQuery === 'pnl' && ( diff --git a/src/pages/overview-bots/components/Performance.tsx b/src/pages/overview-bots/components/Performance.tsx index 57e36dd..5a7560c 100644 --- a/src/pages/overview-bots/components/Performance.tsx +++ b/src/pages/overview-bots/components/Performance.tsx @@ -19,11 +19,11 @@ const Performance: React.FC = ({ }) => { return (
-

+

Active Bots

-
+
= ({ {/* CardBot */}
- {cardBotData.map((item, idx) => ( + {isEmpty ? ( - ))} + ) : ( + cardBotData.map((item, idx) => ( + + )) + )}
{!isEmpty && ( @@ -53,7 +64,7 @@ const Performance: React.FC = ({ ].map((icon, idx) => ( {icon} diff --git a/src/pages/overview-bots/components/StatsTable.tsx b/src/pages/overview-bots/components/StatsTable.tsx index a312e6c..27d20bf 100644 --- a/src/pages/overview-bots/components/StatsTable.tsx +++ b/src/pages/overview-bots/components/StatsTable.tsx @@ -1,5 +1,5 @@ -import CustomBtn from '@components/ui/CustomBtn'; import { IStatsTableData } from '../hooks/useProfile'; +import CustomBtn from '@components/ui/CustomBtn'; import MiniLineChart from './MiniLineChart'; import CustomText from './CustomText'; @@ -51,17 +51,15 @@ const StatsTable: React.FC = ({ text={stat.label} hasQuestionMark={hasQuestionMark} toolTipText={stat.toolTipText} + isEmpty={isEmpty} />
-
+
{/* Chart */} {isEmpty && !stat.progressValue ? ( -
+
) : ( stat.chartData && (
@@ -74,11 +72,14 @@ const StatsTable: React.FC = ({ )} {/* Progress Bar */} {stat.progressValue && ( -
+
{!isEmpty && ( )}
diff --git a/src/pages/overview-bots/components/Stepper.tsx b/src/pages/overview-bots/components/Stepper.tsx index 0eed907..857bc33 100644 --- a/src/pages/overview-bots/components/Stepper.tsx +++ b/src/pages/overview-bots/components/Stepper.tsx @@ -38,7 +38,7 @@ const Stepper: React.FC = ({ currentStep, setCurrentStep }) => {

index + 1, // Previous step text color @@ -70,4 +70,3 @@ const Stepper: React.FC = ({ currentStep, setCurrentStep }) => { }; export default Stepper; - diff --git a/src/pages/overview-bots/components/Training.tsx b/src/pages/overview-bots/components/Training.tsx index 8c7811f..f0d867b 100644 --- a/src/pages/overview-bots/components/Training.tsx +++ b/src/pages/overview-bots/components/Training.tsx @@ -1,25 +1,27 @@ import { strategiesConfigData as config } from '../../../utils/strategyConfigData'; -import { - ArrowDown2Icon, - ArrowUp2Icon, - MangoLogo, - SolanaLogo, - USDCLogo, -} from '@assets/icons'; +import { getDaysBtnDates, isTodayOrFuture } from '@utils/getDaysBtnDates.util'; +import { useAppDispatch, useAppSelector } from '@shared/hooks/useStore'; import { useGetHistoricalCandlesMutation } from '@store/market/api'; import { defaultType } from '../../../utils/defaultType.util'; -import { transformData } from '../../../utils/transformData'; import { updateDefaults } from '../../../utils/updateDefault'; import { SetURLSearchParams } from 'react-router-dom'; -import CandlestickChart from './CandlestickChart'; +import CustomInput from '@components/ui/CustomInput'; import CustomDatePicker from './CustomDatePicker'; import CustomBtn from '@components/ui/CustomBtn'; import CustomDropdown from './CustomDropdown'; import GroupedConfig from './GroupedConfig'; -import { FadeLoader } from 'react-spinners'; -import ButtonList from './ButtonList'; import CustomText from './CustomText'; import Pagination from './Pagination'; +import { + ArrowDown2Icon, + ArrowUp2Icon, + BinanceLogo, + CubeLogo, + MangoLogo, + SolanaLogo, + UniswapLogo, + USDCLogo, +} from '@assets/icons'; import React, { ChangeEvent, Fragment, @@ -40,7 +42,13 @@ import Switcher from './Switcher'; import CardBot from './CardBot'; import GoBack from './GoBack'; import LineTab from './LineTab'; -import CustomInput from '@components/ui/CustomInput'; +import { + setCoinValues, + setEndDate, + setExpensesFee, + setIsFetchCandleData, +} from '@slices/generalSlice'; +import GraphChart from './GraphChart'; export interface ITrainingProps { timeQuery: ITimeTab; @@ -59,6 +67,12 @@ interface ValueType { [key: string]: number | string | boolean; } +interface ITimestamp { + startTime: number; + endTime: number; + endDate: Date | null; +} + const Training: React.FC = ({ timeQuery, resultStatQuery, @@ -89,15 +103,19 @@ const Training: React.FC = ({ const [advancedSettingsOpen, setAdancedSettingsOpen] = useState(false); const [value, setValue] = useState(Object.fromEntries(valueArr)); const [tradePair, setTradePair] = useState('SOL/BNB'); - const [timeStamp, setTimeStamp] = useState({ + const [timeStamp, setTimeStamp] = useState({ startTime: 1727771877, endTime: 1728376677, - endDate: 0, + endDate: null, }); const [coinValue, setCoinValue] = useState<{ [key: string]: string }>({ SOL: '', USDC: '', }); + const { endDate, disabledRunBacktest, isFetchCandleData } = useAppSelector( + (state) => state.general + ); + const dispatch = useAppDispatch(); const uniqueGroups = Array.from( new Set(Object.values(config[cfgName]).map((item) => item.group)) @@ -119,6 +137,26 @@ const Training: React.FC = ({ { label: 'Uniswap', value: '7' }, ]; + const tradingPairOpts = [ + { + label: 'SOL—USDC', + value: '1', + tags: ['Largest Volume'], + logo: , + }, + { + label: 'SOL—USDC', + value: '2', + tags: ['Most frequent'], + logo: , + }, + { label: 'SOL—USDT', value: '3', tags: undefined, logo: }, + { label: 'SOL—JUP', value: '4', tags: undefined, logo: }, + { label: 'SOL—USDT', value: '5', tags: undefined, logo: }, + ]; + + const numOfTradeDays = getDaysBtnDates(endDate ? endDate : new Date()); + const toggleAdancedSettingsOpen = () => setAdancedSettingsOpen((prevState) => !prevState); @@ -151,6 +189,8 @@ const Training: React.FC = ({ const { value, name } = evt.target; if (!/^\d*$/.test(value)) return; setCoinValue((prevState) => ({ ...prevState, [name]: value })); + dispatch(setCoinValues({ [name]: +value })); + dispatch(setIsFetchCandleData(false)); }; const handleOnToggle = (isOn: boolean, key: string) => { @@ -175,33 +215,40 @@ const Training: React.FC = ({ setTimeStamp((prevState) => ({ ...prevState, endTime: unix })); }; - const endDateUnix = (unix: number) => { - setTimeStamp((prevState) => ({ ...prevState, endDate: unix })); + const getEndDate = (date: Date) => { + setTimeStamp((prevState) => ({ ...prevState, endDate: date })); + dispatch(setEndDate(date)); }; const handleCandleData = useCallback(async () => { - try { - await historicalCandlesData({ - connector_name: 'birdeye', - trading_pair: tradePair, - market_address: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs', - interval: '15m', - start_time: timeStamp.startTime, - end_time: timeStamp.endTime, - }); - } catch (error) { - console.log('TRY CATCH ERROR => ', error); + if (isFetchCandleData) { + try { + await historicalCandlesData({ + connector_name: 'birdeye', + trading_pair: tradePair, + market_address: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs', + interval: '15m', + start_time: timeStamp.startTime, + end_time: timeStamp.endTime, + }); + } catch (error) { + console.log('TRY CATCH ERROR => ', error); + } } - }, [tradePair, timeStamp]); + }, [tradePair, timeStamp, isFetchCandleData]); useEffect(() => { handleCandleData(); - }, [tradePair, timeStamp]); + const isEmpty = Object.values(coinValue).every((num) => num !== ''); + dispatch( + setExpensesFee({ expenses: isEmpty ? 8 : 0, fee: isEmpty ? 5 : 0 }) + ); + }, [tradePair, timeStamp, coinValue]); - const disabled = - currentStep === 3 ? + const disabledDeposit = Object.values(coinValue).some((num) => num === '' || +num <= 0) || - timeStamp.endDate === 0 : false; + numOfTradeDays < 0 || + !isTodayOrFuture(timeStamp.endDate); return (

@@ -227,13 +274,19 @@ const Training: React.FC = ({ ? 'Save Strategy' : 'Deposit & Start' }`} - disabled={disabled} - xtraStyles={`!max-w-[20.3125rem] !w-full`} + disabled={ + currentStep === 1 + ? disabledRunBacktest + : currentStep === 3 + ? disabledDeposit + : false + } + xtraStyles={`!max-w-[18rem] lg:!max-w-[20.3125rem] !w-full !mt-5 md:!mt-0`} onClick={handleNextStep} />
{currentStep == 1 || currentStep == 2 ? ( -
+

{`Backtest ${ currentStep === 1 @@ -259,20 +312,16 @@ const Training: React.FC = ({ className="flex flex-col lg:flex-row justify-between gap-y-8 lg:gap-y-0 lg:gap-x-4" >
- {currentStep == 1 || currentStep == 2 ? ( -
-

- {currentStep == 1 - ? 'Adjust settings for each trading pair separately' - : 'Click on Trading Pair to view the Results of the backtest'} -

- -
- ) : null} - {currentStep == 1 ? (
-
+
+ {currentStep == 1 || currentStep == 2 ? ( +
+

Model name

+

Big Brain

+
+ ) : null} +
= ({ config={config} cfgName={cfgName} value={value} + tradingPairOpts={tradingPairOpts} handleOnInputChange={handleOnInputChange} handleOnRangeChange={handleOnRangeChange} handleOnToggle={handleOnToggle} @@ -351,9 +401,9 @@ const Training: React.FC = ({

= ({ />

) : currentStep == 3 ? ( -
+
-
-
-

+
+
+

Connect exchange

= ({ />
-
-

+
+

Trading time limit

= ({ toolTipText={`Select the date when trading will stop. Trading duration impacts Compute expenses and Solana fees.`} xtraStyle="mb-4 font-semibold text-xs uppercase" /> - +

-

+

Deposit both coins to start

-
+
{[ { icon: , text: 'SOL' }, { icon: , text: 'USDC' }, ].map(({ icon, text }, idx) => ( - -
- - +
+
+
-
- - + +

{text}

<>{icon}{' '} + + } + /> +
+ {['$100', '$300', '$500', 'Max'].map((data, idx) => ( + + {data} + + ))}
- +

+ of connected wallet balance +

+
))}
@@ -527,6 +566,7 @@ const Training: React.FC = ({ config={config} cfgName={cfgName} value={value} + tradingPairOpts={tradingPairOpts} handleOnInputChange={handleOnInputChange} handleOnRangeChange={handleOnRangeChange} handleOnToggle={handleOnToggle} @@ -536,12 +576,24 @@ const Training: React.FC = ({ )}
+ {/* Only show chat when on step 1 and 2 */} + {currentStep == 1 || currentStep == 2 ? ( + + ) : null} + {currentStep == 1 || currentStep == 2 ? ( <>
diff --git a/src/pages/overview-bots/hooks/useProfile.ts b/src/pages/overview-bots/hooks/useProfile.ts index 7578dea..905acd2 100644 --- a/src/pages/overview-bots/hooks/useProfile.ts +++ b/src/pages/overview-bots/hooks/useProfile.ts @@ -1,4 +1,5 @@ import { createElement, ReactNode, useEffect, useState } from 'react'; +import { getDaysBtnDates } from '@utils/getDaysBtnDates.util'; import { useSearchParams } from 'react-router-dom'; import usePageTitle from '@shared/hooks/usePageTitle'; import { useAppSelector } from '@shared/hooks/useStore'; @@ -14,6 +15,7 @@ import { export type ITab = 'overview' | 'datasets' | 'training' | 'bots' | 'tutorial'; export type ITimeTab = 'minute' | 'hour' | 'day' | 'week' | 'month'; +export type ITimeBTab = 'hourly' | 'daily' | 'weekly'; export type ICryptoTab = 'all' | 'big' | 'trade' | 'alpha' | 'moon'; export type IDateTab = 'day' | 'week' | 'month' | 'time'; export type IStratTab = 'strat' | 'hyper'; @@ -25,6 +27,7 @@ export interface ITabs { key: | ITab | ITimeTab + | ITimeBTab | IDateTab | ICryptoTab | IStratTab @@ -40,6 +43,7 @@ export interface IStatsTableData { value: string; chartData: null | number[]; progressValue: null | number; + progressValueColor: string[] | null; color: string | null; toolTipText: string | null; } @@ -62,6 +66,7 @@ export interface ICardBotData { name: string; rate: number; isPositive: boolean; + color?: string; pieChartData: ICryptoStats[]; lineChartData: number[] | null; tableData: { @@ -110,20 +115,18 @@ export interface IDepositInfo { export default () => { const { address } = useAppSelector((state) => state.auth); + const { coinValue, endDate, expenses, fee } = useAppSelector( + (state) => state.general + ); const [searchParams, setSearchParams] = useSearchParams(); const { setTitle } = usePageTitle(); - const [search, setSearch] = useState(''); - - // const { data } = useGetUserInfoQuery({ address: session?.address }); - - // const user = data as IUserInfo; - const query: ITab = (searchParams.get('tab') as ITab) || 'overview'; const dateQuery = (searchParams.get('date') as IDateTab) || 'week'; const cryptoQuery = (searchParams.get('crypto') as ICryptoTab) || 'all'; const tradeDateQuery = (searchParams.get('trade_date') as IDateTab) || 'day'; const timeQuery = (searchParams.get('time') as ITimeTab) || 'day'; + const timeBQuery = (searchParams.get('timeb') as ITimeBTab) || 'daily'; const perfQuery = (searchParams.get('perf') as IPerfTab) || 'best'; const stratQuery = (searchParams.get('strat') as IStratTab) || 'strat'; const resultStatQuery = @@ -202,6 +205,12 @@ export default () => { { key: 'month', name: '1M', icon: null }, ]; + const timeBTabs: ITabs[] = [ + { key: 'hourly', name: 'Hourly', icon: null }, + { key: 'daily', name: 'Daily', icon: null }, + { key: 'weekly', name: 'Weekly', icon: null }, + ]; + const perfTabs: ITabs[] = [ { key: 'best', name: 'Best Performance', icon: null }, { key: 'worst', name: 'Worst Performance', icon: null }, @@ -234,34 +243,34 @@ export default () => { { amount: 9186, tag: 'Big Brain', - percentage: 20, + percentage: 47, value: null, isProfit: true, - color: '#3AA8F0', + color: '#FFDDD3', }, { amount: 7036, tag: 'Trade Genius', - percentage: 11, + percentage: 36, value: null, isProfit: true, - color: '#1F609C', + color: '#D7CEE3', }, { amount: 3127, tag: 'Alpha Trader', - percentage: 1, + percentage: 14, value: null, isProfit: false, - color: '#4AB6C4', + color: '#D4E6FC', }, { amount: 550, tag: 'Moon Space', - percentage: 3, + percentage: 1, value: null, isProfit: false, - color: '#2788B2', + color: '#F7D7E6', }, ]; @@ -298,6 +307,7 @@ export default () => { value: '+$3909 (20%)', chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], progressValue: null, + progressValueColor: null, color: '#4CAF50', toolTipText: 'Shows the net gain or loss from your trades over a selected time period, helping you track performance', @@ -307,6 +317,7 @@ export default () => { value: '-$469', chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], progressValue: null, + progressValueColor: null, color: '#4CAF50', toolTipText: 'Represents the total value of all assets traded by your bots during the selected period, providing insight into your trading activity.', @@ -316,6 +327,7 @@ export default () => { value: '59.36%', chartData: [98, 40, 60, 38, 42, 46, 40, 90, 95, 50], progressValue: null, + progressValueColor: null, color: '#F44336', toolTipText: 'The number of all executed buy and sell orders by your bots during the selected period, showing the overall trading activity.', @@ -325,6 +337,7 @@ export default () => { value: '200%', chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], progressValue: null, + progressValueColor: null, color: '#4CAF50', toolTipText: 'The projected annual return on your trading strategies, expressed as a percentage, based on current performance and compounding', @@ -334,6 +347,7 @@ export default () => { value: '2.52', chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], progressValue: null, + progressValueColor: null, color: '#4CAF50', toolTipText: 'The percentage of successful trades made by your bots, indicating how often their predictions were correct.', @@ -346,6 +360,7 @@ export default () => { value: '$36 367', chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], progressValue: null, + progressValueColor: null, color: '#4CAF50', toolTipText: 'Shows the net gain or loss from your trades over a selected time period, helping you track performance', @@ -355,33 +370,27 @@ export default () => { value: '250', chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], progressValue: null, + progressValueColor: null, color: '#4CAF50', toolTipText: 'Represents the total value of all assets traded by your bots during the selected period, providing insight into your trading activity.', }, { - label: 'Successful', - value: '133', + label: 'Average profit', + value: '$15,63', chartData: [98, 40, 60, 38, 42, 46, 40, 90, 95, 50], progressValue: null, + progressValueColor: null, color: '#F44336', toolTipText: 'The number of all executed buy and sell orders by your bots during the selected period, showing the overall trading activity.', }, - { - label: 'Failed', - value: '117', - chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], - progressValue: null, - color: '#4CAF50', - toolTipText: - 'The percentage of successful trades made by your bots, indicating how often their predictions were correct.', - }, { label: 'Total accuracy', value: '53%', - chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], - progressValue: null, + chartData: null, + progressValue: 53, + progressValueColor: ['#A3E5C8', '#FFAFB2'], color: '#4CAF50', toolTipText: 'The projected annual return on your trading strategies, expressed as a percentage, based on current performance and compounding', @@ -394,6 +403,7 @@ export default () => { value: '-$20', chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], progressValue: null, + progressValueColor: null, color: '#F44336', toolTipText: null, }, @@ -402,6 +412,7 @@ export default () => { value: '$9186', chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], progressValue: null, + progressValueColor: null, color: '#4CAF50', toolTipText: null, }, @@ -410,6 +421,7 @@ export default () => { value: '$16532', chartData: [98, 40, 60, 38, 42, 46, 40, 90, 95, 50], progressValue: null, + progressValueColor: null, color: '#4CAF50', toolTipText: null, }, @@ -418,6 +430,7 @@ export default () => { value: '14', chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], progressValue: null, + progressValueColor: null, color: '#F44336', toolTipText: null, }, @@ -426,6 +439,7 @@ export default () => { value: '210%', chartData: null, progressValue: null, + progressValueColor: null, color: null, toolTipText: null, }, @@ -434,26 +448,19 @@ export default () => { value: '2.81', chartData: null, progressValue: null, + progressValueColor: null, color: null, toolTipText: null, }, ]; const statsDataOTN: IStatsTableData[] = [ - { - label: 'OTN Balance', - value: '550', - chartData: null, - progressValue: 50, - color: '', - toolTipText: - "The amount of OTN (Robotter's native token) you hold. Staking OTN can reduce your trading fees and unlock additional rewards for increased profitability.", - }, { label: 'Compute costs', - value: '$150', + value: '$24', chartData: [98, 40, 60, 38, 42, 46, 40, 90, 95, 50], progressValue: null, + progressValueColor: null, color: '#F44336', toolTipText: 'The estimated cost for running your trading bots, including data processing and computational resources, billed monthly', @@ -463,19 +470,30 @@ export default () => { value: '2%', chartData: null, progressValue: 20, + progressValueColor: ['#60B3D7', '#E6F4FE'], color: '', toolTipText: 'The average fee charged for placing limit orders that add liquidity to the market. Lower maker fees can reduce your overall trading costs.', }, { - label: 'Av taker fee', + label: 'Av. taker fee', value: '3%', chartData: null, progressValue: 30, + progressValueColor: ['#60B3D7', '#E6F4FE'], color: '', toolTipText: 'The average fee charged for executing market orders that remove liquidity from the market. Higher taker fees can impact your overall trading profitability.', }, + { + label: 'Fees paid', + value: '$32', + chartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], + progressValue: null, + progressValueColor: null, + color: '#4CAF50', + toolTipText: null, + }, ]; const statsDataLock: IStatsTableData[] = [ @@ -484,6 +502,7 @@ export default () => { value: '550', chartData: null, progressValue: 50, + progressValueColor: ['#60B3D7', '#E6F4FE'], color: null, toolTipText: null, }, @@ -492,6 +511,7 @@ export default () => { value: '$50', chartData: [98, 40, 60, 38, 42, 46, 40, 90, 95, 50], progressValue: null, + progressValueColor: null, color: '#F44336', toolTipText: null, }, @@ -500,6 +520,7 @@ export default () => { value: '2%', chartData: null, progressValue: 20, + progressValueColor: ['#60B3D7', '#E6F4FE'], color: null, toolTipText: null, }, @@ -508,6 +529,7 @@ export default () => { value: '3%', chartData: null, progressValue: 30, + progressValueColor: ['#60B3D7', '#E6F4FE'], color: null, toolTipText: null, }, @@ -518,12 +540,13 @@ export default () => { name: 'Big Brain', rate: 1837, isPositive: true, + color: '#FACFC4', pieChartData: [ { amount: 65, tag: 'profit', percentage: 65, - color: '#218358', + color: '#A3E5C8', isProfit: true, value: null, }, @@ -531,7 +554,7 @@ export default () => { amount: 35, tag: 'loss', percentage: 35, - color: '#CE2C31', + color: '#FFAFB2', isProfit: false, value: null, }, @@ -539,20 +562,20 @@ export default () => { lineChartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], tableData: [ { - labelA: [210, 'APR'], - labelB: [42.6, 'SOL'], + labelA: [`$210`, 'Portfolio'], + labelB: ['42.6%', 'Max drawdown'], percentage: 11, isProfit: true, }, { - labelA: [2.81, 'Sharpe ratio'], - labelB: [0.13, 'BTC'], + labelA: [`187%`, 'APY'], + labelB: [0.13, 'Sharpe ratio'], percentage: 10, isProfit: true, }, { labelA: [14, 'Trades'], - labelB: [550, 'OTN'], + labelB: [`2024-12-29`, 'End date'], percentage: 9, isProfit: true, }, @@ -562,12 +585,13 @@ export default () => { name: 'Trade Genius', rate: 773, isPositive: true, + color: '#D7CEE3', pieChartData: [ { amount: 59, tag: 'profit', percentage: 59, - color: '#218358', + color: '#A3E5C8', isProfit: true, value: null, }, @@ -575,7 +599,7 @@ export default () => { amount: 41, tag: 'loss', percentage: 41, - color: '#CE2C31', + color: '#FFAFB2', isProfit: false, value: null, }, @@ -583,20 +607,20 @@ export default () => { lineChartData: [50, 60, 40, 49, 38, 34, 80, 76, 95, 100], tableData: [ { - labelA: [187, 'APR'], - labelB: [2.35, 'SOL'], + labelA: [`$187`, 'Portfolio'], + labelB: [`45%`, 'Max drawdown'], percentage: 16, isProfit: true, }, { - labelA: [2.01, 'Sharpe ratio'], - labelB: [0.0034, 'BTC'], + labelA: [`164%`, 'APY'], + labelB: [0.0034, 'Sharpe ratio'], percentage: 14, isProfit: true, }, { labelA: [36, 'Trades'], - labelB: [83, 'OTN'], + labelB: [`2024-12-29`, 'End date'], percentage: 2, isProfit: true, }, @@ -606,12 +630,13 @@ export default () => { name: 'Alpha Trader', rate: 31, isPositive: false, + color: '#D4E6FC', pieChartData: [ { amount: 49, tag: 'profit', percentage: 49, - color: '#218358', + color: '#A3E5C8', isProfit: true, value: null, }, @@ -619,7 +644,7 @@ export default () => { amount: 51, tag: 'loss', percentage: 51, - color: '#CE2C31', + color: '#FFAFB2', isProfit: true, value: null, }, @@ -627,20 +652,20 @@ export default () => { lineChartData: [90, 85, 80, 70, 60, 65, 75, 76, 95, 80], tableData: [ { - labelA: [165, 'APR'], - labelB: [3.68, 'SOL'], + labelA: [`$165`, 'Portfolio'], + labelB: [`36%`, 'Max drawdown'], percentage: 1, isProfit: true, }, { - labelA: [1.75, 'Sharpe ratio'], - labelB: [0.0001, 'BTC'], + labelA: [`210%`, 'APY'], + labelB: [0.0001, 'Sharpe ratio'], percentage: 8, isProfit: false, }, { labelA: [120, 'Trades'], - labelB: [89, 'OTN'], + labelB: [`2024-12-29`, 'End date'], percentage: 4, isProfit: true, }, @@ -650,12 +675,13 @@ export default () => { name: 'Moon Space', rate: 62, isPositive: false, + color: '#F7D7E6', pieChartData: [ { amount: 49, tag: 'profit', percentage: 49, - color: '#218358', + color: '#A3E5C8', isProfit: true, value: null, }, @@ -663,7 +689,7 @@ export default () => { amount: 51, tag: 'loss', percentage: 51, - color: '#CE2C31', + color: '#FFAFB2', isProfit: true, value: null, }, @@ -671,20 +697,20 @@ export default () => { lineChartData: [90, 85, 80, 70, 60, 65, 75, 76, 95, 80], tableData: [ { - labelA: [102, 'APR'], - labelB: [0.0003, 'ETH'], + labelA: [`$102`, 'Portfolio'], + labelB: [`47%`, 'Max drawdown'], percentage: 2, isProfit: true, }, { - labelA: [2.43, 'Sharpe ratio'], - labelB: [161, 'JUP'], + labelA: [`164%`, 'APY'], + labelB: [161, 'Sharpe ratio'], percentage: 7, isProfit: false, }, { labelA: [6, 'Trades'], - labelB: [258, 'DRIFT'], + labelB: [`2024-12-29`, 'End date'], percentage: 5, isProfit: false, }, @@ -702,7 +728,7 @@ export default () => { amount: 68, tag: 'profit', percentage: 68, - color: '#218358', + color: '#A3E5C8', isProfit: true, value: null, }, @@ -710,7 +736,7 @@ export default () => { amount: 32, tag: 'loss', percentage: 32, - color: '#CE2C31', + color: '#FFAFB2', isProfit: false, value: null, }, @@ -746,7 +772,7 @@ export default () => { amount: 65, tag: 'profit', percentage: 65, - color: '#218358', + color: '#A3E5C8', isProfit: true, value: null, }, @@ -754,7 +780,7 @@ export default () => { amount: 35, tag: 'loss', percentage: 35, - color: '#CE2C31', + color: '#FFAFB2', isProfit: false, value: null, }, @@ -790,7 +816,7 @@ export default () => { amount: 48, tag: 'profit', percentage: 48, - color: '#218358', + color: '#A3E5C8', isProfit: true, value: null, }, @@ -798,7 +824,7 @@ export default () => { amount: 52, tag: 'loss', percentage: 52, - color: '#CE2C31', + color: '#FFAFB2', isProfit: false, value: null, }, @@ -834,7 +860,7 @@ export default () => { amount: 46, tag: 'profit', percentage: 46, - color: '#218358', + color: '#A3E5C8', isProfit: true, value: null, }, @@ -842,7 +868,7 @@ export default () => { amount: 54, tag: 'loss', percentage: 54, - color: '#CE2C31', + color: '#FFAFB2', isProfit: false, value: null, }, @@ -957,6 +983,7 @@ export default () => { const bigResultTable = [ ['Model Name', 'SOL Big Brain'], + ['Market', 'SOL—USDC'], ['Test P&L', '+$1100 (11%)'], ['Trading Accuracy', '63%'], ['Max. drawdown', '53%'], @@ -968,6 +995,7 @@ export default () => { const bigStatTable = [ ['Model Name', 'SOL Big Brain'], + ['Market', 'SOL—USDC'], ['Exchange', 'Mango Markets'], ['Trading Strategy / Normalized value', 'BarUpDown / 0.5'], ['Market trend', 'Bullish / 67%'], @@ -977,13 +1005,15 @@ export default () => { ['Timespan', '2024-05-01 / 2024-05-31'], ]; + const numOfTradeDays = getDaysBtnDates(endDate ? endDate : new Date()); + const depositInfo: IDepositInfo[] = [ - { l: 'Market', r: 'SOL / USDC', icon: null }, - { l: 'Number of trading days', r: '0', icon: null }, - { l: 'Compute expenses', r: '$0', icon: null }, - { l: 'Solana fees', r: '$0', icon: null }, - { l: 'SOL', r: '0', icon: createElement(SolanaLogo) }, - { l: 'USDC', r: '0', icon: createElement(USDCLogo) }, + { l: 'Market', r: 'SOL—USDC', icon: null }, + { l: 'Number of trading days', r: `${numOfTradeDays}`, icon: null }, + { l: 'Compute expenses', r: `$${expenses}`, icon: null }, + { l: 'Solana fees', r: `$${fee}`, icon: null }, + { l: 'SOL', r: `${coinValue.SOL}`, icon: createElement(SolanaLogo) }, + { l: 'USDC', r: `${coinValue.USDC}`, icon: createElement(USDCLogo) }, { l: 'Total', r: '$0', icon: null }, ]; @@ -995,6 +1025,7 @@ export default () => { tabs, dateTabs, timeTabs, + timeBTabs, cryptoTabs, perfTabs, stratTabs, @@ -1019,6 +1050,7 @@ export default () => { // user, query, dateQuery, + timeBQuery, timeQuery, perfQuery, cryptoQuery, diff --git a/src/pages/overview-bots/index.tsx b/src/pages/overview-bots/index.tsx index 080a007..76fc4eb 100644 --- a/src/pages/overview-bots/index.tsx +++ b/src/pages/overview-bots/index.tsx @@ -14,6 +14,7 @@ const OverviewBots: React.FC = () => { tabs, dateTabs, timeTabs, + timeBTabs, perfTabs, stratTabs, cryptoTabs, @@ -33,6 +34,7 @@ const OverviewBots: React.FC = () => { query, dateQuery, timeQuery, + timeBQuery, perfQuery, stratQuery, cryptoQuery, @@ -75,9 +77,9 @@ const OverviewBots: React.FC = () => { dateQuery={dateQuery} cryptoQuery={cryptoQuery} chartTypeQuery={chartTypeQuery} - timeQuery={timeQuery} + timeBQuery={timeBQuery} perfQuery={perfQuery} - timeTabs={timeTabs} + timeBTabs={timeBTabs} perfTabs={perfTabs} cryptoTabs={cryptoTabs} chartTypeTabsB={chartTypeTabsB} diff --git a/src/slices/generalSlice.ts b/src/slices/generalSlice.ts new file mode 100644 index 0000000..ad4b417 --- /dev/null +++ b/src/slices/generalSlice.ts @@ -0,0 +1,61 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface AppState { + endDate: Date | null; + expenses: number; + isFetchCandleData: boolean; + disabledRunBacktest: boolean; + fee: number; + coinValue: { [key: string]: number }; +} + +const initialState: AppState = { + endDate: null, + expenses: 0, + isFetchCandleData: true, + disabledRunBacktest: true, + fee: 0, + coinValue: { + SOL: 0, + USDC: 0, + }, +}; + +export const generalSlice = createSlice({ + name: 'general', + initialState, + reducers: { + setCoinValues: ( + state, + { payload }: { payload: { [key: string]: number } } + ) => { + state.coinValue = { ...state.coinValue, ...payload }; + }, + setEndDate: (state, action: PayloadAction) => { + state.endDate = action.payload; + }, + setExpensesFee: ( + state, + { payload }: { payload: { expenses: number; fee: number } } + ) => { + state.expenses = payload.expenses; + state.fee = payload.fee; + }, + setDisabledRunBacktest: (state, { payload }: { payload: boolean }) => { + state.disabledRunBacktest = payload; + }, + setIsFetchCandleData: (state, { payload }: { payload: boolean }) => { + state.isFetchCandleData = payload; + } + }, +}); + +export const { + setEndDate, + setCoinValues, + setExpensesFee, + setIsFetchCandleData, + setDisabledRunBacktest, +} = generalSlice.actions; + +export default generalSlice; diff --git a/src/store/auth/api.ts b/src/store/auth/api.ts index f75e7a5..d0b761d 100644 --- a/src/store/auth/api.ts +++ b/src/store/auth/api.ts @@ -15,7 +15,7 @@ const authApi = robotterApi.injectEndpoints({ >({ query: ({ address }) => ({ method: 'POST', - url: `/authorization/challenge?address=${address}&chain=${CHAIN}`, + url: `/api/v1/authorization/challenge?address=${address}&chain=${CHAIN}`, }), }), @@ -30,7 +30,7 @@ const authApi = robotterApi.injectEndpoints({ >({ query: ({ address, signature }) => ({ method: 'POST', - url: `/authorization/solve?address=${address}&chain=${CHAIN}&signature=${signature}`, + url: `/api/v1/authorization/solve?address=${address}&chain=${CHAIN}&signature=${signature}`, }), }), @@ -45,7 +45,7 @@ const authApi = robotterApi.injectEndpoints({ >({ query: ({ token }) => ({ method: 'POST', - url: `/authorization/refresh?token=${token}`, + url: `/api/v1/authorization/refresh?token=${token}`, }), }), }), diff --git a/src/store/instances/api.ts b/src/store/instances/api.ts index c4ec3a3..9c48f93 100644 --- a/src/store/instances/api.ts +++ b/src/store/instances/api.ts @@ -14,7 +14,7 @@ const instanceApi = robotterApi.injectEndpoints({ query: (data) => { console.log('createInstance request body:', data); return { - url: '/instances', + url: '/api/v1/instances', method: 'POST', data, }; @@ -25,7 +25,7 @@ const instanceApi = robotterApi.injectEndpoints({ getInstance: builder.query({ query: ({ instance_id }) => ({ method: 'GET', - url: `/instances/${instance_id}/wallet`, + url: `/api/v1/instances/${instance_id}/wallet`, }), providesTags: ['Instance'], }), @@ -39,7 +39,7 @@ const instanceApi = robotterApi.injectEndpoints({ } >({ query: ({ instanceId, ...data }) => ({ - url: `/instances/${instanceId}/start`, + url: `/api/v1/instances/${instanceId}/start`, method: 'POST', body: data, }), @@ -49,7 +49,7 @@ const instanceApi = robotterApi.injectEndpoints({ stopInstance: builder.mutation({ query: ({ instance_id }) => ({ method: 'POST', - url: `/instances/${instance_id}/stop`, + url: `/api/v1/instances/${instance_id}/stop`, }), invalidatesTags: ['StopInstance'], }), diff --git a/src/store/market/api.ts b/src/store/market/api.ts index e7a8d9e..5d21c17 100644 --- a/src/store/market/api.ts +++ b/src/store/market/api.ts @@ -6,7 +6,7 @@ const instanceApi = robotterApi.injectEndpoints({ getHistoricalCandles: builder.mutation({ query: (data) => ({ method: 'POST', - url: `/historical-candles`, + url: `/api/v1/historical-candles`, data, }), invalidatesTags: ['Candle'], diff --git a/src/store/reducers.ts b/src/store/reducers.ts index 4e6eba0..445d4a8 100644 --- a/src/store/reducers.ts +++ b/src/store/reducers.ts @@ -3,9 +3,11 @@ import appSlice from '@slices/appSlice'; import auth from './auth/slice'; import { robotterApi, transactionsApi } from './config'; import { websocketApi } from './wsApi'; +import generalSlice from '@slices/generalSlice'; export const rootReducer = combineReducers({ app: appSlice.reducer, + general: generalSlice.reducer, auth, // RTK Query Setup diff --git a/src/store/strategies/api.ts b/src/store/strategies/api.ts index b07f36b..cfdffd5 100644 --- a/src/store/strategies/api.ts +++ b/src/store/strategies/api.ts @@ -4,7 +4,7 @@ const strategiesApi = robotterApi.injectEndpoints({ endpoints: (builder) => ({ getStrategies: builder.query>, void>({ query: () => ({ - url: '/strategies', + url: '/api/v1/strategies', method: 'GET', }), }), diff --git a/src/store/transactions/api.ts b/src/store/transactions/api.ts index 0117b6f..11cd612 100644 --- a/src/store/transactions/api.ts +++ b/src/store/transactions/api.ts @@ -5,7 +5,7 @@ const transactionsEndpoints = transactionsApi.injectEndpoints({ endpoints: (builder) => ({ getUserUsdcBalance: builder.query<{ balance: number }, { user: string }>({ query: (params) => ({ - url: '/getUserUsdcBalance', + url: '/api/v1/getUserUsdcBalance', method: 'GET', params, }), @@ -15,7 +15,7 @@ const transactionsEndpoints = transactionsApi.injectEndpoints({ { userAddress: string } >({ query: (params) => ({ - url: '/getBotData', + url: '/api/v1/getBotData', method: 'GET', params, }), diff --git a/src/utils/getDaysBtnDates.util.ts b/src/utils/getDaysBtnDates.util.ts new file mode 100644 index 0000000..46f7274 --- /dev/null +++ b/src/utils/getDaysBtnDates.util.ts @@ -0,0 +1,40 @@ +export const getDaysBtnDates = (date: Date): number => { + const currentDate = new Date(); + + // Normalize both dates to the same base day, keeping time intact + const today = new Date( + currentDate.getFullYear(), + currentDate.getMonth(), + currentDate.getDate(), + currentDate.getHours(), + currentDate.getMinutes() + ); + + const targetDate = new Date( + date.getFullYear(), + date.getMonth(), + date.getDate(), + date.getHours(), + date.getMinutes() + ); + + // If the target date is in the past, return 0 + if (targetDate < today) return 0; + + // Calculate the time difference in milliseconds + const timeDiff = targetDate.getTime() - today.getTime(); + + // Convert time difference to days, ensuring fractional days round up + return Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); +}; + +export const isTodayOrFuture = (date: Date | null): boolean => { + if (!date) return false; + const today = new Date(); + today.setHours(0, 0, 0, 0); // Normalize to start of today for comparison + + const targetDate = new Date(date); + targetDate.setHours(0, 0, 0, 0); // Normalize to start of target date for comparison + + return targetDate >= today; +}; diff --git a/src/utils/truncateText.util.ts b/src/utils/truncateText.util.ts new file mode 100644 index 0000000..d4473e9 --- /dev/null +++ b/src/utils/truncateText.util.ts @@ -0,0 +1,7 @@ +export const truncateText = (text: string, maxLength: number) => { + if (text.length > maxLength) { + return text.substring(0, maxLength - 3) + '...'; + } + + return text; +}; diff --git a/tailwind.config.cjs b/tailwind.config.cjs index bfb7903..b5a0b4f 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -3,6 +3,10 @@ module.exports = { content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], theme: { extend: { + fontFamily: { + inter: ['Inter'], + ubuntumono: ['Ubuntu Mono', 'sans-serif'], + }, boxShadow: { custom: '0px 16px 32px 0px #16253533', }, @@ -24,10 +28,15 @@ module.exports = { 'chart-200': '#1F609C', 'chart-300': '#3AA8F0', 'chart-400': '#2788B2', + 'chart-500': '#FFDDD3', + 'chart-600': '#D7CEE3', + 'chart-700': '#D4E6FC', + 'chart-800': '#F7D7E6', 'green-100': '#218358', 'red-100': '#CE2C31', 'light-20': '#F6F8FB', 'light-200': '#F1FAFD', + 'light-250': '#F5F5F5', 'dark-20': '#91989C', 'dark-40': '#5C6569', 'dark-100': '#84828E', @@ -43,6 +52,7 @@ module.exports = { 'text-dark': '#244141', 'form-bg': '#F6FAFB', states: '#EFB621', + 'yellow-200': '#F6E2AC', turkish: '#80FFED', }, }, diff --git a/tsconfig.json b/tsconfig.json index 14186a4..9f89694 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,8 @@ "@pages/*": ["src/pages/*"], "@shared/*": ["src/shared/*"], "@slices/*": ["src/slices/*"], - "@store/*": ["src/store/*"] + "@store/*": ["src/store/*"], + "@utils/*": ["src/utils/*"] } }, "include": [".eslintrc.json", "src"], diff --git a/vite.config.ts b/vite.config.ts index dae8665..31870d4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -17,6 +17,7 @@ export default defineConfig({ '@shared': path.resolve(__dirname, './src/shared'), '@slices': path.resolve(__dirname, './src/slices'), '@store': path.resolve(__dirname, './src/store'), + '@utils': path.resolve(__dirname, './src/utils'), }, }, });