Live now!

Progress of current post

Create a Text-based Match prediction bar chart with Sportmonks’ Football API and Lua.

Have you ever wondered how JSON data from a football API can be harnessed using Lua?

In this blog, we will explore predictions on Sportmonks’ Football API using Lua, leaving you with a complete piece of code for a text-based match prediction bar chart on any preferred fixture.

Time to read 14 min
Published 9 January 2025
Last updated 14 March 2025
Wesley Van Rooij
Create a Text-based Match prediction bar chart with Sportmonks’ Football API and Lua.
Contents

Adoption – Who Uses Lua?

While Lua is not popular in the mainstream programming world, it can be found in many applications. Adobe Photoshop Lightroom is one of them.

Lua is also applied extensively in video games, with Angry Birds, being one. Popular flight simulator X-Plane uses Lua for its aircraft systems and plugins.

The mobile payment app Venmo, for example, was built using Lua. The programming language was also used in the development of CISCO Systems alongside other networking applications.

Chapter 1: Setting up your environment

In order to write and compile Lua, we need to set up an environment to do so.

While there are a host of development tools available for use, we will choose the option of installing an IDE to get us up and running in no time.

1.1 Installing an IDE for Lua.

The platform-independent ZeroBrane Studio is an ideal choice for today’s exercise. The lightweight IDE is available for free and easy to install.

Head over to the official website to download a suitable version for your workstation.

After installation, we shall need an API token to authenticate all our requests to the endpoint of our Football API.

1.2 Obtaining an API Token

To use the API, sign up for an account. Your account automatically comes with the Danish Super Liga and Scottish Premier League, these are free plans with no costs.

Head over to the create an API Token section.

Creat API Token_Sportmonks

Once you have your API Token, which is required for authentication and accessing the data endpoints, you must keep it private.

Chapter 2 Making Your First API Call

For today’s exercise, we will use the Match Day 1 clash between Juventus and recently promoted Como which ended in a 3-0 win for the Old Lady. That game left Juventus as the club to have won the most games on Match Day 1 in Serie A [60] history.

Head over to the ID Finder where you will obtain the ID value of 19155072 which we shall need for our url of interest.

Juventus-vs-Como-ID-Finder

2.1 Getting Fixture Details.

We will retrieve the details for this fixture using Lua and the complete URI below which you can test out on your browser.

https://api.sportmonks.com/v3/football/fixtures/{ID}?api_token=YOUR_TOKEN

local http = require("socket.http") 

local ltn12 = require("ltn12") -- Needed for response storage 

function fetchFixtureData(fixtureId) 

    local baseUrl = "https://api.sportmonks.com/v3/football/fixtures" 

    local apiToken = "API Token" 

    local apiUrl = string.format("%s/%s?api_token=%s", baseUrl, fixtureId, apiToken) 

    -- Prepare a response table 

    local response = {} 

    local _, statusCode = http.request{ 

        url = apiUrl, 

        sink = ltn12.sink.table(response) -- Capture the response body 

    } 

    -- Check the status code and print the result 

    if statusCode == 200 then 

        print("Response: " .. table.concat(response)) 

    else 

        print("Failed with status: " .. tostring(statusCode)) 

    end 

end 
-- Call the function with a specific fixture ID 

fetchFixtureData("19155150") 
Response:     {"data":{"id":19155150,"sport_id":1,"league_id":384,"season_id":23746,"stage_id":77471748,"group_id":null,"aggregate_id":null,"round_id":341521,"state_id":5,"venue_id":1721,"name":"Inter vs Juventus","starting_at":"2024-10-27 17:00:00","result_info":"Game ended in draw.","leg":"1\/1","details":null,"length":90,"placeholder":false,"has_odds":true,"has_premium_odds":true,"starting_at_timestamp":1730048400},"subscription":[{"meta":{"trial_ends_at":"2024-10-16 14:50:45","ends_at":null,"current_timestamp":1736507811},"plans":[{"plan":"Enterprise plan (loyal)","sport":"Football","category":"Advanced"},{"plan":"Enterprise Plan","sport":"Cricket","category":"Standard"},{"plan":"Formula One","sport":"Formula One","category":"Standard"}],"add_ons":[{"add_on":"All-in News API","sport":"Football","category":"News"},{"add_on":"pressure index add-on","sport":"Football","category":"Default"},{"add_on":"Enterprise Plan Predictions","sport":"Football","category":"Predictions"},{"add_on":"xG Advanced","sport":"Football","category":"Expected"}],"widgets":[{"widget":"Sportmonks Widgets","sport":"Football"}]}],"rate_limit":{"resets_in_seconds":3573,"remaining":2998,"requested_entity":"Fixture"},"timezone":"UTC"}

Explanation

The socket.http is a library for making HTTP requests in Lua, while the statement ltn12: Provides utilities for handling streams and storing response data in Lua tables.

We then created a function fetchFixtureData that accepts one parameter: fixtureId, representing the unique ID of a football fixture.

A statement to construct the url using the Base URL which is the main API endpoint and a token for authentication is then made to dynamically construct our url of interest.

We then check the response, and if the response is code 200 [OK] then it indicates that the request was successful.

The statement table.concat(response) combines all parts of the response table into a single string for easy printing.

If the request fails, the script prints the HTTP status code which you can cross reference with error codes from our documentation. However, there is a drawback. The response is difficult to read. We will fix this in the next section.

2.2 Pretty Print Fixture Details.

You must have heard of the term pretty print while working with an API that returns a JSON response. We shall modify the earlier piece of code to print the JSON response in a much more readable format without the use of an external library.

local http = require("socket.http")
local ltn12 = require("ltn12")

-- Pretty-print function for JSON strings
local function prettyPrintJSON(jsonString, indentSize)
    indentSize = indentSize or 2
    local indent = string.rep(" ", indentSize) -- Create indentation
    local level = 0
    local prettyJSON = ""

    for i = 1, #jsonString do
        local char = jsonString:sub(i, i)

        if char == "{" or char == "[" then
            prettyJSON = prettyJSON .. char .. "\n" .. string.rep(indent, level + 1)
            level = level + 1
        elseif char == "}" or char == "]" then
            level = level - 1
            prettyJSON = prettyJSON .. "\n" .. string.rep(indent, level) .. char
        elseif char == "," then
            prettyJSON = prettyJSON .. char .. "\n" .. string.rep(indent, level)
        else
            prettyJSON = prettyJSON .. char
        end
    end

    return prettyJSON
end

-- Function to fetch and pretty-print fixture data
function fetchFixtureData(fixtureId)
    local baseUrl = "https://api.sportmonks.com/v3/football/fixtures"
    local apiToken = "API_Token"
    local apiUrl = string.format("%s/%s?api_token=%s", baseUrl, fixtureId, apiToken)

    local response = {}
    local _, statusCode = http.request{
        url = apiUrl,
        sink = ltn12.sink.table(response)
    }

    if statusCode == 200 then
        local rawJson = table.concat(response)
        print("Pretty-Printed JSON Response:")
        print(prettyPrintJSON(rawJson))
    else
        print("Failed with status: " .. tostring(statusCode))
    end
end

-- Call the function with a specific fixture ID
fetchFixtureData("19155072")

Pretty-Printed JSON Response:
    {
  "data":{
    "id":19155072,
    "sport_id":1,
    "league_id":384,
    "season_id":23746,
    "stage_id":77471748,
    "group_id":null,
    "aggregate_id":null,
    "round_id":341513,
    "state_id":5,
    "venue_id":118500,
    "name":"Juventus vs Como",
    "starting_at":"2024-08-19 18:45:00",
    "result_info":"Juventus won after full-time.",
    "leg":"1\/1",
    "details":null,
    "length":90,
    "placeholder":false,
    "has_odds":true,
    "has_premium_odds":true,
    "starting_at_timestamp":1724093100
  },
  "subscription":[
    {
      "meta":{
        "trial_ends_at":"2024-10-16 14:50:45",
        "ends_at":null,
        "current_timestamp":1734917581
      },
      "plans":[
        {
          "plan":"Enterprise plan (loyal)",
          "sport":"Football",
          "category":"Advanced"
        },
        {
          "plan":"Enterprise Plan",
          "sport":"Cricket",
          "category":"Standard"
        },
        {
          "plan":"Formula One",
          "sport":"Formula One",
          "category":"Standard"
        }
      ],
      "add_ons":[
        {
          "add_on":"All-in News API",
          "sport":"Football",
          "category":"News"
        },
        {
          "add_on":"pressure index add-on",
          "sport":"Football",
          "category":"Default"
        },
        {
          "add_on":"Enterprise Plan Predictions",
          "sport":"Football",
          "category":"Predictions"
        },
        {
          "add_on":"xG Advanced",
          "sport":"Football",
          "category":"Expected"
        }
      ],
      "widgets":[
        {
          "widget":"Sportmonks Widgets",
          "sport":"Football"
        }
      ]
    }
  ],
  "rate_limit":{
    "resets_in_seconds":3600,
    "remaining":2999,
    "requested_entity":"Fixture"
  },
  "timezone":"UTC"
}

As you can see, we have a more readable format; we shall move on to the next section, where we will retrieve predictions from our fixture of interest, Juventus vs. Como.

2.3. Get Predictions by Fixture ID

While not all fixtures contain sufficient data for reliable predictions, you can enrich your applications with our prediction API, which employs machine learning techniques and models for detailed predictions that include scores, over/under, and both teams to score (BTTS).

We shall focus on predictions for scores in this blog. You may visit the documentation page to learn more about our prediction API.

Here is our URL of interest, which you can try out on your browser.

https://api.sportmonks.com/v3/football/predictions/probabilities/fixtures/FixtureID?api_token=API_Token

local http = require("socket.http")
local ltn12 = require("ltn12")

-- Pretty-print function for JSON strings
local function prettyPrintJSON(jsonString, indentSize)
    indentSize = indentSize or 2
    local indent = string.rep(" ", indentSize) -- Create indentation
    local level = 0
    local prettyJSON = ""

    for i = 1, #jsonString do
        local char = jsonString:sub(i, i)

        if char == "{" or char == "[" then
            prettyJSON = prettyJSON .. char .. "\n" .. string.rep(indent, level + 1)
            level = level + 1
        elseif char == "}" or char == "]" then
            level = level - 1
            prettyJSON = prettyJSON .. "\n" .. string.rep(indent, level) .. char
        elseif char == "," then
            prettyJSON = prettyJSON .. char .. "\n" .. string.rep(indent, level)
        else
            prettyJSON = prettyJSON .. char
        end
    end

    return prettyJSON
end

-- Function to fetch and pretty-print fixture data
function fetchFixtureData(fixtureId)
    local baseUrl = "https://api.sportmonks.com/v3/football/predictions/probabilities/fixtures"
    local apiToken = "API_Token"
    local apiUrl = string.format("%s/%s?api_token=%s", baseUrl, fixtureId, apiToken)

    local response = {}
    local _, statusCode = http.request{
        url = apiUrl,
        sink = ltn12.sink.table(response)
    }

    if statusCode == 200 then
        local rawJson = table.concat(response)
        print("Pretty-Printed JSON Response:")
        print(prettyPrintJSON(rawJson))
    else
        print("Failed with status: " .. tostring(statusCode))
    end
end

-- Call the function with a specific fixture ID
fetchFixtureData("19155072")

Pretty-Printed JSON Response:
    {
  "data":[
    {
      "id":14610512,
      "fixture_id":19155072,
      "predictions":{
        "yes":70.69,
        "no":18.78,
        "equal":10.54
      },
      "type_id":1686
    },
    {
      "id":14610513,
      "fixture_id":19155072,
      "predictions":{
        "yes":10.24,
        "no":89.76
      },
      "type_id":1679
    },
    {
      "id":14610514,
      "fixture_id":19155072,
      "predictions":{
        "yes":94.83,
        "no":2.07,
        "equal":3.1
      },
      "type_id":1690
    },
    {
      "id":14610516,
      "fixture_id":19155072,
      "predictions":{
        "yes":46.2,
        "no":41.38,
        "equal":12.42
      },
      "type_id":1687
    },
    {
      "id":14610517,
      "fixture_id":19155072,
      "predictions":{
        "yes":89.36,
        "no":5.17,
        "equal":5.46
      },
      "type_id":1683
    },
    {
      "id":14610518,
      "fixture_id":19155072,
      "predictions":{
        "yes":58.62,
        "no":29.31,
        "equal":12.07
      },
      "type_id":1689
    },
    {
      "id":14610519,
      "fixture_id":19155072,
      "predictions":{
        "yes":81.22,
        "no":10.64,
        "equal":8.14
      },
      "type_id":1685
    },
    {
      "id":14610520,
      "fixture_id":19155072,
      "predictions":{
        "yes":34.57,
        "no":53.8,
        "equal":11.63
      },
      "type_id":1688
    },
    {
      "id":14610521,
      "fixture_id":19155072,
      "predictions":{
        "yes":24.58,
        "no":65.43,
        "equal":9.99
      },
      "type_id":1684
    },
    {
      "id":14610522,
      "fixture_id":19155072,
      "predictions":{
        "yes":21.54,
        "no":78.46
      },
      "type_id":332
    },
    {
      "id":14610523,
      "fixture_id":19155072,
      "predictions":{
        "yes":34.57,
        "no":65.43,
        "equal":null
      },
      "type_id":1585
    },
    {
      "id":14610524,
      "fixture_id":19155072,
      "predictions":{
        "yes":39.06,
        "no":60.94
      },
      "type_id":331
    },
    {
      "id":14610525,
      "fixture_id":19155072,
      "predictions":{
        "yes":56.75,
        "no":43.25
      },
      "type_id":333
    },
    {
      "id":14610526,
      "fixture_id":19155072,
      "predictions":{
        "yes":15.52,
        "no":84.48
      },
      "type_id":330
    },
    {
      "id":14610527,
      "fixture_id":19155072,
      "predictions":{
        "yes":5.89,
        "no":94.11
      },
      "type_id":328
    },
    {
      "id":14610528,
      "fixture_id":19155072,
      "predictions":{
        "yes":73.39,
        "no":26.61
      },
      "type_id":334
    },
    {
      "id":14610529,
      "fixture_id":19155072,
      "predictions":{
        "yes":1.48,
        "no":98.52
      },
      "type_id":327
    },
    {
      "id":14610530,
      "fixture_id":19155072,
      "predictions":{
        "yes":19.73,
        "no":80.27
      },
      "type_id":236
    },
    {
      "id":14610531,
      "fixture_id":19155072,
      "predictions":{
        "scores":{
          "0-0":10.75,
          "0-1":9.08,
          "0-2":4.24,
          "0-3":1.08,
          "1-0":14.12,
          "1-1":13.11,
          "1-2":5.48,
          "1-3":1.62,
          "2-0":9.33,
          "2-1":9.07,
          "2-2":3.94,
          "2-3":1.2,
          "3-0":4,
          "3-1":3.95,
          "3-2":1.99,
          "3-3":0.51,
          "Other_1":5.06,
          "Other_2":1.47,
          "Other_X":0.01
        }
      },
      "type_id":240
    },
    {
      "id":14610532,
      "fixture_id":19155072,
      "predictions":{
        "yes":5.07,
        "no":94.93
      },
      "type_id":326
    },
    {
      "id":14610533,
      "fixture_id":19155072,
      "predictions":{
        "yes":39.37,
        "no":60.63
      },
      "type_id":235
    },
    {
      "id":14610534,
      "fixture_id":19155072,
      "predictions":{
        "yes":66.06,
        "no":33.94
      },
      "type_id":234
    },
    {
      "id":14610535,
      "fixture_id":19155072,
      "predictions":{
        "home":32.22,
        "away":22.82,
        "draw":44.97
      },
      "type_id":233
    },
    {
      "id":14610536,
      "fixture_id":19155072,
      "predictions":{
        "home_home":25.67,
        "home_away":1.86,
        "home_draw":5.43,
        "away_home":2.5,
        "away_away":14.57,
        "away_draw":5.28,
        "draw_draw":18.72,
        "draw_home":17.19,
        "draw_away":8.78
      },
      "type_id":232
    },
    {
      "id":14610537,
      "fixture_id":19155072,
      "predictions":{
        "draw_home":75.82000000000001,
        "draw_away":52.480000000000004,
        "home_away":71.7
      },
      "type_id":239
    }
  ],
  "pagination":{
    "count":25,
    "per_page":25,
    "current_page":1,
    "next_page":"https:\/\/api.sportmonks.com\/v3\/football\/predictions\/probabilities\/fixtures\/19155072?page=2",
    "has_more":true
  },
  "subscription":[
    {
      "meta":{
        "trial_ends_at":"2024-10-16 14:50:45",
        "ends_at":null,
        "current_timestamp":1734983903
      },
      "plans":[
        {
          "plan":"Enterprise plan (loyal)",
          "sport":"Football",
          "category":"Advanced"
        },
        {
          "plan":"Enterprise Plan",
          "sport":"Cricket",
          "category":"Standard"
        },
        {
          "plan":"Formula One",
          "sport":"Formula One",
          "category":"Standard"
        }
      ],
      "add_ons":[
        {
          "add_on":"All-in News API",
          "sport":"Football",
          "category":"News"
        },
        {
          "add_on":"pressure index add-on",
          "sport":"Football",
          "category":"Default"
        },
        {
          "add_on":"Enterprise Plan Predictions",
          "sport":"Football",
          "category":"Predictions"
        },
        {
          "add_on":"xG Advanced",
          "sport":"Football",
          "category":"Expected"
        }
      ],
      "widgets":[
        {
          "widget":"Sportmonks Widgets",
          "sport":"Football"
        }
      ]
    }
  ],
  "rate_limit":{
    "resets_in_seconds":2803,
    "remaining":2997,
    "requested_entity":"Prediction"
  },
  "timezone":"UTC"
}

A brief explanation of the response can be found on our documentation page.

Chapter 3. Create a Horizontal Bar Chart Using Predictions.

Now that we have our predictions pretty printed, we can now create a bar chart using the scores and probabilities while ignoring the other details from the JSON response. Without the use of any external library, we will create a text-based bar chart.

-- Function to calculate proportions and create a text-based bar chart
function drawTextBarChart(predictions)
    local total = 0
    for _, prediction in ipairs(predictions) do
        total = total + prediction.value
    end

    -- Generate bar chart segments
    print("Text-Based Bar Chart for Predictions")
    for _, prediction in ipairs(predictions) do
        local proportion = math.floor((prediction.value / total) * 50) -- Scale to 50 blocks
        print(string.format("%-10s: %s (%.2f%%)", prediction.name, string.rep(("\226\150\136"), proportion), (prediction.value / total) * 100))
    end
end

-- Function to parse JSON string manually (very basic parsing, only works for simple flat structures)
function simpleJSONParse(jsonString)
    local parsed = {}
    -- Remove curly braces and spaces around key-value pairs
    jsonString = jsonString:match("^%s*(.*)%s*$")
    jsonString = jsonString:sub(2, -2)  -- Remove curly braces
    -- Split the string into key-value pairs
    for key, value in jsonString:gmatch('"([^"]+)":%s*([^,}]+)') do
        -- Try to convert number values
        value = tonumber(value) or value
        parsed[key] = value
    end
    return parsed
end

-- Function to fetch data from API and extract scores for the bar chart
function fetchFixtureData(fixtureId)
    local baseUrl = "https://api.sportmonks.com/v3/football/predictions/probabilities/fixtures"
    local apiToken = "API_Token"  -- Replace with your actual API token
    local apiUrl = string.format("%s/%s?api_token=%s", baseUrl, fixtureId, apiToken)

    local http = require("socket.http")
    local ltn12 = require("ltn12")

    local response = {}
    local _, statusCode = http.request{
        url = apiUrl,
        sink = ltn12.sink.table(response)
    }

    if statusCode == 200 then
        local rawJson = table.concat(response)

        -- Check if "scores" is present in the JSON response
        local scoresStart, scoresEnd = rawJson:find('"scores":%{')
        if scoresStart then
            -- Extract the "scores" part of the JSON response
            local scoresJson = rawJson:sub(scoresStart + 9, rawJson:find("}", scoresStart) - 1)
            -- Parse the scores into a table
            local predictionScores = simpleJSONParse("{" .. scoresJson .. "}")
            local predictions = {}

            -- Convert the scores into a table for the bar chart
            for score, value in pairs(predictionScores) do
                table.insert(predictions, {name = score, value = value})
            end

            -- Call the function to draw the text-based bar chart
            drawTextBarChart(predictions)
        else
            print("Error: 'scores' data not found in the response.")
        end
    else
        print("Failed to fetch data with status: " .. tostring(statusCode))
    end
end

-- Call the function with a specific fixture ID
fetchFixtureData("19155072")

Text-Based Bar Chart for Predictions:
3-3       :  (0.51%)
2-3       :  (1.20%)
Other_2   :  (1.47%)
0-1       : ████ (9.08%)
0-0       : █████ (10.75%)
3-0       : █ (4.00%)
2-2       : █ (3.94%)
1-2       : ██ (5.48%)
3-2       :  (1.99%)
1-0       : ███████ (14.12%)
Other_X   :  (0.01%)
Other_1   : ██ (5.06%)
3-1       : █ (3.95%)
1-3       :  (1.62%)
0-2       : ██ (4.24%)
2-0       : ████ (9.33%)
2-1       : ████ (9.07%)
1-1       : ██████ (13.11%)
0-3       :  (1.08%)

As we can see, the results include undefined predictions for scores that have been represented as Others. It is also noticeable that values below 2% are not being properly represented on our bar chart.

We shall fix this in the next section.

Chapter 3.1 Refine the Bar Chart

By increasing the scale to 100 blocks for finer granularity, we shall ensure a minimum of one block is displayed for very small percentages. This will ensure all predictions are graphically represented by text.

We shall also make our results more readable by including an empty line between each bar chart for better readability. In addition, we will remove all values that begin with ‘Others.’

-- Function to calculate proportions and create a text-based bar chart


function drawTextBarChart(predictions)
    local total = 0
    for _, prediction in ipairs(predictions) do
        total = total + prediction.value
    end

    -- Adjust the scale to accommodate small percentages (e.g., scale to 100 blocks)
    local scale = 100 -- Increase the scale to allow finer granularity

    -- Generate bar chart segments
    print("Text-Based Bar Chart for Predictions:")
    for _, prediction in ipairs(predictions) do
        local proportion = math.floor((prediction.value / total) * scale) -- Scale to more blocks
        proportion = math.max(proportion, 1) -- Ensure at least one block is displayed
        print(string.format("%-10s: %s (%.2f%%)", prediction.name, string.rep(("\226\150\136"), proportion), (prediction.value / total) * 100))
        print() -- Add a blank line after each bar for spacing
    end
end

-- Function to parse JSON string manually (very basic parsing, only works for simple flat structures)
function simpleJSONParse(jsonString)
    local parsed = {}
    -- Remove curly braces and spaces around key-value pairs
    jsonString = jsonString:match("^%s*(.*)%s*$")
    jsonString = jsonString:sub(2, -2)  -- Remove curly braces
    -- Split the string into key-value pairs
    for key, value in jsonString:gmatch('"([^"]+)":%s*([^,}]+)') do
        -- Try to convert number values
        value = tonumber(value) or value
        parsed[key] = value
    end
    return parsed
end

-- Function to fetch data from API and extract scores for the bar chart
function fetchFixtureData(fixtureId)
    local baseUrl = "https://api.sportmonks.com/v3/football/predictions/probabilities/fixtures"
    local apiToken = "API_Token"  -- Replace with your actual API token
    local apiUrl = string.format("%s/%s?api_token=%s", baseUrl, fixtureId, apiToken)

    local http = require("socket.http")
    local ltn12 = require("ltn12")

    local response = {}
    local _, statusCode = http.request{
        url = apiUrl,
        sink = ltn12.sink.table(response)
    }

    if statusCode == 200 then
        local rawJson = table.concat(response)

        -- Check if "scores" is present in the JSON response
        local scoresStart, scoresEnd = rawJson:find('"scores":%{')
        if scoresStart then
            -- Extract the "scores" part of the JSON response
            local scoresJson = rawJson:sub(scoresStart + 9, rawJson:find("}", scoresStart) - 1)
            -- Parse the scores into a table
            local predictionScores = simpleJSONParse("{" .. scoresJson .. "}")
            local predictions = {}

            -- Convert the scores into a table for the bar chart, filtering out "Other" scores
            for score, value in pairs(predictionScores) do
                -- Skip scores that contain "Other"
                if not string.match(score, "^Other") then
                    table.insert(predictions, {name = score, value = value})
                end
            end

            -- Call the function to draw the text-based bar chart
            drawTextBarChart(predictions)
        else
            print("Error: 'scores' data not found in the response.")
        end
    else
        print("Failed to fetch data with status: " .. tostring(statusCode))
    end
end

-- Call the function with a specific fixture ID
fetchFixtureData("19155072")


Text-Based Bar Chart for Predictions:
3-3       : █ (0.55%)

2-3       : █ (1.28%)

0-1       : █████████ (9.71%)

0-0       : ███████████ (11.50%)

3-0       : ████ (4.28%)

2-2       : ████ (4.22%)

1-2       : █████ (5.86%)

3-2       : ██ (2.13%)

1-0       : ███████████████ (15.11%)

3-1       : ████ (4.23%)

1-3       : █ (1.73%)

0-2       : ████ (4.54%)

2-0       : █████████ (9.98%)

2-1       : █████████ (9.70%)

1-1       : ██████████████ (14.03%)

0-3       : █ (1.16%)

Chapter 4.0: Making Our Code Dynamic.

In this chapter, which will be the final, we shall make our code dynamic by accepting the fixture ID from the console, rather than having it hard-coded.

This way we can quickly create a text-based bar chart for any matchup of interest. We will also include a new function to capture the name of the fixture from another API endpoint and have it printed in our first line, just before the bar chart.

-- Define the API token once
local apiToken = "API_Token" -- Replace with your actual API token

-- Function to parse JSON string manually (basic parsing, only for simple flat structures)
function simpleJSONParse(jsonString)
    local parsed = {}
    -- Remove curly braces and spaces around key-value pairs
    jsonString = jsonString:match("^%s*(.*)%s*$")
    jsonString = jsonString:sub(2, -2)  -- Remove curly braces
    -- Split the string into key-value pairs
    for key, value in jsonString:gmatch('"([^"]+)":%s*([^,}]+)') do
        -- Try to convert number values
        value = tonumber(value) or value:match('^"(.*)"$') or value
        parsed[key] = value
    end
    return parsed
end

-- Function to fetch the fixture name from the API
function fetchFixtureName(fixtureId)
    local baseUrl = "https://api.sportmonks.com/v3/football/fixtures"
    local apiUrl = string.format("%s/%s?api_token=%s", baseUrl, fixtureId, apiToken)

    local http = require("socket.http")
    local ltn12 = require("ltn12")

    local response = {}
    local _, statusCode = http.request{
        url = apiUrl,
        sink = ltn12.sink.table(response)
    }

    if statusCode == 200 then
        local rawJson = table.concat(response)

        -- Extract the fixture name
        local nameStart, nameEnd = rawJson:find('"data"%s*:%s*%{.-"name"%s*:%s*"([^"]+)"')
        local fixtureName = nameStart and rawJson:match('"name"%s*:%s*"([^"]+)"') or "Unknown Fixture"

        return fixtureName
    else
        print("Failed to fetch fixture name with status: " .. tostring(statusCode))
        return "Unknown Fixture"
    end
end

-- Function to calculate proportions and create a text-based bar chart
function drawTextBarChart(predictions, fixtureName)
    -- Print the fixture name as the first statement
    print("\nPredictions for " .. fixtureName)
    print("\n")
    local total = 0
    for _, prediction in ipairs(predictions) do
        total = total + prediction.value
    end

    -- Adjust the scale to accommodate small percentages (e.g., scale to 100 blocks)
    local scale = 100 -- Increase the scale to allow finer granularity

    -- Generate bar chart segments
    for _, prediction in ipairs(predictions) do
        local proportion = math.floor((prediction.value / total) * scale) -- Scale to more blocks
        proportion = math.max(proportion, 1) -- Ensure at least one block is displayed
        print(string.format("%-10s %s (%.2f%%)", prediction.name, string.rep("█", proportion), (prediction.value / total) * 100))
        print() -- Add a blank line after each bar for spacing
    end
end

-- Function to fetch data from API and extract scores for the bar chart
function fetchFixtureData(fixtureId)
    local baseUrl = "https://api.sportmonks.com/v3/football/predictions/probabilities/fixtures"
    local apiUrl = string.format("%s/%s?api_token=%s", baseUrl, fixtureId, apiToken)

    local http = require("socket.http")
    local ltn12 = require("ltn12")

    local response = {}
    local _, statusCode = http.request{
        url = apiUrl,
        sink = ltn12.sink.table(response)
    }

    if statusCode == 200 then
        local rawJson = table.concat(response)

        -- Extract scores (if available)
        local scoresStart, scoresEnd = rawJson:find('"scores"%s*:%s*%{')
        if scoresStart then
            local scoresJson = rawJson:sub(scoresStart + 9, rawJson:find("}", scoresStart) - 1)
            local predictionScores = simpleJSONParse("{" .. scoresJson .. "}")
            local predictions = {}

            -- Convert the scores into a table for the bar chart
            for score, value in pairs(predictionScores) do
                if not string.match(score, "^Other") then -- Skip scores with "Other"
                    table.insert(predictions, {name = score, value = value})
                end
            end

            -- Fetch the fixture name using the new function
            local fixtureName = fetchFixtureName(fixtureId)

            -- Call the function to draw the text-based bar chart
            drawTextBarChart(predictions, fixtureName)
        else
            print("Error: 'scores' data not found in the response.")
        end
    else
        print("Failed to fetch data with status: " .. tostring(statusCode))
    end
end

-- Ask the user to input the fixture ID
print("Please enter the fixture ID:")
local fixtureId = io.read()

-- Call the function with the user-provided fixture ID
fetchFixtureData(fixtureId)

Predictions for Juventus vs Como


3-3        █ (0.55%)

2-3        █ (1.28%)

0-1        █████████ (9.71%)

0-0        ███████████ (11.50%)

3-0        ████ (4.28%)

2-2        ████ (4.22%)

1-2        █████ (5.86%)

3-2        ██ (2.13%)

1-0        ███████████████ (15.11%)

3-1        ████ (4.23%)

1-3        █ (1.73%)

0-2        ████ (4.54%)

2-0        █████████ (9.98%)

2-1        █████████ (9.70%)

1-1        ██████████████ (14.03%)

0-3        █ (1.16%)

Conclusion.

With your brand new piece of code, you can be the first to put out a text-based prediction bar chart for the final scoreline right before kickoff.

While not a popular programming language, Lua, in combination with Sportmonks’ Football API, which offers coverage of more than 2300 leagues, has no limit to what can be achieved.

In the next Deverlopers’ guide, we will explore how you can convert the JSON response, as seen here, into a spreadsheet format. Meanwhile, why not explore our list of endpoints?

FAQ

Can I access all Sportmonks Football API endpoints using Postman?
Yes, you can access all Football API endpoints using Postman. The API provides a wide range of endpoints for accessing football-related data, including match fixtures, live scores, player statistics, team information, and more. You can use Postman to explore and interact with these endpoints to retrieve the data you need for your applications or projects. Visit our Postman Collection.
Can I keep my current API token?
You can use the same API token as you are currently using for API 2.0. You don't need to create another token for API 3.0.
How can I test the Sportmonks API for FREE?
You have two options:
  1. Create an account on My Sportmonks and get immediate access to our free plan.
  2. Subscribe to one of our paid plans and receive a one-time-only 14-day free trial.
How do I build and filter API requests?
Use the base URL and include necessary data points like scores, events, and participants. Apply filters to refine the data to match specific criteria. Here’s an example of how to incorporate the filter into your request: https://api.sportmonks.com/v3/football/livescores/inplay?api_token=YOUR_TOKEN&include=events&filters=eventTypes:14,19,20,21 By utilising filtering capabilities, we can tailor the data retrieved from the API to match the specific needs and preferences of our livescore website. For further details on filtering techniques, refer to our dedicated filter tutorial for additional guidance.
How does the API work?
We have built a well-structured, flexible, and fast JSON API. Our API makes it easy to customize your JSON responses. Our API's flexibility demonstrates itself by allowing its users to build their own responses in the form of adding include parameters to their requests. This means you can request the data you need and leave out the data you don't need. Filtering and Sorting is another widely used and loved characteristic of our API. It means you can request the data the way you want it to, by adding the proper filtering and sorting commands. Please check our documentation pages for more information.
How many API calls do I get?
By default, you will have access to 2000 API calls per endpoint per hour. If you feel this is not enough, you can upgrade this amount when selecting your plan.

Written by Wesley Van Rooij

Wesley van Rooij is a marketing and football expert with over 5 years of industry experience. His comprehensive knowledge of the Sportmonks Football API and a focused approach to APIs in the Sports Data industry allow him to offer insights and support to enthusiasts and businesses. His outstanding marketing and communication skills and technical writing expertise enable him to empathise with developers. He understands their needs and challenges to facilitate the development of cutting-edge football applications that stand out in the market.

Unlock your full potential!

Unlock the full potential of your football application with Sportmonks’ Football API.

Our API provides all you need to take your football app to the next level. Sign up now and kickstart your journey to success!