I have a model that predicts the outcome of ATP tennis matches. The quality of predictions varies, and I want to develop a second binary classification model that optimises the decision to bet (or not) based on a number of features about the match. Amongst the second model's features are the probabilities from the first, and archived bookmaker's odds for each match. The size of the bet is determined by the Kelly Criterion. The training data has been classified such that all bets that would have won = 1, all losing bets = 0. I'm using xgboost.I'm attempting to incorporate the Kelly Criterion into my xgb loss function but without success.I've had a look at the custom objective example in the xgb demo. From my understanding, for xgb to maximise the expected value of the logarithmic bankroll, my objective function needs to return the first:$$\frac{\partial}{\partial x}(p\: log(1 + bx)+(1-p)\:log(1-x))=\frac{-(b+1)\:p+b\:x+1}{(x-1)(b\:x+1)}$$and second order derivatives:$$\frac{\partial ^2}{\partial x^2}(p\: log(1 + bx)+(1-p)\:log(1-x))=-\frac{b^{2}p}{(bx+1)^{2}}-\frac{1-p}{(1-x)^{2}}$$where:b is the net odds received on the wager (b to 1); that is, you could win \$b (on top of getting back your \$1 wagered) for a $1 betp is the probability of winning;I've also implemented my own cost function that calculates the profit and loss for each bet.The code I have so far is below.import pandas as pdimport xgboost as xgbimport numpy as npimport StringIO #  ('import io' in python 3.x)import requestsurl_train = 'https://gist.githubusercontent.com/martinstaniforth/162b9691132f7099b4da08fd14defc39/raw/9372c5cac42b545ecde4200503b97f895e24cbfe/train.csv'url_test = 'https://gist.githubusercontent.com/martinstaniforth/4445b884abea22d4ae238cda869b5e0e/raw/84d3dead9122a644f8d273e04b898920a8e5a811/test.csv'train_content = requests.get(url_train).contenttest_content = requests.get(url_test).contenttrain_df = pd.read_csv(StringIO.StringIO(train_content.decode('utf-8')), index_col='match_id')test_df = pd.read_csv(StringIO.StringIO(test_content.decode('utf-8')), index_col='match_id')train_target_df = train_df.reset_index(drop=True)[['bet_wins']]train_df = train_df.reset_index(drop=True).drop(['bet_wins'], axis=1)test_target_df = test_df.reset_index(drop=True)[['bet_wins']]test_df = test_df.reset_index(drop=True).drop(['bet_wins'], axis=1)odds_train = train_df['player_odds'].values - 1probs_train = train_df['win_prob'].valuesodds_test = test_df['player_odds'].values - 1probs_test = test_df['win_prob'].valuesdtrain = xgb.DMatrix(train_df.values, train_target_df.values)dtest = xgb.DMatrix(test_df.values, test_target_df.values)param = {    'max_depth': 3,    'eta': 0.05,    'silent': 1,    'n_estimators': 50,    'seed': 366}watchlist = [(dtest, 'eval'), (dtrain, 'train')]num_round = 200def kelly_loss(odds_train, probs_train, odds_test, probs_test):    def logregobj(x, dmatrix):        bet_outcome = dmatrix.get_label()        odds = odds_train if len(bet_outcome) == len(odds_train) else odds_test        probs = probs_train if len(bet_outcome) == len(probs_train) else probs_test        y = -((odds + 1) * probs + odds * x + 1) / ((x - 1) * (odds * x + 1))        grad = y - bet_outcome        hess = -(np.power(odds, 2) * probs) / np.power(odds * x + 1, 2) - \                (1 - probs) / np.power(1 - x, 2)        return grad, hess    return logregobjdef kelly_error(odds_train, probs_train, odds_test, probs_test):    def evalerror(preds, dmatrix):        bet_outcome = dmatrix.get_label()        odds = odds_train if len(bet_outcome) == len(odds_train) else odds_test        probs = probs_train if len(bet_outcome) == len(probs_train) else probs_test        kelly_fraction = (probs * (odds + 1) - 1) / odds        def value_bets(f):  # ignore any bets with a negative kelly fraction            return 0 if f < 0 else f        kelly_fraction = np.array([value_bets(x) for x in kelly_fraction])        profit = preds * kelly_fraction * odds        loss = (1 - preds) * kelly_fraction        total_profit = float(sum(profit - loss))        return 'error', total_profit    return evalerrorbst = xgb.train(    param,    dtrain,    num_round,    watchlist,    kelly_loss(odds_train, probs_train, odds_test, probs_test),    kelly_error(odds_train, probs_train, odds_test, probs_test))When I execute the code, xgb does not update predictions.  I suspect the logregobj function is incorrect as I haven't fully understood its purpose. Can someone please assist in correctly implementing the Kelly Criterion in a binary classification model?  Training data is available in this gist, as referenced in the code.Thanks in advance.
Kelly Criterion in xgboost loss function
python;optimization;xgboost
I have solved this to a certain extent.  xgb is able to optimise the problem without altering the objective function.  I still don't understand when you should pass in your own function as a parameter but it looks like it isn't necessary.  There were some bugs in my kelly_error function which I have fixed below, including changing the code to optimise for ROI.  You will also need to tell xgb to maximise the output (default is to minimise)def kelly_error(odds_train, probs_train, odds_test, probs_test):    def evalerror(preds, dmatrix):        bet_outcome = dmatrix.get_label()        odds = odds_train if len(bet_outcome) == len(odds_train) else odds_test        probs = probs_train if len(bet_outcome) == len(probs_train) else probs_test        kelly_fraction = (probs * (odds + 1) - 1) / odds        def value_bets(f):  # ignore any bets with a negative kelly fraction            return 0 if f < 0 else f        kelly_fraction = np.array([value_bets(x) for x in kelly_fraction])        profit = preds * bet_outcome * kelly_fraction * odds        loss = preds * (1 - bet_outcome) * kelly_fraction        stake = float(sum(preds * kelly_fraction))        total_profit = float(sum(profit - loss))        roi = 100 * total_profit / stake        return 'roi', roi    return evalerror