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