@ -751,11 +751,13 @@ interface RunConfig {
function RunConfigPopup ( {
strategy ,
initialConfig ,
onRun ,
onClose ,
isSubmitting ,
} : {
strategy : IntradayStrategy ;
initialConfig? : Partial < RunConfig > ;
onRun : ( cfg : RunConfig ) = > void ;
onClose : ( ) = > void ;
isSubmitting : boolean ;
@ -769,6 +771,7 @@ function RunConfigPopup({
universe : strategy.universe ,
compound_returns : strategy.compound_returns ? ? false ,
initialCapital : strategy.initial_capital ? ? 10000 ,
. . . initialConfig ,
} ) ;
const set = < K extends keyof RunConfig > ( k : K , v : RunConfig [ K ] ) = > setCfg ( c = > ( { . . . c , [ k ] : v } ) ) ;
@ -780,15 +783,33 @@ function RunConfigPopup({
< / button >
) ;
const labelStyle : React.CSSProperties = {
fontSize : 12 , color : 'var(--text3)' , display : 'block' ,
marginBottom : 6 , textTransform : 'uppercase' , letterSpacing : '0.07em' , fontWeight : 600 ,
} ;
return (
< div style = { { position : 'fixed' , inset : 0 , background : 'rgba(0,0,0,0.5)' , zIndex : 200 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' } }
onClick = { onClose } >
< div style = { { background : 'var(--bg1)' , border : '1px solid var(--border)' , borderRadius : 10 , padding : '16px 18px' , width : 340 , boxShadow : '0 8px 24px rgba(0,0,0,0.25)' } }
onClick = { e = > e . stopPropagation ( ) } >
< div style = { { fontSize : 15 , fontWeight : 700 , color : 'var(--text1)' , marginBottom : 14 } } > Run : { strategy . name } < / div >
< div style = { { marginBottom : 12 } } >
< div style = { { fontSize : 11 , color : 'var(--text3)' , marginBottom : 5 , textTransform : 'uppercase' , letterSpacing : '0.07em' } } > Period < / div >
< div style = { { display : 'flex' , border : '1px solid var(--border)' , borderRadius : 7 , overflow : 'hidden' } } >
< div style = { { position : 'fixed' , inset : 0 , background : 'rgba(0,0,0,0.55)' , zIndex : 200 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' } } >
< div style = { { background : 'var(--bg1)' , border : '1px solid var(--border)' , borderRadius : 12 , padding : '28px 32px' , width : 500 , boxShadow : '0 12px 40px rgba(0,0,0,0.35)' } } >
{ /* Header */ }
< div style = { { display : 'flex' , alignItems : 'flex-start' , justifyContent : 'space-between' , marginBottom : 24 } } >
< div >
< div style = { { fontSize : 17 , fontWeight : 700 , color : 'var(--text1)' , marginBottom : 3 } } > Run Backtest < / div >
< div style = { { fontSize : 13 , color : 'var(--text3)' , fontFamily : 'var(--font-mono)' } } > { strategy . name } < / div >
< / div >
< button onClick = { onClose }
style = { { background : 'none' , border : 'none' , cursor : 'pointer' , color : 'var(--text3)' , padding : '2px 4px' , borderRadius : 4 , display : 'flex' , lineHeight : 1 } }
onMouseEnter = { e = > ( e . currentTarget . style . color = 'var(--text1)' ) }
onMouseLeave = { e = > ( e . currentTarget . style . color = 'var(--text3)' ) } >
< XCircle size = { 18 } / >
< / button >
< / div >
{ /* Period */ }
< div style = { { marginBottom : 18 } } >
< label style = { labelStyle } > Period < / label >
< div style = { { display : 'flex' , border : '1px solid var(--border)' , borderRadius : 8 , overflow : 'hidden' } } >
{ modeBtn ( 'days' , 'Last N' ) }
{ modeBtn ( 'year' , 'Year' ) }
{ modeBtn ( 'range' , 'Range' ) }
@ -796,59 +817,63 @@ function RunConfigPopup({
< / div >
{ cfg . dateMode === 'days' && (
< div style = { { marginBottom : 1 2 } } >
< label style = { { fontSize : 11 , color : 'var(--text3)' , display : 'block' , marginBottom : 4 , textTransform : 'uppercase' , letterSpacing : '0.07em' } } > Trading Days < / label >
< div style = { { marginBottom : 1 8 } } >
< label style = { labelStyle } > Trading Days < / label >
< input type = "number" value = { cfg . days } onChange = { e = > set ( 'days' , Number ( e . target . value ) ) } min = { 10 } max = { 500 } style = { inputStyle } / >
< / div >
) }
{ cfg . dateMode === 'year' && (
< div style = { { marginBottom : 1 2 } } >
< label style = { { fontSize : 11 , color : 'var(--text3)' , display : 'block' , marginBottom : 4 , textTransform : 'uppercase' , letterSpacing : '0.07em' } } > Year < / label >
< div style = { { marginBottom : 1 8 } } >
< label style = { labelStyle } > Year < / label >
< input type = "text" value = { cfg . year } onChange = { e = > set ( 'year' , e . target . value ) } placeholder = "e.g. 2025" style = { inputStyle } / >
< / div >
) }
{ cfg . dateMode === 'range' && (
< div style = { { marginBottom : 1 2, display : 'flex' , flexDirection : 'column' , gap : 8 } } >
< div style = { { marginBottom : 1 8, display : 'grid' , gridTemplateColumns : '1fr 1fr' , gap : 12 } } >
< div >
< label style = { { fontSize : 11 , color : 'var(--text3)' , display : 'block' , marginBottom : 4 , textTransform : 'uppercase' , letterSpacing : '0.07em' } } > Start ( YYYY / YYYY - MM / YYYY - MM - DD ) < / label >
< input type = "text" value = { cfg . startDate } onChange = { e = > set ( 'startDate' , e . target . value ) } placeholder = " e.g. 2024 or 2024-01-01 " style = { inputStyle } / >
< label style = { labelStyle } > Start Date < / label >
< input type = "text" value = { cfg . startDate } onChange = { e = > set ( 'startDate' , e . target . value ) } placeholder = " YYYY / YYYY-MM / YYYY-MM-DD " style = { inputStyle } / >
< / div >
< div >
< label style = { { fontSize : 11 , color : 'var(--text3)' , display : 'block' , marginBottom : 4 , textTransform : 'uppercase' , letterSpacing : '0.07em' } } > End ( optional — YYYY / YYYY - MM / YYYY - MM - DD ) < / label >
< input type = "text" value = { cfg . endDate } onChange = { e = > set ( 'endDate' , e . target . value ) } placeholder = " e.g. 2024 or 2024-12-31 ( default: today) " style = { inputStyle } / >
< label style = { labelStyle } > End Date < span style = { { fontWeight : 400 , textTransform : 'none' , letterSpacing : 0 } } > ( optional ) < / span > < / label >
< input type = "text" value = { cfg . endDate } onChange = { e = > set ( 'endDate' , e . target . value ) } placeholder = " default: today" style = { inputStyle } / >
< / div >
< / div >
) }
< div style = { { marginBottom : 12 } } >
< label style = { { fontSize : 11 , color : 'var(--text3)' , display : 'block' , marginBottom : 4 , textTransform : 'uppercase' , letterSpacing : '0.07em' } } > Initial Capital ( $ ) < / label >
< input type = "number" value = { cfg . initialCapital } onChange = { e = > set ( 'initialCapital' , Number ( e . target . value ) ) } min = { 1000 } step = { 1000 } style = { inputStyle } / >
< / div >
< div style = { { marginBottom : 12 } } >
< label style = { { fontSize : 11 , color : 'var(--text3)' , display : 'block' , marginBottom : 4 , textTransform : 'uppercase' , letterSpacing : '0.07em' } } > Universe < / label >
< select value = { cfg . universe } onChange = { e = > set ( 'universe' , e . target . value ) } style = { selectStyle } >
{ UNIVERSE_OPTIONS . map ( o = > < option key = { o . value } value = { o . value } > { o . label } < / option > ) }
< / select >
{ /* Two-column row: Capital + Universe */ }
< div style = { { display : 'grid' , gridTemplateColumns : '1fr 1fr' , gap : 16 , marginBottom : 18 } } >
< div >
< label style = { labelStyle } > Initial Capital ( $ ) < / label >
< input type = "number" value = { cfg . initialCapital } onChange = { e = > set ( 'initialCapital' , Number ( e . target . value ) ) } min = { 1000 } step = { 1000 } style = { inputStyle } / >
< / div >
< div >
< label style = { labelStyle } > Universe < / label >
< select value = { cfg . universe } onChange = { e = > set ( 'universe' , e . target . value ) } style = { selectStyle } >
{ UNIVERSE_OPTIONS . map ( o = > < option key = { o . value } value = { o . value } > { o . label } < / option > ) }
< / select >
< / div >
< / div >
< div style = { { marginBottom : 16 , display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' , padding : '8px 10px' , background : 'var(--bg2)' , borderRadius : 7 , border : '1px solid var(--border)' } } >
< span style = { { fontSize : 12 , color : 'var(--text3)' , textTransform : 'uppercase' , letterSpacing : '0.07em' } } > Returns < / span >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 8 } } >
{ /* Returns toggle */ }
< div style = { { marginBottom : 28 , display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' , padding : '12px 14px' , background : 'var(--bg2)' , borderRadius : 8 , border : '1px solid var(--border)' } } >
< span style = { { fontSize : 12 , color : 'var(--text3)' , textTransform : 'uppercase' , letterSpacing : '0.07em' , fontWeight : 600 } } > Returns Mode < / span >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 10 } } >
< span style = { { fontSize : 13 , color : cfg.compound_returns ? 'var(--text3)' : 'var(--cyan)' , fontWeight : cfg.compound_returns ? 400 : 600 } } > 단 리 < / span >
< div onClick = { ( ) = > set ( 'compound_returns' , ! cfg . compound_returns ) }
style = { { width : 4 0, height : 22 , borderRadius : 11 , background : cfg.compound_returns ? 'var(--cyan)' : 'var(--bg3)' , border : '1px solid var(--border)' , cursor : 'pointer' , position : 'relative' , transition : 'background 0.2s' } } >
< div style = { { position : 'absolute' , top : 2, left : cfg.compound_returns ? 19 : 2 , width : 16 , height : 16 , borderRadius : '50%' , background : 'white' , transition : 'left 0.2s' , boxShadow : '0 1px 3px rgba(0,0,0,0.3)' } } / >
style = { { width : 4 2, height : 24 , borderRadius : 12 , background : cfg.compound_returns ? 'var(--cyan)' : 'var(--bg3)' , border : '1px solid var(--border)' , cursor : 'pointer' , position : 'relative' , transition : 'background 0.2s' } } >
< div style = { { position : 'absolute' , top : 3, left : cfg.compound_returns ? 20 : 3 , width : 16 , height : 16 , borderRadius : '50%' , background : 'white' , transition : 'left 0.2s' , boxShadow : '0 1px 3px rgba(0,0,0,0.3)' } } / >
< / div >
< span style = { { fontSize : 13 , color : cfg.compound_returns ? 'var(--cyan)' : 'var(--text3)' , fontWeight : cfg.compound_returns ? 600 : 400 } } > 복 리 < / span >
< / div >
< / div >
< div style = { { display : 'flex' , gap : 8 } } >
< button onClick = { onClose } style = { { flex : 1 , padding : '8px' , borderRadius : 7 , background : 'var(--bg2)' , border : '1px solid var(--border)' , color : 'var(--text2)' , cursor : 'pointer' , fontSize : 13 } } > Cancel < / button >
{ /* Buttons */ }
< div style = { { display : 'flex' , gap : 10 } } >
< button onClick = { onClose } style = { { flex : 1 , padding : '10px' , borderRadius : 8 , background : 'var(--bg2)' , border : '1px solid var(--border)' , color : 'var(--text2)' , cursor : 'pointer' , fontSize : 14 } } > Cancel < / button >
< button onClick = { ( ) = > onRun ( cfg ) } disabled = { isSubmitting }
style = { { flex : 2 , padding : ' 8px', borderRadius : 7 , background : isSubmitting ? 'var(--bg3)' : 'var(--cyan)' , color : 'white' , border : 'none' , cursor : isSubmitting ? 'not-allowed' : 'pointer' , fontSize : 1 3 , fontWeight : 600 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , gap : 6 } } >
< Play size = { 1 3 } / > { isSubmitting ? 'Submitting…' : 'Run' }
style = { { flex : 2 , padding : ' 10px', borderRadius : 8 , background : isSubmitting ? 'var(--bg3)' : 'var(--cyan)' , color : 'white' , border : 'none' , cursor : isSubmitting ? 'not-allowed' : 'pointer' , fontSize : 1 4 , fontWeight : 600 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , gap : 7 } } >
< Play size = { 1 4 } / > { isSubmitting ? 'Submitting…' : 'Run' }
< / button >
< / div >
< / div >
@ -1065,6 +1090,7 @@ export function IntradayBacktestPage() {
const strategies = strategiesData ? . strategies ? ? [ ] ;
const [ runningFor , setRunningFor ] = useState < IntradayStrategy | null > ( null ) ;
const [ duplicateTask , setDuplicateTask ] = useState < IntradayTask | null > ( null ) ;
const [ modalMode , setModalMode ] = useState < 'create' | 'edit' | null > ( null ) ;
const [ modalInitial , setModalInitial ] = useState < Omit < IntradayStrategy , ' slug ' > > ( DEFAULT_FORM ) ;
const [ editingSlug , setEditingSlug ] = useState < string | null > ( null ) ;
@ -1075,6 +1101,7 @@ export function IntradayBacktestPage() {
onSuccess : ( data ) = > {
qc . invalidateQueries ( { queryKey : [ 'intraday-tasks' ] } ) ;
setRunningFor ( null ) ;
setDuplicateTask ( null ) ;
navigate ( ` /intraday/tasks/ ${ data . task_id } ` ) ;
} ,
} ) ;
@ -1120,6 +1147,32 @@ export function IntradayBacktestPage() {
submitMutation . mutate ( req ) ;
}
function getTaskInitialConfig ( task : IntradayTask ) : Partial < RunConfig > {
const base : Partial < RunConfig > = {
days : task.days ,
universe : task.universe ,
compound_returns : task.compound_returns ? ? false ,
} ;
if ( task . year ) return { . . . base , dateMode : 'year' , year : task.year } ;
if ( task . start_date ) return { . . . base , dateMode : 'range' , startDate : task.start_date , endDate : task.end_date ? ? '' } ;
return { . . . base , dateMode : 'days' } ;
}
function handleDuplicateSubmit ( cfg : RunConfig ) {
if ( ! duplicateTask ) return ;
const req : Parameters < typeof intradayApi.submit > [ 0 ] = {
config : duplicateTask.config , days : cfg.days , universe : cfg.universe ,
compound_returns : cfg.compound_returns ,
initial_capital : cfg.initialCapital ,
} ;
if ( cfg . dateMode === 'year' ) req . year = cfg . year ;
else if ( cfg . dateMode === 'range' ) {
req . start_date = cfg . startDate || null ;
req . end_date = cfg . endDate || null ;
}
submitMutation . mutate ( req ) ;
}
function handleEdit ( s : IntradayStrategy ) {
setEditingSlug ( s . slug ) ;
const { slug : _s , . . . rest } = s ;
@ -1137,7 +1190,7 @@ export function IntradayBacktestPage() {
}
return (
< div style = { { padding : '28px 32px' , maxWidth : 1200 , margin : '0 auto' } } onClick = { ( ) = > setRunningFor ( null ) } >
< div style = { { padding : '28px 32px' , maxWidth : 1200 , margin : '0 auto' } } >
< style > { ` @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ` } < / style >
{ /* Header */ }
@ -1180,6 +1233,16 @@ export function IntradayBacktestPage() {
{ runningFor && (
< RunConfigPopup strategy = { runningFor } onRun = { handleRunSubmit } onClose = { ( ) = > setRunningFor ( null ) } isSubmitting = { submitMutation . isPending } / >
) }
{ /* Duplicate run config modal */ }
{ duplicateTask && strategies . find ( s = > s . slug === duplicateTask . config ) && (
< RunConfigPopup
strategy = { strategies . find ( s = > s . slug === duplicateTask . config ) ! }
initialConfig = { getTaskInitialConfig ( duplicateTask ) }
onRun = { handleDuplicateSubmit }
onClose = { ( ) = > setDuplicateTask ( null ) }
isSubmitting = { submitMutation . isPending }
/ >
) }
< / div >
{ /* Run History */ }
@ -1240,13 +1303,22 @@ export function IntradayBacktestPage() {
: new Date ( task . created_at ) . toLocaleString ( 'en-US' , { month : 'short' , day : 'numeric' , hour : '2-digit' , minute : '2-digit' } ) }
< / td >
< td style = { { padding : '10px 14px' } } onClick = { e = > e . stopPropagation ( ) } >
< button onClick = { ( ) = > deleteTaskMutation . mutate ( task . task_id ) }
title = "Delete"
style = { { background : 'none' , border : 'none' , cursor : 'pointer' , color : 'var(--text3)' , padding : '2px 4px' , borderRadius : 4 , display : 'flex' , alignItems : 'center' } }
onMouseEnter = { e = > ( e . currentTarget . style . color = 'var(--red)' ) }
onMouseLeave = { e = > ( e . currentTarget . style . color = 'var(--text3)' ) } >
< Trash2 size = { 13 } / >
< / button >
< div style = { { display : 'flex' , gap : 2 } } >
< button onClick = { ( ) = > setDuplicateTask ( task ) }
title = "Duplicate Run"
style = { { background : 'none' , border : 'none' , cursor : 'pointer' , color : 'var(--text3)' , padding : '2px 4px' , borderRadius : 4 , display : 'flex' , alignItems : 'center' } }
onMouseEnter = { e = > ( e . currentTarget . style . color = 'var(--cyan)' ) }
onMouseLeave = { e = > ( e . currentTarget . style . color = 'var(--text3)' ) } >
< Copy size = { 13 } / >
< / button >
< button onClick = { ( ) = > deleteTaskMutation . mutate ( task . task_id ) }
title = "Delete"
style = { { background : 'none' , border : 'none' , cursor : 'pointer' , color : 'var(--text3)' , padding : '2px 4px' , borderRadius : 4 , display : 'flex' , alignItems : 'center' } }
onMouseEnter = { e = > ( e . currentTarget . style . color = 'var(--red)' ) }
onMouseLeave = { e = > ( e . currentTarget . style . color = 'var(--text3)' ) } >
< Trash2 size = { 13 } / >
< / button >
< / div >
< / td >
< / tr >
) ;