KelvinVail / Betfair

Fast and simple classes for interacting with the Betfair API and Stream.
MIT License
5 stars 3 forks source link

How to use Rest Endpoint to get useful information #5

Open zydjohnHotmail opened 2 days ago

zydjohnHotmail commented 2 days ago

Hi, Thanks for your reply to my last issue. I show you my current code according to your code sample: private async void BetfairDogRaces1Day() { var credentials = new Credentials(UserName, Password, APIKey); using var client = new BetfairApiClient(credentials); var filter = new ApiMarketFilter() .WithMarketTypes(MarketType.Win) .WithCountries(Country.UnitedKingdom, Country.Ireland) .WithEventTypes(EventType.GreyhoundRacing) .FromMarketStart(DateTimeOffset.UtcNow) .ToMarketStart(DateTimeOffset.UtcNow.AddDays(1));

        var query = new MarketCatalogueQuery()
            .Include(MarketProjection.Event)
            .Include(MarketProjection.MarketStartTime)
            .Include(MarketProjection.MarketDescription)
            .Include(MarketProjection.RunnerDescription);

        var marketCatalogues = await client.MarketCatalogue(filter, query);
        var top200Races = new MarketCatalogueQuery()
            .Include(MarketProjection.Event)
            .Include(MarketProjection.MarketStartTime)
            .Include(MarketProjection.MarketDescription)
            .Include(MarketProjection.RunnerDescription)
            .OrderBy(MarketSort.FirstToStart)
            .Take(200); 
        Debug.Print(top200Races.ToString());
    }

However, from debug window, I can't see any useful data. But I show you what I want to get from the Betfair API endpoint, here is my current code using HTTPS post request: public class DataStructure { public class BetfairResponse { public string Jsonrpc { get; set; } public List Result { get; set; } public int Id { get; set; } }

public class MarketCatalogue
{
    public string MarketId { get; set; }
    public string MarketName { get; set; }
    public DateTime MarketStartTime { get; set; }
    public Event Event { get; set; }
    public List<Runner> Runners { get; set; }
}

public class Event
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string CountryCode { get; set; }
    public string Timezone { get; set; }
    public string Venue { get; set; }
    public DateTime OpenDate { get; set; }
}

public class Runner
{
    public long SelectionId { get; set; }
    public string RunnerName { get; set; }
    public double Handicap { get; set; }
    public int SortPriority { get; set; }
}

public class RaceDetails
{
    public string MarketId { get; set; } = string.Empty;
    public DateTime StartTime { get; set; }
    public string CountryCode { get; set; } = string.Empty;
    public string RaceCourse { get; set; } = string.Empty;
}

public class GreyhoundTracks
{
    public int ID { get; set; }
    public string TrackCode { get; set; }
    public string TrackName { get; set; }
}

public class RunnerInfo
{
    public int DogID { get; set; }
    public long SelectionID { get; set; }
    public string DogName { get; set; }
}

public class RaceFixture
{
    public string Bookie { get; set; }
    public int Category { get; set; }
    public string CountryCode { get; set; }
    public string EventID { get; set; }
    public string MarketID { get; set; }
    public string MarketName { get; set; }
    public int RaceID { get; set; }
    public int RaceMeters { get; set; }
    public string TrackCode { get; set; }
    public string TrackName { get; set; }
    public DateTime StartsGMT { get; set; }
}

public class RunnerIDSelection
{
    public string Bookie { get; set; }
    public string Category { get; set; }
    public string JsonRunners { get; set; }
    public string MarketID { get; set; }
    public int RaceID { get; set; }
    public DateTime RaceDate { get; set; }
    public string TrackCode { get; set; }
    public string TrackName { get; set; }
}

    public static void BulkSaveRaceFixtures(List<RaceFixture> fixtures)
    {
        try
        {
            using SLMDB context = new();
            context.Race_Fixture.AddRange(fixtures);
            context.SaveChanges();
        }
        catch (DbUpdateException ex)
        {
            Debug.Print("[BulkSaveRaceFixtures] EX: " + ex.Message);
        }
    }

    public static void BulkSaveRunnerIDSelections(List<RunnerIDSelection> selections)
    {
        try
        {
            using SLMDB context = new();
            context.Race_Runners.AddRange(selections);
            context.SaveChanges();
        }
        catch (DbUpdateException ex)
        {
            Debug.Print("[BulkSaveRunnerIDSelections] EX: " + ex.Message);
        }
    }

    const string BetAPIUrl = @"https://api.betfair.com/exchange/betting/json-rpc/v1";
    private BetfairClient BFClient;
    public async Task GetGreyhoundRacesAsync(string sessionToken)
    {
        DateTime now = DateTime.Now;
        DateTime tomorrow = now.AddHours(24);

        var marketFilter = new
        {
            jsonrpc = "2.0",
            method = "SportsAPING/v1.0/listMarketCatalogue",
            @params = new
            {
                filter = new
                {
                    eventTypeIds = new List<string> { "4339" },
                    marketCountries = new List<string> { "GB","IE" },
                    marketTypeCodes = new List<string> { "WIN" },
                    marketStartTime = new
                    {
                        from = DateTime.Today.ToString("yyyy-MM-ddTHH:mm:ssZ"),
                        to = tomorrow.ToString("yyyy-MM-ddTHH:mm:ssZ")
                    }
                },
                maxResults = "300",
                marketProjection = new List<string> { "MARKET_START_TIME", "RUNNER_DESCRIPTION", "EVENT" }
            },
            id = 1
        };
        string jsonRequest = JsonConvert.SerializeObject(marketFilter);
        Debug.Print(jsonRequest);
        StringContent requestContent = new(jsonRequest);
        requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
        client.DefaultRequestHeaders.Add("X-Authentication", sessionToken);
        client.DefaultRequestHeaders.Add("X-Application", APIKey);
        HttpResponseMessage response = await client.PostAsync(BetAPIUrl, requestContent);
        response.EnsureSuccessStatusCode();
        string responseContent = await response.Content.ReadAsStringAsync();
        Debug.Print(responseContent);
        await SaveGreyhoundRacesAsync(responseContent);
    }

    public async Task SaveGreyhoundRacesAsync(string jsonResponse)
    {
        BetfairResponse<MarketCatalogue>? betfairResponse =
        JsonConvert.DeserializeObject<BetfairResponse<MarketCatalogue>>(jsonResponse);
        List<RaceFixture> raceFixtures = new();
        List<RunnerIDSelection> runnerIDSelections = new();

        Dictionary<string, List<DateTime>> venueRaceIndex = new();

        foreach (MarketCatalogue market in betfairResponse.Result)
        {
            if (!venueRaceIndex.ContainsKey(market.Event.Venue))
            {
                venueRaceIndex[market.Event.Venue] = new List<DateTime>();
            }
            venueRaceIndex[market.Event.Venue].Add(market.MarketStartTime);
        }

        foreach (MarketCatalogue market in betfairResponse.Result)
        {
            RaceFixture raceFixture = new()
            {
                Bookie = "Betfair",
                Category = 4339,
                CountryCode = market.Event.CountryCode,
                EventID = market.Event.Id,
                MarketID = market.MarketId,
                MarketName = market.MarketName,
                RaceID = GetRaceID(market.Event.Venue, market.MarketStartTime, venueRaceIndex),
                RaceMeters = GetRaceMeters(market.MarketName),
                TrackCode = GetTrackCode(market.Event.Venue),
                TrackName = market.Event.Venue,
                StartsGMT = market.MarketStartTime
            };
            raceFixtures.Add(raceFixture);

            List<RunnerInfo> runners = new();
            foreach (Runner runner in market.Runners)
            {
                int dogID = GetDogID(runner.RunnerName, runner.SortPriority);
                string dogName = Regex.Replace(runner.RunnerName, @"^[1-6]\.\s*", ""); // Remove the pattern "1. " to "6. " followed by any number of spaces
                runners.Add(new RunnerInfo
                {
                    DogID = dogID,
                    SelectionID = runner.SelectionId,
                    DogName = dogName
                });
            }

            RunnerIDSelection runnerIDSelection = new()
            {
                Bookie = "Betfair",
                Category = "Greyhound",
                JsonRunners = JsonConvert.SerializeObject(runners),
                MarketID = market.MarketId,
                RaceID = raceFixture.RaceID,
                RaceDate = raceFixture.StartsGMT,
                TrackCode = raceFixture.TrackCode,
                TrackName = raceFixture.TrackName
            };
            runnerIDSelections.Add(runnerIDSelection);
        }

        BulkSaveRaceFixtures(raceFixtures);
        BulkSaveRunnerIDSelections(runnerIDSelections);
    }

    private static int GetRaceID(string venue, DateTime startTime, Dictionary<string, List<DateTime>> venueRaceIndex)
    {
        List<DateTime> sortedTimes = [.. venueRaceIndex[venue].OrderBy(t => t)];
        return sortedTimes.IndexOf(startTime) + 1;
    }

    private static int GetRaceMeters(string marketName)
    {
        string[] parts = marketName.Split(' ');
        foreach (string part in parts)
        {
            if (Regex.IsMatch(part, @"\d{3}[mM]$"))
            {
                int raceMeter1 = int.Parse(part.Replace("m", "").Replace("M", ""));
                return raceMeter1;
            }
        }
        throw new ArgumentException("Market name does not contain a valid distance.");
    }

    private string GetTrackCode(string venue)
    {
        if (DnameCode.TryGetValue(venue, out string? trackCode))
        {
            return trackCode;
        }
        else
        {
            Debug.Print(venue);
            return "UNKNOWN";
        }
    }

    private static int GetDogID(string runnerName, int sortPriority)
    {
        Match match = Regex.Match(runnerName, @"^([1-6])\.");
        if (match.Success)
        {
            return int.Parse(match.Groups[1].Value);
        }
        return sortPriority;
    }

=> The only thing different is that I create a dictionary to use first 3-letter as trackCode for each greyhound race course in both UK & Ireland.
The following is the json payload to request one day's greyhound race fixture: {"jsonrpc":"2.0","method":"SportsAPING/v1.0/listMarketCatalogue","params":{"filter":{"eventTypeIds":["4339"],"marketCountries":["GB","IE"],"marketTypeCodes":["WIN"],"marketStartTime":{"from":"2024-11-22T00:00:00Z","to":"2024-11-23T07:20:10Z"}},"maxResults":"300","marketProjection":["MARKET_START_TIME","RUNNER_DESCRIPTION","EVENT"]},"id":1}

=> The response looks like this: {"jsonrpc":"2.0","result":[{"marketId":"1.236266850","marketName":"A7 415m","marketStartTime":"2024-11-22T10:32:00.000Z","totalMatched":4.8028,"runners":[{"selectionId":55301333,"runnerName":"1. Foxwood Cardi","handicap":0.0,"sortPriority":1},{"selectionId":50748735,"runnerName":"2. Roscrea Karen","handicap":0.0,"sortPriority":2},{"selectionId":75235674,"runnerName":"3. Do It Bono","handicap":0.0,"sortPriority":3},{"selectionId":72963396,"runnerName":"4. Blazeaway Kiss","handicap":0.0,"sortPriority":4},{"selectionId":50748734,"runnerName":"5. Dunbolg Sif","handicap":0.0,"sortPriority":5},{"selectionId":42150090,"runnerName":"6. Keplar Isis","handicap":0.0,"sortPriority":6}],"event":{"id":"33805168","name":"Harlow 22nd Nov","countryCode":"GB","timezone":"Europe/London","venue":"Harlow","openDate":"2024-11-22T10:32:00.000Z"}},{"marketId":"1.236266852","marketName":"D5 238m","marketStartTime":"2024-11-22T10:48:00.000Z","totalMatched":42.64886400000001,"runners":[{"selectionId":61041122,"runnerName":"1. Chilteen Joy","handicap":0.0,"sortPriority":1},{"selectionId":43659624,"runnerName":"2. Glasmeen Tina","handicap":0.0,"sortPriority":2},{"selectionId":70869383,"runnerName":"3. Most Interesting","handicap":0.0,"sortPriority":3},{"selectionId":73964706,"runnerName":"4. Tousers Lad","handicap":0.0,"sortPriority":4},{"selectionId":62670031,"runnerName":"5. Buff Egan","handicap":0.0,"sortPriority":5},{"selectionId":43998090,"runnerName":"6. Burgess Mentor","handicap":0.0,"sortPriority":6}],"event":{"id":"33805168","name":"Harlow 22nd Nov","countryCode":"GB","timezone":"Europe/London","venue":"Harlow","openDate":"2024-11-22T10:32:00.000Z"}},{"marketId":"1.236266641","marketName":"A7 500m","marketStartTime":"2024-11-22T11:01:00.000Z","totalMatched":130.131866,"runners":[{"selectionId":52391251,"runnerName":"1. Insane Simone","handicap":0.0,"sortPriority":1},{"selectionId":57194876,"runnerName":"2. Antigua Anna","handicap":0.0,"sortPriority":2},{"selectionId":67127936,"runnerName":"3. Fatboy Stu","handicap":0.0,"sortPriority":3},{"selectionId":54380251,"runnerName":"4. Punk Rock => But I have to write rather complicated logic to parse the greyhound races fixture to get useful information. If you can provide some example to use your repo/Nuget package to get necessary information, then it will be much helpful. I show you some records I saved in SQL Server data table, but you don't have to use SQL Server database, just return a list of tuple, corresponding to the SQL Server data table is enough. Here is the data table design code: CREATE TABLE [dbo].[RaceFixture]( [Bookie] nvarchar NOT NULL, [Category] [int] NOT NULL, [CountryCode] nvarchar NOT NULL, [EventID] nvarchar NOT NULL, [MarketID] nvarchar NOT NULL, [MarketName] nvarchar NOT NULL, [RaceID] [int] NOT NULL, [RaceMeters] [int] NOT NULL, [TrackCode] nvarchar NOT NULL, [TrackName] nvarchar NOT NULL, [StartsGMT] [datetime] NOT NULL, CONSTRAINT [PK_RaceFixture] PRIMARY KEY CLUSTERED ( [Bookie] ASC, [EventID] ASC, [MarketID] ASC, [RaceID] ASC, [TrackCode] ASC, [StartsGMT] ASC) WITH (PAD_INDEX=OFF,STATISTICS_NORECOMPUTE=OFF,IGNORE_DUP_KEY=OFF,ALLOW_ROW_LOCKS=ON,ALLOW_PAGE_LOCKS=ON) ON [PRIMARY] )ON [PRIMARY] GO => RaceID is the order for each race during the day for the same greyhound race course, so its value can be: 1, 2, 3, 4, 5, 6, 7, 8, etc. RaceMeter is the distance of each race.

CREATE TABLE [dbo].[RunnerIDSelection]( [Bookie] nvarchar NOT NULL, [Category] nvarchar NOT NULL, [JsonRunners] nvarcharNOT NULL, [MarketID] nvarchar NOT NULL, [RaceID] [int] NOT NULL, [RaceDate] [DateTime] NOT NULL, [TrackCode] nvarchar NOT NULL, [TrackName] nvarchar NOT NULL, CONSTRAINT [PK_RunnerIDSelection] PRIMARY KEY CLUSTERED ( [Bookie] ASC, [MarketID] ASC, [RaceID] ASC, [TrackCode] ASC ) WITH(PAD_INDEX=OFF,STATISTICS_NORECOMPUTE=OFF,IGNORE_DUP_KEY=OFF,ALLOW_ROW_LOCKS=ON, ALLOW_PAGE_LOCKS=ON, OPTIMIZE_FOR_SEQUENTIAL_KEY=OFF)ON [PRIMARY] )ON [PRIMARY] GO => I want to use NewTonSoft.json to serialize all the dogs in one race. I show you some records for this table: SELECT * FROM RunnerIDSelection Bookie Category JsonRunners MarketID RaceID RaceDate TrackCode TrackName Betfair Greyhound [{"DogID":1,"SelectionID":52391251,"DogName":"Insane Simone"},{"DogID":2,"SelectionID":57194876,"DogName":"Antigua Anna"},{"DogID":3,"SelectionID":67127936,"DogName":"Fatboy Stu"},{"DogID":4,"SelectionID":54380251,"DogName":"Punk Rock Zelda"},{"DogID":5,"SelectionID":70217730,"DogName":"Sullane Biscuit"},{"DogID":6,"SelectionID":72455527,"DogName":"Pocket Penny"}] 1.236266641 1 2024-11-22 11:01:00.000 GBHOV Hove

The JsonRunners is something like this: [{"DogID":1,"SelectionID":52391251,"DogName":"Insane Simone"},{"DogID":2,"SelectionID":57194876,"DogName":"Antigua Anna"},{"DogID":3,"SelectionID":67127936,"DogName":"Fatboy Stu"},{"DogID":4,"SelectionID":54380251,"DogName":"Punk Rock Zelda"},{"DogID":5,"SelectionID":70217730,"DogName":"Sullane Biscuit"},{"DogID":6,"SelectionID":72455527,"DogName":"Pocket Penny"}]

I show you some records from data table [RaceFixture] SELECT * FROM RaceFixture Bookie Category CountryCode EventID MarketID MarketName RaceID RaceMeters TrackCode TrackName StartsGMT Betfair 4339 GB 33805140 1.236266641 A7 500m 1 500 GBHOV Hove 2024-11-22 11:01:00.000 Betfair 4339 GB 33805140 1.236266643 D2 285m 2 285 GBHOV Hove 2024-11-22 11:18:00.000 Betfair 4339 GB 33805140 1.236266645 A5 500m 3 500 GBHOV Hove 2024-11-22 11:34:00.000

=> I got total 159 races for today, unfortunately, none of them is from Ireland, I think maybe tomorrow, I can get some races from Ireland. So daily races count is around 140 to 160, averagely around 150, but in Weekend, if there are some Irish races, then the number could be more than 200; so I think a better to add more conditions, in Weekend, try to list UK & Irish races separately, but during weekdays (Monday to Friday), simply put them together. Thanks,

KelvinVail commented 2 days ago

The information from Betfair is returned in the response from the call to await client.MarketCatalogue(filter, query);. Apologies, I didn't make that clear in my last reply.

So you should do something like this:

var credentials = new Credentials(UserName, Password, APIKey);
using var client = new BetfairApiClient(credentials);
var filter = new ApiMarketFilter()
    .WithMarketTypes(MarketType.Win)
    .WithCountries(Country.UnitedKingdom, Country.Ireland)
    .WithEventTypes(EventType.GreyhoundRacing)
    .FromMarketStart(DateTimeOffset.UtcNow)
    .ToMarketStart(DateTimeOffset.UtcNow.AddDays(1));

var query = new MarketCatalogueQuery()
    .Include(MarketProjection.Event)
    .Include(MarketProjection.MarketStartTime)
    .Include(MarketProjection.RunnerDescription);

var marketCatalogues = await client.MarketCatalogue(filter, query);

Debug.Print(JsonConvert.SerializeObject(marketCatalogues));

foreach (var market in marketCatalogues)
{
    // Process each market.
}

The MarketCatalogueQuery() object is the query sent to Betfair. It does not contain any response data. It is equivalent to the MarketProjection in the request you are sending to Betfair in your current code "marketProjection":["MARKET_START_TIME","RUNNER_DESCRIPTION","EVENT"].

If you do want to output that query to the debug window though, you just need to serialize it into a string first.

Let me know if you need more info.

zydjohnHotmail commented 1 day ago

Hi, Do you have code to receive the response, like this response: {"jsonrpc":"2.0","result":[{"marketId":"1.236266850","marketName":"A7 415m","marketStartTime":"2024-11-22T10:32:00.000Z","totalMatched":4.8028,"runners":[{"selectionId":55301333,"runnerName":"1. Foxwood Cardi","handicap":0.0,"sortPriority":1},{"selectionId":50748735,"runnerName":"2. Roscrea Karen","handicap":0.0,"sortPriority":2},{"selectionId":75235674,"runnerName":"3. Do It Bono","handicap":0.0,"sortPriority":3},{"selectionId":72963396,"runnerName":"4. Blazeaway Kiss","handicap":0.0,"sortPriority":4},

By the way, I think it is strange, today is Friday, so there were quite a number of Irish greyhound races, as I can see the from this url: https://www.grireland.ie/racing/upcoming-race-cards/ But all returned 158 races are from UK. Ireland should have at least 90 or nearly 100 races, but the response returned no such races at all. Can you check your code, just return some greyhound races in Ireland? But I want you show me the code to see the response in json format, not only the request in json format. Thanks,

zydjohnHotmail commented 1 day ago

Hi, Basically, I understand how to use your repo to do the job, but still I have some question about the returned results. Here is the json format request I made via Postman HTTP request, the payload: { "jsonrpc": "2.0", "method": "SportsAPING/v1.0/listMarketCatalogue", "params": { "filter": { "eventTypeIds": [ "4339" ], "marketCountries": [ "IE", "GB" ], "marketTypeCodes": [ "WIN" ], "marketStartTime": { "from": "2024-11-22T23:59:00+00:00", "to": "2024-11-23T23:59:00+00:00" } }, "maxResults": "300", "marketProjection": [ "MARKET_START_TIME", "RUNNER_DESCRIPTION", "EVENT" ] }, "id": 1 } => From the returned results, both from your repo & Postman HTTP response, I can see there are 181 greyhound races, only 12 races are from Ireland in one greyhound race course: Shelbourne Park However, according to this web page: https://www.grireland.ie/racing/upcoming-race-cards/ There are the following Irish greyhound race courses have races on November 23, 2024: 23-Nov-24 - Limerick 23-Nov-24 - Tralee 23-Nov-24 - Mullingar 23-Nov-24 - Dundalk 23-Nov-24 - Shelbourne Park 23-Nov-24 - Waterford 23-Nov-24 - Galway 23-Nov-24 - Drumbo Park 23-Nov-24 - Youghal 23-Nov-24 - Lifford 23-Nov-24 - Curraheen Park

=> But API returned results contained only one greyhound race course, but actually, total 11 Irish greyhound race courses have races on 23-Nov-2024, the API return only 1, but missing 10 Irish greyhound race courses' races, is there any issue with the API?

KelvinVail commented 1 day ago

The issue is that Betfair does not cover every single Irish Greyhound race. If you check the Betfair website you'll see they only list one venue on 23-Nov, Shelbourne.