├── .gitignore ├── ESASwaggerSchema.json ├── README.md ├── clojure-app ├── .gitignore ├── project.clj ├── src │ └── clojure_app │ │ ├── async_sockets │ │ └── async_sockets.clj │ │ └── core.clj └── test │ └── clojure_app │ └── core_test.clj ├── csharp ├── .gitignore ├── Betfair.ESAClient │ ├── Betfair.ESAClient.Test │ │ ├── Auth │ │ │ └── AppKeyAndSessionProviderTest.cs │ │ ├── BaseTest.cs │ │ ├── Betfair.ESAClient.Test.csproj │ │ ├── ClientCacheTest.cs │ │ ├── ClientTest.cs │ │ ├── Impl │ │ │ └── RequestResponseProcessorTest.cs │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ └── Retry.cs │ ├── Betfair.ESAClient.sln │ ├── Betfair.ESAClient │ │ ├── Auth │ │ │ ├── AppKeyAndSession.cs │ │ │ └── AppKeyAndSessionProvider.cs │ │ ├── Betfair.ESAClient.csproj │ │ ├── Cache │ │ │ ├── LevelPriceSize.cs │ │ │ ├── LevelPriceSizeLadder.cs │ │ │ ├── Market.cs │ │ │ ├── MarketCache.cs │ │ │ ├── MarketRunner.cs │ │ │ ├── MarketRunnerPrices.cs │ │ │ ├── MarketRunnerSnap.cs │ │ │ ├── MarketSnap.cs │ │ │ ├── OrderCache.cs │ │ │ ├── OrderMarket.cs │ │ │ ├── OrderMarketRunner.cs │ │ │ ├── OrderMarketRunnerSnap.cs │ │ │ ├── OrderMarketSnap.cs │ │ │ ├── PriceSize.cs │ │ │ ├── PriceSizeLadder.cs │ │ │ ├── ReverseComparer.cs │ │ │ ├── RunnerId.cs │ │ │ └── Utils.cs │ │ ├── Client.cs │ │ ├── ClientCache.cs │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ └── Protocol │ │ │ ├── ChangeMessage.cs │ │ │ ├── ChangeMessageFactory.cs │ │ │ ├── ChangeType.cs │ │ │ ├── ConnectionStatus.cs │ │ │ ├── IChangeMessageHandler.cs │ │ │ ├── RequestResponse.cs │ │ │ ├── RequestResponseProcessor.cs │ │ │ ├── SegmentationType.cs │ │ │ ├── StatusException.cs │ │ │ └── SubscriptionHandler.cs │ ├── Betfair.ESAConsoleApp │ │ ├── App.config │ │ ├── Betfair.ESAConsoleApp.csproj │ │ ├── Program.cs │ │ └── Properties │ │ │ ├── AssemblyInfo.cs │ │ │ ├── Settings.Designer.cs │ │ │ └── Settings.settings │ ├── Betfair.ESASwagger │ │ ├── Betfair.ESASwagger.csproj │ │ └── Model │ │ │ ├── AllRequestTypesExample.cs │ │ │ ├── AllResponseTypesExample.cs │ │ │ ├── AuthenticationMessage.cs │ │ │ ├── ConnectionMessage.cs │ │ │ ├── HeartbeatMessage.cs │ │ │ ├── MarketChange.cs │ │ │ ├── MarketChangeMessage.cs │ │ │ ├── MarketDataFilter.cs │ │ │ ├── MarketDefinition.cs │ │ │ ├── MarketFilter.cs │ │ │ ├── MarketSubscriptionMessage.cs │ │ │ ├── Order.cs │ │ │ ├── OrderChangeMessage.cs │ │ │ ├── OrderFilter.cs │ │ │ ├── OrderMarketChange.cs │ │ │ ├── OrderRunnerChange.cs │ │ │ ├── OrderSubscriptionMessage.cs │ │ │ ├── RequestMessage.cs │ │ │ ├── ResponseMessage.cs │ │ │ ├── RunnerChange.cs │ │ │ ├── RunnerDefinition.cs │ │ │ └── StatusMessage.cs │ ├── nuget.bat │ └── nuget.exe ├── esa-csharp.runsettings └── pom.xml ├── docker-compose.yml ├── java ├── .gitignore ├── Dockerfile ├── client │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── betfair │ │ │ └── esa │ │ │ └── client │ │ │ ├── Client.java │ │ │ ├── ClientCache.java │ │ │ ├── auth │ │ │ ├── AppKeyAndSessionProvider.java │ │ │ ├── AppKeyAndToken.java │ │ │ └── InvalidCredentialException.java │ │ │ ├── cache │ │ │ ├── market │ │ │ │ ├── Market.java │ │ │ │ ├── MarketCache.java │ │ │ │ ├── MarketRunner.java │ │ │ │ ├── MarketRunnerPrices.java │ │ │ │ ├── MarketRunnerSnap.java │ │ │ │ └── MarketSnap.java │ │ │ ├── order │ │ │ │ ├── OrderCache.java │ │ │ │ └── OrderMarket.java │ │ │ └── util │ │ │ │ ├── LevelPriceSize.java │ │ │ │ ├── LevelPriceSizeLadder.java │ │ │ │ ├── OrderMarketRunner.java │ │ │ │ ├── OrderMarketRunnerSnap.java │ │ │ │ ├── OrderMarketSnap.java │ │ │ │ ├── PriceSize.java │ │ │ │ ├── PriceSizeLadder.java │ │ │ │ ├── RunnerId.java │ │ │ │ └── Utils.java │ │ │ └── protocol │ │ │ ├── ChangeMessage.java │ │ │ ├── ChangeMessageFactory.java │ │ │ ├── ChangeMessageHandler.java │ │ │ ├── ChangeType.java │ │ │ ├── ConnectionException.java │ │ │ ├── ConnectionStatus.java │ │ │ ├── ConnectionStatusChangeEvent.java │ │ │ ├── ConnectionStatusListener.java │ │ │ ├── FutureResponse.java │ │ │ ├── MixInResponseMessage.java │ │ │ ├── RequestResponse.java │ │ │ ├── RequestResponseProcessor.java │ │ │ ├── RequestSender.java │ │ │ ├── SegmentType.java │ │ │ ├── StatusException.java │ │ │ └── SubscriptionHandler.java │ │ └── test │ │ └── java │ │ └── com │ │ └── betfair │ │ └── esa │ │ └── client │ │ ├── BaseTest.java │ │ ├── ClientCacheTest.java │ │ └── ClientTest.java ├── console │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── betfair │ │ │ └── esa │ │ │ └── console │ │ │ ├── ClientMain.java │ │ │ └── commands │ │ │ └── ClientCommands.java │ │ └── resources │ │ ├── META-INF │ │ └── spring │ │ │ └── spring-shell-plugin.xml │ │ └── banner.txt ├── pom.xml └── swagger │ └── pom.xml ├── node.js ├── app.js └── package.json └── stream-api-specification.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | ############################## 2 | ## Java 3 | ############################## 4 | .mtj.tmp/ 5 | *.class 6 | *.jar 7 | *.war 8 | *.ear 9 | *.nar 10 | hs_err_pid* 11 | 12 | ############################## 13 | ## Maven 14 | ############################## 15 | target/ 16 | pom.xml.tag 17 | pom.xml.releaseBackup 18 | pom.xml.versionsBackup 19 | pom.xml.next 20 | pom.xml.bak 21 | release.properties 22 | dependency-reduced-pom.xml 23 | buildNumber.properties 24 | .mvn/timing.properties 25 | .mvn/wrapper/maven-wrapper.jar 26 | 27 | ############################## 28 | ## Gradle 29 | ############################## 30 | bin/ 31 | build/ 32 | .gradle 33 | .gradletasknamecache 34 | gradle-app.setting 35 | !gradle-wrapper.jar 36 | 37 | ############################## 38 | ## IntelliJ 39 | ############################## 40 | out/ 41 | .idea/ 42 | .idea_modules/ 43 | *.iml 44 | *.ipr 45 | *.iws 46 | 47 | ############################## 48 | ## Eclipse 49 | ############################## 50 | .settings/ 51 | bin/ 52 | tmp/ 53 | .metadata 54 | .classpath 55 | .project 56 | *.tmp 57 | *.bak 58 | *.swp 59 | *~.nib 60 | local.properties 61 | .loadpath 62 | .factorypath 63 | 64 | ############################## 65 | ## NetBeans 66 | ############################## 67 | nbproject/private/ 68 | build/ 69 | nbbuild/ 70 | dist/ 71 | nbdist/ 72 | nbactions.xml 73 | nb-configuration.xml 74 | 75 | ############################## 76 | ## Visual Studio Code 77 | ############################## 78 | .vscode/ 79 | .code-workspace 80 | 81 | ############################## 82 | ## OS X 83 | ############################## 84 | .DS_Store 85 | 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stream-api-sample-code 2 | Sample code for the exchange stream api which provides real-time market and order data from the betfair exchange. 3 | 4 | # Console App 5 | The console app enables you to explore most of the API functions; please note that: 6 | 7 | 8 | 1. Your appkey must be setup for streaming (contact Developer Support [here](https://betfair-developer-docs.atlassian.net/wiki/spaces/1smk3cen4v3lu3yomq5qye0ni/pages/2687786/Getting+Started) ) 9 | 10 | 2. Credentials for .net are stored in AppData in plain text 11 | 12 | **Note: Use stream-api.betfair.com in production (stream-api-integration.betfair.com is for testing and has no backup).** 13 | 14 | # Client overview 15 | The basic client structure is separated into a number of components: 16 | * AppKeyAndSessionProvider - this class is used to authenticate & provide a session token. 17 | * Client - this class provides a connection to the stream 18 | * ClientCache - this class provides a thread safe cache that may be used to: 19 | * Respond to discrete changes 20 | * Respond to batch changes 21 | * Directly query the cache 22 | 23 | # Getting started 24 | The below is found in the ClientCacheTest and exhibits the basic setup user story: 25 | 26 | //1: Create a session provider 27 | AppKeyAndSessionProvider sessionProvider = new AppKeyAndSessionProvider( 28 | AppKeyAndSessionProvider.SSO_HOST_COM, 29 | AppKey, 30 | UserName, 31 | Password); 32 | 33 | //2: Create a client 34 | Client client = new Client( 35 | "stream-api-integration.betfair.com", //NOTE: use production endpoint in prod: stream-api.betfair.com 36 | 443, 37 | sessionProvider); 38 | 39 | //3: Create a cache 40 | ClientCache cache = new ClientCache(client); 41 | 42 | //4: Setup order subscription 43 | //Register for change events 44 | cache.OrderCache.OrderMarketChanged += 45 | (sender, arg) => Console.WriteLine("Order:" + arg.Snap); 46 | //Subscribe to orders 47 | cache.SubscribeOrders(); 48 | 49 | //5: Setup market subscription 50 | //Register for change events 51 | cache.MarketCache.MarketChanged += 52 | (sender, arg) => Console.WriteLine("Market:" + arg.Snap); 53 | //Subscribe to markets (use a valid market id or filter) 54 | cache.SubscribeMarkets("1.125037533"); 55 | 56 | ## Connection semantics 57 | A few tips on basic connections: 58 | 59 | 1. No need to explicitly start / stop if using client cache 60 | 2. Status event allows you to monitor status 61 | 3. AutoReconnect is enabled by default and will establish and re-subscribe any subscriptions. 62 | 4. Exceptions are routed up to subscribe methods 63 | 64 | ## Market Subscription 65 | A few tips on market subscription: 66 | 67 | 1. MarketDataFilter - correctly setting this on the cache improves performance 68 | 2. MarketFilter - you are not limited to knowing specific market ids; wildcards let you know as soon as a market appears 69 | 3. ConflateMs - you can slow down the data rate (to say a GUI refresh rate). 70 | 4. By default markets are deleted on close 71 | 72 | ## Order Subscription 73 | A few tips on order subscription: 74 | 75 | 1. Orders are retrieved for your id 76 | 2. On initial connection (or re-connect) only unmatched / executable orders are returned. 77 | 3. Matches are price point aggregated 78 | 4. By default markets are deleted on close 79 | 80 | ## Docker 81 | It is possible to run the java application through a docker container, if you want to build yourself and run, execute on parent directory: 82 | 1. docker build -t "tag" -f java/Dockerfile . 83 | 2. docker run -it "tag" 84 | 85 | Otherwise make use of docker-compose features: 86 | 1. Simply with a single command "docker-compose run esaconsole" will set everything up. 87 | 88 | ## Specification & Schema 89 | ###Specification: 90 | https://github.com/betfair/stream-api-sample-code/blob/master/stream-api-specification.pdf 91 | 92 | ###Schema 93 | We publish a swagger schema to define the object model: 94 | http://editor.swagger.io/#/?import=https://raw.githubusercontent.com/betfair/stream-api-sample-code/master/ESASwaggerSchema.json 95 | (_Note: the stream is not a rest service so generated swagger clients are of limited use although you can use the generated object model_) 96 | -------------------------------------------------------------------------------- /clojure-app/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .hgignore 12 | .hg/ 13 | -------------------------------------------------------------------------------- /clojure-app/project.clj: -------------------------------------------------------------------------------- 1 | (defproject clojure-app "0.1.0-SNAPSHOT" 2 | :description "BF streaming" 3 | :url "http://example.com/FIXME" 4 | :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" 5 | :url "https://www.eclipse.org/legal/epl-2.0/"} 6 | :dependencies [[org.clojure/clojure "1.10.1"] 7 | [org.clojure/data.json "0.2.6"] 8 | [org.clojure/core.async "0.4.500"] 9 | [org.clojure/tools.logging "0.5.0"]] 10 | :main ^:skip-aot clojure-app.core 11 | :target-path "target/%s" 12 | :profiles {:uberjar {:aot :all}}) 13 | -------------------------------------------------------------------------------- /clojure-app/src/clojure_app/core.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-app.core 2 | (:require [clojure.data.json :as json] 3 | [clojure.core.async :as async] 4 | [clojure-app.async-sockets.async-sockets :as async-socket]) 5 | (:gen-class)) 6 | 7 | (def options {:host "stream-api.betfair.com" 8 | :port 443}) 9 | 10 | (def auth {:op "authentication" 11 | :appKey "" 12 | :session ""}) 13 | 14 | (def market-req {:op "marketSubscription" 15 | :marketFilter {:marketIds [] 16 | :bettingTypes ["ODDS"] 17 | :eventTypeIds [1 4] 18 | :eventIds [] 19 | :marketTypes ["MATCH_ODDS"]} 20 | :marketDataFilter {}}) 21 | 22 | (defn event-loop [socket] 23 | (loop [] 24 | (when-let [line (async/!! (:out socket) (json/write-str auth)) 31 | (prn "Send authentication message")) 32 | (when (= op "status") 33 | (async/>!! (:out socket) (json/write-str market-req)) 34 | (prn "Subscribe to order/market stream")) 35 | (recur))))) 36 | 37 | (defn -main [& _] 38 | (let [socket (async-socket/socket-client (:port options) (:host options))] 39 | (event-loop socket))) 40 | -------------------------------------------------------------------------------- /clojure-app/test/clojure_app/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-app.core-test 2 | (:require [clojure.test :refer :all] 3 | [clojure-app.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 1 1)))) 8 | -------------------------------------------------------------------------------- /csharp/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient.Test/Auth/AppKeyAndSessionProviderTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Security.Authentication; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Betfair.ESAClient.Test.Auth { 6 | [TestClass] 7 | public class AppKeyAndSessionProviderTest : BaseTest { 8 | [TestMethod] 9 | public void TestValidSession() { 10 | var session = ValidSessionProvider.GetOrCreateNewSession(); 11 | Assert.IsNotNull(session); 12 | } 13 | 14 | [TestMethod] 15 | [ExpectedException(typeof(IOException))] 16 | public void TestInvalidHost() { 17 | InvalidHostSessionProvider.GetOrCreateNewSession(); 18 | } 19 | 20 | [TestMethod] 21 | [ExpectedException(typeof(InvalidCredentialException))] 22 | public void TestInvalidLogin() { 23 | InvalidLoginSessionProvider.GetOrCreateNewSession(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient.Test/BaseTest.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Betfair.ESAClient.Auth; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Betfair.ESAClient.Test { 6 | public abstract class BaseTest { 7 | static BaseTest() { 8 | //setup logging 9 | var traceListener = new ConsoleTraceListener(); 10 | traceListener.TraceOutputOptions = TraceOptions.DateTime; 11 | Trace.Listeners.Add(traceListener); 12 | } 13 | 14 | public TestContext TestContext { get; set; } 15 | 16 | public string SsoHost => (string) TestContext.Properties["SsoHost"]; 17 | public string AppKey => (string) TestContext.Properties["AppKey"]; 18 | public string UserName => (string) TestContext.Properties["UserName"]; 19 | public string Password => (string) TestContext.Properties["Password"]; 20 | 21 | public AppKeyAndSessionProvider ValidSessionProvider => new AppKeyAndSessionProvider(SsoHost, 22 | AppKey, 23 | UserName, 24 | Password); 25 | 26 | public AppKeyAndSessionProvider InvalidHostSessionProvider => new AppKeyAndSessionProvider("www.betfair.com", 27 | "a", 28 | "b", 29 | "c"); 30 | 31 | public AppKeyAndSessionProvider InvalidLoginSessionProvider => new AppKeyAndSessionProvider(AppKeyAndSessionProvider.SSO_HOST_COM, 32 | "appkey", 33 | "username", 34 | "password"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient.Test/Betfair.ESAClient.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {19CC59D3-3664-4AA7-919A-CB39470976EA} 7 | Library 8 | Properties 9 | Betfair.ESAClient.Test 10 | Betfair.ESAClient.Test 11 | v4.7.2 12 | 512 13 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {C6F11E1B-DA85-41BC-9DEE-B941D3C6A805} 59 | Betfair.ESAClient 60 | 61 | 62 | {84a4f60c-5348-4ea0-849c-e6a1b204ec28} 63 | Betfair.ESASwagger 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | False 74 | 75 | 76 | False 77 | 78 | 79 | False 80 | 81 | 82 | False 83 | 84 | 85 | 86 | 87 | 88 | 89 | 96 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient.Test/ClientCacheTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Betfair.ESAClient.Auth; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace Betfair.ESAClient.Test { 7 | [TestClass] 8 | public class ClientCacheTest : BaseTest { 9 | [TestMethod] 10 | public void TestUserStory() { 11 | //1: Create a session provider 12 | var sessionProvider = new AppKeyAndSessionProvider(AppKeyAndSessionProvider.SSO_HOST_COM, 13 | AppKey, 14 | UserName, 15 | Password); 16 | 17 | //2: Create a client 18 | var client = new Client("stream-api-integration.betfair.com", 443, sessionProvider); 19 | 20 | //3: Create a cache 21 | var cache = new ClientCache(client); 22 | 23 | //4: Setup order subscription 24 | //Register for change events 25 | cache.OrderCache.OrderMarketChanged += (sender, arg) => Console.WriteLine("Order:" + arg.Snap); 26 | //Subscribe to orders 27 | cache.SubscribeOrders(); 28 | 29 | //5: Setup market subscription 30 | //Register for change events 31 | cache.MarketCache.MarketChanged += (sender, arg) => Console.WriteLine("Market:" + arg.Snap); 32 | //Subscribe to markets (use a valid market id or filter) 33 | cache.SubscribeMarkets("1.125037533"); 34 | 35 | Thread.Sleep(5000); //pause for a bit 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient.Test/ClientTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Betfair.ESAClient.Protocol; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Betfair.ESAClient.Test { 6 | [TestClass] 7 | public class ClientTest : BaseTest { 8 | private Client Client; 9 | 10 | [TestInitialize] 11 | public void TestInitialize() { 12 | Client = new Client("stream-api-integration.betfair.com", 443, ValidSessionProvider); 13 | } 14 | 15 | [TestCleanup] 16 | public void TestCleanup() { 17 | Client.Stop(); 18 | } 19 | 20 | 21 | [TestMethod] 22 | [ExpectedException(typeof(TimeoutException))] 23 | public void TestInvalidHost() { 24 | var invalidClient = new Client("www.betfair.com", 443, ValidSessionProvider); 25 | invalidClient.Timeout = TimeSpan.FromMilliseconds(100); 26 | invalidClient.Start(); 27 | } 28 | 29 | [TestMethod] 30 | public void TestStartStop() { 31 | Assert.AreEqual(ConnectionStatus.STOPPED, Client.Status); 32 | Client.Start(); 33 | Assert.AreEqual(ConnectionStatus.AUTHENTICATED, Client.Status); 34 | Client.Stop(); 35 | Assert.AreEqual(ConnectionStatus.STOPPED, Client.Status); 36 | } 37 | 38 | [TestMethod] 39 | public void TestStartHearbeatStop() { 40 | Client.Start(); 41 | Client.Heartbeat(); 42 | Client.Stop(); 43 | } 44 | 45 | [TestMethod] 46 | public void TestReentrantStartStop() { 47 | Client.Start(); 48 | Assert.AreEqual(ConnectionStatus.AUTHENTICATED, Client.Status); 49 | Client.Heartbeat(); 50 | Client.Stop(); 51 | Assert.AreEqual(ConnectionStatus.STOPPED, Client.Status); 52 | 53 | Client.Start(); 54 | Assert.AreEqual(ConnectionStatus.AUTHENTICATED, Client.Status); 55 | Client.Heartbeat(); 56 | Client.Stop(); 57 | Assert.AreEqual(ConnectionStatus.STOPPED, Client.Status); 58 | } 59 | 60 | [TestMethod] 61 | public void TestDoubleStartStop() { 62 | Client.Start(); 63 | Client.Start(); 64 | Assert.AreEqual(ConnectionStatus.AUTHENTICATED, Client.Status); 65 | Client.Heartbeat(); 66 | Client.Stop(); 67 | Client.Stop(); 68 | Assert.AreEqual(ConnectionStatus.STOPPED, Client.Status); 69 | } 70 | 71 | [TestMethod] 72 | public void TestDisconnectWithNoAutoReconnect() { 73 | Client.AutoReconnect = false; 74 | Client.Start(); 75 | Assert.AreEqual(ConnectionStatus.AUTHENTICATED, Client.Status); 76 | Client.Heartbeat(); 77 | 78 | //socket disconnect 79 | Client.Disconnect(); 80 | 81 | //wait for status to go disconnected 82 | Retry.Action(() => Assert.AreEqual(ConnectionStatus.STOPPED, Client.Status)); 83 | } 84 | 85 | [TestMethod] 86 | public void TestDisconnectWithAutoReconnect() { 87 | Client.ReconnectBackOff = TimeSpan.FromMilliseconds(100); 88 | Client.Start(); 89 | Assert.AreEqual(ConnectionStatus.AUTHENTICATED, Client.Status); 90 | Client.Heartbeat(); 91 | 92 | //socket disconnect 93 | Assert.AreEqual(0, Client.DisconnectCounter); 94 | Client.Disconnect(); 95 | Assert.AreEqual(0, Client.DisconnectCounter); 96 | 97 | //retry until connected 98 | Retry.Action(() => Client.Heartbeat()); 99 | Assert.AreEqual(ConnectionStatus.AUTHENTICATED, Client.Status); 100 | Assert.AreEqual(1, Client.DisconnectCounter); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient.Test/Impl/RequestResponseProcessorTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Betfair.ESAClient.Protocol; 3 | using Betfair.ESASwagger.Model; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Newtonsoft.Json; 6 | 7 | namespace Betfair.ESAClient.Test.Impl { 8 | [TestClass] 9 | public class RequestResponseProcessorTest { 10 | private RequestResponseProcessor Processor { get; set; } 11 | public string LastLine { get; private set; } 12 | 13 | [TestInitialize] 14 | public void Init() { 15 | Processor = new RequestResponseProcessor(line => LastLine = line); 16 | } 17 | 18 | [TestMethod] 19 | public void TestJsonNoOp() { 20 | Processor.ReceiveLine("{}"); 21 | } 22 | 23 | [TestMethod] 24 | public void TestJsonUnknownOp() { 25 | Processor.ReceiveLine("{\"op\": \"rubbish\"}"); 26 | } 27 | 28 | [TestMethod] 29 | public void TestOpNotFirst() { 30 | var msg = (ConnectionMessage) Processor.ReceiveLine("{\"connectionId\":\"aconnid\", \"op\":\"connection\"}"); 31 | Assert.AreEqual("aconnid", msg.ConnectionId); 32 | } 33 | 34 | [TestMethod] 35 | public void TestExtraJsonField() { 36 | var msg = (ConnectionMessage) Processor.ReceiveLine("{\"op\":\"connection\", \"connectionId\":\"aconnid\", \"extraField\":\"extraValue\"}"); 37 | Assert.AreEqual("aconnid", msg.ConnectionId); 38 | } 39 | 40 | [TestMethod] 41 | public void TestJsonMissingField() { 42 | var msg = (ConnectionMessage) Processor.ReceiveLine("{\"op\":\"connection\"}"); 43 | Assert.IsNotNull(msg); 44 | } 45 | 46 | [TestMethod] 47 | [ExpectedException(typeof(JsonException), AllowDerivedTypes = true)] 48 | public void TestInvalidJson() { 49 | Processor.ReceiveLine("rubbish"); 50 | } 51 | 52 | [TestMethod] 53 | public void TestConnectionMessageUnwind() { 54 | //wait and get timeout 55 | Assert.IsFalse(Processor.ConnectionMessage() 56 | .Wait(10)); 57 | 58 | //process 59 | var msg = (ConnectionMessage) Processor.ReceiveLine("{\"op\":\"connection\", \"connectionId\":\"aconnid\"}"); 60 | 61 | //now unwound 62 | Assert.IsTrue(Processor.ConnectionMessage() 63 | .Wait(10)); 64 | Assert.AreEqual("aconnid", 65 | Processor.ConnectionMessage() 66 | .Result.ConnectionId); 67 | Assert.AreEqual(ConnectionStatus.CONNECTED, Processor.Status); 68 | } 69 | 70 | [TestMethod] 71 | public void TestAuthentication() { 72 | var authTask = Processor.Authenticate(new AuthenticationMessage {Session = "asession", AppKey = "aappkey"}); 73 | Console.WriteLine(LastLine); 74 | 75 | //wait and get timeout 76 | Assert.IsFalse(authTask.Wait(10)); 77 | 78 | Processor.ReceiveLine("{\"op\":\"status\",\"id\":1,\"statusCode\":\"SUCCESS\""); 79 | 80 | 81 | //wait and pass 82 | Assert.IsTrue(authTask.Wait(10)); 83 | Assert.AreEqual(StatusMessage.StatusCodeEnum.Success, authTask.Result.StatusCode); 84 | Assert.AreEqual(ConnectionStatus.AUTHENTICATED, Processor.Status); 85 | } 86 | 87 | [TestMethod] 88 | public void TestAuthenticationFailed() { 89 | var authTask = Processor.Authenticate(new AuthenticationMessage {Session = "asession", AppKey = "aappkey"}); 90 | 91 | //wait and get timeout 92 | Assert.IsFalse(authTask.Wait(10)); 93 | 94 | Processor.ReceiveLine("{\"op\":\"status\",\"id\":1,\"statusCode\":\"FAILURE\", \"errorCode\":\"NO_SESSION\"}"); 95 | 96 | //wait and pass 97 | Assert.IsTrue(authTask.Wait(10)); 98 | 99 | Assert.AreEqual(StatusMessage.StatusCodeEnum.Failure, authTask.Result.StatusCode); 100 | Assert.AreEqual(StatusMessage.ErrorCodeEnum.NoSession, authTask.Result.ErrorCode); 101 | Assert.AreEqual(ConnectionStatus.STOPPED, Processor.Status); 102 | } 103 | 104 | [TestMethod] 105 | public void TestHeartbeat() { 106 | var authTask = Processor.Heartbeat(new HeartbeatMessage()); 107 | Console.WriteLine(LastLine); 108 | 109 | //wait and get timeout 110 | Assert.IsFalse(authTask.Wait(10)); 111 | 112 | Processor.ReceiveLine("{\"op\":\"status\",\"id\":1,\"statusCode\":\"SUCCESS\""); 113 | 114 | 115 | //wait and pass 116 | Assert.IsTrue(authTask.Wait(10)); 117 | Assert.AreEqual(StatusMessage.StatusCodeEnum.Success, authTask.Result.StatusCode); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Betfair.ESAClient.Test")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Betfair.ESAClient.Test")] 12 | [assembly: AssemblyCopyright("Copyright © 2016")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("19cc59d3-3664-4aa7-919a-cb39470976ea")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient.Test/Retry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | 5 | namespace Betfair.ESAClient.Test { 6 | public class Retry { 7 | public static TimeSpan RetryInterval { get; set; } 8 | public static TimeSpan RetryTimeout { get; set; } 9 | 10 | static Retry() { 11 | RetryInterval = TimeSpan.FromMilliseconds(500); 12 | RetryTimeout = TimeSpan.FromSeconds(5); 13 | } 14 | 15 | public static void Action(Action action) { 16 | Exception lastException = null; 17 | var startTime = DateTime.UtcNow; 18 | var counter = 0; 19 | while (DateTime.UtcNow < startTime + RetryTimeout) { 20 | if (counter > 0) 21 | Trace.TraceWarning("Retry attempt={0} action={1}", counter, action); 22 | try { 23 | action(); 24 | return; 25 | } 26 | catch (Exception e) { 27 | lastException = e; 28 | counter++; 29 | Thread.Sleep(RetryInterval); 30 | } 31 | } 32 | 33 | throw lastException; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30804.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Betfair.ESAClient", "Betfair.ESAClient\Betfair.ESAClient.csproj", "{C6F11E1B-DA85-41BC-9DEE-B941D3C6A805}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Betfair.ESAConsoleApp", "Betfair.ESAConsoleApp\Betfair.ESAConsoleApp.csproj", "{5099FDF2-C990-4334-A305-33C30F1C9950}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Betfair.ESAClient.Test", "Betfair.ESAClient.Test\Betfair.ESAClient.Test.csproj", "{19CC59D3-3664-4AA7-919A-CB39470976EA}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Betfair.ESASwagger", "Betfair.ESASwagger\Betfair.ESASwagger.csproj", "{84A4F60C-5348-4EA0-849C-E6A1B204EC28}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {C6F11E1B-DA85-41BC-9DEE-B941D3C6A805}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {C6F11E1B-DA85-41BC-9DEE-B941D3C6A805}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {C6F11E1B-DA85-41BC-9DEE-B941D3C6A805}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {C6F11E1B-DA85-41BC-9DEE-B941D3C6A805}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {5099FDF2-C990-4334-A305-33C30F1C9950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {5099FDF2-C990-4334-A305-33C30F1C9950}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {5099FDF2-C990-4334-A305-33C30F1C9950}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {5099FDF2-C990-4334-A305-33C30F1C9950}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {19CC59D3-3664-4AA7-919A-CB39470976EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {19CC59D3-3664-4AA7-919A-CB39470976EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {19CC59D3-3664-4AA7-919A-CB39470976EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {19CC59D3-3664-4AA7-919A-CB39470976EA}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {84A4F60C-5348-4EA0-849C-E6A1B204EC28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {84A4F60C-5348-4EA0-849C-E6A1B204EC28}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {84A4F60C-5348-4EA0-849C-E6A1B204EC28}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {84A4F60C-5348-4EA0-849C-E6A1B204EC28}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {0D51C665-AA80-4BF7-9604-B48E22B5A756} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Auth/AppKeyAndSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Auth { 8 | /// 9 | /// Wraps an appkey & it's current session 10 | /// 11 | public class AppKeyAndSession { 12 | public AppKeyAndSession(string appkey, string session) { 13 | AppKey = appkey; 14 | Session = session; 15 | CreateTime = DateTime.UtcNow; 16 | } 17 | 18 | public string AppKey { get; private set; } 19 | public DateTime CreateTime { get; private set; } 20 | public string Session { get; private set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Auth/AppKeyAndSessionProvider.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Runtime.Serialization; 9 | using System.Security.Authentication; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace Betfair.ESAClient.Auth { 14 | /// 15 | /// Utility class to provide a session & token via identity SSO 16 | /// 17 | public class AppKeyAndSessionProvider { 18 | private string _appkey; 19 | private string _host; 20 | private string _password; 21 | private string _username; 22 | 23 | private AppKeyAndSession _session; 24 | 25 | public const string SSO_HOST_COM = "identitysso.betfair.com"; 26 | public const string SSO_HOST_IT = "identitysso.betfair.it"; 27 | public const string SSO_HOST_ES = "identitysso.betfair.es"; 28 | 29 | public AppKeyAndSessionProvider(string ssoHost, string appkey, string username, string password) { 30 | _host = ssoHost; 31 | _appkey = appkey; 32 | _username = username; 33 | _password = password; 34 | Timeout = TimeSpan.FromSeconds(30); 35 | //4hrs is normal expire time 36 | SessionExpireTime = TimeSpan.FromHours(3); 37 | } 38 | 39 | /// 40 | /// AppKey being used 41 | /// 42 | public string Appkey { 43 | get { return _appkey; } 44 | } 45 | 46 | /// 47 | /// Session expire time (default 3hrs) 48 | /// 49 | public TimeSpan SessionExpireTime { get; set; } 50 | 51 | /// 52 | /// Specifies the timeout 53 | /// 54 | public TimeSpan Timeout { get; set; } 55 | 56 | 57 | /// 58 | /// Constructs a new session token via identity SSO. 59 | /// Note: These are not cached. 60 | /// 61 | /// Thrown if authentication response is fail 62 | /// Thrown if authentication call fails 63 | /// 64 | public AppKeyAndSession GetOrCreateNewSession() { 65 | if (_session != null) { 66 | //have a cached session - is it expired 67 | if ((_session.CreateTime + SessionExpireTime) > DateTime.UtcNow) { 68 | Trace.TraceInformation("SSO Login - session not expired - re-using"); 69 | return _session; 70 | } 71 | else { 72 | Trace.TraceInformation("SSO Login - session expired"); 73 | } 74 | } 75 | 76 | Trace.TraceInformation("SSO Login host={0}, appkey={1}, username={2}", 77 | _host, 78 | _appkey, 79 | _username); 80 | SessionDetails sessionDetails; 81 | try { 82 | string uri = string.Format("https://{0}/api/login?username={1}&password={2}", 83 | _host, 84 | _username, 85 | _password); 86 | 87 | HttpWebRequest loginRequest = (HttpWebRequest) WebRequest.Create(uri); 88 | loginRequest.Headers.Add("X-Application", _appkey); 89 | loginRequest.Accept = "application/json"; 90 | loginRequest.Method = "POST"; 91 | loginRequest.Timeout = (int) Timeout.TotalMilliseconds; 92 | WebResponse thePage = loginRequest.GetResponse(); 93 | using (StreamReader reader = new StreamReader(thePage.GetResponseStream())) { 94 | string response = reader.ReadToEnd(); 95 | Trace.TraceInformation("{0}: Response: {1}", _host, response); 96 | sessionDetails = JsonConvert.DeserializeObject(response); 97 | } 98 | } 99 | catch (Exception e) { 100 | throw new IOException("SSO Authentication - call failed:", e); 101 | } 102 | 103 | //got a response - decode 104 | if (sessionDetails != null && "SUCCESS".Equals(sessionDetails.status)) { 105 | _session = new AppKeyAndSession(_appkey, sessionDetails.token); 106 | } 107 | else { 108 | throw new InvalidCredentialException("SSO Authentication - response is fail: " + sessionDetails.error); 109 | } 110 | 111 | return _session; 112 | } 113 | 114 | /// 115 | /// Expires cached token 116 | /// 117 | public void ExpireTokenNow() { 118 | Trace.TraceInformation("SSO Login - expiring session token now"); 119 | _session = null; 120 | } 121 | } 122 | 123 | class SessionDetails { 124 | public string token; 125 | public string product; 126 | public string status; 127 | public string error; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Betfair.ESAClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 11.0 6 | Debug 7 | AnyCPU 8 | {C6F11E1B-DA85-41BC-9DEE-B941D3C6A805} 9 | Library 10 | Properties 11 | Betfair.ESAClient 12 | Betfair.ESAClient 13 | en-US 14 | 512 15 | v4.7.2 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.5\Profile\Profile111\System.Runtime.Serialization.Primitives.dll 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {84a4f60c-5348-4ea0-849c-e6a1b204ec28} 82 | Betfair.ESASwagger 83 | 84 | 85 | 86 | 93 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/LevelPriceSize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Cache { 8 | /// 9 | /// Immutable triple of level, price size. 10 | /// 11 | public class LevelPriceSize { 12 | private readonly int _level; 13 | private readonly double _price; 14 | private readonly double _size; 15 | public static readonly IList EmptyList = new LevelPriceSize[0]; 16 | 17 | public LevelPriceSize(List levelPriceSize) { 18 | _level = (int) levelPriceSize[0]; 19 | _price = (double) levelPriceSize[1]; 20 | _size = (double) levelPriceSize[2]; 21 | } 22 | 23 | public LevelPriceSize(int level, double price, double size) { 24 | _level = level; 25 | _price = price; 26 | _size = size; 27 | } 28 | 29 | public int Level { 30 | get { return _level; } 31 | } 32 | 33 | public double Price { 34 | get { return _price; } 35 | } 36 | 37 | public double Size { 38 | get { return _size; } 39 | } 40 | 41 | public override string ToString() { 42 | return _level + ": " + _size + "@" + _price; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/LevelPriceSizeLadder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Cache 8 | { 9 | /// 10 | /// A level price size ladder with copy on write snapshot 11 | /// 12 | public class LevelPriceSizeLadder 13 | { 14 | /// 15 | /// Dictionary of level to LevelPriceSize 16 | /// 17 | private readonly SortedDictionary _levelToPriceSize = new SortedDictionary(); 18 | private IList _snap = LevelPriceSize.EmptyList; 19 | 20 | public IList OnPriceChange(bool isImage, List> prices) 21 | { 22 | if (isImage) 23 | { 24 | //image is replace 25 | _levelToPriceSize.Clear(); 26 | } 27 | 28 | if (prices != null) 29 | { 30 | //changes to apply 31 | foreach (List price in prices) 32 | { 33 | LevelPriceSize levelPriceSize = new LevelPriceSize(price); 34 | //keep zero's in the ladder as it is fixed depth 35 | _levelToPriceSize[levelPriceSize.Level] = levelPriceSize; 36 | } 37 | } 38 | 39 | if (isImage || prices != null) 40 | { 41 | //update snap on image or if we had cell changes 42 | _snap = new List(_levelToPriceSize.Values); 43 | } 44 | return _snap; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/Market.cs: -------------------------------------------------------------------------------- 1 | using Betfair.ESASwagger.Model; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Betfair.ESAClient.Cache 10 | { 11 | /// 12 | /// Thread safe, reference invariant reference to a market. 13 | /// Repeatedly calling will return atomic snapshots of the market. 14 | /// 15 | public class Market 16 | { 17 | private readonly MarketCache _marketCache; 18 | private readonly string _marketId; 19 | private readonly Dictionary _marketRunners = new Dictionary(); 20 | private MarketDefinition _marketDefinition; 21 | private double _tv; 22 | private MarketSnap _snap; 23 | 24 | public Market(MarketCache marketCache, string marketId) 25 | { 26 | _marketCache = marketCache; 27 | _marketId = marketId; 28 | } 29 | 30 | internal void OnMarketChange(MarketChange marketChange) 31 | { 32 | //initial image means we need to wipe our data 33 | bool isImage = marketChange.Img == true; 34 | 35 | if (marketChange.MarketDefinition != null) 36 | { 37 | //market definition changed 38 | OnMarketDefinitionChange(marketChange.MarketDefinition); 39 | } 40 | if (marketChange.Rc != null) 41 | { 42 | //runners changed 43 | foreach (RunnerChange runnerChange in marketChange.Rc) 44 | { 45 | OnPriceChange(isImage, runnerChange); 46 | } 47 | } 48 | 49 | MarketSnap newSnap = new MarketSnap(); 50 | newSnap.MarketId = _marketId; 51 | newSnap.MarketDefinition = _marketDefinition; 52 | newSnap.MarketRunners = _marketRunners.Values.Select(runner => runner.Snap).ToList(); 53 | newSnap.TradedVolume = Utils.SelectPrice(isImage, ref _tv, marketChange.Tv); 54 | _snap = newSnap; 55 | } 56 | 57 | private MarketRunner GetOrAdd(RunnerId rid) 58 | { 59 | MarketRunner runner; 60 | if (!_marketRunners.TryGetValue(rid, out runner)) 61 | { 62 | runner = new MarketRunner(this, rid); 63 | _marketRunners[rid] = runner; 64 | } 65 | return runner; 66 | } 67 | 68 | private void OnPriceChange(bool isImage, RunnerChange runnerChange) 69 | { 70 | MarketRunner marketRunner = GetOrAdd(new RunnerId(runnerChange.Id, runnerChange.Hc)); 71 | //update the runner 72 | marketRunner.OnPriceChange(isImage, runnerChange); 73 | } 74 | 75 | private void OnMarketDefinitionChange(MarketDefinition marketDefinition) 76 | { 77 | _marketDefinition = marketDefinition; 78 | if (marketDefinition.Runners != null) 79 | { 80 | foreach (RunnerDefinition runnerDefinition in marketDefinition.Runners) 81 | { 82 | OnRunnerDefinitionChange(runnerDefinition); 83 | } 84 | } 85 | } 86 | 87 | private void OnRunnerDefinitionChange(RunnerDefinition runnerDefinition) 88 | { 89 | MarketRunner marketRunner = GetOrAdd(new RunnerId(runnerDefinition.Id, runnerDefinition.Hc)); 90 | //update runner 91 | marketRunner.OnRunnerDefinitionChange(runnerDefinition); 92 | } 93 | 94 | /// 95 | /// Market id 96 | /// 97 | public string MarketId 98 | { 99 | get 100 | { 101 | return _marketId; 102 | } 103 | } 104 | 105 | /// 106 | /// Whether the market is closed. 107 | /// 108 | public bool IsClosed 109 | { 110 | get 111 | { 112 | return _marketDefinition != null && _marketDefinition.Status == MarketDefinition.StatusEnum.Closed; 113 | } 114 | } 115 | 116 | /// 117 | /// An atomic snapshot of the state of the market. 118 | /// 119 | public MarketSnap Snap 120 | { 121 | get 122 | { 123 | return _snap; 124 | } 125 | } 126 | 127 | public override string ToString() 128 | { 129 | return "Market{" + 130 | "MarketId=" + MarketId + 131 | ", MarketDefinition=" + _marketDefinition + 132 | ", MarketRunners=" + String.Join(", ", _marketRunners.Values) + 133 | "}"; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/MarketRunner.cs: -------------------------------------------------------------------------------- 1 | using Betfair.ESASwagger.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Betfair.ESAClient.Cache 9 | { 10 | /// 11 | /// Represents a market runner within a market 12 | /// 13 | public class MarketRunner 14 | { 15 | private readonly Market _market; 16 | private readonly RunnerId _runnerId; 17 | 18 | // Level / Depth Based Ladders 19 | private MarketRunnerPrices _runnerPrices = MarketRunnerPrices.EMPTY; 20 | private PriceSizeLadder _atlPrices = PriceSizeLadder.NewLay(); 21 | private PriceSizeLadder _atbPrices = PriceSizeLadder.NewBack(); 22 | private PriceSizeLadder _trdPrices = PriceSizeLadder.NewLay(); 23 | private PriceSizeLadder _spbPrices = PriceSizeLadder.NewBack(); 24 | private PriceSizeLadder _splPrices = PriceSizeLadder.NewLay(); 25 | 26 | // Full depth Ladders 27 | private LevelPriceSizeLadder _batbPrices = new LevelPriceSizeLadder(); 28 | private LevelPriceSizeLadder _batlPrices = new LevelPriceSizeLadder(); 29 | private LevelPriceSizeLadder _bdatbPrices = new LevelPriceSizeLadder(); 30 | private LevelPriceSizeLadder _bdatlPrices = new LevelPriceSizeLadder(); 31 | 32 | // special prices 33 | private double _spn; 34 | private double _spf; 35 | private double _ltp; 36 | private double _tv; 37 | private RunnerDefinition _runnerDefinition; 38 | private MarketRunnerSnap _snap; 39 | 40 | 41 | 42 | public MarketRunner(Market market, RunnerId runnerId) 43 | { 44 | _market = market; 45 | _runnerId = runnerId; 46 | } 47 | 48 | internal void OnPriceChange(bool isImage, RunnerChange runnerChange) 49 | { 50 | //snap is invalid 51 | _snap = null; 52 | 53 | MarketRunnerPrices newPrices = new MarketRunnerPrices(); 54 | 55 | 56 | newPrices.AvailableToLay = _atlPrices.OnPriceChange(isImage, runnerChange.Atl); 57 | newPrices.AvailableToBack = _atbPrices.OnPriceChange(isImage, runnerChange.Atb); 58 | newPrices.Traded = _trdPrices.OnPriceChange(isImage, runnerChange.Trd); 59 | newPrices.StartingPriceBack = _spbPrices.OnPriceChange(isImage, runnerChange.Spb); 60 | newPrices.StartingPriceLay = _splPrices.OnPriceChange(isImage, runnerChange.Spl); 61 | 62 | 63 | newPrices.BestAvailableToBack = _batbPrices.OnPriceChange(isImage, runnerChange.Batb); 64 | newPrices.BestAvailableToLay = _batlPrices.OnPriceChange(isImage, runnerChange.Batl); 65 | newPrices.BestDisplayAvailableToBack = _bdatbPrices.OnPriceChange(isImage, runnerChange.Bdatb); 66 | newPrices.BestDisplayAvailableToLay = _bdatlPrices.OnPriceChange(isImage, runnerChange.Bdatl); 67 | 68 | 69 | newPrices.StartingPriceNear = Utils.SelectPrice(isImage, ref _spn, runnerChange.Spn); 70 | newPrices.StartingPriceFar = Utils.SelectPrice(isImage, ref _spf, runnerChange.Spf); 71 | newPrices.LastTradedPrice = Utils.SelectPrice(isImage, ref _ltp, runnerChange.Ltp); 72 | newPrices.TradedVolume = Utils.SelectPrice(isImage, ref _tv, runnerChange.Tv); 73 | 74 | //copy on write 75 | _runnerPrices = newPrices; 76 | } 77 | 78 | internal void OnRunnerDefinitionChange(RunnerDefinition runnerDefinition) 79 | { 80 | //snap is invalid 81 | _snap = null; 82 | 83 | _runnerDefinition = runnerDefinition; 84 | } 85 | 86 | public RunnerId RunnerId 87 | { 88 | get 89 | { 90 | return _runnerId; 91 | } 92 | } 93 | 94 | /// 95 | /// Takes or returns an existing immutable snap of the runner. 96 | /// 97 | public MarketRunnerSnap Snap 98 | { 99 | get 100 | { 101 | if(_snap == null) 102 | { 103 | _snap = new MarketRunnerSnap() 104 | { 105 | RunnerId = RunnerId, 106 | Definition = _runnerDefinition, 107 | Prices = _runnerPrices 108 | }; 109 | } 110 | return _snap; 111 | } 112 | } 113 | 114 | 115 | public override string ToString() 116 | { 117 | return "MarketRunner{" + 118 | "runnerId=" + _runnerId + 119 | ", prices=" + _runnerPrices + 120 | ", runnerDefinition=" + _runnerDefinition + 121 | '}'; 122 | } 123 | 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/MarketRunnerPrices.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Cache 8 | { 9 | /// 10 | /// Atomic snap of the prices associated with a runner. 11 | /// 12 | public class MarketRunnerPrices 13 | { 14 | public static readonly MarketRunnerPrices EMPTY = new MarketRunnerPrices() 15 | { 16 | AvailableToLay = PriceSize.EmptyList, 17 | AvailableToBack = PriceSize.EmptyList, 18 | Traded = PriceSize.EmptyList, 19 | StartingPriceBack = PriceSize.EmptyList, 20 | StartingPriceLay = PriceSize.EmptyList, 21 | 22 | BestAvailableToBack = LevelPriceSize.EmptyList, 23 | BestAvailableToLay = LevelPriceSize.EmptyList, 24 | BestDisplayAvailableToBack = LevelPriceSize.EmptyList, 25 | BestDisplayAvailableToLay = LevelPriceSize.EmptyList, 26 | }; 27 | 28 | public IList AvailableToLay { get; internal set; } 29 | public IList AvailableToBack { get; internal set; } 30 | public IList Traded { get; internal set; } 31 | public IList StartingPriceBack { get; internal set; } 32 | public IList StartingPriceLay { get; internal set; } 33 | 34 | public IList BestAvailableToBack { get; internal set; } 35 | public IList BestAvailableToLay { get; internal set; } 36 | public IList BestDisplayAvailableToBack { get; internal set; } 37 | public IList BestDisplayAvailableToLay { get; internal set; } 38 | 39 | public double LastTradedPrice { get; internal set; } 40 | public double StartingPriceNear { get; internal set; } 41 | public double StartingPriceFar { get; internal set; } 42 | public double TradedVolume { get; internal set; } 43 | 44 | public override string ToString() 45 | { 46 | return "MarketRunnerPrices{" + 47 | "AvailableToLay=" + String.Join(", ", AvailableToLay) + 48 | ", AvailableToBack=" + String.Join(", ", AvailableToBack) + 49 | ", Traded=" + String.Join(", ", Traded) + 50 | ", StartingPriceBack=" + String.Join(", ", StartingPriceBack) + 51 | ", StartingPriceLay=" + String.Join(", ", StartingPriceLay) + 52 | 53 | ", BestAvailableToBack=" + String.Join(", ", BestAvailableToBack) + 54 | ", BestAvailableToLay=" + String.Join(", ", BestAvailableToLay) + 55 | ", BestDisplayAvailableToBack=" + String.Join(", ", BestDisplayAvailableToBack) + 56 | ", BestDisplayAvailableToLay=" + String.Join(", ", BestDisplayAvailableToLay) + 57 | 58 | ", LastTradedPrice=" + LastTradedPrice + 59 | ", StartingPriceNear=" + StartingPriceNear + 60 | ", StartingPriceFar=" + StartingPriceFar + 61 | ", TradedVolume=" + TradedVolume + 62 | "}"; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/MarketRunnerSnap.cs: -------------------------------------------------------------------------------- 1 | using Betfair.ESASwagger.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Betfair.ESAClient.Cache 9 | { 10 | /// 11 | /// Thread safe atomic snapshot of a market runner. 12 | /// Reference only changes if the snapshot changes: 13 | /// i.e. if snap1 == snap2 then they are the same. 14 | /// (same is true for sub-objects) 15 | /// 16 | public class MarketRunnerSnap 17 | { 18 | public RunnerId RunnerId { get; internal set; } 19 | public RunnerDefinition Definition { get; internal set; } 20 | public MarketRunnerPrices Prices { get; internal set; } 21 | 22 | public override string ToString() 23 | { 24 | return "MarketRunnerSnap{" + 25 | "runnerId=" + RunnerId + 26 | ", prices=" + Prices + 27 | ", definition=" + Definition + 28 | '}'; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/MarketSnap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Betfair.ESASwagger.Model; 7 | 8 | namespace Betfair.ESAClient.Cache 9 | { 10 | /// 11 | /// Thread safe atomic snapshot of a market. 12 | /// Reference only changes if the snapshot changes: 13 | /// i.e. if snap1 == snap2 then they are the same. 14 | /// (same is true for sub-objects) 15 | /// 16 | public class MarketSnap 17 | { 18 | public MarketDefinition MarketDefinition { get; internal set; } 19 | public string MarketId { get; internal set; } 20 | public IList MarketRunners { get; internal set; } 21 | public double TradedVolume { get; internal set; } 22 | 23 | public override string ToString() 24 | { 25 | return "MarketSnap{" + 26 | "MarketId=" + MarketId + 27 | ", MarketDefinition=" + MarketDefinition + 28 | ", MarketRunners=" + String.Join(", ", MarketRunners) + 29 | ", TradedVolume=" + TradedVolume + 30 | "}"; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/OrderMarket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Betfair.ESASwagger.Model; 8 | 9 | namespace Betfair.ESAClient.Cache 10 | { 11 | /// 12 | /// The cached state of the market 13 | /// 14 | public class OrderMarket 15 | { 16 | private readonly OrderCache _orderCache; 17 | private readonly string _marketId; 18 | private readonly Dictionary _marketRunners = new Dictionary(); 19 | private OrderMarketSnap _snap; 20 | 21 | public OrderMarket(OrderCache orderCache, string marketId) 22 | { 23 | _orderCache = orderCache; 24 | _marketId = marketId; 25 | } 26 | 27 | internal void OnOrderMarketChange(OrderMarketChange orderMarketChange) 28 | { 29 | 30 | OrderMarketSnap newSnap = new OrderMarketSnap(); 31 | newSnap.MarketId = _marketId; 32 | 33 | //update runners 34 | if (orderMarketChange.Orc != null) 35 | { 36 | //runners changed 37 | foreach (OrderRunnerChange orderRunnerChange in orderMarketChange.Orc) 38 | { 39 | OnOrderRunnerChange(orderRunnerChange); 40 | } 41 | } 42 | newSnap.OrderMarketRunners = _marketRunners.Values.Select(omr => omr.Snap); 43 | 44 | //update closed 45 | IsClosed = orderMarketChange.Closed == true; 46 | newSnap.IsClosed = IsClosed; 47 | 48 | _snap = newSnap; 49 | } 50 | 51 | private void OnOrderRunnerChange(OrderRunnerChange orderRunnerChange) 52 | { 53 | RunnerId rid = new RunnerId(orderRunnerChange.Id, orderRunnerChange.Hc); 54 | OrderMarketRunner orderMarketRunner; 55 | if (!_marketRunners.TryGetValue(rid, out orderMarketRunner)) 56 | { 57 | orderMarketRunner = new OrderMarketRunner(this, rid); 58 | _marketRunners[rid] = orderMarketRunner; 59 | } 60 | //update the runner 61 | orderMarketRunner.OnOrderRunnerChange(orderRunnerChange); 62 | } 63 | 64 | public string MarketId 65 | { 66 | get 67 | { 68 | return _marketId; 69 | } 70 | } 71 | 72 | public bool IsClosed { get; private set; } 73 | 74 | /// 75 | /// Takes or returns an existing immutable snap of the market. 76 | /// 77 | public OrderMarketSnap Snap 78 | { 79 | get 80 | { 81 | return _snap; 82 | } 83 | } 84 | 85 | public override string ToString() 86 | { 87 | return "OrderMarket{" + 88 | "MarketId=" + MarketId + 89 | ", Runners=" + String.Join(", ", _marketRunners.Values) + 90 | "}"; 91 | } 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/OrderMarketRunner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Betfair.ESASwagger.Model; 7 | 8 | namespace Betfair.ESAClient.Cache 9 | { 10 | /// 11 | /// Cached state of the runner 12 | /// 13 | public class OrderMarketRunner 14 | { 15 | private readonly OrderMarket _market; 16 | private readonly RunnerId _runnerId; 17 | 18 | private OrderMarketRunnerSnap _snap; 19 | private PriceSizeLadder _matchedLay = PriceSizeLadder.NewLay(); 20 | private PriceSizeLadder _matchedBack = PriceSizeLadder.NewBack(); 21 | private Dictionary _unmatchedOrders = new Dictionary(); 22 | 23 | 24 | public OrderMarketRunner(OrderMarket market, RunnerId runnerId) 25 | { 26 | _market = market; 27 | _runnerId = runnerId; 28 | } 29 | 30 | internal void OnOrderRunnerChange(OrderRunnerChange orderRunnerChange) 31 | { 32 | bool isImage = orderRunnerChange.FullImage == true; 33 | 34 | if (isImage) 35 | { 36 | //image so clear down 37 | _unmatchedOrders.Clear(); 38 | } 39 | 40 | if(orderRunnerChange.Uo != null) 41 | { 42 | //have order changes 43 | foreach(Order order in orderRunnerChange.Uo){ 44 | _unmatchedOrders[order.Id] = order; 45 | } 46 | } 47 | 48 | 49 | OrderMarketRunnerSnap newSnap = new OrderMarketRunnerSnap(); 50 | newSnap.RunnerId = _runnerId; 51 | newSnap.UnmatchedOrders = new Dictionary(_unmatchedOrders); 52 | 53 | newSnap.MatchedLay = _matchedLay.OnPriceChange(isImage, orderRunnerChange.Ml); 54 | newSnap.MatchedBack = _matchedBack.OnPriceChange(isImage, orderRunnerChange.Mb); 55 | 56 | _snap = newSnap; 57 | } 58 | 59 | public OrderMarket Market 60 | { 61 | get 62 | { 63 | return _market; 64 | } 65 | } 66 | 67 | public RunnerId RunnerId 68 | { 69 | get 70 | { 71 | return _runnerId; 72 | } 73 | } 74 | 75 | /// 76 | /// Takes or returns an existing immutable snap of the runner. 77 | /// 78 | public OrderMarketRunnerSnap Snap 79 | { 80 | get 81 | { 82 | return _snap; 83 | } 84 | } 85 | 86 | public override string ToString() 87 | { 88 | return _snap == null ? "null" : _snap.ToString(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/OrderMarketRunnerSnap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Betfair.ESASwagger.Model; 7 | 8 | namespace Betfair.ESAClient.Cache 9 | { 10 | 11 | /// 12 | /// Thread safe atomic snapshot of a runner. 13 | /// Reference only changes if the snapshot changes: 14 | /// i.e. if snap1 == snap2 then they are the same. 15 | /// (same is true for sub-objects) 16 | /// 17 | public class OrderMarketRunnerSnap 18 | { 19 | /// 20 | /// Runner id. 21 | /// 22 | public RunnerId RunnerId { get; internal set; } 23 | /// 24 | /// Price point aggregations of matches 25 | /// 26 | public IList MatchedLay { get; internal set; } 27 | /// 28 | /// Price point aggregations of matches 29 | /// 30 | public IList MatchedBack { get; internal set; } 31 | /// 32 | /// Orders that are unmatched (this includes the transiont to Execution Complete; but this 33 | /// is not the case on an initial image). 34 | /// 35 | public Dictionary UnmatchedOrders { get; internal set; } 36 | 37 | 38 | public override string ToString() 39 | { 40 | return "OrderMarketRunnerSnap{" + 41 | "RunnerId=" + RunnerId + 42 | ", UnmatchedOrders=" + String.Join(", ", UnmatchedOrders.Values) + 43 | ", MatchedLay=" + String.Join(", ", MatchedLay) + 44 | ", MatchedBack=" + String.Join(", ", MatchedBack) + 45 | "}"; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/OrderMarketSnap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Cache { 8 | /// 9 | /// Thread safe atomic snapshot of a market. 10 | /// Reference only changes if the snapshot changes: 11 | /// i.e. if snap1 == snap2 then they are the same. 12 | /// (same is true for sub-objects) 13 | /// 14 | public class OrderMarketSnap { 15 | public string MarketId { get; internal set; } 16 | public bool IsClosed { get; internal set; } 17 | public IEnumerable OrderMarketRunners { get; internal set; } 18 | 19 | public override string ToString() { 20 | return "OrderMarketSnap{" + "MarketId=" + MarketId + ", IsClosed=" + IsClosed + ", OrderMarketRunners=" + String.Join(", ", OrderMarketRunners) + "}"; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/PriceSize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Cache 8 | { 9 | /// 10 | /// Immutable tuple of price, size 11 | /// 12 | public class PriceSize 13 | { 14 | private readonly double _price; 15 | private readonly double _size; 16 | public static readonly IList EmptyList = new PriceSize[0]; 17 | 18 | public PriceSize(List priceSize) 19 | { 20 | _price = (double)priceSize[0]; 21 | _size = (double)priceSize[1]; 22 | } 23 | 24 | public PriceSize(double price, double size) 25 | { 26 | _price = price; 27 | _size = size; 28 | } 29 | 30 | public double Price 31 | { 32 | get 33 | { 34 | return _price; 35 | } 36 | } 37 | 38 | public double Size 39 | { 40 | get 41 | { 42 | return _size; 43 | } 44 | } 45 | 46 | public override string ToString() 47 | { 48 | return _size + "@" + _price; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/PriceSizeLadder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Cache 8 | { 9 | /// 10 | /// A price size ladder with copy on write snapshot 11 | /// 12 | public class PriceSizeLadder 13 | { 14 | public static readonly IComparer BACK_ORDER = new ReverseComparer(Comparer.Default); 15 | public static readonly IComparer LAY_ORDER = Comparer.Default; 16 | 17 | /// 18 | /// Dictionary of price to PriceSize. 19 | /// 20 | private readonly SortedDictionary _priceToSize; 21 | private IList _snap = PriceSize.EmptyList; 22 | 23 | 24 | public static PriceSizeLadder NewBack() 25 | { 26 | return new PriceSizeLadder(BACK_ORDER); 27 | } 28 | 29 | public static PriceSizeLadder NewLay() 30 | { 31 | return new PriceSizeLadder(LAY_ORDER); 32 | } 33 | 34 | private PriceSizeLadder(IComparer comparer) 35 | { 36 | _priceToSize = new SortedDictionary(comparer); 37 | } 38 | 39 | public IList OnPriceChange(bool isImage, List> prices) 40 | { 41 | if (isImage) 42 | { 43 | //initial image - so clear cache 44 | _priceToSize.Clear(); 45 | } 46 | if (prices != null) 47 | { 48 | //changes to apply 49 | foreach (List price in prices) 50 | { 51 | PriceSize priceSize = new PriceSize(price); 52 | if (priceSize.Size == 0.0) 53 | { 54 | //zero signifies remove 55 | _priceToSize.Remove(priceSize.Price); 56 | } 57 | else 58 | { 59 | //update price 60 | _priceToSize[priceSize.Price] = priceSize; 61 | } 62 | } 63 | } 64 | if (isImage || prices != null) 65 | { 66 | //update snap on image or if we had cell changes 67 | _snap = new List(_priceToSize.Values); 68 | } 69 | return _snap; 70 | } 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/ReverseComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Cache 8 | { 9 | public class ReverseComparer : IComparer 10 | { 11 | private readonly IComparer _comparer; 12 | 13 | public ReverseComparer(IComparer comparer) 14 | { 15 | _comparer = comparer; 16 | } 17 | 18 | public int Compare(T x, T y) 19 | { 20 | return _comparer.Compare(y, x); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/RunnerId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Cache 8 | { 9 | public class RunnerId 10 | { 11 | private readonly long _selectionId; 12 | private readonly double? _handicap; 13 | 14 | public RunnerId(long? selectionId, double? handicap) 15 | { 16 | _selectionId = (long)selectionId; 17 | _handicap = handicap; 18 | } 19 | 20 | public long SelectionId 21 | { 22 | get 23 | { 24 | return _selectionId; 25 | } 26 | } 27 | 28 | public double? Handicap 29 | { 30 | get 31 | { 32 | return _handicap; 33 | } 34 | } 35 | 36 | // override object.Equals 37 | public override bool Equals(object obj) 38 | { 39 | if (obj == null || GetType() != obj.GetType()) 40 | { 41 | return false; 42 | } 43 | 44 | RunnerId runnerId = (RunnerId)obj; 45 | 46 | if (_selectionId != runnerId._selectionId) return false; 47 | return _handicap == runnerId._handicap; 48 | } 49 | 50 | // override object.GetHashCode 51 | public override int GetHashCode() 52 | { 53 | int result = (int)(_selectionId ^ (_selectionId >> 32)); 54 | result = 31 * result + (_handicap != null ? _handicap.GetHashCode() : 0); 55 | return result; 56 | } 57 | 58 | public override string ToString() 59 | { 60 | return _handicap == null ? _selectionId.ToString() : _selectionId + ":" + _handicap; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Cache/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Cache 8 | { 9 | public class Utils 10 | { 11 | public static double SelectPrice(bool isImage, ref double currentPrice, double? newPrice) 12 | { 13 | if (isImage) 14 | { 15 | currentPrice = newPrice ?? 0.0; 16 | } 17 | else 18 | { 19 | currentPrice = newPrice ?? currentPrice; 20 | } 21 | return currentPrice; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("Betfair.ESAClient")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("Betfair.ESAClient")] 14 | [assembly: AssemblyCopyright("Copyright © 2016")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: NeutralResourcesLanguage("en")] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Build and Revision Numbers 27 | // by using the '*' as shown below: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | [assembly: AssemblyVersion("1.0.0.0")] 30 | [assembly: AssemblyFileVersion("1.0.0.0")] 31 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Protocol/ChangeMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Protocol 8 | { 9 | /// 10 | /// Adapted version of a change message. 11 | /// 12 | /// 13 | public class ChangeMessage 14 | { 15 | private readonly DateTime _arrivalTime; 16 | 17 | public ChangeMessage() 18 | { 19 | _arrivalTime = DateTime.UtcNow; 20 | } 21 | 22 | public long? Pt { get; set; } 23 | 24 | public int Id { get; set; } 25 | 26 | public string Clk { get; set; } 27 | 28 | public string InitialClk { get; set; } 29 | 30 | public long? HeartbeatMs { get; set; } 31 | 32 | public long? ConflateMs { get; set; } 33 | 34 | public List Items { get; set; } 35 | 36 | public SegmentType SegmentType { get; set; } 37 | 38 | public ChangeType ChangeType { get; set; } 39 | 40 | /// 41 | /// Start of new subscription (not resubscription) 42 | /// 43 | public bool IsStartOfNewSubscription 44 | { 45 | get 46 | { 47 | return ChangeType == ChangeType.SUB_IMAGE && 48 | (SegmentType == SegmentType.NONE || SegmentType == SegmentType.SEG_START); 49 | } 50 | } 51 | 52 | /// 53 | /// Start of subscription / resubscription 54 | /// 55 | public bool IsStartOfRecovery 56 | { 57 | get 58 | { 59 | return (ChangeType == ChangeType.SUB_IMAGE || ChangeType == ChangeType.RESUB_DELTA) && 60 | (SegmentType == SegmentType.NONE || SegmentType == SegmentType.SEG_START); 61 | } 62 | } 63 | 64 | /// 65 | /// End of subscription / resubscription 66 | /// 67 | public bool IsEndOfRecovery 68 | { 69 | get 70 | { 71 | return (ChangeType == ChangeType.SUB_IMAGE || ChangeType == ChangeType.RESUB_DELTA) && 72 | (SegmentType == SegmentType.NONE || SegmentType == SegmentType.SEG_END); 73 | } 74 | } 75 | 76 | public DateTime ArrivalTime 77 | { 78 | get 79 | { 80 | return _arrivalTime; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Protocol/ChangeMessageFactory.cs: -------------------------------------------------------------------------------- 1 | using Betfair.ESASwagger.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Betfair.ESAClient.Protocol 9 | { 10 | /// 11 | /// Adapts market or order changes to a common change message 12 | /// 13 | public class ChangeMessageFactory 14 | { 15 | 16 | public static ChangeMessage ToChangeMessage(MarketChangeMessage message) 17 | { 18 | ChangeMessage change = new ChangeMessage() 19 | { 20 | Id = (int)message.Id, 21 | Pt = message.Pt, 22 | Clk = message.Clk, 23 | InitialClk = message.InitialClk, 24 | ConflateMs = message.ConflateMs, 25 | HeartbeatMs = message.HeartbeatMs, 26 | }; 27 | 28 | change.Items = message.Mc; 29 | 30 | switch (message.SegmentType) 31 | { 32 | case MarketChangeMessage.SegmentTypeEnum.SegStart: 33 | change.SegmentType = SegmentType.SEG_START; 34 | break; 35 | case MarketChangeMessage.SegmentTypeEnum.SegEnd: 36 | change.SegmentType = SegmentType.SEG_END; 37 | break; 38 | case MarketChangeMessage.SegmentTypeEnum.Seg: 39 | change.SegmentType = SegmentType.SEG; 40 | break; 41 | 42 | } 43 | switch (message.Ct) 44 | { 45 | case MarketChangeMessage.CtEnum.Heartbeat: 46 | change.ChangeType = ChangeType.HEARTBEAT; 47 | break; 48 | case MarketChangeMessage.CtEnum.ResubDelta: 49 | change.ChangeType = ChangeType.RESUB_DELTA; 50 | break; 51 | case MarketChangeMessage.CtEnum.SubImage: 52 | change.ChangeType = ChangeType.SUB_IMAGE; 53 | break; 54 | } 55 | return change; 56 | } 57 | 58 | public static ChangeMessage ToChangeMessage(OrderChangeMessage message) 59 | { 60 | ChangeMessage change = new ChangeMessage() 61 | { 62 | Id = (int)message.Id, 63 | Pt = message.Pt, 64 | Clk = message.Clk, 65 | InitialClk = message.InitialClk, 66 | ConflateMs = message.ConflateMs, 67 | HeartbeatMs = message.HeartbeatMs, 68 | }; 69 | 70 | change.Items = message.Oc; 71 | 72 | switch (message.SegmentType) 73 | { 74 | case OrderChangeMessage.SegmentTypeEnum.SegStart: 75 | change.SegmentType = SegmentType.SEG_START; 76 | break; 77 | case OrderChangeMessage.SegmentTypeEnum.SegEnd: 78 | change.SegmentType = SegmentType.SEG_END; 79 | break; 80 | case OrderChangeMessage.SegmentTypeEnum.Seg: 81 | change.SegmentType = SegmentType.SEG; 82 | break; 83 | 84 | } 85 | switch (message.Ct) 86 | { 87 | case OrderChangeMessage.CtEnum.Heartbeat: 88 | change.ChangeType = ChangeType.HEARTBEAT; 89 | break; 90 | case OrderChangeMessage.CtEnum.ResubDelta: 91 | change.ChangeType = ChangeType.RESUB_DELTA; 92 | break; 93 | case OrderChangeMessage.CtEnum.SubImage: 94 | change.ChangeType = ChangeType.SUB_IMAGE; 95 | break; 96 | } 97 | return change; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Protocol/ChangeType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Protocol 8 | { 9 | /// 10 | /// Common change type (as change type is local to market / order in swagger). 11 | /// 12 | public enum ChangeType 13 | { 14 | /// 15 | /// Update 16 | /// 17 | UPDATE, 18 | /// 19 | /// Initial subscription image 20 | /// 21 | SUB_IMAGE, 22 | /// 23 | /// Resubscription delta image 24 | /// 25 | RESUB_DELTA, 26 | /// 27 | /// Heartbeat 28 | /// 29 | HEARTBEAT, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Protocol/ConnectionStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Protocol 8 | { 9 | public enum ConnectionStatus 10 | { 11 | STOPPED, 12 | CONNECTED, 13 | AUTHENTICATED, 14 | SUBSCRIBED, 15 | DISCONNECTED, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Protocol/IChangeMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Betfair.ESASwagger.Model; 7 | 8 | namespace Betfair.ESAClient.Protocol 9 | { 10 | /// 11 | /// This interface abstracts connection & cache implementation. 12 | /// 13 | public interface IChangeMessageHandler 14 | { 15 | void OnOrderChange(ChangeMessage change); 16 | void OnMarketChange(ChangeMessage change); 17 | void OnErrorStatusNotification(StatusMessage message); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Protocol/RequestResponse.cs: -------------------------------------------------------------------------------- 1 | using Betfair.ESASwagger.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Betfair.ESAClient.Protocol 9 | { 10 | /// 11 | /// Wraps a standard completion source to create a pairing of request message to status message 12 | /// 13 | public class RequestResponse 14 | { 15 | private readonly TaskCompletionSource _completionSource = new TaskCompletionSource(); 16 | public readonly RequestMessage Request; 17 | public Action OnSuccess { get; set; } 18 | 19 | public RequestResponse(int id, RequestMessage request, Action onSuccess) 20 | { 21 | Id = id; 22 | Request = request; 23 | OnSuccess = onSuccess; 24 | } 25 | 26 | public void ProcesStatusMessage(StatusMessage statusMessage) 27 | { 28 | if(statusMessage.StatusCode == StatusMessage.StatusCodeEnum.Success) 29 | { 30 | if(OnSuccess != null) OnSuccess(this); 31 | } 32 | _completionSource.TrySetResult(statusMessage); 33 | } 34 | 35 | public StatusMessage Result 36 | { 37 | get 38 | { 39 | return _completionSource.Task.Result; 40 | } 41 | } 42 | 43 | public int Id { get; private set; } 44 | 45 | public Task Task 46 | { 47 | get 48 | { 49 | return _completionSource.Task; 50 | } 51 | } 52 | 53 | internal void Cancelled() 54 | { 55 | _completionSource.TrySetCanceled(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Protocol/SegmentationType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Betfair.ESAClient.Protocol 8 | { 9 | /// 10 | /// Common segmentation type (as change type is local to market / order in swagger). 11 | /// 12 | public enum SegmentType 13 | { 14 | NONE, 15 | SEG_START, 16 | SEG, 17 | SEG_END, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAClient/Protocol/StatusException.cs: -------------------------------------------------------------------------------- 1 | using Betfair.ESASwagger.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Betfair.ESAClient.Protocol 9 | { 10 | /// 11 | /// Exception used by api to raise a status fail. 12 | /// 13 | public class StatusException : Exception 14 | { 15 | public readonly StatusMessage.ErrorCodeEnum ErrorCode; 16 | public readonly string ErrorMessage; 17 | 18 | public StatusException(StatusMessage message) : base(message.ErrorCode +": " +message.ErrorMessage) 19 | { 20 | ErrorCode = (StatusMessage.ErrorCodeEnum)message.ErrorCode; 21 | ErrorMessage = message.ErrorMessage; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAConsoleApp/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAConsoleApp/Betfair.ESAConsoleApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5099FDF2-C990-4334-A305-33C30F1C9950} 8 | Exe 9 | Properties 10 | Betfair.ESAConsoleApp 11 | Betfair.ESAConsoleApp 12 | v4.7.2 13 | 512 14 | true 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | True 51 | True 52 | Settings.settings 53 | 54 | 55 | 56 | 57 | 58 | SettingsSingleFileGenerator 59 | Settings.Designer.cs 60 | 61 | 62 | 63 | 64 | {C6F11E1B-DA85-41BC-9DEE-B941D3C6A805} 65 | Betfair.ESAClient 66 | 67 | 68 | {84a4f60c-5348-4ea0-849c-e6a1b204ec28} 69 | Betfair.ESASwagger 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 85 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAConsoleApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Betfair.ESAConsoleApp")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Betfair.ESAConsoleApp")] 12 | [assembly: AssemblyCopyright("Copyright © 2016")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("5099fdf2-c990-4334-a305-33c30f1c9950")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAConsoleApp/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Betfair.ESAConsoleApp.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("apitestuk2")] 29 | public string UserName { 30 | get { 31 | return ((string)(this["UserName"])); 32 | } 33 | set { 34 | this["UserName"] = value; 35 | } 36 | } 37 | 38 | [global::System.Configuration.UserScopedSettingAttribute()] 39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 40 | [global::System.Configuration.DefaultSettingValueAttribute("password03")] 41 | public string Password { 42 | get { 43 | return ((string)(this["Password"])); 44 | } 45 | set { 46 | this["Password"] = value; 47 | } 48 | } 49 | 50 | [global::System.Configuration.UserScopedSettingAttribute()] 51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 52 | [global::System.Configuration.DefaultSettingValueAttribute("Xl0tQcOo5i5rqEfG")] 53 | public string AppKey { 54 | get { 55 | return ((string)(this["AppKey"])); 56 | } 57 | set { 58 | this["AppKey"] = value; 59 | } 60 | } 61 | 62 | [global::System.Configuration.UserScopedSettingAttribute()] 63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 64 | [global::System.Configuration.DefaultSettingValueAttribute("identitysso.betfair.com")] 65 | public string SsoHost { 66 | get { 67 | return ((string)(this["SsoHost"])); 68 | } 69 | set { 70 | this["SsoHost"] = value; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESAConsoleApp/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESASwagger/Betfair.ESASwagger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472;net5.0;netstandard2.1 5 | default 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESASwagger/Model/AuthenticationMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Text; 4 | using Newtonsoft.Json; 5 | 6 | namespace Betfair.ESASwagger.Model { 7 | /// 8 | /// 9 | [DataContract] 10 | public class AuthenticationMessage : RequestMessage, IEquatable { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The operation type. 16 | /// Client generated unique id to link request with response (like json rpc). 17 | /// Session. 18 | /// AppKey. 19 | public AuthenticationMessage(string Op = null, int? Id = null, string Session = null, string AppKey = null) { 20 | this.Op = Op; 21 | this.Id = Id; 22 | this.Session = Session; 23 | this.AppKey = AppKey; 24 | } 25 | 26 | 27 | /// 28 | /// The operation type 29 | /// 30 | /// The operation type 31 | [DataMember(Name = "op", EmitDefaultValue = false)] 32 | public string Op { get; set; } 33 | 34 | /// 35 | /// Client generated unique id to link request with response (like json rpc) 36 | /// 37 | /// Client generated unique id to link request with response (like json rpc) 38 | [DataMember(Name = "id", EmitDefaultValue = false)] 39 | public int? Id { get; set; } 40 | 41 | /// 42 | /// Gets or Sets Session 43 | /// 44 | [DataMember(Name = "session", EmitDefaultValue = false)] 45 | public string Session { get; set; } 46 | 47 | /// 48 | /// Gets or Sets AppKey 49 | /// 50 | [DataMember(Name = "appKey", EmitDefaultValue = false)] 51 | public string AppKey { get; set; } 52 | 53 | /// 54 | /// Returns the string presentation of the object 55 | /// 56 | /// String presentation of the object 57 | public override string ToString() { 58 | var sb = new StringBuilder(); 59 | sb.Append("class AuthenticationMessage {\n"); 60 | sb.Append(" Op: ") 61 | .Append(Op) 62 | .Append("\n"); 63 | sb.Append(" Id: ") 64 | .Append(Id) 65 | .Append("\n"); 66 | sb.Append(" Session: ") 67 | .Append(Session) 68 | .Append("\n"); 69 | sb.Append(" AppKey: ") 70 | .Append(AppKey) 71 | .Append("\n"); 72 | 73 | sb.Append("}\n"); 74 | return sb.ToString(); 75 | } 76 | 77 | /// 78 | /// Returns the JSON string presentation of the object 79 | /// 80 | /// JSON string presentation of the object 81 | public new string ToJson() { 82 | return JsonConvert.SerializeObject(this, Formatting.Indented); 83 | } 84 | 85 | /// 86 | /// Returns true if objects are equal 87 | /// 88 | /// Object to be compared 89 | /// Boolean 90 | public override bool Equals(object obj) { 91 | // credit: http://stackoverflow.com/a/10454552/677735 92 | return Equals(obj as AuthenticationMessage); 93 | } 94 | 95 | /// 96 | /// Returns true if AuthenticationMessage instances are equal 97 | /// 98 | /// Instance of AuthenticationMessage to be compared 99 | /// Boolean 100 | public bool Equals(AuthenticationMessage other) { 101 | // credit: http://stackoverflow.com/a/10454552/677735 102 | if (other == null) 103 | return false; 104 | 105 | return (Op == other.Op || Op != null && Op.Equals(other.Op)) && 106 | (Id == other.Id || Id != null && Id.Equals(other.Id)) && 107 | (Session == other.Session || Session != null && Session.Equals(other.Session)) && 108 | (AppKey == other.AppKey || AppKey != null && AppKey.Equals(other.AppKey)); 109 | } 110 | 111 | /// 112 | /// Gets the hash code 113 | /// 114 | /// Hash code 115 | public override int GetHashCode() { 116 | // credit: http://stackoverflow.com/a/263416/677735 117 | unchecked // Overflow is fine, just wrap 118 | { 119 | var hash = 41; 120 | // Suitable nullity checks etc, of course :) 121 | 122 | if (Op != null) 123 | hash = hash * 59 + Op.GetHashCode(); 124 | 125 | if (Id != null) 126 | hash = hash * 59 + Id.GetHashCode(); 127 | 128 | if (Session != null) 129 | hash = hash * 59 + Session.GetHashCode(); 130 | 131 | if (AppKey != null) 132 | hash = hash * 59 + AppKey.GetHashCode(); 133 | 134 | return hash; 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESASwagger/Model/ConnectionMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Text; 4 | using Newtonsoft.Json; 5 | 6 | namespace Betfair.ESASwagger.Model { 7 | /// 8 | /// 9 | [DataContract] 10 | public class ConnectionMessage : ResponseMessage, IEquatable { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The operation type. 16 | /// Client generated unique id to link request with response (like json rpc). 17 | /// The connection id. 18 | public ConnectionMessage(string Op = null, int? Id = null, string ConnectionId = null) { 19 | this.Op = Op; 20 | this.Id = Id; 21 | this.ConnectionId = ConnectionId; 22 | } 23 | 24 | 25 | /// 26 | /// The operation type 27 | /// 28 | /// The operation type 29 | [DataMember(Name = "op", EmitDefaultValue = false)] 30 | public string Op { get; set; } 31 | 32 | /// 33 | /// Client generated unique id to link request with response (like json rpc) 34 | /// 35 | /// Client generated unique id to link request with response (like json rpc) 36 | [DataMember(Name = "id", EmitDefaultValue = false)] 37 | public int? Id { get; set; } 38 | 39 | /// 40 | /// The connection id 41 | /// 42 | /// The connection id 43 | [DataMember(Name = "connectionId", EmitDefaultValue = false)] 44 | public string ConnectionId { get; set; } 45 | 46 | /// 47 | /// Returns the string presentation of the object 48 | /// 49 | /// String presentation of the object 50 | public override string ToString() { 51 | var sb = new StringBuilder(); 52 | sb.Append("class ConnectionMessage {\n"); 53 | sb.Append(" Op: ") 54 | .Append(Op) 55 | .Append("\n"); 56 | sb.Append(" Id: ") 57 | .Append(Id) 58 | .Append("\n"); 59 | sb.Append(" ConnectionId: ") 60 | .Append(ConnectionId) 61 | .Append("\n"); 62 | 63 | sb.Append("}\n"); 64 | return sb.ToString(); 65 | } 66 | 67 | /// 68 | /// Returns the JSON string presentation of the object 69 | /// 70 | /// JSON string presentation of the object 71 | public new string ToJson() { 72 | return JsonConvert.SerializeObject(this, Formatting.Indented); 73 | } 74 | 75 | /// 76 | /// Returns true if objects are equal 77 | /// 78 | /// Object to be compared 79 | /// Boolean 80 | public override bool Equals(object obj) { 81 | // credit: http://stackoverflow.com/a/10454552/677735 82 | return Equals(obj as ConnectionMessage); 83 | } 84 | 85 | /// 86 | /// Returns true if ConnectionMessage instances are equal 87 | /// 88 | /// Instance of ConnectionMessage to be compared 89 | /// Boolean 90 | public bool Equals(ConnectionMessage other) { 91 | // credit: http://stackoverflow.com/a/10454552/677735 92 | if (other == null) 93 | return false; 94 | 95 | return (Op == other.Op || Op != null && Op.Equals(other.Op)) && 96 | (Id == other.Id || Id != null && Id.Equals(other.Id)) && 97 | (ConnectionId == other.ConnectionId || ConnectionId != null && ConnectionId.Equals(other.ConnectionId)); 98 | } 99 | 100 | /// 101 | /// Gets the hash code 102 | /// 103 | /// Hash code 104 | public override int GetHashCode() { 105 | // credit: http://stackoverflow.com/a/263416/677735 106 | unchecked // Overflow is fine, just wrap 107 | { 108 | var hash = 41; 109 | // Suitable nullity checks etc, of course :) 110 | 111 | if (Op != null) 112 | hash = hash * 59 + Op.GetHashCode(); 113 | 114 | if (Id != null) 115 | hash = hash * 59 + Id.GetHashCode(); 116 | 117 | if (ConnectionId != null) 118 | hash = hash * 59 + ConnectionId.GetHashCode(); 119 | 120 | return hash; 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESASwagger/Model/HeartbeatMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Text; 4 | using Newtonsoft.Json; 5 | 6 | namespace Betfair.ESASwagger.Model { 7 | /// 8 | /// 9 | [DataContract] 10 | public class HeartbeatMessage : RequestMessage, IEquatable { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The operation type. 16 | /// Client generated unique id to link request with response (like json rpc). 17 | public HeartbeatMessage(string Op = null, int? Id = null) { 18 | this.Op = Op; 19 | this.Id = Id; 20 | } 21 | 22 | 23 | /// 24 | /// The operation type 25 | /// 26 | /// The operation type 27 | [DataMember(Name = "op", EmitDefaultValue = false)] 28 | public string Op { get; set; } 29 | 30 | /// 31 | /// Client generated unique id to link request with response (like json rpc) 32 | /// 33 | /// Client generated unique id to link request with response (like json rpc) 34 | [DataMember(Name = "id", EmitDefaultValue = false)] 35 | public int? Id { get; set; } 36 | 37 | /// 38 | /// Returns the string presentation of the object 39 | /// 40 | /// String presentation of the object 41 | public override string ToString() { 42 | var sb = new StringBuilder(); 43 | sb.Append("class HeartbeatMessage {\n"); 44 | sb.Append(" Op: ") 45 | .Append(Op) 46 | .Append("\n"); 47 | sb.Append(" Id: ") 48 | .Append(Id) 49 | .Append("\n"); 50 | 51 | sb.Append("}\n"); 52 | return sb.ToString(); 53 | } 54 | 55 | /// 56 | /// Returns the JSON string presentation of the object 57 | /// 58 | /// JSON string presentation of the object 59 | public new string ToJson() { 60 | return JsonConvert.SerializeObject(this, Formatting.Indented); 61 | } 62 | 63 | /// 64 | /// Returns true if objects are equal 65 | /// 66 | /// Object to be compared 67 | /// Boolean 68 | public override bool Equals(object obj) { 69 | // credit: http://stackoverflow.com/a/10454552/677735 70 | return Equals(obj as HeartbeatMessage); 71 | } 72 | 73 | /// 74 | /// Returns true if HeartbeatMessage instances are equal 75 | /// 76 | /// Instance of HeartbeatMessage to be compared 77 | /// Boolean 78 | public bool Equals(HeartbeatMessage other) { 79 | // credit: http://stackoverflow.com/a/10454552/677735 80 | if (other == null) 81 | return false; 82 | 83 | return (Op == other.Op || Op != null && Op.Equals(other.Op)) && (Id == other.Id || Id != null && Id.Equals(other.Id)); 84 | } 85 | 86 | /// 87 | /// Gets the hash code 88 | /// 89 | /// Hash code 90 | public override int GetHashCode() { 91 | // credit: http://stackoverflow.com/a/263416/677735 92 | unchecked // Overflow is fine, just wrap 93 | { 94 | var hash = 41; 95 | // Suitable nullity checks etc, of course :) 96 | 97 | if (Op != null) 98 | hash = hash * 59 + Op.GetHashCode(); 99 | 100 | if (Id != null) 101 | hash = hash * 59 + Id.GetHashCode(); 102 | 103 | return hash; 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESASwagger/Model/MarketDataFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Converters; 8 | 9 | namespace Betfair.ESASwagger.Model { 10 | /// 11 | /// 12 | [DataContract] 13 | public class MarketDataFilter : IEquatable { 14 | [JsonConverter(typeof(StringEnumConverter))] 15 | public enum FieldsEnum { 16 | [EnumMember(Value = "EX_BEST_OFFERS_DISP")] 17 | ExBestOffersDisp, 18 | 19 | [EnumMember(Value = "EX_BEST_OFFERS")] 20 | ExBestOffers, 21 | 22 | [EnumMember(Value = "EX_ALL_OFFERS")] 23 | ExAllOffers, 24 | 25 | [EnumMember(Value = "EX_TRADED")] 26 | ExTraded, 27 | 28 | [EnumMember(Value = "EX_TRADED_VOL")] 29 | ExTradedVol, 30 | 31 | [EnumMember(Value = "EX_LTP")] 32 | ExLtp, 33 | 34 | [EnumMember(Value = "EX_MARKET_DEF")] 35 | ExMarketDef, 36 | 37 | [EnumMember(Value = "SP_TRADED")] 38 | SpTraded, 39 | 40 | [EnumMember(Value = "SP_PROJECTED")] 41 | SpProjected 42 | } 43 | 44 | 45 | /// 46 | /// Initializes a new instance of the class. 47 | /// Initializes a new instance of the class. 48 | /// 49 | /// LadderLevels. 50 | /// Fields. 51 | public MarketDataFilter(int? LadderLevels = null, List Fields = null) { 52 | this.LadderLevels = LadderLevels; 53 | this.Fields = Fields; 54 | } 55 | 56 | 57 | /// 58 | /// Gets or Sets LadderLevels 59 | /// 60 | [DataMember(Name = "ladderLevels", EmitDefaultValue = false)] 61 | public int? LadderLevels { get; set; } 62 | 63 | /// 64 | /// Gets or Sets Fields 65 | /// 66 | [DataMember(Name = "fields", EmitDefaultValue = false)] 67 | public List Fields { get; set; } 68 | 69 | /// 70 | /// Returns the string presentation of the object 71 | /// 72 | /// String presentation of the object 73 | public override string ToString() { 74 | var sb = new StringBuilder(); 75 | sb.Append("class MarketDataFilter {\n"); 76 | sb.Append(" LadderLevels: ") 77 | .Append(LadderLevels) 78 | .Append("\n"); 79 | sb.Append(" Fields: ") 80 | .Append(Fields) 81 | .Append("\n"); 82 | 83 | sb.Append("}\n"); 84 | return sb.ToString(); 85 | } 86 | 87 | /// 88 | /// Returns the JSON string presentation of the object 89 | /// 90 | /// JSON string presentation of the object 91 | public string ToJson() { 92 | return JsonConvert.SerializeObject(this, Formatting.Indented); 93 | } 94 | 95 | /// 96 | /// Returns true if objects are equal 97 | /// 98 | /// Object to be compared 99 | /// Boolean 100 | public override bool Equals(object obj) { 101 | // credit: http://stackoverflow.com/a/10454552/677735 102 | return Equals(obj as MarketDataFilter); 103 | } 104 | 105 | /// 106 | /// Returns true if MarketDataFilter instances are equal 107 | /// 108 | /// Instance of MarketDataFilter to be compared 109 | /// Boolean 110 | public bool Equals(MarketDataFilter other) { 111 | // credit: http://stackoverflow.com/a/10454552/677735 112 | if (other == null) 113 | return false; 114 | 115 | return (LadderLevels == other.LadderLevels || LadderLevels != null && LadderLevels.Equals(other.LadderLevels)) && 116 | (Fields == other.Fields || Fields != null && Fields.SequenceEqual(other.Fields)); 117 | } 118 | 119 | /// 120 | /// Gets the hash code 121 | /// 122 | /// Hash code 123 | public override int GetHashCode() { 124 | // credit: http://stackoverflow.com/a/263416/677735 125 | unchecked // Overflow is fine, just wrap 126 | { 127 | var hash = 41; 128 | // Suitable nullity checks etc, of course :) 129 | 130 | if (LadderLevels != null) 131 | hash = hash * 59 + LadderLevels.GetHashCode(); 132 | 133 | if (Fields != null) 134 | hash = hash * 59 + Fields.GetHashCode(); 135 | 136 | return hash; 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESASwagger/Model/OrderFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | using Newtonsoft.Json; 7 | 8 | namespace Betfair.ESASwagger.Model { 9 | /// 10 | /// 11 | [DataContract] 12 | public class OrderFilter : IEquatable { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// AccountIds. 18 | public OrderFilter(List AccountIds = null) { 19 | this.AccountIds = AccountIds; 20 | } 21 | 22 | 23 | /// 24 | /// Gets or Sets AccountIds 25 | /// 26 | [DataMember(Name = "accountIds", EmitDefaultValue = false)] 27 | public List AccountIds { get; set; } 28 | 29 | /// 30 | /// Returns the string presentation of the object 31 | /// 32 | /// String presentation of the object 33 | public override string ToString() { 34 | var sb = new StringBuilder(); 35 | sb.Append("class OrderFilter {\n"); 36 | sb.Append(" AccountIds: ") 37 | .Append(AccountIds) 38 | .Append("\n"); 39 | 40 | sb.Append("}\n"); 41 | return sb.ToString(); 42 | } 43 | 44 | /// 45 | /// Returns the JSON string presentation of the object 46 | /// 47 | /// JSON string presentation of the object 48 | public string ToJson() { 49 | return JsonConvert.SerializeObject(this, Formatting.Indented); 50 | } 51 | 52 | /// 53 | /// Returns true if objects are equal 54 | /// 55 | /// Object to be compared 56 | /// Boolean 57 | public override bool Equals(object obj) { 58 | // credit: http://stackoverflow.com/a/10454552/677735 59 | return Equals(obj as OrderFilter); 60 | } 61 | 62 | /// 63 | /// Returns true if OrderFilter instances are equal 64 | /// 65 | /// Instance of OrderFilter to be compared 66 | /// Boolean 67 | public bool Equals(OrderFilter other) { 68 | // credit: http://stackoverflow.com/a/10454552/677735 69 | if (other == null) 70 | return false; 71 | 72 | return AccountIds == other.AccountIds || AccountIds != null && AccountIds.SequenceEqual(other.AccountIds); 73 | } 74 | 75 | /// 76 | /// Gets the hash code 77 | /// 78 | /// Hash code 79 | public override int GetHashCode() { 80 | // credit: http://stackoverflow.com/a/263416/677735 81 | unchecked // Overflow is fine, just wrap 82 | { 83 | var hash = 41; 84 | // Suitable nullity checks etc, of course :) 85 | 86 | if (AccountIds != null) 87 | hash = hash * 59 + AccountIds.GetHashCode(); 88 | 89 | return hash; 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESASwagger/Model/RequestMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Text; 4 | using Newtonsoft.Json; 5 | 6 | namespace Betfair.ESASwagger.Model { 7 | /// 8 | /// 9 | [DataContract] 10 | public class RequestMessage : IEquatable { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The operation type. 16 | /// Client generated unique id to link request with response (like json rpc). 17 | public RequestMessage(string Op = null, int? Id = null) { 18 | this.Op = Op; 19 | this.Id = Id; 20 | } 21 | 22 | 23 | /// 24 | /// The operation type 25 | /// 26 | /// The operation type 27 | [DataMember(Name = "op", EmitDefaultValue = false)] 28 | public string Op { get; set; } 29 | 30 | /// 31 | /// Client generated unique id to link request with response (like json rpc) 32 | /// 33 | /// Client generated unique id to link request with response (like json rpc) 34 | [DataMember(Name = "id", EmitDefaultValue = false)] 35 | public int? Id { get; set; } 36 | 37 | /// 38 | /// Returns the string presentation of the object 39 | /// 40 | /// String presentation of the object 41 | public override string ToString() { 42 | var sb = new StringBuilder(); 43 | sb.Append("class RequestMessage {\n"); 44 | sb.Append(" Op: ") 45 | .Append(Op) 46 | .Append("\n"); 47 | sb.Append(" Id: ") 48 | .Append(Id) 49 | .Append("\n"); 50 | 51 | sb.Append("}\n"); 52 | return sb.ToString(); 53 | } 54 | 55 | /// 56 | /// Returns the JSON string presentation of the object 57 | /// 58 | /// JSON string presentation of the object 59 | public string ToJson() { 60 | return JsonConvert.SerializeObject(this, Formatting.Indented); 61 | } 62 | 63 | /// 64 | /// Returns true if objects are equal 65 | /// 66 | /// Object to be compared 67 | /// Boolean 68 | public override bool Equals(object obj) { 69 | // credit: http://stackoverflow.com/a/10454552/677735 70 | return Equals(obj as RequestMessage); 71 | } 72 | 73 | /// 74 | /// Returns true if RequestMessage instances are equal 75 | /// 76 | /// Instance of RequestMessage to be compared 77 | /// Boolean 78 | public bool Equals(RequestMessage other) { 79 | // credit: http://stackoverflow.com/a/10454552/677735 80 | if (other == null) 81 | return false; 82 | 83 | return (Op == other.Op || Op != null && Op.Equals(other.Op)) && (Id == other.Id || Id != null && Id.Equals(other.Id)); 84 | } 85 | 86 | /// 87 | /// Gets the hash code 88 | /// 89 | /// Hash code 90 | public override int GetHashCode() { 91 | // credit: http://stackoverflow.com/a/263416/677735 92 | unchecked // Overflow is fine, just wrap 93 | { 94 | var hash = 41; 95 | // Suitable nullity checks etc, of course :) 96 | 97 | if (Op != null) 98 | hash = hash * 59 + Op.GetHashCode(); 99 | 100 | if (Id != null) 101 | hash = hash * 59 + Id.GetHashCode(); 102 | 103 | return hash; 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/Betfair.ESASwagger/Model/ResponseMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Text; 4 | using Newtonsoft.Json; 5 | 6 | namespace Betfair.ESASwagger.Model { 7 | /// 8 | /// 9 | [DataContract] 10 | public class ResponseMessage : IEquatable { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The operation type. 16 | /// Client generated unique id to link request with response (like json rpc). 17 | public ResponseMessage(string Op = null, int? Id = null) { 18 | this.Op = Op; 19 | this.Id = Id; 20 | } 21 | 22 | 23 | /// 24 | /// The operation type 25 | /// 26 | /// The operation type 27 | [DataMember(Name = "op", EmitDefaultValue = false)] 28 | public string Op { get; set; } 29 | 30 | /// 31 | /// Client generated unique id to link request with response (like json rpc) 32 | /// 33 | /// Client generated unique id to link request with response (like json rpc) 34 | [DataMember(Name = "id", EmitDefaultValue = false)] 35 | public int? Id { get; set; } 36 | 37 | /// 38 | /// Returns the string presentation of the object 39 | /// 40 | /// String presentation of the object 41 | public override string ToString() { 42 | var sb = new StringBuilder(); 43 | sb.Append("class ResponseMessage {\n"); 44 | sb.Append(" Op: ") 45 | .Append(Op) 46 | .Append("\n"); 47 | sb.Append(" Id: ") 48 | .Append(Id) 49 | .Append("\n"); 50 | 51 | sb.Append("}\n"); 52 | return sb.ToString(); 53 | } 54 | 55 | /// 56 | /// Returns the JSON string presentation of the object 57 | /// 58 | /// JSON string presentation of the object 59 | public string ToJson() { 60 | return JsonConvert.SerializeObject(this, Formatting.Indented); 61 | } 62 | 63 | /// 64 | /// Returns true if objects are equal 65 | /// 66 | /// Object to be compared 67 | /// Boolean 68 | public override bool Equals(object obj) { 69 | // credit: http://stackoverflow.com/a/10454552/677735 70 | return Equals(obj as ResponseMessage); 71 | } 72 | 73 | /// 74 | /// Returns true if ResponseMessage instances are equal 75 | /// 76 | /// Instance of ResponseMessage to be compared 77 | /// Boolean 78 | public bool Equals(ResponseMessage other) { 79 | // credit: http://stackoverflow.com/a/10454552/677735 80 | if (other == null) 81 | return false; 82 | 83 | return (Op == other.Op || Op != null && Op.Equals(other.Op)) && (Id == other.Id || Id != null && Id.Equals(other.Id)); 84 | } 85 | 86 | /// 87 | /// Gets the hash code 88 | /// 89 | /// Hash code 90 | public override int GetHashCode() { 91 | // credit: http://stackoverflow.com/a/263416/677735 92 | unchecked // Overflow is fine, just wrap 93 | { 94 | var hash = 41; 95 | // Suitable nullity checks etc, of course :) 96 | 97 | if (Op != null) 98 | hash = hash * 59 + Op.GetHashCode(); 99 | 100 | if (Id != null) 101 | hash = hash * 59 + Id.GetHashCode(); 102 | 103 | return hash; 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/nuget.bat: -------------------------------------------------------------------------------- 1 | 2 | 3 | SET CSCPATH=%SYSTEMROOT%\Microsoft.NET\Framework\v4.0.30319 4 | 5 | if not exist ".\nuget.exe" powershell -Command "(new-object System.Net.WebClient).DownloadFile('https://nuget.org/nuget.exe', '.\nuget.exe')" 6 | .\nuget.exe restore 7 | 8 | -------------------------------------------------------------------------------- /csharp/Betfair.ESAClient/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betfair/stream-api-sample-code/9cad8a877caa768740ea96f44ce571bb6206ac73/csharp/Betfair.ESAClient/nuget.exe -------------------------------------------------------------------------------- /csharp/esa-csharp.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /csharp/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.betfair.platform.exchange.stream.api 8 | esa-csharp-client 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | 13 | 14 | 15 | esa-csharp.runsettings 16 | true 17 | ${project.basedir} 18 | ${user.home}\stream-api-sample-code 19 | 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-resources-plugin 25 | 3.0.0 26 | 27 | 28 | UTF-8 29 | true 30 | false 31 | 32 | 33 | 34 | io.swagger 35 | swagger-codegen-maven-plugin 36 | 2.1.6 37 | 38 | 39 | 40 | generate 41 | 42 | 43 | ${project.basedir}/../ESASwaggerSchema.json 44 | csharp 45 | ${project.basedir}/Betfair.ESAClient/Betfair.ESASwagger 46 | 47 | Betfair.ESASwagger 48 | 1.3.0 49 | . 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | esaconsole: 5 | build: 6 | context: . 7 | dockerfile: java/Dockerfile 8 | tty: true 9 | stdin_open: true 10 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | system_props.properties 2 | -------------------------------------------------------------------------------- /java/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.8.5-openjdk-17 AS dependencies 2 | WORKDIR /build 3 | COPY java/console/pom.xml console/pom.xml 4 | COPY java/client/pom.xml client/pom.xml 5 | COPY java/swagger/pom.xml swagger/pom.xml 6 | COPY java/pom.xml . 7 | RUN mvn -B -e -C org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline 8 | # Copy the dependencies from the dependencies stage 9 | FROM maven:3.8.5-openjdk-17 as builder 10 | WORKDIR /build 11 | COPY --from=dependencies /root/.m2 /root/.m2 12 | COPY --from=dependencies /build/ /build 13 | COPY java/client/src /build/client/src 14 | COPY java/console/src /build/console/src 15 | COPY ESASwaggerSchema.json ../../ESASwaggerSchema.json 16 | RUN mvn -B -e clean install -DskipTests=true 17 | # At this point, BUILDER stage should have .jar file 18 | FROM openjdk:17-jdk-slim 19 | WORKDIR /build 20 | COPY --from=builder /build/console/target/console-2.0.0.jar app.jar 21 | CMD [ "java", "-jar", "/build/app.jar"] 22 | -------------------------------------------------------------------------------- /java/client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | esa-java-client 7 | com.betfair.esa 8 | 2.0.0 9 | 10 | 4.0.0 11 | 12 | client 13 | 14 | 15 | 16 | com.betfair.esa 17 | swagger 18 | 2.0.0 19 | 20 | 21 | org.slf4j 22 | slf4j-api 23 | 24 | 25 | org.slf4j 26 | slf4j-ext 27 | 28 | 29 | com.fasterxml.jackson.core 30 | jackson-databind 31 | 32 | 33 | com.fasterxml.jackson.datatype 34 | jackson-datatype-jsr310 35 | 36 | 37 | com.sun.jersey 38 | jersey-client 39 | 40 | 41 | 42 | org.slf4j 43 | slf4j-simple 44 | 45 | 46 | org.testng 47 | testng 48 | 49 | 50 | com.jayway.awaitility 51 | awaitility 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/ClientCache.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client; 2 | 3 | import com.betfair.esa.client.auth.InvalidCredentialException; 4 | import com.betfair.esa.client.cache.market.MarketCache; 5 | import com.betfair.esa.client.cache.order.OrderCache; 6 | import com.betfair.esa.client.protocol.ChangeMessage; 7 | import com.betfair.esa.client.protocol.ChangeMessageHandler; 8 | import com.betfair.esa.client.protocol.ConnectionException; 9 | import com.betfair.esa.client.protocol.ConnectionStatus; 10 | import com.betfair.esa.client.protocol.ConnectionStatusListener; 11 | import com.betfair.esa.client.protocol.StatusException; 12 | import com.betfair.esa.swagger.model.MarketChange; 13 | import com.betfair.esa.swagger.model.MarketDataFilter; 14 | import com.betfair.esa.swagger.model.MarketFilter; 15 | import com.betfair.esa.swagger.model.MarketSubscriptionMessage; 16 | import com.betfair.esa.swagger.model.OrderMarketChange; 17 | import com.betfair.esa.swagger.model.OrderSubscriptionMessage; 18 | import com.betfair.esa.swagger.model.StatusMessage; 19 | import java.util.Arrays; 20 | 21 | /** Created by mulveyj on 07/07/2016. */ 22 | public class ClientCache implements ChangeMessageHandler { 23 | private final MarketCache marketCache = new MarketCache(); 24 | private final OrderCache orderCache = new OrderCache(); 25 | private final Client client; 26 | 27 | public ClientCache(Client client) { 28 | this.client = client; 29 | client.setChangeHandler(this); 30 | } 31 | 32 | public void addConnectionStatusListener(ConnectionStatusListener listener) { 33 | client.addConnectionStatusListener(listener); 34 | } 35 | 36 | public void removeConnectionStatusListener(ConnectionStatusListener listener) { 37 | client.removeConnectionStatusListener(listener); 38 | } 39 | 40 | public ConnectionStatus getStatus() { 41 | return client.getStatus(); 42 | } 43 | 44 | public void setHeartbeatMs(Long heartbeatMs) { 45 | client.setHeartbeatMs(heartbeatMs); 46 | } 47 | 48 | public Long getHeartbeatMs() { 49 | return client.getHeartbeatMs(); 50 | } 51 | 52 | public void setConflateMs(Long conflateMs) { 53 | client.setConflateMs(conflateMs); 54 | } 55 | 56 | public Long getConflateMs() { 57 | return client.getConflateMs(); 58 | } 59 | 60 | public void setMarketDataFilter(MarketDataFilter marketDataFilter) { 61 | client.setMarketDataFilter(marketDataFilter); 62 | } 63 | 64 | public MarketDataFilter getMarketDataFilter() { 65 | return client.getMarketDataFilter(); 66 | } 67 | 68 | public Client getClient() { 69 | return client; 70 | } 71 | 72 | public MarketCache getMarketCache() { 73 | return marketCache; 74 | } 75 | 76 | public OrderCache getOrderCache() { 77 | return orderCache; 78 | } 79 | 80 | /** Subscribe to all orders. (starting the client if needed). */ 81 | public void subscribeOrders() 82 | throws InvalidCredentialException, StatusException, ConnectionException { 83 | subscribeOrders(new OrderSubscriptionMessage()); 84 | } 85 | 86 | /** Explict order subscription. (starting the client if needed). */ 87 | public void subscribeOrders(OrderSubscriptionMessage subscription) 88 | throws InvalidCredentialException, StatusException, ConnectionException { 89 | client.start(); 90 | client.orderSubscription(subscription); 91 | } 92 | 93 | /** Subscribe to the specified market ids. (starting the client if needed). */ 94 | public void subscribeMarkets(String... markets) 95 | throws InvalidCredentialException, StatusException, ConnectionException { 96 | MarketFilter marketFilter = new MarketFilter(); 97 | marketFilter.setMarketIds(Arrays.asList(markets)); 98 | subscribeMarkets(marketFilter); 99 | } 100 | 101 | /** 102 | * Subscribe to the specified markets (matching your filter). (starting the client if needed). 103 | */ 104 | public void subscribeMarkets(MarketFilter marketFilter) 105 | throws InvalidCredentialException, StatusException, ConnectionException { 106 | MarketSubscriptionMessage marketSubscriptionMessage = new MarketSubscriptionMessage(); 107 | marketSubscriptionMessage.setMarketFilter(marketFilter); 108 | subscribeMarkets(marketSubscriptionMessage); 109 | } 110 | 111 | /// 112 | /// Explicit order subscripion. (starting the client if needed). 113 | /// 114 | /// 115 | public void subscribeMarkets(MarketSubscriptionMessage subscription) 116 | throws InvalidCredentialException, StatusException, ConnectionException { 117 | client.start(); 118 | client.marketSubscription(subscription); 119 | } 120 | 121 | @Override 122 | public void onOrderChange(ChangeMessage change) { 123 | orderCache.onOrderChange(change); 124 | } 125 | 126 | @Override 127 | public void onMarketChange(ChangeMessage change) { 128 | marketCache.onMarketChange(change); 129 | } 130 | 131 | @Override 132 | public void onErrorStatusNotification(StatusMessage message) {} 133 | } 134 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/auth/AppKeyAndSessionProvider.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.auth; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sun.jersey.api.client.Client; 5 | import com.sun.jersey.api.client.ClientResponse; 6 | import com.sun.jersey.api.client.WebResource; 7 | import java.io.IOException; 8 | import java.net.URLEncoder; 9 | import java.nio.charset.StandardCharsets; 10 | import java.time.Clock; 11 | import java.time.Duration; 12 | import java.time.Instant; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** Created by hoszua on 07/07/2016. */ 17 | public class AppKeyAndSessionProvider { 18 | 19 | private final Logger logger = LoggerFactory.getLogger(getClass().getName()); 20 | 21 | private final Duration timeout; 22 | private final Duration sessionExpireTime; 23 | 24 | private final String appkey; 25 | private final String host; 26 | private final String password; 27 | private final String username; 28 | 29 | private AppKeyAndToken session; 30 | 31 | public static final String SSO_HOST_COM = "identitysso.betfair.com"; 32 | public static final String SSO_HOST_IT = "identitysso.betfair.it"; 33 | public static final String SSO_HOST_ES = "identitysso.betfair.es"; 34 | private ObjectMapper mapper = new ObjectMapper(); 35 | 36 | public AppKeyAndSessionProvider( 37 | String ssoHost, String appkey, String username, String password) { 38 | this.host = ssoHost; 39 | this.appkey = appkey; 40 | this.username = username; 41 | this.password = password; 42 | this.timeout = Duration.ofSeconds(30); 43 | // 4hrs is normal expire time 44 | this.sessionExpireTime = Duration.ofHours(3); 45 | } 46 | 47 | /** 48 | * Constructs a new session token via identity SSO. Note: These are not cached. 49 | * 50 | * @return 51 | * @throws IOException Thrown if authentication call fails 52 | * @throws InvalidCredentialException Thrown if authentication response is fail 53 | */ 54 | public AppKeyAndToken getOrCreateNewSession() throws IOException, InvalidCredentialException { 55 | if (session != null) { 56 | // have a cached session - is it expired 57 | if ((session.getCreateTime().plus(sessionExpireTime)) 58 | .isAfter(Instant.now(Clock.systemUTC()))) { 59 | logger.info("SSO Login - session not expired - re-using"); 60 | return session; 61 | } else { 62 | logger.info("SSO Login - session expired"); 63 | } 64 | } 65 | logger.info("SSO Login host={}, appkey={}, username={}", host, appkey, username); 66 | SessionDetails sessionDetails; 67 | try { 68 | String uri = 69 | String.format( 70 | "https://%s/api/login?username=%s&password=%s", 71 | SSO_HOST_COM, 72 | URLEncoder.encode(username, StandardCharsets.UTF_8), 73 | URLEncoder.encode(password, StandardCharsets.UTF_8)); 74 | 75 | Client client = Client.create(); 76 | client.setConnectTimeout((int) (timeout.getSeconds() * 1000)); 77 | WebResource webResource = client.resource(uri); 78 | 79 | ClientResponse clientResponse = 80 | webResource 81 | .accept("application/json") 82 | .header("X-Application", appkey) 83 | .header("Content-Type", "application/x-www-form-urlencoded") 84 | .post(ClientResponse.class); 85 | 86 | mapper = new ObjectMapper(); 87 | sessionDetails = 88 | mapper.readValue(clientResponse.getEntityInputStream(), SessionDetails.class); 89 | 90 | logger.info("{}: Response: {}", host, sessionDetails); 91 | 92 | } catch (Exception e) { 93 | logger.error("Error while retrieving new session", e); 94 | throw new IOException("SSO Authentication - call failed:", e); 95 | } 96 | 97 | // got a response - decode 98 | if (sessionDetails != null && "SUCCESS".equals(sessionDetails.status)) { 99 | session = new AppKeyAndToken(appkey, sessionDetails.token); 100 | } else { 101 | throw new InvalidCredentialException( 102 | "SSO Authentication - response is fail: " + sessionDetails.error); 103 | } 104 | return session; 105 | } 106 | 107 | /** Expires cached token */ 108 | public void expireTokenNow() { 109 | logger.info("SSO Login - expiring session token now"); 110 | session = null; 111 | } 112 | 113 | public static class SessionDetails { 114 | public String token; 115 | public String product; 116 | public String status; 117 | public String error; 118 | } 119 | 120 | public Duration getTimeout() { 121 | return timeout; 122 | } 123 | 124 | public Duration getSessionExpireTime() { 125 | return sessionExpireTime; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/auth/AppKeyAndToken.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.auth; 2 | 3 | import java.time.Clock; 4 | import java.time.Instant; 5 | 6 | /** Created by mulveyj on 07/07/2016. */ 7 | public class AppKeyAndToken { 8 | 9 | private final String appKey; 10 | private final String token; 11 | private Instant createTime; 12 | 13 | public AppKeyAndToken(String appKey, String token) { 14 | this.appKey = appKey; 15 | this.token = token; 16 | createTime = Instant.now(Clock.systemUTC()); 17 | } 18 | 19 | public Instant getCreateTime() { 20 | return createTime; 21 | } 22 | 23 | public void setCreateTime(Instant createTime) { 24 | this.createTime = createTime; 25 | } 26 | 27 | public String getToken() { 28 | return token; 29 | } 30 | 31 | public String getAppKey() { 32 | return appKey; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/auth/InvalidCredentialException.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.auth; 2 | 3 | /** Created by mulveyj on 08/07/2016. */ 4 | public class InvalidCredentialException extends Exception { 5 | public InvalidCredentialException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/market/Market.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.market; 2 | 3 | import com.betfair.esa.client.cache.util.RunnerId; 4 | import com.betfair.esa.client.cache.util.Utils; 5 | import com.betfair.esa.swagger.model.MarketChange; 6 | import com.betfair.esa.swagger.model.MarketDefinition; 7 | import com.betfair.esa.swagger.model.RunnerChange; 8 | import com.betfair.esa.swagger.model.RunnerDefinition; 9 | import java.util.Map; 10 | import java.util.Optional; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * Thread safe, reference invariant reference to a market. Repeatedly calling 16 | * will return atomic snapshots of the market. 17 | */ 18 | public class Market { 19 | private final MarketCache marketCache; 20 | private final String marketId; 21 | private final Map marketRunners = new ConcurrentHashMap<>(); 22 | private MarketDefinition marketDefinition; 23 | private double tv; 24 | // An atomic snapshot of the state of the market. 25 | private MarketSnap snap; 26 | 27 | public Market(MarketCache clientMarketCache, String marketId) { 28 | this.marketCache = clientMarketCache; 29 | this.marketId = marketId; 30 | } 31 | 32 | void onMarketChange(MarketChange marketChange) { 33 | // initial image means we need to wipe our data 34 | boolean isImage = Boolean.TRUE.equals(marketChange.isImg()); 35 | // market definition changed 36 | Optional.ofNullable(marketChange.getMarketDefinition()) 37 | .ifPresent(this::onMarketDefinitionChange); 38 | // runners changed 39 | Optional.ofNullable(marketChange.getRc()) 40 | .ifPresent(l -> l.forEach(p -> onPriceChange(isImage, p))); 41 | 42 | MarketSnap newSnap = new MarketSnap(); 43 | newSnap.setMarketId(marketId); 44 | newSnap.setMarketDefinition(marketDefinition); 45 | newSnap.setMarketRunners( 46 | marketRunners.values().stream() 47 | .map(MarketRunner::getSnap) 48 | .collect(Collectors.toList())); 49 | newSnap.setTradedVolume(tv = Utils.selectPrice(isImage, tv, marketChange.getTv())); 50 | snap = newSnap; 51 | } 52 | 53 | private void onPriceChange(boolean isImage, RunnerChange runnerChange) { 54 | MarketRunner marketRunner = 55 | getOrAdd(new RunnerId(runnerChange.getId(), runnerChange.getHc())); 56 | // update runner 57 | marketRunner.onPriceChange(isImage, runnerChange); 58 | } 59 | 60 | private void onMarketDefinitionChange(MarketDefinition marketDefinition) { 61 | this.marketDefinition = marketDefinition; 62 | Optional.ofNullable(marketDefinition.getRunners()) 63 | .ifPresent(rds -> rds.forEach(this::onRunnerDefinitionChange)); 64 | } 65 | 66 | private void onRunnerDefinitionChange(RunnerDefinition runnerDefinition) { 67 | MarketRunner marketRunner = 68 | getOrAdd(new RunnerId(runnerDefinition.getId(), runnerDefinition.getHc())); 69 | // update runner 70 | marketRunner.onRunnerDefinitionChange(runnerDefinition); 71 | } 72 | 73 | private MarketRunner getOrAdd(RunnerId runnerId) { 74 | MarketRunner runner = 75 | marketRunners.computeIfAbsent(runnerId, k -> new MarketRunner(this, k)); 76 | return runner; 77 | } 78 | 79 | public String getMarketId() { 80 | return marketId; 81 | } 82 | 83 | public boolean isClosed() { 84 | // whether the market is closed 85 | return (marketDefinition != null 86 | && marketDefinition.getStatus() == MarketDefinition.StatusEnum.CLOSED); 87 | } 88 | 89 | public MarketSnap getSnap() { 90 | return snap; 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return "Market{" 96 | + "marketId='" 97 | + marketId 98 | + '\'' 99 | + ", marketRunners=" 100 | + marketRunners 101 | + ", marketDefinition=" 102 | + marketDefinition 103 | + '}'; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/market/MarketRunner.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.market; 2 | 3 | import com.betfair.esa.client.cache.util.LevelPriceSizeLadder; 4 | import com.betfair.esa.client.cache.util.PriceSizeLadder; 5 | import com.betfair.esa.client.cache.util.RunnerId; 6 | import com.betfair.esa.client.cache.util.Utils; 7 | import com.betfair.esa.swagger.model.RunnerChange; 8 | import com.betfair.esa.swagger.model.RunnerDefinition; 9 | 10 | public class MarketRunner { 11 | private final Market market; 12 | private final RunnerId runnerId; 13 | 14 | // Level / Depth Based Ladders 15 | private MarketRunnerPrices marketRunnerPrices = new MarketRunnerPrices(); 16 | private final PriceSizeLadder atlPrices = PriceSizeLadder.newLay(); 17 | private final PriceSizeLadder atbPrices = PriceSizeLadder.newBack(); 18 | private final PriceSizeLadder trdPrices = PriceSizeLadder.newLay(); 19 | private final PriceSizeLadder spbPrices = PriceSizeLadder.newBack(); 20 | private final PriceSizeLadder splPrices = PriceSizeLadder.newLay(); 21 | 22 | // Full depth Ladders 23 | private final LevelPriceSizeLadder batbPrices = new LevelPriceSizeLadder(); 24 | private final LevelPriceSizeLadder batlPrices = new LevelPriceSizeLadder(); 25 | private final LevelPriceSizeLadder bdatbPrices = new LevelPriceSizeLadder(); 26 | private final LevelPriceSizeLadder bdatlPrices = new LevelPriceSizeLadder(); 27 | 28 | // special prices 29 | private double spn; 30 | private double spf; 31 | private double ltp; 32 | private double tv; 33 | private RunnerDefinition runnerDefinition; 34 | private MarketRunnerSnap snap; 35 | 36 | public MarketRunner(Market market, RunnerId runnerId) { 37 | this.market = market; 38 | this.runnerId = runnerId; 39 | } 40 | 41 | void onPriceChange(boolean isImage, RunnerChange runnerChange) { 42 | // snap is invalid 43 | snap = null; 44 | 45 | MarketRunnerPrices newPrices = new MarketRunnerPrices(); 46 | 47 | newPrices.atl = atlPrices.onPriceChange(isImage, runnerChange.getAtl()); 48 | newPrices.atb = atbPrices.onPriceChange(isImage, runnerChange.getAtb()); 49 | newPrices.trd = trdPrices.onPriceChange(isImage, runnerChange.getTrd()); 50 | newPrices.spb = spbPrices.onPriceChange(isImage, runnerChange.getSpb()); 51 | newPrices.spl = splPrices.onPriceChange(isImage, runnerChange.getSpl()); 52 | 53 | newPrices.batb = batbPrices.onPriceChange(isImage, runnerChange.getBatb()); 54 | newPrices.batl = batlPrices.onPriceChange(isImage, runnerChange.getBatl()); 55 | newPrices.bdatb = bdatbPrices.onPriceChange(isImage, runnerChange.getBdatb()); 56 | newPrices.bdatl = bdatlPrices.onPriceChange(isImage, runnerChange.getBdatl()); 57 | 58 | newPrices.spn = spn = Utils.selectPrice(isImage, spn, runnerChange.getSpn()); 59 | newPrices.spf = spf = Utils.selectPrice(isImage, spf, runnerChange.getSpf()); 60 | newPrices.ltp = ltp = Utils.selectPrice(isImage, ltp, runnerChange.getLtp()); 61 | newPrices.tv = tv = Utils.selectPrice(isImage, tv, runnerChange.getTv()); 62 | 63 | // copy on write 64 | marketRunnerPrices = newPrices; 65 | } 66 | 67 | void onRunnerDefinitionChange(RunnerDefinition runnerDefinition) { 68 | // snap is invalid 69 | snap = null; 70 | this.runnerDefinition = runnerDefinition; 71 | } 72 | 73 | public RunnerId getRunnerId() { 74 | return runnerId; 75 | } 76 | 77 | public MarketRunnerSnap getSnap() { 78 | // takes or returns an existing immutable snap of the runner 79 | if (snap == null) { 80 | snap = new MarketRunnerSnap(getRunnerId(), runnerDefinition, marketRunnerPrices); 81 | } 82 | return snap; 83 | } 84 | 85 | @Override 86 | public String toString() { 87 | return "MarketRunner{" 88 | + "runnerId=" 89 | + runnerId 90 | + ", prices=" 91 | + marketRunnerPrices 92 | + ", runnerDefinition=" 93 | + runnerDefinition 94 | + '}'; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/market/MarketRunnerPrices.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.market; 2 | 3 | import com.betfair.esa.client.cache.util.LevelPriceSize; 4 | import com.betfair.esa.client.cache.util.PriceSize; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | public class MarketRunnerPrices { 9 | List atl = Collections.emptyList(); 10 | List atb = Collections.emptyList(); 11 | List trd = Collections.emptyList(); 12 | List spb = Collections.emptyList(); 13 | List spl = Collections.emptyList(); 14 | 15 | List batb = Collections.emptyList(); 16 | List batl = Collections.emptyList(); 17 | List bdatb = Collections.emptyList(); 18 | List bdatl = Collections.emptyList(); 19 | 20 | double ltp; 21 | double spn; 22 | double spf; 23 | double tv; 24 | 25 | public List getAtl() { 26 | return atl; 27 | } 28 | 29 | public List getAtb() { 30 | return atb; 31 | } 32 | 33 | public List getTrd() { 34 | return trd; 35 | } 36 | 37 | public List getSpb() { 38 | return spb; 39 | } 40 | 41 | public List getSpl() { 42 | return spl; 43 | } 44 | 45 | public List getBatb() { 46 | return batb; 47 | } 48 | 49 | public List getBatl() { 50 | return batl; 51 | } 52 | 53 | public List getBdatb() { 54 | return bdatb; 55 | } 56 | 57 | public List getBdatl() { 58 | return bdatl; 59 | } 60 | 61 | public double getLtp() { 62 | return ltp; 63 | } 64 | 65 | public double getSpn() { 66 | return spn; 67 | } 68 | 69 | public double getSpf() { 70 | return spf; 71 | } 72 | 73 | public double getTv() { 74 | return tv; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "MarketRunnerPrices{" 80 | + "atl=" 81 | + atl 82 | + ", atb=" 83 | + atb 84 | + ", trd=" 85 | + trd 86 | + ", spb=" 87 | + spb 88 | + ", spl=" 89 | + spl 90 | + ", batb=" 91 | + batb 92 | + ", batl=" 93 | + batl 94 | + ", bdatb=" 95 | + bdatb 96 | + ", bdatl=" 97 | + bdatl 98 | + ", ltp=" 99 | + ltp 100 | + ", spn=" 101 | + spn 102 | + ", spf=" 103 | + spf 104 | + ", tv=" 105 | + tv 106 | + '}'; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/market/MarketRunnerSnap.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.market; 2 | 3 | import com.betfair.esa.client.cache.util.RunnerId; 4 | import com.betfair.esa.swagger.model.RunnerDefinition; 5 | 6 | /** 7 | * Thread safe atomic snapshot of a market runner. Reference only changes if the snapshot changes: 8 | * i.e. if snap1 == snap2 then they are the same (same is true for sub-objects) 9 | */ 10 | public class MarketRunnerSnap { 11 | private RunnerId runnerId; 12 | private RunnerDefinition definition; 13 | private MarketRunnerPrices prices; 14 | 15 | public MarketRunnerSnap( 16 | RunnerId runnerId, RunnerDefinition definition, MarketRunnerPrices prices) { 17 | this.runnerId = runnerId; 18 | this.definition = definition; 19 | this.prices = prices; 20 | } 21 | 22 | public RunnerId getRunnerId() { 23 | return runnerId; 24 | } 25 | 26 | void setRunnerId(RunnerId runnerId) { 27 | this.runnerId = runnerId; 28 | } 29 | 30 | public RunnerDefinition getDefinition() { 31 | return definition; 32 | } 33 | 34 | void setDefinition(RunnerDefinition definition) { 35 | this.definition = definition; 36 | } 37 | 38 | public MarketRunnerPrices getPrices() { 39 | return prices; 40 | } 41 | 42 | void setPrices(MarketRunnerPrices prices) { 43 | this.prices = prices; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "MarketRunnerSnap{" 49 | + "runnerId=" 50 | + runnerId 51 | + ", definition=" 52 | + definition 53 | + ", prices=" 54 | + prices 55 | + '}'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/market/MarketSnap.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.market; 2 | 3 | import com.betfair.esa.swagger.model.MarketDefinition; 4 | import java.util.List; 5 | 6 | /** 7 | * Thread safe atomic snapshot of a market. Reference only changes if the snapshot changes: i.e. if 8 | * snap1 == snap2 then they are the same (same is true for sub-objects) 9 | */ 10 | public class MarketSnap { 11 | private String marketId; 12 | private MarketDefinition marketDefinition; 13 | private List marketRunners; 14 | private double tradedVolume; 15 | 16 | public String getMarketId() { 17 | return marketId; 18 | } 19 | 20 | void setMarketId(String marketId) { 21 | this.marketId = marketId; 22 | } 23 | 24 | public MarketDefinition getMarketDefinition() { 25 | return marketDefinition; 26 | } 27 | 28 | void setMarketDefinition(MarketDefinition marketDefinition) { 29 | this.marketDefinition = marketDefinition; 30 | } 31 | 32 | public List getMarketRunners() { 33 | return marketRunners; 34 | } 35 | 36 | void setMarketRunners(List marketRunners) { 37 | this.marketRunners = marketRunners; 38 | } 39 | 40 | public double getTradedVolume() { 41 | return tradedVolume; 42 | } 43 | 44 | void setTradedVolume(double tradedVolume) { 45 | this.tradedVolume = tradedVolume; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "MarketSnap{" 51 | + "marketId='" 52 | + marketId 53 | + '\'' 54 | + ", marketDefinition=" 55 | + marketDefinition 56 | + ", marketRunners=" 57 | + marketRunners 58 | + ", tradedVolume=" 59 | + tradedVolume 60 | + '}'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/order/OrderMarket.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.order; 2 | 3 | import com.betfair.esa.client.cache.util.OrderMarketRunner; 4 | import com.betfair.esa.client.cache.util.OrderMarketRunnerSnap; 5 | import com.betfair.esa.client.cache.util.OrderMarketSnap; 6 | import com.betfair.esa.client.cache.util.RunnerId; 7 | import com.betfair.esa.swagger.model.OrderMarketChange; 8 | import com.betfair.esa.swagger.model.OrderRunnerChange; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | public class OrderMarket { 15 | 16 | private final OrderCache orderCache; 17 | private final String marketId; 18 | private final Map marketRunners = new ConcurrentHashMap<>(); 19 | private OrderMarketSnap orderMarketSnap; 20 | private boolean isClosed; 21 | 22 | public OrderMarket(OrderCache orderCache, String marketId) { 23 | this.orderCache = orderCache; 24 | this.marketId = marketId; 25 | } 26 | 27 | public void onOrderMarketChange(OrderMarketChange orderMarketChange) { 28 | OrderMarketSnap newSnap = new OrderMarketSnap(); 29 | newSnap.setMarketId(this.marketId); 30 | 31 | // update runners 32 | if (orderMarketChange.getOrc() != null) { 33 | for (OrderRunnerChange orderRunnerChange : orderMarketChange.getOrc()) { 34 | onOrderRunnerChange(orderRunnerChange); 35 | } 36 | } 37 | 38 | List snaps = new ArrayList<>(marketRunners.size()); 39 | for (OrderMarketRunner orderMarketRunner : marketRunners.values()) { 40 | snaps.add(orderMarketRunner.getOrderMarketRunnerSnap()); 41 | } 42 | 43 | newSnap.setOrderMarketRunners(snaps); 44 | 45 | isClosed = Boolean.TRUE.equals(orderMarketChange.isClosed()); 46 | newSnap.setClosed(isClosed); 47 | 48 | orderMarketSnap = newSnap; 49 | } 50 | 51 | private void onOrderRunnerChange(OrderRunnerChange orderRunnerChange) { 52 | 53 | RunnerId runnerId = new RunnerId(orderRunnerChange.getId(), orderRunnerChange.getHc()); 54 | 55 | OrderMarketRunner orderMarketRunner = 56 | marketRunners.computeIfAbsent(runnerId, r -> new OrderMarketRunner(this, r)); 57 | 58 | // update the runner 59 | orderMarketRunner.onOrderRunnerChange(orderRunnerChange); 60 | } 61 | 62 | public boolean isClosed() { 63 | return isClosed; 64 | } 65 | 66 | public OrderMarketSnap getOrderMarketSnap() { 67 | return orderMarketSnap; 68 | } 69 | 70 | public String getMarketId() { 71 | return marketId; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | StringBuilder runnersSb = new StringBuilder(" "); 77 | for (OrderMarketRunner runner : marketRunners.values()) { 78 | runnersSb.append(runner).append(" "); 79 | } 80 | 81 | return "OrderMarket{" 82 | + "marketRunners=" 83 | + runnersSb 84 | + ", marketId='" 85 | + marketId 86 | + '\'' 87 | + '}'; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/util/LevelPriceSize.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.util; 2 | 3 | import java.util.List; 4 | 5 | public class LevelPriceSize { 6 | private final int level; 7 | private final double price; 8 | private final double size; 9 | 10 | public LevelPriceSize(List levelPriceSize) { 11 | level = levelPriceSize.get(0).intValue(); 12 | price = levelPriceSize.get(1); 13 | size = levelPriceSize.get(2); 14 | } 15 | 16 | public LevelPriceSize(int level, double price, double size) { 17 | this.level = level; 18 | this.price = price; 19 | this.size = size; 20 | } 21 | 22 | public int getLevel() { 23 | return level; 24 | } 25 | 26 | public double getPrice() { 27 | return price; 28 | } 29 | 30 | public double getSize() { 31 | return size; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return level + ":" + size + "@" + price; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/util/LevelPriceSizeLadder.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.TreeMap; 8 | 9 | /** A level price size ladder with copy on write snapshot */ 10 | public class LevelPriceSizeLadder { 11 | private final Map levelToPriceSize = new TreeMap<>(); 12 | private List snap = Collections.emptyList(); 13 | 14 | public List onPriceChange(boolean isImage, List> prices) { 15 | if (isImage) { 16 | // image is replace 17 | levelToPriceSize.clear(); 18 | } 19 | 20 | if (prices != null) { 21 | // changes to apply 22 | for (List price : prices) { 23 | LevelPriceSize levelPriceSize = new LevelPriceSize(price); 24 | levelToPriceSize.put(levelPriceSize.getLevel(), levelPriceSize); 25 | } 26 | } 27 | 28 | if (isImage || prices != null) { 29 | // update snap on image or if we had cell changes 30 | snap = new ArrayList<>(levelToPriceSize.values()); 31 | } 32 | return snap; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/util/OrderMarketRunner.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.util; 2 | 3 | import com.betfair.esa.client.cache.order.OrderMarket; 4 | import com.betfair.esa.swagger.model.Order; 5 | import com.betfair.esa.swagger.model.OrderRunnerChange; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | public class OrderMarketRunner { 11 | 12 | private final OrderMarket orderMarket; 13 | private final RunnerId runnerId; 14 | 15 | private OrderMarketRunnerSnap orderMarketRunnerSnap; 16 | private final PriceSizeLadder layMatches = PriceSizeLadder.newLay(); 17 | private final PriceSizeLadder backMatches = PriceSizeLadder.newBack(); 18 | private final Map unmatchedOrders = new ConcurrentHashMap<>(); 19 | 20 | public OrderMarketRunner(OrderMarket orderMarket, RunnerId runnerId) { 21 | this.orderMarket = orderMarket; 22 | this.runnerId = runnerId; 23 | } 24 | 25 | public void onOrderRunnerChange(OrderRunnerChange orderRunnerChange) { 26 | boolean isImage = Boolean.TRUE.equals(orderRunnerChange.isFullImage()); 27 | 28 | if (isImage) { 29 | unmatchedOrders.clear(); 30 | } 31 | 32 | if (orderRunnerChange.getUo() != null) { 33 | for (Order order : orderRunnerChange.getUo()) { 34 | unmatchedOrders.put(order.getId(), order); 35 | } 36 | } 37 | 38 | OrderMarketRunnerSnap newSnap = new OrderMarketRunnerSnap(); 39 | newSnap.setUnmatchedOrders(new HashMap<>(unmatchedOrders)); 40 | 41 | newSnap.setLayMatches(layMatches.onPriceChange(isImage, orderRunnerChange.getMl())); 42 | newSnap.setBackMatches(backMatches.onPriceChange(isImage, orderRunnerChange.getMb())); 43 | 44 | orderMarketRunnerSnap = newSnap; 45 | } 46 | 47 | public OrderMarketRunnerSnap getOrderMarketRunnerSnap() { 48 | return orderMarketRunnerSnap; 49 | } 50 | 51 | public RunnerId getRunnerId() { 52 | return runnerId; 53 | } 54 | 55 | public OrderMarket getOrderMarket() { 56 | return orderMarket; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return orderMarketRunnerSnap == null ? "null" : orderMarketRunnerSnap.toString(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/util/OrderMarketRunnerSnap.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.util; 2 | 3 | import com.betfair.esa.swagger.model.Order; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public class OrderMarketRunnerSnap { 8 | 9 | private List layMatches; 10 | private List backMatches; 11 | 12 | private Map unmatchedOrders; 13 | 14 | public List getLayMatches() { 15 | return layMatches; 16 | } 17 | 18 | public void setLayMatches(List layMatches) { 19 | this.layMatches = layMatches; 20 | } 21 | 22 | public List getBackMatches() { 23 | return backMatches; 24 | } 25 | 26 | public void setBackMatches(List backMatches) { 27 | this.backMatches = backMatches; 28 | } 29 | 30 | public Map getUnmatchedOrders() { 31 | return unmatchedOrders; 32 | } 33 | 34 | public void setUnmatchedOrders(Map unmatchedOrders) { 35 | this.unmatchedOrders = unmatchedOrders; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | 41 | StringBuilder uoOrdersSb = new StringBuilder(" "); 42 | for (Order order : unmatchedOrders.values()) { 43 | uoOrdersSb.append(order).append(" "); 44 | } 45 | 46 | return "OrderMarketRunnerSnap{" 47 | + "layMatches=" 48 | + layMatches 49 | + ", backMatches=" 50 | + backMatches 51 | + ", unmatchedOrders=" 52 | + uoOrdersSb 53 | + '}'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/util/OrderMarketSnap.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.util; 2 | 3 | public class OrderMarketSnap { 4 | 5 | private String marketId; 6 | private boolean isClosed; 7 | private Iterable orderMarketRunners; 8 | 9 | public boolean isClosed() { 10 | return isClosed; 11 | } 12 | 13 | public void setClosed(boolean closed) { 14 | isClosed = closed; 15 | } 16 | 17 | public Iterable getOrderMarketRunners() { 18 | return orderMarketRunners; 19 | } 20 | 21 | public void setOrderMarketRunners(Iterable orderMarketRunners) { 22 | this.orderMarketRunners = orderMarketRunners; 23 | } 24 | 25 | public String getMarketId() { 26 | return marketId; 27 | } 28 | 29 | public void setMarketId(String marketId) { 30 | this.marketId = marketId; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "OrderMarketSnap{" 36 | + "marketId='" 37 | + marketId 38 | + '\'' 39 | + ", isClosed=" 40 | + isClosed 41 | + ", orderMarketRunners=" 42 | + orderMarketRunners 43 | + '}'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/util/PriceSize.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.util; 2 | 3 | import java.util.List; 4 | 5 | public class PriceSize { 6 | private final double price; 7 | private final double size; 8 | 9 | public PriceSize(List priceSize) { 10 | this.price = priceSize.get(0); 11 | this.size = priceSize.get(1); 12 | } 13 | 14 | public double getPrice() { 15 | return price; 16 | } 17 | 18 | public double getSize() { 19 | return size; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return size + "@" + price; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/util/PriceSizeLadder.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.TreeMap; 9 | 10 | public class PriceSizeLadder { 11 | private final Map priceToSize; 12 | private List snap = Collections.emptyList(); 13 | 14 | public static PriceSizeLadder newBack() { 15 | return new PriceSizeLadder(Comparator.reverseOrder()); 16 | } 17 | 18 | public static PriceSizeLadder newLay() { 19 | return new PriceSizeLadder(Comparator.naturalOrder()); 20 | } 21 | 22 | private PriceSizeLadder(Comparator comparator) { 23 | priceToSize = new TreeMap<>(comparator); 24 | } 25 | 26 | public List onPriceChange(boolean isImage, List> prices) { 27 | if (isImage) { 28 | priceToSize.clear(); 29 | } 30 | if (prices != null) { 31 | for (List price : prices) { 32 | PriceSize priceSize = new PriceSize(price); 33 | if (priceSize.getSize() == 0.0) { 34 | priceToSize.remove(priceSize.getPrice()); 35 | } else { 36 | priceToSize.put(priceSize.getPrice(), priceSize); 37 | } 38 | } 39 | } 40 | if (isImage || prices != null) { 41 | // update snap on image or if we had cell changes 42 | snap = new ArrayList<>(priceToSize.values()); 43 | } 44 | return snap; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "{" + priceToSize.values() + '}'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/util/RunnerId.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.util; 2 | 3 | import java.util.Objects; 4 | 5 | public record RunnerId(long selectionId, Double handicap) { 6 | 7 | @Override 8 | public boolean equals(Object o) { 9 | if (this == o) return true; 10 | if (o == null || getClass() != o.getClass()) return false; 11 | 12 | RunnerId runnerId = (RunnerId) o; 13 | 14 | if (selectionId != runnerId.selectionId) return false; 15 | return Objects.equals(handicap, runnerId.handicap); 16 | } 17 | 18 | @Override 19 | public int hashCode() { 20 | int result = (int) (selectionId ^ (selectionId >>> 32)); 21 | result = 31 * result + (handicap != null ? handicap.hashCode() : 0); 22 | return result; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "RunnerId{" + "selectionId=" + selectionId + ", handicap=" + handicap + '}'; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/cache/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.cache.util; 2 | 3 | /** Utils class */ 4 | public class Utils { 5 | 6 | public static double selectPrice(boolean isImage, double currentPrice, Double newPrice) { 7 | if (isImage) { 8 | return newPrice == null ? 0.0 : newPrice; 9 | } else { 10 | return newPrice == null ? currentPrice : newPrice; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/ChangeMessage.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | import java.util.List; 4 | 5 | /** Created by mulveyj on 07/07/2016. */ 6 | public class ChangeMessage { 7 | private long arrivalTime; 8 | private long publishTime; 9 | private int id; 10 | private String clk; 11 | private String initialClk; 12 | private Long heartbeatMs; 13 | private Long conflateMs; 14 | private List items; 15 | private SegmentType segmentType; 16 | private ChangeType changeType; 17 | 18 | public ChangeMessage() { 19 | arrivalTime = System.currentTimeMillis(); 20 | } 21 | 22 | /** 23 | * Start of new subscription (not resubscription) 24 | * 25 | * @return 26 | */ 27 | public boolean isStartOfNewSubscription() { 28 | return changeType == ChangeType.SUB_IMAGE 29 | && (segmentType == SegmentType.NONE || segmentType == SegmentType.SEG_START); 30 | } 31 | 32 | /** 33 | * Start of subscription / resubscription 34 | * 35 | * @return 36 | */ 37 | public boolean isStartOfRecovery() { 38 | return (changeType == ChangeType.SUB_IMAGE || changeType == ChangeType.RESUB_DELTA) 39 | && (segmentType == SegmentType.NONE || segmentType == SegmentType.SEG_START); 40 | } 41 | 42 | /** 43 | * End of subscription / resubscription 44 | * 45 | * @return 46 | */ 47 | public boolean isEndOfRecovery() { 48 | return (changeType == ChangeType.SUB_IMAGE || changeType == ChangeType.RESUB_DELTA) 49 | && (segmentType == SegmentType.NONE || segmentType == SegmentType.SEG_END); 50 | } 51 | 52 | public ChangeType getChangeType() { 53 | return changeType; 54 | } 55 | 56 | public void setChangeType(ChangeType changeType) { 57 | this.changeType = changeType; 58 | } 59 | 60 | public long getArrivalTime() { 61 | return arrivalTime; 62 | } 63 | 64 | public void setArrivalTime(long arrivalTime) { 65 | this.arrivalTime = arrivalTime; 66 | } 67 | 68 | public long getPublishTime() { 69 | return publishTime; 70 | } 71 | 72 | public void setPublishTime(long publishTime) { 73 | this.publishTime = publishTime; 74 | } 75 | 76 | public int getId() { 77 | return id; 78 | } 79 | 80 | public void setId(int id) { 81 | this.id = id; 82 | } 83 | 84 | public String getClk() { 85 | return clk; 86 | } 87 | 88 | public void setClk(String clk) { 89 | this.clk = clk; 90 | } 91 | 92 | public String getInitialClk() { 93 | return initialClk; 94 | } 95 | 96 | public void setInitialClk(String initialClk) { 97 | this.initialClk = initialClk; 98 | } 99 | 100 | public Long getHeartbeatMs() { 101 | return heartbeatMs; 102 | } 103 | 104 | public void setHeartbeatMs(Long heartbeatMs) { 105 | this.heartbeatMs = heartbeatMs; 106 | } 107 | 108 | public Long getConflateMs() { 109 | return conflateMs; 110 | } 111 | 112 | public void setConflateMs(Long conflateMs) { 113 | this.conflateMs = conflateMs; 114 | } 115 | 116 | public List getItems() { 117 | return items; 118 | } 119 | 120 | public void setItems(List items) { 121 | this.items = items; 122 | } 123 | 124 | public SegmentType getSegmentType() { 125 | return segmentType; 126 | } 127 | 128 | public void setSegmentType(SegmentType segmentType) { 129 | this.segmentType = segmentType; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/ChangeMessageFactory.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | import com.betfair.esa.swagger.model.MarketChange; 4 | import com.betfair.esa.swagger.model.MarketChangeMessage; 5 | import com.betfair.esa.swagger.model.OrderChangeMessage; 6 | import com.betfair.esa.swagger.model.OrderMarketChange; 7 | 8 | /** Adapts market or order changes to a common change message Created by mulveyj on 07/07/2016. */ 9 | public class ChangeMessageFactory { 10 | 11 | public static ChangeMessage ToChangeMessage(MarketChangeMessage message) { 12 | ChangeMessage change = new ChangeMessage(); 13 | change.setId(message.getId()); 14 | change.setPublishTime(message.getPt()); 15 | change.setClk(message.getClk()); 16 | change.setInitialClk(message.getInitialClk()); 17 | change.setConflateMs(message.getConflateMs()); 18 | change.setHeartbeatMs(message.getHeartbeatMs()); 19 | 20 | change.setItems(message.getMc()); 21 | 22 | SegmentType segmentType = SegmentType.NONE; 23 | if (message.getSegmentType() != null) { 24 | switch (message.getSegmentType()) { 25 | case SEG_START -> segmentType = SegmentType.SEG_START; 26 | case SEG_END -> segmentType = SegmentType.SEG_END; 27 | case SEG -> segmentType = SegmentType.SEG; 28 | } 29 | } 30 | change.setSegmentType(segmentType); 31 | 32 | ChangeType changeType = ChangeType.UPDATE; 33 | if (message.getCt() != null) { 34 | switch (message.getCt()) { 35 | case HEARTBEAT -> changeType = ChangeType.HEARTBEAT; 36 | case RESUB_DELTA -> changeType = ChangeType.RESUB_DELTA; 37 | case SUB_IMAGE -> changeType = ChangeType.SUB_IMAGE; 38 | } 39 | } 40 | change.setChangeType(changeType); 41 | 42 | return change; 43 | } 44 | 45 | public static ChangeMessage ToChangeMessage(OrderChangeMessage message) { 46 | ChangeMessage change = new ChangeMessage<>(); 47 | change.setId(message.getId()); 48 | change.setPublishTime(message.getPt()); 49 | change.setClk(message.getClk()); 50 | change.setInitialClk(message.getInitialClk()); 51 | change.setConflateMs(message.getConflateMs()); 52 | change.setHeartbeatMs(message.getHeartbeatMs()); 53 | 54 | change.setItems(message.getOc()); 55 | 56 | SegmentType segmentType = SegmentType.NONE; 57 | if (message.getSegmentType() != null) { 58 | switch (message.getSegmentType()) { 59 | case SEG_START -> segmentType = SegmentType.SEG_START; 60 | case SEG_END -> segmentType = SegmentType.SEG_END; 61 | case SEG -> segmentType = SegmentType.SEG; 62 | } 63 | } 64 | change.setSegmentType(segmentType); 65 | 66 | ChangeType changeType = ChangeType.UPDATE; 67 | if (message.getCt() != null) { 68 | switch (message.getCt()) { 69 | case HEARTBEAT -> changeType = ChangeType.HEARTBEAT; 70 | case RESUB_DELTA -> changeType = ChangeType.RESUB_DELTA; 71 | case SUB_IMAGE -> changeType = ChangeType.SUB_IMAGE; 72 | } 73 | } 74 | change.setChangeType(changeType); 75 | 76 | return change; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/ChangeMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | import com.betfair.esa.swagger.model.MarketChange; 4 | import com.betfair.esa.swagger.model.OrderMarketChange; 5 | import com.betfair.esa.swagger.model.StatusMessage; 6 | 7 | /** This interface abstracts connection & cache implementation. Created by mulveyj on 07/07/2016. */ 8 | public interface ChangeMessageHandler { 9 | void onOrderChange(ChangeMessage change); 10 | 11 | void onMarketChange(ChangeMessage change); 12 | 13 | void onErrorStatusNotification(StatusMessage message); 14 | } 15 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/ChangeType.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | /** 4 | * Common change type (as change type is local to market / order in swagger). Created by mulveyj on 5 | * 07/07/2016. 6 | */ 7 | public enum ChangeType { 8 | /** Update */ 9 | UPDATE, 10 | /** Initial subscription image */ 11 | SUB_IMAGE, 12 | /** Resubscription delta image */ 13 | RESUB_DELTA, 14 | /** Heartbeat */ 15 | HEARTBEAT, 16 | } 17 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/ConnectionException.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | /** Created by mulveyJ on 11/07/2016. */ 4 | public class ConnectionException extends Exception { 5 | 6 | public ConnectionException(String message) { 7 | super(message); 8 | } 9 | 10 | public ConnectionException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/ConnectionStatus.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | /** Created by mulveyj on 07/07/2016. */ 4 | public enum ConnectionStatus { 5 | STOPPED, 6 | CONNECTED, 7 | AUTHENTICATED, 8 | SUBSCRIBED, 9 | DISCONNECTED, 10 | } 11 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/ConnectionStatusChangeEvent.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | import java.util.EventObject; 4 | 5 | /** Created by mulveyJ on 11/07/2016. */ 6 | public class ConnectionStatusChangeEvent extends EventObject { 7 | private final ConnectionStatus oldStatus; 8 | private final ConnectionStatus newStatus; 9 | 10 | /** 11 | * Constructs a prototypical Event. 12 | * 13 | * @param source The object on which the Event initially occurred. 14 | * @throws IllegalArgumentException if source is null. 15 | */ 16 | public ConnectionStatusChangeEvent( 17 | Object source, ConnectionStatus oldStatus, ConnectionStatus newStatus) { 18 | super(source); 19 | this.oldStatus = oldStatus; 20 | this.newStatus = newStatus; 21 | } 22 | 23 | public ConnectionStatus getOldStatus() { 24 | return oldStatus; 25 | } 26 | 27 | public ConnectionStatus getNewStatus() { 28 | return newStatus; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/ConnectionStatusListener.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | import java.util.EventListener; 4 | 5 | /** Created by mulveyJ on 11/07/2016. */ 6 | public interface ConnectionStatusListener extends EventListener { 7 | void connectionStatusChange(ConnectionStatusChangeEvent statusChangeEvent); 8 | } 9 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/FutureResponse.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | import java.util.concurrent.FutureTask; 4 | 5 | /** Created by mulveyj on 07/07/2016. */ 6 | public class FutureResponse extends FutureTask { 7 | private static final Runnable NULL = () -> {}; 8 | 9 | public FutureResponse() { 10 | super(NULL, null); 11 | } 12 | 13 | public void setResponse(T response) { 14 | set(response); 15 | } 16 | 17 | public void setException(Throwable t) { 18 | super.setException(t); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/MixInResponseMessage.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | import com.betfair.esa.swagger.model.ConnectionMessage; 4 | import com.betfair.esa.swagger.model.MarketChangeMessage; 5 | import com.betfair.esa.swagger.model.OrderChangeMessage; 6 | import com.betfair.esa.swagger.model.StatusMessage; 7 | import com.fasterxml.jackson.annotation.JsonSubTypes; 8 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 9 | 10 | @JsonTypeInfo( 11 | use = JsonTypeInfo.Id.NAME, 12 | include = JsonTypeInfo.As.PROPERTY, 13 | property = "op", 14 | visible = true) 15 | @JsonSubTypes({ 16 | @JsonSubTypes.Type(value = ConnectionMessage.class, name = "connection"), 17 | @JsonSubTypes.Type(value = StatusMessage.class, name = "status"), 18 | @JsonSubTypes.Type(value = MarketChangeMessage.class, name = "mcm"), 19 | @JsonSubTypes.Type(value = OrderChangeMessage.class, name = "ocm"), 20 | }) 21 | interface MixInResponseMessage {} 22 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/RequestResponse.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | import com.betfair.esa.swagger.model.RequestMessage; 4 | import com.betfair.esa.swagger.model.StatusMessage; 5 | import java.util.function.Consumer; 6 | 7 | /** Created by mulveyj on 07/07/2016. */ 8 | public class RequestResponse { 9 | private final FutureResponse future = new FutureResponse<>(); 10 | private final RequestMessage request; 11 | private final Consumer onSuccess; 12 | private final int id; 13 | 14 | public RequestResponse(int id, RequestMessage request, Consumer onSuccess) { 15 | this.id = id; 16 | this.request = request; 17 | this.onSuccess = onSuccess; 18 | } 19 | 20 | public void processStatusMessage(StatusMessage statusMessage) { 21 | if (statusMessage.getStatusCode() == StatusMessage.StatusCodeEnum.SUCCESS) { 22 | if (onSuccess != null) onSuccess.accept(this); 23 | future.setResponse(statusMessage); 24 | } 25 | } 26 | 27 | public FutureResponse getFuture() { 28 | return future; 29 | } 30 | 31 | public RequestMessage getRequest() { 32 | return request; 33 | } 34 | 35 | public int getId() { 36 | return id; 37 | } 38 | 39 | public void setException(Exception e) { 40 | future.setException(e); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/RequestSender.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | /** Created by mulveyj on 07/07/2016. */ 4 | public interface RequestSender { 5 | 6 | void sendLine(String line) throws ConnectionException; 7 | } 8 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/SegmentType.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | /** 4 | * Common segmentation type (as change type is local to market / order in swagger). Created by 5 | * mulveyj on 07/07/2016. 6 | */ 7 | public enum SegmentType { 8 | NONE, 9 | SEG_START, 10 | SEG, 11 | SEG_END, 12 | } 13 | -------------------------------------------------------------------------------- /java/client/src/main/java/com/betfair/esa/client/protocol/StatusException.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client.protocol; 2 | 3 | import com.betfair.esa.swagger.model.StatusMessage; 4 | 5 | /** Created by mulveyj on 07/07/2016. */ 6 | public class StatusException extends Exception { 7 | 8 | private final StatusMessage.ErrorCodeEnum errorCode; 9 | private final String errorMessage; 10 | 11 | public StatusException(StatusMessage message) { 12 | super(message.getErrorCode() + ": " + message.getErrorMessage()); 13 | errorCode = message.getErrorCode(); 14 | errorMessage = message.getErrorMessage(); 15 | } 16 | 17 | public StatusMessage.ErrorCodeEnum getErrorCode() { 18 | return errorCode; 19 | } 20 | 21 | public String getErrorMessage() { 22 | return errorMessage; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /java/client/src/test/java/com/betfair/esa/client/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client; 2 | 3 | import com.betfair.esa.client.auth.AppKeyAndSessionProvider; 4 | import org.testng.annotations.BeforeClass; 5 | 6 | /** Created by mulveyj on 08/07/2016. */ 7 | public class BaseTest { 8 | 9 | private static String appKey; 10 | private static String userName; 11 | private static String password; 12 | 13 | @BeforeClass 14 | public static void beforeClass() { 15 | appKey = getSystemProperty("AppKey"); 16 | userName = getSystemProperty("UserName"); 17 | password = getSystemProperty("Password"); 18 | } 19 | 20 | private static String getSystemProperty(String key) { 21 | String value = System.getProperty(key); 22 | if (value == null) { 23 | throw new IllegalArgumentException( 24 | String.format("System property %s must be set for tests to run", key)); 25 | } 26 | return value; 27 | } 28 | 29 | public static String getAppKey() { 30 | return appKey; 31 | } 32 | 33 | public static String getUserName() { 34 | return userName; 35 | } 36 | 37 | public static String getPassword() { 38 | return password; 39 | } 40 | 41 | public AppKeyAndSessionProvider getValidSessionProvider() { 42 | return new AppKeyAndSessionProvider( 43 | AppKeyAndSessionProvider.SSO_HOST_COM, appKey, userName, password); 44 | } 45 | 46 | public AppKeyAndSessionProvider getInvalidHostSessionProvider() { 47 | return new AppKeyAndSessionProvider("www.betfair.com", "a", "b", "c"); 48 | } 49 | 50 | public AppKeyAndSessionProvider getInvalidLoginSessionProvider() { 51 | return new AppKeyAndSessionProvider(AppKeyAndSessionProvider.SSO_HOST_COM, "a", "b", "c"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /java/client/src/test/java/com/betfair/esa/client/ClientCacheTest.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client; 2 | 3 | import com.betfair.esa.client.auth.AppKeyAndSessionProvider; 4 | import com.betfair.esa.client.auth.InvalidCredentialException; 5 | import com.betfair.esa.client.protocol.ConnectionException; 6 | import com.betfair.esa.client.protocol.StatusException; 7 | import org.testng.annotations.Test; 8 | 9 | /** Created by mulveyj on 08/07/2016. */ 10 | public class ClientCacheTest extends BaseTest { 11 | 12 | @Test 13 | public void testUserStory() 14 | throws InvalidCredentialException, StatusException, ConnectionException, 15 | InterruptedException { 16 | // 1: Create a session provider 17 | AppKeyAndSessionProvider sessionProvider = 18 | new AppKeyAndSessionProvider( 19 | AppKeyAndSessionProvider.SSO_HOST_COM, 20 | getAppKey(), 21 | getUserName(), 22 | getPassword()); 23 | 24 | // 2: Create a client 25 | Client client = new Client("stream-api-integration.betfair.com", 443, sessionProvider); 26 | 27 | // 3: Create a cache 28 | ClientCache cache = new ClientCache(client); 29 | 30 | // 4: Setup order subscription 31 | // Register for change events 32 | // cache.getOrderCache().OrderMarketChanged += 33 | // (sender, arg) => Console.WriteLine("Order:" + arg.Snap); 34 | // Subscribe to orders 35 | cache.subscribeOrders(); 36 | 37 | // 5: Setup market subscription 38 | // Register for change events 39 | cache.getMarketCache() 40 | .addMarketChangeListener((e) -> System.out.println("Market:" + e.getSnap())); 41 | // cache.MarketCache.MarketChanged += 42 | // (sender, arg) => Console.WriteLine("Market:" + arg.Snap); 43 | // Subscribe to markets (use a valid market id or filter) 44 | cache.subscribeMarkets("1.215134342"); 45 | 46 | Thread.sleep(5000); // pause for a bit 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /java/client/src/test/java/com/betfair/esa/client/ClientTest.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.client; 2 | 3 | import com.betfair.esa.client.auth.InvalidCredentialException; 4 | import com.betfair.esa.client.protocol.ConnectionException; 5 | import com.betfair.esa.client.protocol.ConnectionStatus; 6 | import com.betfair.esa.client.protocol.StatusException; 7 | import com.jayway.awaitility.Awaitility; 8 | import com.jayway.awaitility.Duration; 9 | import org.testng.Assert; 10 | import org.testng.annotations.AfterMethod; 11 | import org.testng.annotations.BeforeMethod; 12 | import org.testng.annotations.Test; 13 | 14 | /** Created by mulveyJ on 11/07/2016. */ 15 | public class ClientTest extends BaseTest { 16 | 17 | private Client client; 18 | 19 | @BeforeMethod 20 | public void beforeMethod() { 21 | client = new Client("stream-api-integration.betfair.com", 443, getValidSessionProvider()); 22 | } 23 | 24 | @AfterMethod 25 | public void afterMethod() { 26 | client.stop(); 27 | } 28 | 29 | @Test(expectedExceptions = ConnectionException.class) 30 | public void testInvalidHost() 31 | throws InvalidCredentialException, StatusException, ConnectionException { 32 | Client invalidClient = new Client("www.betfair.com", 443, getValidSessionProvider()); 33 | invalidClient.setTimeout(100); 34 | invalidClient.start(); 35 | } 36 | 37 | @Test 38 | public void testStartStop() 39 | throws InvalidCredentialException, StatusException, ConnectionException { 40 | Assert.assertEquals(client.getStatus(), ConnectionStatus.STOPPED); 41 | client.start(); 42 | Assert.assertEquals(client.getStatus(), ConnectionStatus.AUTHENTICATED); 43 | client.stop(); 44 | Assert.assertEquals(client.getStatus(), ConnectionStatus.STOPPED); 45 | } 46 | 47 | @Test 48 | public void testStartHeartbeatStop() 49 | throws InvalidCredentialException, StatusException, ConnectionException { 50 | client.start(); 51 | client.heartbeat(); 52 | client.stop(); 53 | } 54 | 55 | @Test 56 | public void testReentrantStartStop() 57 | throws InvalidCredentialException, StatusException, ConnectionException { 58 | client.start(); 59 | Assert.assertEquals(client.getStatus(), ConnectionStatus.AUTHENTICATED); 60 | client.heartbeat(); 61 | client.stop(); 62 | Assert.assertEquals(client.getStatus(), ConnectionStatus.STOPPED); 63 | 64 | client.start(); 65 | Assert.assertEquals(client.getStatus(), ConnectionStatus.AUTHENTICATED); 66 | client.heartbeat(); 67 | client.stop(); 68 | Assert.assertEquals(client.getStatus(), ConnectionStatus.STOPPED); 69 | } 70 | 71 | @Test 72 | public void testDoubleStartStop() 73 | throws InvalidCredentialException, StatusException, ConnectionException { 74 | client.start(); 75 | client.start(); 76 | Assert.assertEquals(client.getStatus(), ConnectionStatus.AUTHENTICATED); 77 | client.heartbeat(); 78 | client.stop(); 79 | client.stop(); 80 | Assert.assertEquals(client.getStatus(), ConnectionStatus.STOPPED); 81 | } 82 | 83 | @Test 84 | public void testDisconnectWithAutoReconnect() 85 | throws InvalidCredentialException, StatusException, ConnectionException { 86 | client.start(); 87 | Assert.assertEquals(client.getStatus(), ConnectionStatus.AUTHENTICATED); 88 | client.heartbeat(); 89 | 90 | // socket disconnect 91 | Assert.assertEquals(client.getDisconnectCounter(), 0); 92 | client.disconnect(); 93 | 94 | // retry until connected 95 | Awaitility.await() 96 | .catchUncaughtExceptions() 97 | .atMost(Duration.ONE_MINUTE) 98 | .until( 99 | () -> { 100 | try { 101 | client.heartbeat(); 102 | return true; 103 | } catch (Throwable e) { 104 | return false; 105 | } 106 | }); 107 | Assert.assertEquals(client.getStatus(), ConnectionStatus.AUTHENTICATED); 108 | Assert.assertEquals(client.getDisconnectCounter(), 1); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /java/console/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | esa-java-client 7 | com.betfair.esa 8 | 2.0.0 9 | 10 | 4.0.0 11 | console 12 | 13 | 14 | 15 | 16 | org.apache.maven.plugins 17 | maven-shade-plugin 18 | 3.3.0 19 | 20 | 21 | package 22 | 23 | shade 24 | 25 | 26 | 27 | 28 | com.betfair.esa.console.ClientMain 29 | 30 | 31 | META-INF/spring.handlers 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | com.betfair.esa 44 | client 45 | ${project.version} 46 | 47 | 48 | org.springframework.shell 49 | spring-shell-starter 50 | 51 | 52 | org.slf4j 53 | slf4j-simple 54 | compile 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /java/console/src/main/java/com/betfair/esa/console/ClientMain.java: -------------------------------------------------------------------------------- 1 | package com.betfair.esa.console; 2 | 3 | /** Created by HoszuA on 11/07/2016. */ 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | /* If you are running this in an IDE please be aware of console issues: 8 | * Spring Shell uses Jline. You will have to edit the run configuration inside your Intellij and add the vm options arguments as follow: 9 | * Unix machines: 10 | * -Djline.terminal=org.springframework.shell.core.IdeTerminal 11 | * On Windows machines: 12 | * -Djline.WindowsTerminal.directConsole=false -Djline.terminal=jline.UnsupportedTerminal 13 | */ 14 | 15 | @SpringBootApplication 16 | public class ClientMain { 17 | public static void main(String[] args) { 18 | SpringApplication.run(ClientMain.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java/console/src/main/resources/META-INF/spring/spring-shell-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /java/console/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ======================================= 2 | * * 3 | * Exchange Streaming Client * 4 | * * 5 | ======================================= 6 | Version: 2.0.0 7 | Welcome to ESA. For assistance type 'help' then hit ENTER 8 | -------------------------------------------------------------------------------- /node.js/app.js: -------------------------------------------------------------------------------- 1 | var tls = require('tls'); 2 | 3 | /* Socket connection options */ 4 | 5 | var options = { 6 | host: 'stream-api.betfair.com', 7 | port: 443 8 | } 9 | 10 | /* Establish connection to the socket */ 11 | 12 | var client = tls.connect(options, function () { 13 | console.log("Connected"); 14 | }); 15 | 16 | /* Send authentication message */ 17 | 18 | client.write('{"op": "authentication", "appKey": "", "session": ""}\r\n'); 19 | 20 | 21 | /* Subscribe to order/market stream */ 22 | client.write('{"op":"orderSubscription","orderFilter":{"includeOverallPosition":false,"customerStrategyRefs":["betstrategy1"],"partitionMatchedByStrategyRef":true},"segmentationEnabled":true}\r\n'); 23 | client.write('{"op":"marketSubscription","id":2,"marketFilter":{"marketIds":["1.120684740"],"bspMarket":true,"bettingTypes":["ODDS"],"eventTypeIds":["1"],"eventIds":["27540841"],"turnInPlayEnabled":true,"marketTypes":["MATCH_ODDS"],"countryCodes":["ES"]},"marketDataFilter":{}}\r\n'); 24 | 25 | client.on('data', function(data) { 26 | console.log('Received: ' + data); 27 | }); 28 | 29 | client.on('close', function() { 30 | console.log('Connection closed'); 31 | }); 32 | 33 | client.on('error', function(err) { 34 | console.log('Error:' + err); 35 | }); 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /node.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app.js", 3 | "version": "1.0.0", 4 | "description": "Simple app for connecting to Betfair Stream Api", 5 | "main": "app.js", 6 | "dependencies": {}, 7 | "devDependencies": {}, 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Betfair", 12 | "license": "Betfair" 13 | } 14 | -------------------------------------------------------------------------------- /stream-api-specification.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betfair/stream-api-sample-code/9cad8a877caa768740ea96f44ce571bb6206ac73/stream-api-specification.pdf --------------------------------------------------------------------------------