XGBRegressor: Custom objective function parameters

I’m trying to write an XGBoost regressor for a specific use-case where overestimating the predicted value is more desirable than underpredicting it. Therefore, I wrote an underestimate_penalty_factor variable to use in my custom objective penalizing underestimates more than overestimates. Ultimately, I wanted my objective to be a “weighted” mean square error of the form

Error(pred, label) = d(pred, label) * (pred - label)**2,

where d(pred, label) = 1 whenever pred >= label and d(pred, label) = underestimate_penalty_factor otherwise. In other words, if underestimate_penalty_factor is set to 1, this is just the square error, if it is set higher, underpredictions are penalized more than overpredictions. My model is initialized as so:

model = XGBRegressor(**other_params, objective=custom_loss)

and as far as I understand it, custom_loss is expected to receive the predicted values and labels (in this order) and output the gradient and the “hessian” of the objective function to be minimized. So I wrote the function in the following way.

def custom_loss(
        predt: np.ndarray,
        dtrain: xgboost.DMatrix) -> tuple[np.ndarray, np.ndarray]:
    d = predt - dtrain
    d[d>0] = 1
    d[d<=0] = underestimate_penalty_factor
    return d * (predt - dtrain), d

This however produces the exact opposite results than expected, i.e. underestimates seem to be preferred when underestimate_penalty_factor is set to a high number. The prediction seems to do exactly what I want when I write it in the opposite way, that is

def custom_loss(
        predt: np.ndarray,
        dtrain: xgboost.DMatrix) -> tuple[np.ndarray, np.ndarray]:
    d = predt - dtrain
    d[d>0] = underestimate_penalty_factor
    d[d<=0] = 1
    return d * (dtrain - predt), d

I guess I must be interpreting the custom objective function and its parameters in the wrong way. Could someone please clarify how it is?