Sunday, March 31, 2024

How You Doin?

WCMI "How You Doin'?"

To borrow a phrase from one of Central Perk's charming patrons, Joey Tribbiani: "How you doin'?" Or in our present context, how is your sports trading performing?

Sports traders use various tools and metrics to measure their strategies and manage risks. Here, we will derive some non-controversial measures:

  • Stake Average,
  • Stake Volatility,
  • Profit Average,
  • Profit Volatility, and
  • Profit Skewness.

And some approximate, non-standard ones:

  • Win Odds (Average),
  • Win Probability (Average), and
  • Edge (Average).

The following trading results were randomly generated and then curated to show the best and worst results. They are not suggestions or recommendations for staking or risk profiles.

How-You-Doin.csv

Market MarketStakes MarketPnLs
Alpha 130.00 530.00
Bravo 130.00 450.00
Charlie 100.00 330.00
Delta 105.00 325.00
Echo 145.00 425.00
Foxtrot 135.00 390.00
Golf 160.00 320.00
Hotel 185.00 310.00
India 445.00 510.00
Juliet 295.00 305.00
...
Quebec 500.00 -500.00
Romeo 335.00 -335.00
Sierra 255.00 -255.00
Tango 245.00 -245.00
Uniform 255.00 -255.00
Victor 280.00 -280.00
Whiskey 540.00 -540.00
X-Ray 360.00 -360.00
Yankee 300.00 -300.00
Zulu 430.00 -430.00

How-You-Doin.py

# -*- coding: latin-1 -*-

import numpy as np
import pandas as pd

from datetime import date
from functools import reduce
from fuzzy import Soundex
from scipy.stats import skew
from tabulate import tabulate

__date__ = str(date.today())
__query__ = ''

__title__ = 'How You Doin? (2024)'
__author__ = 'matekus'
__version__ = 'v1.02'
__copyright__ = '(c) 2024, ' + __author__ + ', All Rights Reserved.'
__address__ = '[ https://vendire-ludorum.blogspot.com/ ]'

def read_results(state) -> dict:
    # Read data from CSV file into data frame.
    new_state = state.copy()
    df = pd.read_csv(
        new_state["input_results_path"],
        sep=new_state["input_results_separator"],
        names=new_state["input_results_columns"],
        encoding=new_state["input_results_encoding"],
    )
    df = df.iloc[1:]
    new_state['Trading_Results'] = df
    return new_state

def calc_market_odds(row):
    if float(row['MarketStakes']) > 0:
        return (float(row['MarketPnLs']) / float(row['MarketStakes'])) + 1.00
    else:
        return 0

def calc_implied_odds(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    data['WinOdds'] = data.apply(calc_market_odds, axis=1)
    new_state['Trading_Results'] = data
    return new_state

def calc_win_odds_average(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    winning_bets = data[data['MarketPnLs'].astype(float) > 0]
    average_odds_winning = winning_bets['WinOdds'].mean() if not winning_bets.empty else 0
    new_state['AverageOddsWinning'] = average_odds_winning
    return new_state

def calc_performance_by_odds_range(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    # Define odds ranges
    low_odds_range = (1.01, 2.00)  # Example range for 'low' odds
    high_odds_range = (2.01, 1000)  # Example range for 'high' odds
    # Calculate PnL for each range
    low_odds_range_pnl = data[(data['WinOdds'] > low_odds_range[0]) & (data['WinOdds'] <= low_odds_range[1])]['MarketPnLs'].astype(float).sum()
    high_odds_range_pnl = data[(data['WinOdds'] > high_odds_range[0]) & (data['WinOdds'] <= high_odds_range[1])]['MarketPnLs'].astype(float).sum()
    new_state['LowOdds_PnL'] = low_odds_range_pnl
    new_state['HighOdds_PnL'] = high_odds_range_pnl
    return new_state

def calc_win_probs_average(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    positive_pnls = data[data['MarketPnLs'].astype(float) > 0]
    win_probs_average = len(positive_pnls) / (len(positive_pnls) + len(data) - len(positive_pnls))
    new_state['WinProbsAverage'] = win_probs_average
    return new_state

def calc_edge_average(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    odds_win = new_state['AverageOddsWinning']
    prob_win = new_state['WinProbsAverage']
    edge_win = (odds_win * prob_win) - 1.00
    new_state['WinEdgeAverage'] = edge_win
    return new_state

def calc_stakes_average(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    mean_pnls = data['MarketStakes'].astype(float).mean()
    new_state['StakesAverage'] = mean_pnls
    return new_state

def calc_stakes_volatility(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    sd_pnls = data['MarketStakes'].astype(float).std()
    new_state['StakesSD'] = sd_pnls
    return new_state

def calc_pnl_average(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    mean_pnls = data['MarketPnLs'].astype(float).mean()
    new_state['PnLsAverage'] = mean_pnls
    return new_state

def calc_pnl_volatility(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    sd_pnls = data['MarketPnLs'].astype(float).std()
    new_state['PnLsSD'] = sd_pnls
    return new_state

def calc_pnl_skewness(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    skew_pnls = skew(data['MarketPnLs'].astype(float))
    new_state['PnLsSkew'] = skew_pnls
    return new_state

def calc_market_roi(state) -> dict:
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    total_stakes = data['MarketStakes'].astype(float).sum()
    total_pnls = data['MarketPnLs'].astype(float).sum()
    market_roi = (total_pnls / total_stakes) if total_stakes > 0 else 0
    new_state['MarketRoI'] = market_roi
    return new_state

def print_data(state) -> dict:
    # Print data to console
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    print(tabulate(data.head(11), 
        headers=new_state['output_results_columns'], 
        tablefmt='fancy_grid',
        floatfmt='.2f'))
    print('')
    # Prepare the data for tabulate with additional metrics
    table_data = [
        [
            'Average (Stake)',
            'SD (Stake)',
            'Average (PnL)',
            'SD (Pnl)',
            'Skew (PnL)',
            'Market RoI',
            'Avg Odds (Win)',
            'Avg Probs (Win)',
            'Avg Edge (Win)',
        ],
        [
            new_state['StakesAverage'],
            new_state['StakesSD'],
            new_state['PnLsAverage'],
            new_state['PnLsSD'],
            new_state['PnLsSkew'],
            new_state['MarketRoI'],
            new_state['AverageOddsWinning'],
            new_state['WinProbsAverage'],
            new_state['WinEdgeAverage'],
        ]
    ]
    tabulate_format = ['.2f', '.2f', '.2f', '.2f', '.2f', '.4f', '.2f', '.2f', '.5f']
    print(tabulate(table_data, headers='firstrow', tablefmt='fancy_grid', floatfmt=tabulate_format))    
    print('')
    return new_state

def write_data(state) -> dict:
    # Save data frame to CSV.
    new_state = state.copy()
    data = new_state['Trading_Results'].copy(deep=True)
    data.to_csv(new_state['output_results_path'], 
        sep=new_state['output_results_separator'], 
        index=False)
    return new_state

def main() -> None:
    print('')
    print(__title__)
    print(__copyright__)
    print(__address__)
    print('')
    print('[', __date__, ']')
    print('')
    # Define pipeline with updated steps
    pipeline = [
        read_results,
        calc_market_roi,
        calc_implied_odds,
        calc_performance_by_odds_range,  
        calc_win_odds_average,
        calc_win_probs_average,
        calc_edge_average,
        calc_stakes_average,
        calc_stakes_volatility,
        calc_pnl_average,
        calc_pnl_volatility,
        calc_pnl_skewness,
        write_data,
        print_data,
    ]
    # Define initial state.
    initial_state = {
        'input_results_path' : r'C:\data\How-You-Doin.csv',
        'input_results_separator' : ';',
        'input_results_encoding' : 'latin-1',
        'input_results_columns' : ['Market', 'MarketStakes', 'MarketPnLs'],
        'output_results_path' : r'C:\data\How-You-Doin_Updated.csv',
        'output_results_separator' : ';',
        'output_results_columns' : ['Market', 'MarketStakes', 'MarketPnLs', 'MarketOdds', 'MarketIPs'],
    }
    # Run pipeline.
    final_state = reduce(lambda v, f: f(v), pipeline, initial_state)
    print('')
    print(f"Output saved to {final_state['output_results_path']}.")
    print('')
    print('Fini!')
    print('')

if __name__ == '__main__':
    main()

# EOF.

How You Doin? (2024)
(c) 2024, matekus, All Rights Reserved.
[ https://vendire-ludorum.blogspot.com/ ]

[ 2024-03-31 ]

╒══════════╤════════════════╤══════════════╤══════════════╤═════════════╕
│   Market │ MarketStakes   │   MarketPnLs │   MarketOdds │   MarketIPs │
╞══════════╪════════════════╪══════════════╪══════════════╪═════════════╡
│        1 │ Alpha          │       130.00 │       530.00 │        5.08 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│        2 │ Bravo          │       130.00 │       450.00 │        4.46 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│        3 │ Charlie        │       100.00 │       330.00 │        4.30 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│        4 │ Delta          │       105.00 │       325.00 │        4.10 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│        5 │ Echo           │       145.00 │       425.00 │        3.93 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│        6 │ Foxtrot        │       135.00 │       390.00 │        3.89 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│        7 │ Golf           │       160.00 │       320.00 │        3.00 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│        8 │ Hotel          │       185.00 │       310.00 │        2.68 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│        9 │ India          │       445.00 │       510.00 │        2.15 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│       10 │ Juliet         │       295.00 │       305.00 │        2.03 │
├──────────┼────────────────┼──────────────┼──────────────┼─────────────┤
│       11 │ Quebec         │       500.00 │      -500.00 │        0.00 │
╘══════════╧════════════════╧══════════════╧══════════════╧═════════════╛

╒═══════════════════╤══════════════╤═════════════════╤════════════╤══════════════╤══════════════╤══════════════════╤═══════════════════╤══════════════════╕
│   Average (Stake) │   SD (Stake) │   Average (PnL) │   SD (Pnl) │   Skew (PnL) │   Market RoI │   Avg Odds (Win) │   Avg Probs (Win) │   Avg Edge (Win) │
╞═══════════════════╪══════════════╪═════════════════╪════════════╪══════════════╪══════════════╪══════════════════╪═══════════════════╪══════════════════╡
│            266.50 │       134.81 │           19.75 │     390.77 │        -0.04 │       0.0741 │             3.56 │              0.50 │          0.78046 │
╘═══════════════════╧══════════════╧═════════════════╧════════════╧══════════════╧══════════════╧══════════════════╧═══════════════════╧══════════════════╛

Output saved to C:\data\How-You-Doin_Updated.csv.

Fini!
Note: There are a couple of additional functions (e.g. calc_performance_by_odds_range), with which you can experiment to provide additional insights.

Trading Performance Metrics

Understanding the metrics of trading performance can be the difference between a thriving portfolio and a stagnant one. Here's a brief overview of each metric: Stake Average This represents the typical amount invested in a trade. It helps us gauge our comfort level with the stakes we're placing. Stake Volatility Volatility measures the degree of variation in our stakes, indicating the consistency of our investment amounts. Profit Average The average profit metric provides insight into the typical return we're seeing from our trades. Profit Volatility This tells us how much our profits can swing, helping us understand the potential highs and lows in our returns. Profit Skewness Skewness gives us a sense of the asymmetry in our profit distribution. It can reveal whether we're experiencing infrequent large wins or frequent small losses. Win Odds and Win Probability These metrics offer a perspective on our chances of winning based on past performance. They are approximations but can guide future betting strategies. Edge The edge is perhaps the most critical metric, as it represents our advantage over the market. It's the expected value of our betting strategy and can indicate long-term profitability.

Edge Volatility And Skewness

In sports betting, the concepts of "edge," "volatility," and "skewness" refer to different aspects of the betting process and outcomes. Here's how each term is typically understood in the context of sports betting: Edge The "edge" in sports betting is a measure of the bettor's advantage over the bookmaker or the market. It represents the expected value of a bet and is usually expressed as a percentage. If a bettor has an edge, it means that over the long run, they can expect to make a profit. Calculating the edge involves comparing the bettor's assessment of the true probability of an outcome with the implied probability of the odds being offered. For example, if a bettor believes Team A has a 60% chance of winning, but the bookmaker's odds imply a 50% chance, the bettor would have an edge. This is because they are getting better odds than their own assessment suggests they should. Volatility Volatility in sports betting refers to the variation in results from what is expected over a period. It's a measure of risk and indicates how much a bettor's bankroll could fluctuate in the short term due to the unpredictable nature of sports events. High volatility means that outcomes can greatly vary, leading to potentially big wins or losses; low volatility implies more consistent outcomes closer to the expected value. For example, betting on underdogs is generally considered more volatile because they win less frequently, but when they do win, the payouts are large. Betting on favorites is less volatile because they win more often, but the payouts are smaller. Skewness Skewness in the context of sports betting refers to the asymmetry of the distribution of betting returns. It indicates whether the results of a series of bets are likely to be skewed towards wins or losses, rather than being symmetrically distributed around the mean. Positive Skewness: This means that there's a higher probability of obtaining a return significantly above the average, but with a lower frequency of occurrence (e.g., betting on long shots with high odds). Negative Skewness: This suggests a higher probability of small gains and a smaller chance of large losses (e.g., betting on heavy favorites). Example: If a bettor frequently bets on outcomes with high odds (and thus high payouts), the distribution of their returns will show positive skewness. Most of their bets will lose (resulting in small losses each time), but the occasional big win will pull the average return up. Understanding these concepts can help bettors manage their bankrolls, choose their betting strategies, and ultimately aim for long-term profitability. However, it's important to note that having an edge does not guarantee profit in the short term due to volatility and the potential for skewed outcomes. Successful betting requires a combination of finding value (edge), managing risk (volatility), and understanding the distribution of your betting returns (skewness).
Though the code and the sample data are mine, I relied on GPT-4 for the definitions and examples in the section on Edge Volatility And Skewness. As ever, the script has little or no "errr0r" handling and is only a starting point for your own explorations. Enjoy!