@@ -14,6 +14,8 @@ interface BotState {
1414 equity : number ;
1515 peakEquity : number ;
1616 dailyPnl : number ;
17+ dailyStartEquity : number ;
18+ dailyStartDate : string ; // YYYY-MM-DD for daily reset
1719 fills : number ;
1820 totalPnl : number ;
1921 rebalanceCount : number ;
@@ -44,6 +46,8 @@ export async function runBot(
4446 equity : 0 ,
4547 peakEquity : 0 ,
4648 dailyPnl : 0 ,
49+ dailyStartEquity : 0 ,
50+ dailyStartDate : new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ,
4751 fills : 0 ,
4852 totalPnl : 0 ,
4953 rebalanceCount : 0 ,
@@ -80,6 +84,7 @@ export async function runBot(
8084 const bal = await adapter . getBalance ( ) ;
8185 state . equity = parseFloat ( bal . equity ) ;
8286 state . peakEquity = state . equity ;
87+ state . dailyStartEquity = state . equity ;
8388 log ( ` Starting equity: $${ state . equity . toFixed ( 2 ) } ` ) ;
8489 } catch {
8590 log ( chalk . yellow ( ` Could not fetch initial balance` ) ) ;
@@ -100,7 +105,13 @@ export async function runBot(
100105 const bal = await adapter . getBalance ( ) ;
101106 state . equity = parseFloat ( bal . equity ) ;
102107 if ( state . equity > state . peakEquity ) state . peakEquity = state . equity ;
103- state . dailyPnl = state . equity - state . peakEquity ; // simplified
108+ // Reset daily baseline at day boundary
109+ const today = new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ;
110+ if ( today !== state . dailyStartDate ) {
111+ state . dailyStartEquity = state . equity ;
112+ state . dailyStartDate = today ;
113+ }
114+ state . dailyPnl = state . equity - state . dailyStartEquity ;
104115 } catch { /* non-critical */ }
105116
106117 const context = {
@@ -144,12 +155,15 @@ export async function runBot(
144155
145156 // Check risk limits
146157 const drawdown = state . peakEquity - state . equity ;
147- const riskBreached = drawdown > config . risk . max_drawdown ;
158+ const dailyLossBreached = state . dailyPnl < - config . risk . max_daily_loss ;
159+ const riskBreached = drawdown > config . risk . max_drawdown || dailyLossBreached ;
148160
149161 if ( shouldExit || riskBreached ) {
150- const reason = riskBreached
151- ? `drawdown $${ drawdown . toFixed ( 2 ) } > limit $${ config . risk . max_drawdown } `
152- : "exit condition met" ;
162+ const reason = dailyLossBreached
163+ ? `daily loss $${ Math . abs ( state . dailyPnl ) . toFixed ( 2 ) } > limit $${ config . risk . max_daily_loss } `
164+ : drawdown > config . risk . max_drawdown
165+ ? `drawdown $${ drawdown . toFixed ( 2 ) } > limit $${ config . risk . max_drawdown } `
166+ : "exit condition met" ;
153167 log ( chalk . yellow ( ` ⚠ Exiting: ${ reason } ` ) ) ;
154168 state . phase = "exiting" ;
155169 } else {
@@ -324,6 +338,7 @@ async function placeGridOrders(
324338 currentPrice : number ,
325339 log : BotLog ,
326340) {
341+ if ( params . grids < 2 ) throw new Error ( "Grid requires at least 2 grid lines" ) ;
327342 const step = ( state . gridUpper - state . gridLower ) / ( params . grids - 1 ) ;
328343 const sizePerGrid = params . size / params . grids ;
329344 let placed = 0 ;
@@ -364,6 +379,7 @@ async function manageGrid(
364379 snapshot : MarketSnapshot ,
365380 log : BotLog ,
366381) {
382+ if ( params . grids < 2 ) throw new Error ( "Grid requires at least 2 grid lines" ) ;
367383 const step = ( state . gridUpper - state . gridLower ) / ( params . grids - 1 ) ;
368384 const sizePerGrid = params . size / params . grids ;
369385
@@ -374,9 +390,22 @@ async function manageGrid(
374390 openOrders . filter ( o => o . symbol . toUpperCase ( ) === symbol . toUpperCase ( ) ) . map ( o => o . orderId )
375391 ) ;
376392
393+ // Build filled order IDs from order history for accurate fill detection
394+ let filledIds : Set < string > | null = null ;
395+ try {
396+ const history = await adapter . getOrderHistory ( 100 ) ;
397+ filledIds = new Set ( history . filter ( o => o . status === "filled" ) . map ( o => o . orderId ) ) ;
398+ } catch { /* non-critical — fall back to assuming fills */ }
399+
377400 let newFills = 0 ;
378401 for ( const [ idx , orderId ] of state . gridOrders . entries ( ) ) {
379402 if ( ! openIds . has ( orderId ) ) {
403+ // Verify the order was actually filled (not just cancelled)
404+ if ( filledIds && ! filledIds . has ( orderId ) ) {
405+ log ( chalk . yellow ( ` [GRID] Order ${ orderId } missing — likely cancelled, skipping` ) ) ;
406+ state . gridOrders . delete ( idx ) ;
407+ continue ;
408+ }
380409 // Order filled — place opposite order
381410 newFills ++ ;
382411 state . fills ++ ;
0 commit comments