From 13864ff371598fbb21d5fb7bcae5d918c924ad64 Mon Sep 17 00:00:00 2001 From: "Jesse-PC\\Jesse" Date: Sun, 1 Mar 2015 21:34:31 +1100 Subject: [PATCH] Initial Commit --- .gitignore | 2 + Bet.cs | 101 +++++++++++++ BetAmount.cs | 15 ++ BetListType.cs | 8 + BetStatus.cs | 17 +++ BetType.cs | 14 ++ ClientBalance.cs | 19 +++ CurrenciesResponse.cs | 15 ++ Currency.cs | 16 ++ Event.cs | 73 +++++++++ Feed.cs | 18 +++ FeedLeague.cs | 18 +++ FeedResponse.cs | 13 ++ FeedSport.cs | 18 +++ GetBetsResponse.cs | 11 ++ GetInRunningResponse.cs | 16 ++ GetLineResponse.cs | 47 ++++++ GetLineResponseStatus.cs | 12 ++ InRunningState.cs | 17 +++ League.cs | 19 +++ LeaguesResponse.cs | 15 ++ MoneyLine.cs | 16 ++ OddsFormat.cs | 16 ++ Period.cs | 33 ++++ PinnacleClient.cs | 303 +++++++++++++++++++++++++++++++++++++ PinnacleWrapper.csproj | 102 +++++++++++++ PlaceBetErrorCode.cs | 28 ++++ PlaceBetRequest.cs | 56 +++++++ PlaceBetResponse.cs | 23 +++ PlaceBetResponseStatus.cs | 13 ++ Properties/AssemblyInfo.cs | 36 +++++ ResponseError.cs | 16 ++ SideType.cs | 12 ++ Sport.cs | 19 +++ SportsResponse.cs | 15 ++ Spread.cs | 22 +++ Status.cs | 25 +++ Team.cs | 18 +++ TeamType.cs | 13 ++ WinRiskType.cs | 12 ++ XmlResponse.cs | 26 ++++ packages.config | 5 + 42 files changed, 1293 insertions(+) create mode 100644 .gitignore create mode 100644 Bet.cs create mode 100644 BetAmount.cs create mode 100644 BetListType.cs create mode 100644 BetStatus.cs create mode 100644 BetType.cs create mode 100644 ClientBalance.cs create mode 100644 CurrenciesResponse.cs create mode 100644 Currency.cs create mode 100644 Event.cs create mode 100644 Feed.cs create mode 100644 FeedLeague.cs create mode 100644 FeedResponse.cs create mode 100644 FeedSport.cs create mode 100644 GetBetsResponse.cs create mode 100644 GetInRunningResponse.cs create mode 100644 GetLineResponse.cs create mode 100644 GetLineResponseStatus.cs create mode 100644 InRunningState.cs create mode 100644 League.cs create mode 100644 LeaguesResponse.cs create mode 100644 MoneyLine.cs create mode 100644 OddsFormat.cs create mode 100644 Period.cs create mode 100644 PinnacleClient.cs create mode 100644 PinnacleWrapper.csproj create mode 100644 PlaceBetErrorCode.cs create mode 100644 PlaceBetRequest.cs create mode 100644 PlaceBetResponse.cs create mode 100644 PlaceBetResponseStatus.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100644 ResponseError.cs create mode 100644 SideType.cs create mode 100644 Sport.cs create mode 100644 SportsResponse.cs create mode 100644 Spread.cs create mode 100644 Status.cs create mode 100644 Team.cs create mode 100644 TeamType.cs create mode 100644 WinRiskType.cs create mode 100644 XmlResponse.cs create mode 100644 packages.config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd42ee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ diff --git a/Bet.cs b/Bet.cs new file mode 100644 index 0000000..ca6977e --- /dev/null +++ b/Bet.cs @@ -0,0 +1,101 @@ +using System; +using Newtonsoft.Json; + +namespace PinnacleWrapper +{ + public class Bet + { + [JsonProperty(PropertyName = "betId")] + public int BetId; + + [JsonProperty(PropertyName = "wagerNumber")] + public int WagerNumber; + + [JsonProperty(PropertyName = "placedAt")] + public DateTime PlacedAt; + + [JsonProperty(PropertyName = "win")] + public decimal Win; + + [JsonProperty(PropertyName = "risk")] + public decimal Risk; + + [JsonProperty(PropertyName = "winLoss")] + public decimal WinLoss; + + [JsonProperty(PropertyName = "betStatus")] + public BetStatus BetStatus; + + [JsonProperty(PropertyName = "betType")] + public BetType BetType; + + [JsonProperty(PropertyName = "sportId")] + public int SportId; + + [JsonProperty(PropertyName = "leagueId")] + public int LeagueId; + + [JsonProperty(PropertyName = "eventId")] + public int EventId; + + [JsonProperty(PropertyName = "handicap")] + public decimal? Handicap; + + [JsonProperty(PropertyName = "price")] + public decimal Price; + + [JsonProperty(PropertyName = "teamName")] + public string TeamName; + + [JsonProperty(PropertyName = "side")] + public SideType? Side; + + [JsonProperty(PropertyName = "oddsFormat")] + public OddsFormat OddsFormat; + + [JsonProperty(PropertyName = "customerCommission")] + public decimal? ClientCommission; + + [JsonProperty(PropertyName = "pitcher1")] + public string Pitcher1; + + [JsonProperty(PropertyName = "pitcher2")] + public string Pitcher2; + + [JsonProperty(PropertyName = "pitcher1MustStart")] + public bool? Pitcher1MustStart; + + [JsonProperty(PropertyName = "pitcher2MustStart")] + public bool? Pitcher2MustStart; + + [JsonProperty(PropertyName = "team1")] + public string Team1; + + [JsonProperty(PropertyName = "team2")] + public string Team2; + + [JsonProperty(PropertyName = "periodNumber")] + public string PeriodNumber; + + [JsonProperty(PropertyName = "team1Score")] + public decimal? Team1Score; + + [JsonProperty(PropertyName = "team2Score")] + public decimal? Team2Score; + + [JsonProperty(PropertyName = "ftTeam1Score")] + public decimal FullTimeTeam1Score; + + [JsonProperty(PropertyName = "ftTeam2Score")] + public decimal FullTimeTeam2Score; + + [JsonProperty(PropertyName = "pTeam1Score")] + public decimal? PartTimeTeam1Score; + + [JsonProperty(PropertyName = "pTeam2Score")] + public decimal? PartTimeTeam2Score; + + [JsonProperty(PropertyName = "isLive")] + public bool IsLive; + } +} diff --git a/BetAmount.cs b/BetAmount.cs new file mode 100644 index 0000000..1cc04ce --- /dev/null +++ b/BetAmount.cs @@ -0,0 +1,15 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + public class BetAmount + { + [XmlElement("spread")] + public double Spread { get; set; } + + [XmlElement("moneyLine")] + public double MoneyLine { get; set; } + } +} diff --git a/BetListType.cs b/BetListType.cs new file mode 100644 index 0000000..e88f1e0 --- /dev/null +++ b/BetListType.cs @@ -0,0 +1,8 @@ +namespace PinnacleWrapper +{ + public enum BetListType + { + Settled, + Running + } +} diff --git a/BetStatus.cs b/BetStatus.cs new file mode 100644 index 0000000..847cd98 --- /dev/null +++ b/BetStatus.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PinnacleWrapper +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum BetStatus + { + Accepted, + PendingAcceptance, + Rejected, + Refunded, + Cancelled, + Lose, + Won + } +} diff --git a/BetType.cs b/BetType.cs new file mode 100644 index 0000000..dfd793f --- /dev/null +++ b/BetType.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PinnacleWrapper +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum BetType + { + Spread, + MoneyLine, + TotalPoints, + TeamTotalPoints + } +} diff --git a/ClientBalance.cs b/ClientBalance.cs new file mode 100644 index 0000000..e1fd269 --- /dev/null +++ b/ClientBalance.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace PinnacleWrapper +{ + public class ClientBalance + { + [JsonProperty(PropertyName = "availableBalance")] + public decimal AvailableBalance; + + [JsonProperty(PropertyName = "outstandingTransactions")] + public decimal OutstandingTransactions; + + [JsonProperty(PropertyName = "givenCredit")] + public decimal GivenCredit; + + [JsonProperty(PropertyName = "currency")] + public string Currency; + } +} diff --git a/CurrenciesResponse.cs b/CurrenciesResponse.cs new file mode 100644 index 0000000..73b1443 --- /dev/null +++ b/CurrenciesResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("rsp")] + public class CurrenciesResponse : XmlResponse + { + [XmlArray("currencies")] + [XmlArrayItem("currency")] + public List Currencies { get; set; } + } +} diff --git a/Currency.cs b/Currency.cs new file mode 100644 index 0000000..354b868 --- /dev/null +++ b/Currency.cs @@ -0,0 +1,16 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("currency")] + public class Currency + { + [XmlAttribute("code")] + public string Code { get; set; } + + [XmlText] + public string Name { get; set; } + } +} diff --git a/Event.cs b/Event.cs new file mode 100644 index 0000000..7520ba2 --- /dev/null +++ b/Event.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("event")] + public class Event + { + [XmlElement("startDateTime")] + public DateTime StartDateTime { get; set; } + + [XmlElement("id")] + public long Id { get; set; } + + [XmlElement("IsLive")] + public string IsLiveString { get; set; } + + [XmlIgnore] + public bool IsLive + { + get + { + return IsLiveString.Equals("Yes", StringComparison.OrdinalIgnoreCase); + } + + set + { + IsLiveString = (value ? "Yes" : "No"); + } + } + + [XmlElement("status")] + public string StatusString { get; set; } + + [XmlIgnore] + public Status Status + { + get + { + if (!string.IsNullOrWhiteSpace(StatusString)) + { + switch (StatusString.ToLower()) + { + case "o": + return Status.Open; + case "i": + return Status.LowerMaximum; + case "h": + return Status.Unavailable; + case "x": + return Status.Cancelled; + default: + throw new Exception("Unrecognized status: " + StatusString); + } + } + + throw new Exception("No status string"); + } + } + + [XmlElement("homeTeam")] + public Team HomeTeam { get; set; } + + [XmlElement("awayTeam")] + public Team AwayTeam { get; set; } + + [XmlArray("periods")] + [XmlArrayItem("period")] + public List Periods { get; set; } + } +} diff --git a/Feed.cs b/Feed.cs new file mode 100644 index 0000000..c3e6c57 --- /dev/null +++ b/Feed.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("fd")] + public class Feed + { + [XmlElement("fdTime")] + public long Timestamp { get; set; } + + [XmlArray("sports")] + [XmlArrayItem("sport")] + public List Sports { get; set; } + } +} diff --git a/FeedLeague.cs b/FeedLeague.cs new file mode 100644 index 0000000..e1fd9ac --- /dev/null +++ b/FeedLeague.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("league")] + public class FeedLeague + { + [XmlElement("id")] + public int Id { get; set; } + + [XmlArray("events")] + [XmlArrayItem("event")] + public List Events { get; set; } + } +} diff --git a/FeedResponse.cs b/FeedResponse.cs new file mode 100644 index 0000000..d5973ad --- /dev/null +++ b/FeedResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("rsp")] + public class FeedResponse : XmlResponse + { + [XmlElement("fd")] + public Feed Feed { get; set; } + } +} diff --git a/FeedSport.cs b/FeedSport.cs new file mode 100644 index 0000000..e9d3a31 --- /dev/null +++ b/FeedSport.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("sport")] + public class FeedSport + { + [XmlElement("id")] + public int Id { get; set; } + + [XmlArray("leagues")] + [XmlArrayItem("league")] + public List Leagues { get; set; } + } +} diff --git a/GetBetsResponse.cs b/GetBetsResponse.cs new file mode 100644 index 0000000..928bd2b --- /dev/null +++ b/GetBetsResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace PinnacleWrapper +{ + public class GetBetsResponse + { + [JsonProperty(PropertyName = "bets")] + public List Bets; + } +} diff --git a/GetInRunningResponse.cs b/GetInRunningResponse.cs new file mode 100644 index 0000000..9a39df0 --- /dev/null +++ b/GetInRunningResponse.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace PinnacleWrapper +{ + public class GetInRunningResponse + { + [JsonProperty(PropertyName = "elapsed")] + public int Elapsed; + + [JsonProperty(PropertyName = "state")] + public InRunningState State; + + [JsonProperty(PropertyName = "id")] + public int Id; + } +} diff --git a/GetLineResponse.cs b/GetLineResponse.cs new file mode 100644 index 0000000..5a959e9 --- /dev/null +++ b/GetLineResponse.cs @@ -0,0 +1,47 @@ +using System; +using Newtonsoft.Json; + +namespace PinnacleWrapper +{ + public class GetLineResponse + { + [JsonProperty(PropertyName = "status")] + public GetLineResponseStatus Status; // if the value is NOT_EXISTS, then this will be the only parameter in the response. All other params would be empty. + + [JsonProperty(PropertyName = "price")] + public decimal? Price; + + [JsonProperty(PropertyName = "lineId")] + public int? LineId; + + [JsonProperty(PropertyName = "altLineId")] + public int? AltLineId; // This would be needed to place the bet if the handicap is on alternate line, otherwise it will not be in the response. + + [JsonProperty(PropertyName = "team1Score")] + public int? Team1Score; // Soccer only + + [JsonProperty(PropertyName = "team2Score")] + public int? Team2Score; // Soccer only + + [JsonProperty(PropertyName = "team1RedCards")] + public int? Team1RedCards; // Soccer only + + [JsonProperty(PropertyName = "team2RedCards")] + public int? Team2RedCards; // Soccer only + + [JsonProperty(PropertyName = "maxRiskStake")] + public decimal? MaxRiskStake; + + [JsonProperty(PropertyName = "minRiskStake")] + public decimal? MinRiskStake; + + [JsonProperty(PropertyName = "maxWinStake")] + public decimal? MaxWinStake; + + [JsonProperty(PropertyName = "minWinStake")] + public decimal? MinWinStake; + + [JsonProperty(PropertyName = "effectiveAsOf")] + public DateTime? EffectiveAsOf; // Line is effective as of this date and time + } +} diff --git a/GetLineResponseStatus.cs b/GetLineResponseStatus.cs new file mode 100644 index 0000000..3aab3a7 --- /dev/null +++ b/GetLineResponseStatus.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PinnacleWrapper +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum GetLineResponseStatus + { + Success, + NotExists // Line no longer offered! + } +} diff --git a/InRunningState.cs b/InRunningState.cs new file mode 100644 index 0000000..9280bfa --- /dev/null +++ b/InRunningState.cs @@ -0,0 +1,17 @@ +namespace PinnacleWrapper +{ + public enum InRunningState + { + FirstHalf = 1, + HalfTime = 2, + SecondHalf = 3, + EndRegularTime = 4, + FirstHalfExtraTime = 5, + HalfTimeExtraTime = 6, + SecondHalfExtraTime = 7, + EndOfExtraTime = 8, + EndOfGame = 9, + GameTemporarilySuspended = 10, + PenaltiesInProgress = 11 + } +} diff --git a/League.cs b/League.cs new file mode 100644 index 0000000..391bc93 --- /dev/null +++ b/League.cs @@ -0,0 +1,19 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("league")] + public class League + { + [XmlAttribute("id")] + public int Id { get; set; } + + [XmlAttribute("feedContents")] + public int FeedContents { get; set; } + + [XmlText] + public string Title { get; set; } + } +} diff --git a/LeaguesResponse.cs b/LeaguesResponse.cs new file mode 100644 index 0000000..da6ccdc --- /dev/null +++ b/LeaguesResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("rsp")] + public class LeaguesResponse : XmlResponse + { + [XmlArray("leagues")] + [XmlArrayItem("league")] + public List Leagues { get; set; } + } +} diff --git a/MoneyLine.cs b/MoneyLine.cs new file mode 100644 index 0000000..cc6444a --- /dev/null +++ b/MoneyLine.cs @@ -0,0 +1,16 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("moneyLine")] + public class MoneyLine + { + [XmlElement("awayPrice")] + public double AwayPrice { get; set; } + + [XmlElement("homePrice")] + public double HomePrice { get; set; } + } +} diff --git a/OddsFormat.cs b/OddsFormat.cs new file mode 100644 index 0000000..8aeda4a --- /dev/null +++ b/OddsFormat.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PinnacleWrapper +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum OddsFormat + { + American, + Decimal, + HongKong, + Indonesian, + Malay, + Fraction + } +} diff --git a/Period.cs b/Period.cs new file mode 100644 index 0000000..933299e --- /dev/null +++ b/Period.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("period")] + public class Period + { + [XmlAttribute("lineId")] + public long LineId { get; set; } + + [XmlElement("number")] + public int Number { get; set; } + + [XmlElement("description")] + public string Description { get; set; } + + [XmlElement("cutoffDateTime")] + public DateTime CutoffDateTime { get; set; } + + [XmlArray("spreads")] + [XmlArrayItem("spread")] + public List Spreads { get; set; } + + [XmlElement("moneyLine")] + public MoneyLine MoneyLine { get; set; } + + [XmlElement("maxBetAmount")] + public BetAmount MaxBetAmount { get; set; } + } +} diff --git a/PinnacleClient.cs b/PinnacleClient.cs new file mode 100644 index 0000000..3d10bd6 --- /dev/null +++ b/PinnacleClient.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace PinnacleWrapper +{ + public class PinnacleClient + { + private HttpClient _httpClient; + + private string _clientId; + private string _password; + + public string CurrencyCode { get; private set; } + public OddsFormat OddsFormat { get; private set; } + + private const int MinimumFeedRefreshWithLast = 5; // minimum time in seconds between calls when supplying the last timestamp parameter + private const int MinimumFeedRefresh = 60; // minimum time in seconds between calls without last timestamp parameter + + private DateTime? _lastFeedRequest; + + private const string BaseAddress = "https://api.pinnaclesports.com/v1/"; + + public PinnacleClient(string clientId, string password, string currencyCode, OddsFormat oddsFormat) + { + _clientId = clientId; + _password = password; + CurrencyCode = currencyCode; + OddsFormat = oddsFormat; + + _httpClient = new HttpClient {BaseAddress = new Uri(BaseAddress)}; + + // put auth header into httpclient + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", + Convert.ToBase64String( + Encoding.ASCII.GetBytes(string.Format("{0}:{1}", _clientId, _password)))); + } + + protected T GetXmlAsync(string requestType, params object[] values) + where T : XmlResponse + { + var response = _httpClient.GetAsync(string.Format(requestType, values)).Result; + + response.EnsureSuccessStatusCode(); // throw if web request failed + + var xmlFormatter = new XmlMediaTypeFormatter { UseXmlSerializer = true }; + var apiResponse = response.Content.ReadAsAsync(new[] { xmlFormatter }).Result; + + if (apiResponse.IsValid) + { + return apiResponse; + } + + throw new Exception("Pinnacle Sports API error: " + apiResponse.Error.Message); + } + + public List GetSports() + { + return GetXmlAsync("sports").Sports; + } + + public List GetLeagues(int sportId) + { + return GetXmlAsync("leagues?sportid={0}", sportId).Leagues; + } + + public List GetCurrencies() + { + return GetXmlAsync("currencies").Currencies; + } + + #region GetFeed + + protected string GetFeedRequestUri(int sportId, int[] leagueId, OddsFormat format, string currency, long lastTimestamp, int isLive) + { + var sb = new StringBuilder(); + sb.AppendFormat("feed?sportid={0}", sportId); + sb.AppendFormat("&leagueid={0}", string.Join("-", leagueId)); + sb.AppendFormat("&oddsformat={0}", (int)format); + sb.AppendFormat("¤cycode={0}", currency); + + if (lastTimestamp > 0) + { + sb.AppendFormat("&last={0}", lastTimestamp); + } + + if (isLive == 0 || isLive == 1) + { + sb.AppendFormat("&islive={0}", isLive); + } + + return sb.ToString(); + } + + protected bool IsFairFeedRequest(long lastTimestamp) + { + if (_lastFeedRequest.HasValue) + { + var minRequestInterval = (lastTimestamp > 0 ? MinimumFeedRefreshWithLast : MinimumFeedRefresh); + var interval = DateTime.Now - _lastFeedRequest.Value; + + if (interval.TotalSeconds < minRequestInterval) + { + return false; + } + } + + return true; + } + + protected Feed GetFeed(int sportId, int[] leagueIds, OddsFormat format, string currency, long lastTimestamp, int isLive) + { + if (string.IsNullOrWhiteSpace(_clientId)) + { + throw new Exception("Client ID is mandatory when requesting feeds"); + } + + if (string.IsNullOrWhiteSpace(_password)) + { + throw new Exception("Password is mandatory when requesting feeds"); + } + + if (!IsFairFeedRequest(lastTimestamp)) + throw new Exception( + string.Format( + "Too many feed requests. Minimum interval time between request is {0} seconds or {1} seconds when specifying the last parameter", + MinimumFeedRefresh, + MinimumFeedRefreshWithLast)); + + _lastFeedRequest = DateTime.Now; + var uri = GetFeedRequestUri(sportId, leagueIds, format, currency, lastTimestamp, isLive); + + return GetXmlAsync(uri).Feed; + } + + public Feed GetFeed(int sportId, int[] leagueIds) + { + return GetFeed(sportId, leagueIds, OddsFormat, CurrencyCode, -1, -1); + } + + public Feed GetFeed(int sportId, int[] leagueIds, long lastTimestamp) + { + return GetFeed(sportId, leagueIds, OddsFormat, CurrencyCode, lastTimestamp, -1); + } + + public Feed GetFeed(int sportId, int[] leagueIds, OddsFormat format, string currency) + { + return GetFeed(sportId, leagueIds, format, currency, -1, -1); + } + + public Feed GetFeed(int sportId, int[] leagueIds, OddsFormat format, string currency, bool isLive) + { + return GetFeed(sportId, leagueIds, format, currency, -1, isLive ? 1 : 0); + } + + public Feed GetFeed(int sportId, int[] leagueIds, OddsFormat format, string currency, long lastTimestamp) + { + return GetFeed(sportId, leagueIds, format, currency, lastTimestamp, -1); + } + + public Feed GetFeed(int sportId, int[] leagueIds, OddsFormat format, string currency, long lastTimestamp, bool isLive) + { + return GetFeed(sportId, leagueIds, format, currency, lastTimestamp, isLive ? 1 : 0); + } + + #endregion + + protected T GetJsonAsync(string requestType, params object[] values) + { + var response = _httpClient.GetAsync(string.Format(requestType, values)).Result; + + response.EnsureSuccessStatusCode(); // throw if web request failed + + var json = response.Content.ReadAsStringAsync().Result; + + // deserialise json async + var apiResponse = Task.Factory.StartNew(() => JsonConvert.DeserializeObject(json)).Result; + + return apiResponse; + } + + // ToDo: replace requestData object type with "IJsonSerialisable" + protected T PostJsonAsync(string requestType, object requestData) + { + var requestPostData = JsonConvert.SerializeObject(requestData); + + var response = _httpClient.PostAsync(requestType, + new StringContent(requestPostData, Encoding.UTF8, "application/json")).Result; + + response.EnsureSuccessStatusCode(); // throw if web request failed + + var json = response.Content.ReadAsStringAsync().Result; + + // deserialise async + return Task.Factory.StartNew(() => JsonConvert.DeserializeObject(json)).Result; + } + + public ClientBalance GetClientBalance() + { + const string uri = "client/balance"; + return GetJsonAsync(uri); + } + + public GetBetsResponse GetBets(BetListType type, DateTime startDate, DateTime endDate) + { + // get request uri + var sb = new StringBuilder(); + sb.AppendFormat("bets?betlist={0}", type.ToString().ToLower()); + sb.AppendFormat("&fromDate={0}", startDate.ToString("yyyy-MM-dd")); + sb.AppendFormat("&toDate={0}", endDate.ToString("yyyy-MM-dd")); + + var uri = sb.ToString(); + + return GetJsonAsync(uri); + } + + public GetBetsResponse GetBets(List betIds) + { + // get request uri + var sb = new StringBuilder(); + + sb.AppendFormat("bets?betids={0}", string.Join(",", betIds)); + + var uri = sb.ToString(); + + return GetJsonAsync(uri); + } + + public PlaceBetResponse PlaceBet(PlaceBetRequest placeBetRequest) + { + return PostJsonAsync("bets/place", placeBetRequest); + } + + /// + /// + /// + /// + /// + /// + /// 0 for sports other than soccer, soccer: 0 = Game, 1 = 1st Half, 2 = 2nd Half + /// + /// + /// + /// + /// + /// + public GetLineResponse GetLine(int sportId, int leagueId, long eventId, int periodNumber, BetType betType, OddsFormat oddsFormat, + TeamType? team = null, SideType? side = null, decimal? handicap = null) + { + if (team == null) + { + if (betType == BetType.MoneyLine || betType == BetType.Spread || betType == BetType.TeamTotalPoints) + throw new Exception(string.Format("TeamType is required for {0} Bets!", betType)); + } + + if (side == null) + { + if (betType == BetType.TotalPoints || betType == BetType.TeamTotalPoints) + throw new Exception(string.Format("SideType is required for {0} Bets!", betType)); + } + + if (handicap == null) + { + if (betType == BetType.Spread || betType == BetType.TotalPoints || betType == BetType.TeamTotalPoints) + throw new Exception(string.Format("Handicap is required for {0} Bets!", betType)); + } + + // get request uri + var sb = new StringBuilder(); + sb.AppendFormat("line?sportId={0}", sportId); + sb.AppendFormat("&leagueId={0}", leagueId); + sb.AppendFormat("&eventId={0}", eventId); + sb.AppendFormat("&betType={0}", betType.ToString().ToUpper()); + sb.AppendFormat("&oddsFormat={0}", oddsFormat.ToString().ToUpper()); + sb.AppendFormat("&periodNumber={0}", periodNumber); // i.e. for soccer: 0 = Game, 1 = 1st Half, 2 = 2nd Half + + if (team != null) + sb.AppendFormat("&team={0}", team.ToString().ToUpper()); + + if (side != null) + sb.AppendFormat("&side={0}", side.ToString().ToUpper()); + + if (handicap != null) + sb.AppendFormat("&handicap={0}", handicap.ToString().ToUpper()); + + var uri = sb.ToString(); + + return GetJsonAsync(uri); + } + + public GetInRunningResponse GetInRunning() + { + const string uri = "inrunning"; + return GetJsonAsync(uri); + } + } +} + diff --git a/PinnacleWrapper.csproj b/PinnacleWrapper.csproj new file mode 100644 index 0000000..5ab59a6 --- /dev/null +++ b/PinnacleWrapper.csproj @@ -0,0 +1,102 @@ + + + + + Debug + AnyCPU + {49CCCA7D-98C3-44BA-94F6-45DA4AFF7886} + Library + Properties + PinnacleWrapper + PinnacleWrapper + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + + + + + + False + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PlaceBetErrorCode.cs b/PlaceBetErrorCode.cs new file mode 100644 index 0000000..626c43c --- /dev/null +++ b/PlaceBetErrorCode.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PinnacleWrapper +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum PlaceBetErrorCode + { + AllBettingClosed, //ALL_BETTING_CLOSED Betting is not allowed at this moment + AllLiveBettingClosed, //ALL_LIVE_BETTING_CLOSED Live betting is not allowed at this moment + BlockedClient, //BLOCKED_CLIENT Client is no longer active + InvalidCountry, //INVALID_COUNTRY Client country is not allowed for betting + BlockedBetting, //BLOCKED_BETTING Not allowed betting for the client + InvalidEvent, //INVALID_EVENT Invalid eventid + AboveMaxBetAmount, //ABOVE_MAX_BET_AMOUNT Stake is above allowed maximum amount + BelowMinBetAmount, //BELOW_MIN_BET_AMOUNT Stake is below allowed minimum amount + OfflineEvent, //OFFLINE_EVENT Bet is submitted on a event that is offline + InsufficientFunds, //INSUFFICIENT_FUNDS Bet is submitted by a client with insufficient funds + LineChanged, //LINE_CHANGED Bet is submitted on a line that has changed + RedCardsChanged, //RED_CARDS_CHANGED Bet is submitted on a live soccer event with changed red card count + ScoreChanged, //SCORE_CHANGED Bet is submitted on a live soccer event with changed score + TimeRestriction, //TIME_RESTRICTION Bet is submitted within too short of a period from the same bet previously placed by a client + PastCutOffTime, //PAST_CUTOFFTIME Bet is submitted on a game after the betting cutoff time + AboveEventMax, //ABOVE_EVENT_MAX Bet cannot be placed because client exceeded allowed maximum of risk on a line + InvalidOddsFormat, //INVALID_ODDS_FORMAT If a bet was submitted with the odds format that is not allowed for the client + ListedPitchersSelectionError //LISTED_PITCHERS_SELECTION_ERROR If bet was submitted with pitcher1MustStart and/or pitcher2MustStart parameters in Place Bet request with values that are not allowed. + } +} diff --git a/PlaceBetRequest.cs b/PlaceBetRequest.cs new file mode 100644 index 0000000..e7e16c1 --- /dev/null +++ b/PlaceBetRequest.cs @@ -0,0 +1,56 @@ +using System; +using Newtonsoft.Json; + +namespace PinnacleWrapper +{ + public class PlaceBetRequest + { + [JsonProperty(PropertyName = "uniqueRequestId")] + public Guid UniqueRequestId; + + [JsonProperty(PropertyName = "acceptBetterLine")] + public bool AcceptBetterLine; + + [JsonProperty(PropertyName = "customerReference")] // not required + public string CustomerReference; + + [JsonProperty(PropertyName = "ODDS_FORMAT")] + public OddsFormat OddsFormat; + + [JsonProperty(PropertyName = "stake")] + public decimal Stake; + + [JsonProperty(PropertyName = "winRiskStake")] + public WinRiskType WinRiskType; + + [JsonProperty(PropertyName = "sportId")] + public int SportId; + + [JsonProperty(PropertyName = "eventId")] + public decimal EventId; + + [JsonProperty(PropertyName = "periodNumber")] // This represents the period of the match. For example, for soccer we have: 0 - Game, 1 - 1st Half, 2 - 2nd Half + public int PeriodNumber; + + [JsonProperty(PropertyName = "betType")] + public BetType BetType; + + [JsonProperty(PropertyName = "team")] // Chosen team type. This is needed only for SPREAD, MONEYLINE and TEAM_TOTAL_POINTS bet types + public TeamType? TeamType; + + [JsonProperty(PropertyName = "side")] // Chosen side. This is needed only for TOTAL_POINTS and TEAM_TOTAL_POINTS bet type + public SideType? SideType; + + [JsonProperty(PropertyName = "lineId")] + public int LineId; + + [JsonProperty(PropertyName = "altLineId")] // Alternate line identification. Not required + public int? AltLineId; + + [JsonProperty(PropertyName = "pitcher1MustStart")] // Baseball only. Refers to the pitcher for TEAM_TYPE.Team1. This applicable only for MONEYLINE bet type, for all other bet types this has to be TRUE + public bool? Pitcher1MustStart; + + [JsonProperty(PropertyName = "pitcher2MustStart")] // Baseball only. Refers to the pitcher for TEAM_TYPE.Team1. This applicable only for MONEYLINE bet type, for all other bet types this has to be TRUE + public bool? Pitcher2MustStart; + } +} diff --git a/PlaceBetResponse.cs b/PlaceBetResponse.cs new file mode 100644 index 0000000..5f85d9e --- /dev/null +++ b/PlaceBetResponse.cs @@ -0,0 +1,23 @@ +using System; +using Newtonsoft.Json; + +namespace PinnacleWrapper +{ + public class PlaceBetResponse + { + [JsonProperty(PropertyName = "status")] + public PlaceBetResponseStatus Status; + + [JsonProperty(PropertyName = "errorCode")] // If Status is PROCESSED_WITH_ERROR, errorCode will be in the response. + public PlaceBetErrorCode ErrorCode; + + [JsonProperty(PropertyName = "betId")] // The bet ID of the new bet. May be empty on failure. + public int? BetId; + + [JsonProperty(PropertyName = "uniqueRequestId")] // Echo of the uniqueRequestId from the request. + public Guid UniquerequestId; + + [JsonProperty(PropertyName = "betterLineWasAccepted")] // Whether or not the bet was accepted on the line that changed in favour of client. + public bool BetterLineWasAccepted; + } +} diff --git a/PlaceBetResponseStatus.cs b/PlaceBetResponseStatus.cs new file mode 100644 index 0000000..8b22657 --- /dev/null +++ b/PlaceBetResponseStatus.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PinnacleWrapper +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum PlaceBetResponseStatus + { + Accepted, + PendingAcceptance, // live bets in the danger zone + ProcessedWithError + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d0c34a3 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PinnacleWrapper")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PinnacleWrapper")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e419dc30-9a72-4481-afdc-aed639138278")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ResponseError.cs b/ResponseError.cs new file mode 100644 index 0000000..d89f088 --- /dev/null +++ b/ResponseError.cs @@ -0,0 +1,16 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("err")] + public class ResponseError + { + [XmlAttribute("code")] + public int Code { get; set; } + + [XmlText] + public string Message { get; set; } + } +} diff --git a/SideType.cs b/SideType.cs new file mode 100644 index 0000000..82663ff --- /dev/null +++ b/SideType.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PinnacleWrapper +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum SideType + { + Over, + Under + } +} diff --git a/Sport.cs b/Sport.cs new file mode 100644 index 0000000..1b2c53a --- /dev/null +++ b/Sport.cs @@ -0,0 +1,19 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("sport")] + public class Sport + { + [XmlAttribute("id")] + public int Id { get; set; } + + [XmlAttribute("feedContents")] + public int FeedContents { get; set; } + + [XmlText] + public string Title { get; set; } + } +} diff --git a/SportsResponse.cs b/SportsResponse.cs new file mode 100644 index 0000000..a987c6b --- /dev/null +++ b/SportsResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("rsp")] + public class SportsResponse : XmlResponse + { + [XmlArray("sports")] + [XmlArrayItem("sport")] + public List Sports { get; set; } + } +} diff --git a/Spread.cs b/Spread.cs new file mode 100644 index 0000000..85a972c --- /dev/null +++ b/Spread.cs @@ -0,0 +1,22 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("spread")] + public class Spread + { + [XmlElement("awaySpread")] + public double AwaySpread { get; set; } + + [XmlElement("awayPrice")] + public double AwayPrice { get; set; } + + [XmlElement("homeSpread")] + public double HomeSpread { get; set; } + + [XmlElement("homePrice")] + public double HomePrice { get; set; } + } +} diff --git a/Status.cs b/Status.cs new file mode 100644 index 0000000..7dabc15 --- /dev/null +++ b/Status.cs @@ -0,0 +1,25 @@ +namespace PinnacleWrapper +{ + public enum Status + { + /// + /// This is the starting status of a game. It means that the lines are open for betting. + /// + Open, + + /// + /// This status indicates that one or more lines have a red circle (a lower maximum bet amount). + /// + LowerMaximum, + + /// + /// This status indicates that the lines are temporarily unavailable for betting. + /// + Unavailable, + + /// + /// When a game is cancelled all bets on the game are refunded and the status becomes canceled. + /// + Cancelled + } +} diff --git a/Team.cs b/Team.cs new file mode 100644 index 0000000..bb75ae4 --- /dev/null +++ b/Team.cs @@ -0,0 +1,18 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + public class Team + { + [XmlAttribute("type")] + public string Type { get; set; } + + [XmlElement("name")] + public string Name { get; set; } + + [XmlElement("rotNum")] + public int RotNum { get; set; } + } +} diff --git a/TeamType.cs b/TeamType.cs new file mode 100644 index 0000000..651daa0 --- /dev/null +++ b/TeamType.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PinnacleWrapper +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum TeamType + { + Team1, + Team2, + Draw + } +} diff --git a/WinRiskType.cs b/WinRiskType.cs new file mode 100644 index 0000000..3258570 --- /dev/null +++ b/WinRiskType.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PinnacleWrapper +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum WinRiskType + { + Win, + Risk + } +} diff --git a/XmlResponse.cs b/XmlResponse.cs new file mode 100644 index 0000000..601a9ee --- /dev/null +++ b/XmlResponse.cs @@ -0,0 +1,26 @@ +using System; +using System.Xml.Serialization; + +namespace PinnacleWrapper +{ + [Serializable] + [XmlRoot("rsp")] + public abstract class XmlResponse + { + [XmlAttribute("status")] + public string Status { get; set; } + + [XmlIgnore] + public bool IsValid + { + get + { + return !string.IsNullOrWhiteSpace(Status) + && Status.Equals("ok", StringComparison.OrdinalIgnoreCase); + } + } + + [XmlElement("err")] + public ResponseError Error { get; set; } + } +} diff --git a/packages.config b/packages.config new file mode 100644 index 0000000..9ce6161 --- /dev/null +++ b/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file