├── .gitignore ├── README.md ├── benchmark-c ├── .gitignore └── readPerf.c ├── build.sbt ├── src └── main │ └── scala │ └── ch │ └── epfl │ └── ts │ ├── benchmark │ ├── marketSimulator │ │ ├── BenchmarkMarketRules.scala │ │ ├── BenchmarkMarketSimulator.scala │ │ ├── BenchmarkTrader.scala │ │ ├── MarketSimulatorBenchmark.scala │ │ ├── OrderFeeder.scala │ │ ├── TimeCounter.scala │ │ └── TraderReactionBenchmark.scala │ └── scala │ │ ├── BenchmarkCommands.scala │ │ ├── CommunicationBenchmark.scala │ │ ├── FetchActors.scala │ │ ├── FileProcessBenchActor.scala │ │ ├── LoadFromFileBenchmark.scala │ │ └── PullFetchBenchmarkImpl.scala │ ├── component │ ├── Component.scala │ ├── fetch │ │ ├── BitfinexFetcher.scala │ │ ├── BitstampFetcher.scala │ │ ├── BtceFetcher.scala │ │ ├── CSVFetcher.scala │ │ ├── Fetch.scala │ │ ├── MarketNames.scala │ │ └── TwitterFetchComponent.scala │ ├── persist │ │ ├── OrderPersistor.scala │ │ ├── Persistance.scala │ │ ├── TransactionPersistor.scala │ │ └── TweetPersistor.scala │ ├── replay │ │ └── Replay.scala │ └── utils │ │ ├── BackLoop.scala │ │ ├── BatcherComponent.scala │ │ └── Printer.scala │ ├── data │ └── Messages.scala │ ├── engine │ ├── Actors.scala │ ├── Controller.scala │ ├── MarketRules.scala │ ├── MarketSimulator.scala │ ├── Messages.scala │ ├── OrderBook.scala │ ├── RevenueCompute.scala │ └── WalletManager.scala │ ├── example │ ├── BTCArbitrage.scala │ ├── BtceTransactionFlowTesterWithStorage.scala │ ├── ReplayFlowTesterFromStorage.scala │ ├── ReplayOrdersLoop.scala │ └── TwitterFlowTesterWithStorage.scala │ ├── indicators │ ├── EmaIndicator.scala │ ├── MaIndicator.scala │ ├── OhlcIndicator.scala │ └── SmaIndicator.scala │ └── traders │ ├── Arbitrageur.scala │ ├── DoubleCrossoverTrader.scala │ ├── DoubleEnvelopeTrader.scala │ ├── SimpleTrader.scala │ ├── SobiTrader.scala │ └── TransactionVwapTrader.scala ├── twitter-classifier ├── classifier.pickle ├── fList.pickle ├── sentiment.py └── stopwords.txt └── wiki └── figures ├── arbitrage.png ├── disp.png ├── live.png ├── rep.png ├── replay.png ├── sobi.png ├── useCaseReplayTraders.png └── vwap.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache/ 6 | .history/ 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | 19 | 20 | .cache 21 | .classpath 22 | .project 23 | 24 | 25 | TradingSimulation.iml 26 | .idea/ 27 | project/ 28 | 29 | #testing-ilia 30 | src/test/scala/ch/epfl/ts/test/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TradingSimulation 2 | ================= 3 | 4 | TradingSimulation is a modular framework designed for back-testing trading strategies. The components are built on top of akka Actors. 5 | 6 | ### Documentation 7 | 8 | Detailed documentation can be found on the dedicated [wiki](https://github.com/kebetsi/TradingSimulation/wiki). 9 | 10 | ### Build: 11 | 12 | - Install the [SBT](http://www.scala-sbt.org/) build tool. 13 | 14 | - `sbt compile` to compile the source files 15 | 16 | - `sbt run` displays a list of examples that can be run 17 | 18 | ### License 19 | 20 | Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 -------------------------------------------------------------------------------- /benchmark-c/.gitignore: -------------------------------------------------------------------------------- 1 | bench 2 | -------------------------------------------------------------------------------- /benchmark-c/readPerf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /* OSX time functions */ 12 | #ifdef __MACH__ 13 | #include 14 | #define CLOCK_REALTIME 0 15 | #define CLOCK_MONOTONIC 0 16 | //clock_gettime is not implemented on OSX 17 | int clock_gettime(int clk_id, struct timespec* t) { 18 | struct timeval now; 19 | int rv = gettimeofday(&now, NULL); 20 | if (rv) return rv; 21 | t->tv_sec = now.tv_sec; 22 | t->tv_nsec = now.tv_usec * 1000; 23 | return 0; 24 | } 25 | #endif 26 | 27 | const char * FILENAME = "../fakeData.csv"; 28 | 29 | 30 | void readFileToMemory(); 31 | void readFileToMemoryItearting(); 32 | void readFileLineByLine(); 33 | void networkSend(); 34 | void networkSendWithFile(); 35 | 36 | int main() { 37 | 38 | readFileToMemory(); 39 | readFileToMemoryItearting(); 40 | readFileLineByLine(); 41 | //networkSend(); 42 | networkSendWithFile(); 43 | return 0; 44 | } 45 | 46 | 47 | /* Get file size*/ 48 | size_t getFileSize(const char * filename) { 49 | struct stat st; 50 | stat(filename, &st); 51 | return st.st_size; 52 | } 53 | 54 | /* Reads file into memory with fread */ 55 | void readFileToMemory() { 56 | size_t size = getFileSize(FILENAME); 57 | char *mFile = (char*)malloc(size*sizeof(char)); 58 | FILE *f = fopen(FILENAME,"rb"); 59 | struct timespec tw1, tw2; 60 | 61 | clock_gettime(CLOCK_MONOTONIC, &tw1); 62 | 63 | fread( mFile, sizeof(char), size, f); 64 | fclose(f); 65 | 66 | clock_gettime(CLOCK_MONOTONIC, &tw2); 67 | 68 | free(mFile); 69 | 70 | double posix_wall = 1000.0*tw2.tv_sec + 1e-6*tw2.tv_nsec - (1000.0*tw1.tv_sec + 1e-6*tw1.tv_nsec); 71 | printf("Reads file into memory with fread ==> time passed: %.2f ms\n", posix_wall); 72 | } 73 | 74 | /* Read file to memory char by char */ 75 | void readFileToMemoryItearting() { 76 | size_t size = getFileSize(FILENAME); 77 | char *mFile = (char*)malloc(size*sizeof(char)); 78 | FILE *f = fopen(FILENAME,"rb"); 79 | 80 | struct timespec tw1, tw2; 81 | clock_gettime(CLOCK_MONOTONIC, &tw1); 82 | 83 | for (int i = 0; i < size; ++i) { 84 | mFile[i] = fgetc(f); 85 | } 86 | fclose(f); 87 | 88 | clock_gettime(CLOCK_MONOTONIC, &tw2); 89 | 90 | free(mFile); 91 | 92 | double posix_wall = 1000.0*tw2.tv_sec + 1e-6*tw2.tv_nsec - (1000.0*tw1.tv_sec + 1e-6*tw1.tv_nsec); 93 | printf("Read file to memory char by char ==> time passed: %.2f ms\n", posix_wall); 94 | } 95 | 96 | /* Read file line by line */ 97 | void readFileLineByLine() { 98 | char *buffer = (char*)malloc(256*sizeof(char)); 99 | FILE *f = fopen(FILENAME,"rb"); 100 | struct timespec tw1, tw2; 101 | 102 | clock_gettime(CLOCK_MONOTONIC, &tw1); 103 | 104 | while (fgets(buffer, 256, f) != NULL) { 105 | int i = 0; 106 | for (; buffer[i] != '\0' && i < 256; ++i); 107 | char *line = (char*)malloc(i*sizeof(char)); 108 | free(line); 109 | } 110 | 111 | fclose(f); 112 | 113 | clock_gettime(CLOCK_MONOTONIC, &tw2); 114 | 115 | double posix_wall = 1000.0*tw2.tv_sec + 1e-6*tw2.tv_nsec - (1000.0*tw1.tv_sec + 1e-6*tw1.tv_nsec); 116 | printf("Read file line by line ==> time passed: %.2f ms\n", posix_wall); 117 | } 118 | 119 | /* Read file line by line */ 120 | void readFileLineBuffer() { 121 | char *buffer = (char*)malloc(256*sizeof(char)); 122 | FILE *f = fopen(FILENAME,"rb"); 123 | struct timespec tw1, tw2; 124 | 125 | clock_gettime(CLOCK_MONOTONIC, &tw1); 126 | 127 | while (fgets(buffer, 256, f) != NULL) { 128 | int i = 0; 129 | for (; buffer[i] != '\0' && i < 256; ++i); 130 | char *line = (char*)malloc(i*sizeof(char)); 131 | free(line); 132 | } 133 | 134 | fclose(f); 135 | 136 | clock_gettime(CLOCK_MONOTONIC, &tw2); 137 | 138 | double posix_wall = 1000.0*tw2.tv_sec + 1e-6*tw2.tv_nsec - (1000.0*tw1.tv_sec + 1e-6*tw1.tv_nsec); 139 | printf("Read file line by line ==> time passed: %.2f ms\n", posix_wall); 140 | } 141 | 142 | /* Send data over socket */ 143 | void* consume(void *arg) { 144 | int sockfd, newsockfd, portno = 10240; 145 | socklen_t clilen; 146 | char buffer[256]; 147 | struct sockaddr_in serv_addr, cli_addr; 148 | int n, counter = 0; 149 | size_t size = getFileSize(FILENAME); 150 | 151 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 152 | 153 | bzero((char *) &serv_addr, sizeof(serv_addr)); 154 | serv_addr.sin_family = AF_INET; 155 | serv_addr.sin_addr.s_addr = INADDR_ANY; 156 | serv_addr.sin_port = htons(portno); 157 | bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); 158 | listen(sockfd,5); 159 | clilen = sizeof(cli_addr); 160 | newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); 161 | 162 | do { 163 | n = read(newsockfd,buffer,255); 164 | counter += n; 165 | bzero(buffer,256); 166 | } while (counter < size); 167 | 168 | close(newsockfd); 169 | close(sockfd); 170 | clock_gettime(CLOCK_MONOTONIC, (struct timespec*)arg); 171 | } 172 | 173 | void* produce(void *arg) { 174 | clock_gettime(CLOCK_MONOTONIC, (struct timespec*)arg); 175 | 176 | int sockfd, portno= 10240; 177 | struct sockaddr_in serv_addr; 178 | struct hostent *server; 179 | char buffer[256]; 180 | size_t size = getFileSize(FILENAME); 181 | 182 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 183 | server = gethostbyname("localhost"); 184 | 185 | if (server == NULL) { 186 | fprintf(stderr,"ERROR, no such host\n"); 187 | exit(0); 188 | } 189 | bzero((char *) &serv_addr, sizeof(serv_addr)); 190 | serv_addr.sin_family = AF_INET; 191 | bcopy((char *)(server->h_addr), (char *)(&serv_addr.sin_addr.s_addr), (size_t)(server->h_length)); 192 | serv_addr.sin_port = htons(portno); 193 | connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)); 194 | 195 | for( int i = 0; i < (1024*1024*30)/255; ++i) { 196 | write(sockfd,&buffer,255); 197 | } 198 | 199 | close(sockfd); 200 | } 201 | 202 | void networkSend(int count) { 203 | pthread_t producer, consumer; 204 | struct timespec tw1, tw2; 205 | void *status; 206 | 207 | pthread_create(&consumer, NULL, &consume, &tw2); 208 | pthread_create(&producer, NULL, &produce, &tw1); 209 | 210 | pthread_join(producer, &status); 211 | pthread_join(consumer, &status); 212 | 213 | double posix_wall = 1000.0*tw2.tv_sec + 1e-6*tw2.tv_nsec - (1000.0*tw1.tv_sec + 1e-6*tw1.tv_nsec); 214 | printf("networkSend ==> Network time passed: %.2f ms\n", posix_wall); 215 | } 216 | 217 | 218 | 219 | /* Send data over socket */ 220 | void* consumeLineByLine(void *arg) { 221 | int sockfd, newsockfd, portno = 10240; 222 | socklen_t clilen; 223 | const int BUFFER_SIZE = 1024000; 224 | char buffer[BUFFER_SIZE]; 225 | struct sockaddr_in serv_addr, cli_addr; 226 | int n, counter = 0; 227 | size_t size = getFileSize(FILENAME); 228 | 229 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 230 | 231 | bzero((char *) &serv_addr, sizeof(serv_addr)); 232 | serv_addr.sin_family = AF_INET; 233 | serv_addr.sin_addr.s_addr = INADDR_ANY; 234 | serv_addr.sin_port = htons(portno); 235 | bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); 236 | listen(sockfd,5); 237 | clilen = sizeof(cli_addr); 238 | newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); 239 | 240 | do { 241 | n = read(newsockfd,buffer,BUFFER_SIZE); 242 | counter += n; 243 | for (int i = 0; buffer[i] != '\0' && i < n; ++i); 244 | char *line = (char*)malloc(n*sizeof(char)); 245 | free(line); 246 | bzero(buffer,BUFFER_SIZE); 247 | } while (counter < size); 248 | 249 | close(newsockfd); 250 | close(sockfd); 251 | clock_gettime(CLOCK_MONOTONIC, (struct timespec*)arg); 252 | } 253 | 254 | void* produceFromFile(void *arg) { 255 | clock_gettime(CLOCK_MONOTONIC, (struct timespec*)arg); 256 | 257 | int sockfd, portno= 10240; 258 | struct sockaddr_in serv_addr; 259 | struct hostent *server; 260 | const int BUFFER_SIZE = 4096; 261 | char buffer[BUFFER_SIZE]; 262 | 263 | FILE *f = fopen(FILENAME,"r"); 264 | struct timespec tw1, tw2; 265 | 266 | 267 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 268 | server = gethostbyname("localhost"); 269 | 270 | if (server == NULL) { 271 | fprintf(stderr,"ERROR, no such host\n"); 272 | exit(0); 273 | } 274 | bzero((char *) &serv_addr, sizeof(serv_addr)); 275 | serv_addr.sin_family = AF_INET; 276 | bcopy((char *)(server->h_addr), (char *)(&serv_addr.sin_addr.s_addr), (size_t)(server->h_length)); 277 | serv_addr.sin_port = htons(portno); 278 | connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)); 279 | 280 | size_t read = 0; 281 | while((read = fread( &buffer, 1, BUFFER_SIZE, f)) != 0) { 282 | write(sockfd,buffer,read); 283 | } 284 | 285 | fclose(f); 286 | close(sockfd); 287 | } 288 | 289 | void networkSendWithFile() { 290 | pthread_t producer, consumer; 291 | struct timespec tw1, tw2; 292 | void *status; 293 | 294 | pthread_create(&consumer, NULL, &consumeLineByLine, &tw2); 295 | pthread_create(&producer, NULL, &produceFromFile, &tw1); 296 | 297 | pthread_join(producer, &status); 298 | pthread_join(consumer, &status); 299 | 300 | double posix_wall = 1000.0*tw2.tv_sec + 1e-6*tw2.tv_nsec - (1000.0*tw1.tv_sec + 1e-6*tw1.tv_nsec); 301 | printf("networkSendWithFile ==> Network time passed: %.2f ms\n", posix_wall); 302 | } 303 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.Keys._ 2 | import sbt._ 3 | 4 | name := "TradingSimProject" 5 | 6 | version := "0.1" 7 | 8 | scalaVersion := "2.11.2" 9 | 10 | scalacOptions ++= Seq("-deprecation", "-feature") 11 | 12 | libraryDependencies ++= Seq( 13 | "com.typesafe.akka" %% "akka-actor" % "2.3.6" withSources() withJavadoc(), 14 | "com.typesafe.slick" %% "slick" % "2.1.0" withSources() withJavadoc(), 15 | "net.liftweb" %% "lift-json" % "2.6-RC1" withSources() withJavadoc(), 16 | "org.apache.httpcomponents" % "fluent-hc" % "4.3.6" withSources() withJavadoc(), 17 | "org.twitter4j" % "twitter4j-stream" % "3.0.3" withSources() withJavadoc(), 18 | "org.xerial" % "sqlite-jdbc" % "3.8.7" withSources() withJavadoc() 19 | ) 20 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/marketSimulator/BenchmarkMarketRules.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.marketSimulator 2 | 3 | import ch.epfl.ts.data.Currency._ 4 | import ch.epfl.ts.data.{DelOrder, LimitAskOrder, LimitBidOrder, MarketAskOrder, MarketBidOrder, Order, Streamable, Transaction} 5 | import ch.epfl.ts.engine.{MarketRules, PartialOrderBook} 6 | 7 | class BenchmarkMarketRules extends MarketRules { 8 | 9 | override def matchingFunction(marketId: Long, 10 | newOrder: Order, 11 | newOrdersBook: PartialOrderBook, 12 | bestMatchsBook: PartialOrderBook, 13 | send: Streamable => Unit, 14 | matchExists: (Double, Double) => Boolean = alwaysTrue, 15 | oldTradingPrice: Double, 16 | enqueueOrElse: (Order, PartialOrderBook) => Unit): Double = { 17 | if (bestMatchsBook.isEmpty) { 18 | enqueueOrElse(newOrder, newOrdersBook) 19 | oldTradingPrice 20 | } else { 21 | val bestMatch = bestMatchsBook.head 22 | 23 | // check if a matching order exists when used with a limit order, if market order: matchExists = true 24 | if (matchExists(bestMatch.price, newOrder.price)) { 25 | 26 | bestMatchsBook delete bestMatch 27 | send(DelOrder(bestMatch.oid, bestMatch.uid, newOrder.timestamp, DEF, DEF, 0.0, 0.0)) 28 | 29 | if (bestMatch.volume == newOrder.volume) { 30 | bestMatch match { 31 | case lbo: LimitBidOrder => 32 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.uid, bestMatch.oid, newOrder.uid, newOrder.oid)) 33 | case lao: LimitAskOrder => 34 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, newOrder.uid, newOrder.oid, bestMatch.uid, bestMatch.oid)) 35 | case _ => 36 | } 37 | } else if (bestMatch.volume > newOrder.volume) { 38 | bestMatch match { 39 | case lbo: LimitBidOrder => 40 | bestMatchsBook insert LimitBidOrder(bestMatch.oid, bestMatch.uid, bestMatch.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.volume - newOrder.volume, bestMatch.price) 41 | send(Transaction(marketId, bestMatch.price, newOrder.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.uid, bestMatch.oid, newOrder.uid, newOrder.oid)) 42 | send(LimitBidOrder(bestMatch.oid, bestMatch.uid, bestMatch.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.volume - newOrder.volume, bestMatch.price)) 43 | case lao: LimitAskOrder => 44 | bestMatchsBook insert LimitAskOrder(bestMatch.oid, bestMatch.uid, bestMatch.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.volume - newOrder.volume, bestMatch.price) 45 | send(Transaction(marketId, bestMatch.price, newOrder.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, newOrder.uid, newOrder.oid, bestMatch.uid, bestMatch.oid)) 46 | send(LimitAskOrder(bestMatch.oid, bestMatch.uid, bestMatch.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.volume - newOrder.volume, bestMatch.price)) 47 | case _ => 48 | } 49 | } else { 50 | bestMatch match { 51 | case lbo: LimitBidOrder => 52 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, newOrder.uid, newOrder.oid, bestMatch.uid, bestMatch.oid)) 53 | matchingFunction(marketId, LimitBidOrder(newOrder.oid, newOrder.uid, newOrder.timestamp, newOrder.whatC, newOrder.withC, newOrder.volume - bestMatch.volume, bestMatch.price), newOrdersBook, bestMatchsBook, send, matchExists, oldTradingPrice, enqueueOrElse) 54 | case lao: LimitAskOrder => 55 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.uid, bestMatch.oid, newOrder.uid, newOrder.oid)) 56 | matchingFunction(marketId, LimitAskOrder(newOrder.oid, newOrder.uid, newOrder.timestamp, newOrder.whatC, newOrder.withC, newOrder.volume - bestMatch.volume, bestMatch.price), newOrdersBook, bestMatchsBook, send, matchExists, oldTradingPrice, enqueueOrElse) 57 | case mbo: MarketBidOrder => 58 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, newOrder.uid, newOrder.oid, bestMatch.uid, bestMatch.oid)) 59 | matchingFunction(marketId, MarketBidOrder(newOrder.oid, newOrder.uid, newOrder.timestamp, newOrder.whatC, newOrder.withC, newOrder.volume - bestMatch.volume, newOrder.price), newOrdersBook, bestMatchsBook, send, matchExists, oldTradingPrice, enqueueOrElse) 60 | case mao: MarketAskOrder => 61 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.uid, bestMatch.oid, newOrder.uid, newOrder.oid)) 62 | matchingFunction(marketId, MarketAskOrder(newOrder.oid, newOrder.uid, newOrder.timestamp, newOrder.whatC, newOrder.withC, newOrder.volume - bestMatch.volume, newOrder.price), newOrdersBook, bestMatchsBook, send, matchExists, oldTradingPrice, enqueueOrElse) 63 | case _ => 64 | } 65 | } 66 | bestMatch.price 67 | } else { 68 | enqueueOrElse(newOrder, newOrdersBook) 69 | oldTradingPrice 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/marketSimulator/BenchmarkMarketSimulator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.marketSimulator 2 | 3 | import ch.epfl.ts.data._ 4 | import ch.epfl.ts.engine.{MarketRules, MarketSimulator} 5 | 6 | /** 7 | * Slightly modified MarketSimulator used for the benchmarks. 8 | * It manages the case when it receives a LastOrder to notify 9 | * the end of the benchmark. 10 | */ 11 | class BenchmarkMarketSimulator(marketId: Long, rules: MarketRules) extends MarketSimulator(marketId, rules) { 12 | 13 | 14 | override def receiver = { 15 | case last: LastOrder => 16 | send(FinishedProcessingOrders(book.asks.size, book.bids.size)); 17 | case limitBid: LimitBidOrder => 18 | tradingPrice = rules.matchingFunction(marketId, limitBid, book.bids, book.asks, this.send[Streamable], (a, b) => a <= b, tradingPrice, (limitBid, bidOrdersBook) => { bidOrdersBook insert limitBid; send(limitBid) }) 19 | case limitAsk: LimitAskOrder => 20 | tradingPrice = rules.matchingFunction(marketId, limitAsk, book.asks, book.bids, this.send[Streamable], (a, b) => a >= b, tradingPrice, (limitAsk, askOrdersBook) => { askOrdersBook insert limitAsk; send(limitAsk) }) 21 | case marketBid: MarketBidOrder => 22 | tradingPrice = rules.matchingFunction(marketId, marketBid, book.bids, book.asks, this.send[Streamable], (a, b) => true, tradingPrice, (marketBid, bidOrdersBook) => ()) 23 | case marketAsk: MarketAskOrder => 24 | tradingPrice = rules.matchingFunction(marketId, marketAsk, book.asks, book.bids, this.send[Streamable], (a, b) => true, tradingPrice, (marketAsk, askOrdersBook) => ()) 25 | case del: DelOrder => 26 | send(del) 27 | book delete del 28 | case t: Transaction => 29 | tradingPrice = t.price 30 | case _ => 31 | println("MS: got unknown") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/marketSimulator/BenchmarkTrader.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.marketSimulator 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.data.Currency._ 5 | import ch.epfl.ts.data.Transaction 6 | 7 | /** 8 | * Trader used to compute a trader's reaction in the 9 | * TraderReactionBenchmark. 10 | */ 11 | class BenchmarkTrader extends Component { 12 | 13 | def receiver = { 14 | case t:Transaction => send(LastOrder(0L, 0L, 0L, BTC, USD, 0.0, 0.0)) 15 | case _ => 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/marketSimulator/MarketSimulatorBenchmark.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.marketSimulator 2 | 3 | import akka.actor.Props 4 | import ch.epfl.ts.component.ComponentBuilder 5 | import ch.epfl.ts.component.persist.OrderPersistor 6 | import ch.epfl.ts.data.Currency._ 7 | import ch.epfl.ts.data.{DelOrder, LimitAskOrder, LimitBidOrder, MarketAskOrder, MarketBidOrder, Order} 8 | import scala.collection.mutable.ListBuffer 9 | 10 | /** 11 | * This performance test calculates the time it takes for the MarketSimulator 12 | * to process a certain amount of orders. 13 | * It is possible to use a Persistor as source or to generate orders for 14 | * input data. 15 | * To use data stored in a Persistor, use the loadOrdersFromPersistor() 16 | * function to fill the orders variable. 17 | * To use generated orders, use the generateOrders() method. 18 | */ 19 | object MarketSimulatorBenchmark { 20 | 21 | var r = scala.util.Random 22 | 23 | def main(args: Array[String]) { 24 | // val orders = loadOrdersFromPersistor(500000, "finance") 25 | val orders = generateOrders(250000) 26 | 27 | println(orders.length) 28 | 29 | // create factory 30 | implicit val builder = new ComponentBuilder("MarketSimulatorBenchmarkSystem") 31 | 32 | // Create Components 33 | val orderFeeder = builder.createRef(Props(classOf[OrderFeeder], orders), "feeder") 34 | val market = builder.createRef(Props(classOf[BenchmarkMarketSimulator], 1L, new BenchmarkMarketRules()), "marketSim") 35 | val timeCounter = builder.createRef(Props(classOf[TimeCounter]), "timeCounter") 36 | 37 | // Create Connections 38 | //orders 39 | orderFeeder.addDestination(market, classOf[LimitAskOrder]) 40 | orderFeeder.addDestination(market, classOf[LimitBidOrder]) 41 | orderFeeder.addDestination(market, classOf[MarketAskOrder]) 42 | orderFeeder.addDestination(market, classOf[MarketBidOrder]) 43 | orderFeeder.addDestination(market, classOf[DelOrder]) 44 | orderFeeder.addDestination(market, classOf[LastOrder]) 45 | // start and end signals 46 | orderFeeder.addDestination(timeCounter, classOf[StartSending]) 47 | market.addDestination(timeCounter, classOf[FinishedProcessingOrders]) 48 | 49 | // start the benchmark 50 | builder.start 51 | 52 | readLine("Press ENTER to Exit... ") 53 | builder.system.shutdown() 54 | builder.system.awaitTermination() 55 | } 56 | 57 | def generateOrders(count: Int): List[Order] = { 58 | val initTime = System.currentTimeMillis() 59 | // first generate a list of orders to feed the market simulator 60 | // we'll take the percentages from finance.csv and make 10% of LA and LB orders MA and MB orders respectively 61 | // since we have removed 10% of limit orders, we will also remove 10% of delete orders and add them 62 | // to MA and MB orders 63 | // so: LB = 23.4%, LA = 35.1%, MB = 4.2%, MA = 5.7%, DEL = 31.6% 64 | // implementation: LB=0-233, LA=234-584, MB=585-626, MA=627-683, DEL=684-999 65 | var orders: ListBuffer[Order] = ListBuffer[Order]() 66 | var oid: Int = 0 67 | // store used order ids 68 | var lbOids: Set[Int] = Set[Int]() 69 | var laOids: Set[Int] = Set[Int]() 70 | // set trading price params 71 | val tradingPrice = 100 72 | val spread = 20 73 | 74 | // generate relevant prices 75 | while (orders.length <= count) { 76 | if (orders.length % 10000 == 0) { 77 | println("generating " + orders.length + "th order.") 78 | } 79 | 80 | val it = r.nextInt(1000) 81 | // Limit Bid Order 82 | if ((it >= 0) && (it < 234)) { 83 | oid = oid + 1 84 | lbOids += oid 85 | orders.append(LimitBidOrder(oid, 0L, System.currentTimeMillis(), BTC, USD, generateOrderVolume(10), generatePrice(tradingPrice, spread))) 86 | } 87 | // Limit Ask Order 88 | else if ((it >= 243) && (it < 585)) { 89 | oid = oid + 1 90 | laOids += oid 91 | orders.append(LimitAskOrder(oid, 0L, System.currentTimeMillis(), BTC, USD, generateOrderVolume(10), generatePrice(tradingPrice, spread))) 92 | } 93 | // Market Bid Order 94 | else if ((it >= 585) && (it < 627)) { 95 | oid = oid + 1 96 | orders.append(MarketBidOrder(oid, 0L, System.currentTimeMillis(), BTC, USD, generateOrderVolume(10), 0.0)) 97 | } 98 | // Market Ask Order 99 | else if ((it >= 627) && (it < 684)) { 100 | oid = oid + 1 101 | orders.append(MarketAskOrder(oid, 0L, System.currentTimeMillis(), BTC, USD, generateOrderVolume(10), 0.0)) 102 | } 103 | // Del Order 104 | else if ((it >= 684) && (it <= 1000)) { 105 | val it2 = r.nextInt(585) 106 | // generate delete order for limit bid order 107 | if ((it2 >= 0) && (it2 < 234)) { 108 | if (lbOids.size > 0) { 109 | val lbIdToDelete = lbOids.toVector(r.nextInt(lbOids.size)) 110 | orders.append(DelOrder(lbIdToDelete, 0L, System.currentTimeMillis(), BTC, USD, 0.0, 0.0)) 111 | lbOids -= lbIdToDelete 112 | } 113 | } 114 | // generate delete order for limit ask order 115 | else if ((it2 >= 234) && (it2 <= 585)) { 116 | if (laOids.size > 0) { 117 | val laIdToDelete = laOids.toVector(r.nextInt(laOids.size)) 118 | orders.append(DelOrder(laIdToDelete, 0L, System.currentTimeMillis(), BTC, USD, 0.0, 0.0)) 119 | laOids -= laIdToDelete 120 | } 121 | } 122 | } 123 | } 124 | println("generated " + orders.size + " orders in " + (System.currentTimeMillis() - initTime) + " ms.") 125 | countOrderTypes(orders.toList) 126 | orders.toList 127 | } 128 | 129 | // generates a price value in the range: [tradingPrice - spread/2; tradingPrice + spread/2] 130 | def generatePrice(tradingPrice: Double, spread: Double): Double = { 131 | tradingPrice - (spread / 2) + r.nextDouble() * spread 132 | } 133 | 134 | // generates a value between 10 and rangeTimesTen * 10 135 | def generateOrderVolume(rangeTimesTen: Int): Int = { 136 | (r.nextInt(rangeTimesTen) + 1) * 10 137 | } 138 | 139 | def loadOrdersFromPersistor(count: Int, persistorName: String): List[Order] = { 140 | val financePersistor = new OrderPersistor(persistorName) // requires to have run CSVFetcher on finance.csv (obtained by mail from Milos) 141 | financePersistor.init() 142 | var orders: List[Order] = Nil 143 | orders = financePersistor.loadBatch(count) 144 | 145 | // for(i <- 1 to count) { 146 | // if (i % 100 == 0) println("loaded " + i + "th order from persistor") 147 | // orders = financePersistor.loadSingle(i) :: orders 148 | // } 149 | orders 150 | } 151 | 152 | // print the generated orders distribution 153 | def countOrderTypes(orders: List[Order]) = { 154 | var laCount = 0 155 | var lbCount = 0 156 | var maCount = 0 157 | var mbCount = 0 158 | var delCount = 0 159 | orders.map { 160 | case o: LimitBidOrder => lbCount = lbCount + 1 161 | case o: LimitAskOrder => laCount = laCount + 1 162 | case o: MarketAskOrder => maCount = maCount + 1 163 | case o: MarketBidOrder => mbCount = mbCount + 1 164 | case o: DelOrder => delCount = delCount + 1 165 | } 166 | println("Total: " + orders.size + ", LA: " + laCount + ", LB: " + lbCount + ", MA: " + maCount + ", MB: " + mbCount + ", DEL: " + delCount) 167 | } 168 | 169 | // count the occurences of different types of orders in finance.csv to get an idea of how to generate fake orders for the benchmarking 170 | // results: LB orders= 26%, LA orders = 39%, DEL orders = 35% 171 | def countOrderDistributionInFinanceCSV(): Unit = { 172 | val financePersistor = new OrderPersistor("finance") // requires to have run CSVFetcher on finance.csv (obtained by mail from Milos) 173 | financePersistor.init() 174 | var laOrders: Int = 0 175 | var lbOrders: Int = 0 176 | var delOrders: Int = 0 177 | var order: Order = null 178 | for (i <- 1 to 50000) { 179 | financePersistor.loadSingle(i) match { 180 | case lb: LimitBidOrder => laOrders = laOrders + 1 181 | case la: LimitAskOrder => lbOrders = lbOrders + 1 182 | case del: DelOrder => delOrders = delOrders + 1 183 | case _ => 184 | } 185 | } 186 | println("LB orders: " + lbOrders + ", LA orders: " + laOrders + ", DEL orders: " + delOrders) 187 | } 188 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/marketSimulator/OrderFeeder.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.marketSimulator 2 | 3 | import ch.epfl.ts.component.{Component, StartSignal} 4 | import ch.epfl.ts.data.Currency._ 5 | import ch.epfl.ts.data.Order 6 | 7 | case class LastOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) extends Order(oid, uid, timestamp, whatC, withC, volume, price) 8 | 9 | /** 10 | * Component used to send orders to the MarketSimulator for the MarketSimulatorBenchmark. 11 | * It appends a LastOrder to the list of orders to send to notify the MarketSimulator 12 | * that there are no more orders to process. 13 | */ 14 | class OrderFeeder(orders: List[Order]) extends Component { 15 | 16 | def receiver = { 17 | case StartSignal() => { 18 | val ordersSent = orders :+ LastOrder(0L, 0L, System.currentTimeMillis(), DEF, DEF, 0.0, 0.0) 19 | send(StartSending(orders.size)) 20 | ordersSent.map { o => send(o) } 21 | } 22 | case _ => println("OrderFeeder: unknown received") 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/marketSimulator/TimeCounter.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.marketSimulator 2 | 3 | import ch.epfl.ts.component.Component 4 | 5 | case class StartSending(ordersCount: Int) 6 | case class FinishedProcessingOrders(asksSize: Int, bidsSize: Int) 7 | 8 | /** 9 | * Simple component used to compute the time it takes for the MarketSimulatorBenchmark 10 | * to process the orders. It receives a start and stop signal, computes the time 11 | * difference and prints the result. 12 | */ 13 | class TimeCounter extends Component { 14 | 15 | var initSendingTime: Long = 0L 16 | var ordersCount = 0 17 | 18 | def receiver = { 19 | case StartSending(o) => { 20 | ordersCount = o 21 | initSendingTime = System.currentTimeMillis(); println("TimeCounter: feeding " + o + " orders started.") 22 | } 23 | case FinishedProcessingOrders(aSize, bSize) => { 24 | println("TimeCounter: processed " + ordersCount + " orders in " + (System.currentTimeMillis() - initSendingTime) + " ms.") 25 | println("TimeCounter: askOrdersBook size = " + aSize + ", bidOrdersBook size = " + bSize) 26 | } 27 | case _ => 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/marketSimulator/TraderReactionBenchmark.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.marketSimulator 2 | 3 | import akka.actor.Props 4 | import ch.epfl.ts.component.ComponentBuilder 5 | import ch.epfl.ts.component.persist.TransactionPersistor 6 | import ch.epfl.ts.data.Currency._ 7 | import ch.epfl.ts.data.{DelOrder, LimitAskOrder, LimitBidOrder, MarketAskOrder, MarketBidOrder, Order, Transaction} 8 | import ch.epfl.ts.component.utils.BackLoop 9 | 10 | /** 11 | * The goal of this test is to measure the time it takes for a trader’s order to be executed 12 | * since the moment when the order that will trigger the trader’s action is sent directly to 13 | * the MarketSimulator. 14 | */ 15 | object TraderReactionBenchmark { 16 | 17 | def main(args: Array[String]) { 18 | var orders: List[Order] = Nil 19 | orders = MarketAskOrder(0L, 0L, System.currentTimeMillis(), BTC, USD, 50.0, 0.0) :: orders 20 | orders = LimitBidOrder(0L, 0L, System.currentTimeMillis(), BTC, USD, 50.0, 50.0) :: orders 21 | 22 | // create factory 23 | implicit val builder = new ComponentBuilder("MarketSimulatorBenchmarkSystem") 24 | 25 | // Persistor 26 | val persistor = new TransactionPersistor("bench-persistor") 27 | persistor.init() 28 | 29 | // Create Components 30 | val orderFeeder = builder.createRef(Props(classOf[OrderFeeder], orders), "orderFeeder") 31 | val market = builder.createRef(Props(classOf[BenchmarkMarketSimulator], 1L, new BenchmarkMarketRules()), "market") 32 | val backloop = builder.createRef(Props(classOf[BackLoop], 1L, persistor), "backloop") 33 | val trader = builder.createRef(Props(classOf[BenchmarkTrader]), "trader") 34 | val timeCounter = builder.createRef(Props(classOf[TimeCounter]), "timeCounter") 35 | 36 | // Create Connections 37 | //orders 38 | orderFeeder.addDestination(market, classOf[LimitAskOrder]) 39 | orderFeeder.addDestination(market, classOf[LimitBidOrder]) 40 | orderFeeder.addDestination(market, classOf[MarketAskOrder]) 41 | orderFeeder.addDestination(market, classOf[MarketBidOrder]) 42 | orderFeeder.addDestination(market, classOf[DelOrder]) 43 | orderFeeder.addDestination(market, classOf[LastOrder]) 44 | // used to test without the backloop 45 | // market.addDestination(trader, classOf[Transaction]) 46 | market.addDestination(backloop, classOf[Transaction]) 47 | backloop.addDestination(trader, classOf[Transaction]) 48 | trader.addDestination(market, classOf[LastOrder]) 49 | // start and end signals 50 | orderFeeder.addDestination(timeCounter, classOf[StartSending]) 51 | market.addDestination(timeCounter, classOf[FinishedProcessingOrders]) 52 | 53 | // start the benchmark 54 | builder.start 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/scala/BenchmarkCommands.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.scala 2 | 3 | import akka.actor.Actor 4 | import ch.epfl.ts.data.{Order, Transaction} 5 | 6 | case class Start(offset: Long) 7 | 8 | case class Stop() 9 | 10 | case class Report(source: String, startTime: Long, endTime: Long) 11 | 12 | class Reporter extends Actor { 13 | var genStart: Long = 0 14 | var conEnd: Long = 0 15 | override def receive = { 16 | case r: Report => { 17 | println(r.source, "time", r.endTime - r.startTime) 18 | if (r.source == "Generator") { 19 | genStart = r.startTime 20 | } else if (r.source == "Consumer") { 21 | conEnd = r.endTime 22 | } 23 | 24 | if (genStart != 0 && conEnd != 0) {println("Total time", conEnd - genStart)} 25 | } 26 | } 27 | } 28 | 29 | class Printer extends Actor { 30 | override def receive = { 31 | case t: Transaction => println(System.currentTimeMillis, "Trans", t.toString) 32 | case o: Order => println(System.currentTimeMillis, "Order", o.toString) 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/scala/CommunicationBenchmark.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.scala 2 | 3 | import java.io.{BufferedInputStream, BufferedOutputStream, ObjectInputStream, ObjectOutputStream} 4 | import java.net.{ServerSocket, Socket} 5 | 6 | import akka.actor.{Actor, ActorRef, ActorSystem, Props} 7 | 8 | /** 9 | * Compares message transferring throughput between Java Sockets (serialized Tuples over Buffered socket) and akka Actors. 10 | * There is an implementation for Tuple2(Int, Int) and Tuple3(Int, Int, Int). The amount of tuples sent can be defined 11 | * using the msgQuantity variable. The throughput is computed as the time delta when the first message is sent and the last 12 | * tuple is received. 13 | * 14 | */ 15 | object CommunicationBenchmark { 16 | // val writer = new PrintWriter(new File("test.txt")) 17 | // val outStream = new PrintStream(new FileOutputStream("commBench.txt", true)) 18 | // Console.out = outStream 19 | // System.setOut(outStream) 20 | val msgQuantity = 10000000 21 | 22 | def main(args: Array[String]) { 23 | 24 | /** 25 | * Tuple2 26 | */ 27 | 28 | /** 29 | * Java sockets 30 | */ 31 | // javaSockets2(msgQuantity) 32 | 33 | /** 34 | * akka actors 35 | */ 36 | // actorsTuple2(msgQuantity) 37 | 38 | /** 39 | * triple actors 40 | */ 41 | // tripleActor2(msgQuantity) 42 | 43 | /** 44 | * quadruple actors 45 | */ 46 | quadrupleActor2(msgQuantity) 47 | 48 | /** 49 | * Tuple3 50 | */ 51 | 52 | /** 53 | * Java sockets 54 | */ 55 | // javaSockets3(msgQuantity) 56 | 57 | /** 58 | * akka actors 59 | */ 60 | // actorsTuple3(msgQuantity) 61 | 62 | /** 63 | * triple actors 64 | */ 65 | // tripleActor3(msgQuantity) 66 | 67 | /** 68 | * quadruple actors 69 | */ 70 | // quadrupleActor3(msgQuantity) 71 | 72 | } 73 | 74 | /** 75 | * 76 | * 77 | * Tuples 2 78 | * 79 | * 80 | */ 81 | 82 | def generateTuple2(quantity: Int): List[Tuple2[Int, Int]] = { 83 | println("#####----- Tuples of size 2 -----#####") 84 | var elemsList: List[Tuple2[Int, Int]] = List() 85 | for (i <- 1 to quantity) { 86 | elemsList = new Tuple2(i, i) :: elemsList 87 | } 88 | elemsList = (-1, -1) :: elemsList 89 | elemsList = elemsList.reverse 90 | println("generated list of " + elemsList.size + " tuples.") 91 | elemsList 92 | } 93 | 94 | /** 95 | * Java Sockets 96 | */ 97 | 98 | def javaSockets2(quantity: Int) = { 99 | val elemsList = generateTuple2(quantity) 100 | println("###--- Java Sockets ---###") 101 | val server = new javaServer2(8765) 102 | val client = new Socket() 103 | server.start() 104 | client.connect(server.server.getLocalSocketAddress) 105 | val stream = new ObjectOutputStream(new BufferedOutputStream(client.getOutputStream)) 106 | server.startTime = System.currentTimeMillis(); 107 | elemsList.map(a => { stream.writeObject(a) }) 108 | stream.close() 109 | } 110 | 111 | class javaServer2(port: Int) extends Thread { 112 | 113 | val server = new ServerSocket(port) 114 | var isRunning = true 115 | var startTime: Long = 0 116 | 117 | override def run() { 118 | 119 | val worker = server.accept() 120 | val ois = new ObjectInputStream(new BufferedInputStream(worker.getInputStream)) 121 | var newObject: Any = null 122 | while ({ newObject = ois.readObject(); (newObject != (-1, -1)) }) { 123 | // println("Java server: received: " + newObject) 124 | } 125 | println("javaTime: " + (System.currentTimeMillis() - startTime) + "ms") 126 | this.stop() 127 | } 128 | } 129 | 130 | /** 131 | * akka Actors 132 | */ 133 | 134 | def actorsTuple2(quantity: Int) = { 135 | val elemsList = generateTuple2(quantity) 136 | println("###--- Akka actors ---###") 137 | 138 | val system = ActorSystem("CommBenchmark") 139 | val receiver = system.actorOf(Props(new ReceiverActor2), "receiver") 140 | val sender = system.actorOf(Props(new SenderActor2(receiver)), "sender") 141 | sender ! StartTuples2(elemsList) 142 | } 143 | 144 | case class StartTuples2(tuples: List[Tuple2[Int, Int]]) 145 | 146 | class SenderActor2(receiver: ActorRef) extends Actor { 147 | var startTime: Long = 0 148 | def receive = { 149 | case StartTuples2(tuples) => { 150 | startTime = System.currentTimeMillis() 151 | tuples.map(x => receiver ! x); 152 | receiver ! "Stop" 153 | } 154 | 155 | case endTime: Long => { 156 | println("akka time: " + (endTime - startTime) + " ms.") 157 | context.system.shutdown() 158 | } 159 | } 160 | } 161 | 162 | class ReceiverActor2 extends Actor { 163 | def receive = { 164 | case Tuple2(a, b) => {} 165 | case "Stop" => sender ! System.currentTimeMillis() 166 | } 167 | } 168 | 169 | /** 170 | * Triple message passing 171 | */ 172 | 173 | def tripleActor2(quantity: Int) = { 174 | val elemsList = generateTuple2(quantity) 175 | val system3 = ActorSystem("TripleActor2") 176 | val receiver3 = system3.actorOf(Props(new ReceiverActor2), "receiver3") 177 | val middle3 = system3.actorOf(Props(new middleActor2(receiver3)), "middle3") 178 | val sender3 = system3.actorOf(Props(new SenderActor2(middle3)), "sender3") 179 | middle3 ! sender3 180 | sender3 ! StartTuples2(elemsList) 181 | } 182 | 183 | class middleActor2(dest: ActorRef) extends Actor { 184 | var source: ActorRef = null 185 | def receive = { 186 | case t: Tuple2[Int, Int] => { 187 | dest ! t 188 | } 189 | case "Stop" => dest ! "Stop" 190 | case endTime: Long => source ! endTime 191 | case a: ActorRef => source = a 192 | } 193 | } 194 | 195 | /** 196 | * Quadruple message passing 197 | */ 198 | 199 | def quadrupleActor2(quantity: Int) = { 200 | val elemsList = generateTuple2(quantity) 201 | val system4 = ActorSystem("QuadActors2") 202 | val receiver3 = system4.actorOf(Props(new ReceiverActor2), "receiver3") 203 | val middle1 = system4.actorOf(Props(new middleActor2(receiver3)), "middle1") 204 | val middle2 = system4.actorOf(Props(new middleActor2(middle1)), "middle2") 205 | val sender3 = system4.actorOf(Props(new SenderActor2(middle2)), "sender3") 206 | middle1 ! middle2 207 | middle2 ! sender3 208 | sender3 ! StartTuples2(elemsList) 209 | } 210 | 211 | /** 212 | * 213 | * Tuple3 214 | * 215 | */ 216 | 217 | def generateTuple3(quantity: Int): List[Tuple3[Int, Int, Int]] = { 218 | println("#####----- Tuples of size 3 -----#####") 219 | var elemsList3: List[Tuple3[Int, Int, Int]] = List() 220 | for (i <- 1 to quantity) { 221 | elemsList3 = new Tuple3(i, i, i) :: elemsList3 222 | } 223 | elemsList3 = (-1, -1, -1) :: elemsList3 224 | elemsList3 = elemsList3.reverse 225 | println("generated list of " + elemsList3.size + " tuples.") 226 | elemsList3 227 | } 228 | 229 | /** 230 | * Java Sockets 231 | */ 232 | 233 | def javaSockets3(quantity: Int) = { 234 | val elemsList3 = generateTuple3(quantity) 235 | println("###--- Java Sockets ---###") 236 | val server = new javaServer3(8765) 237 | val client = new Socket() 238 | server.start() 239 | client.connect(server.server.getLocalSocketAddress) 240 | val stream = new ObjectOutputStream(new BufferedOutputStream(client.getOutputStream)) 241 | server.startTime = System.currentTimeMillis(); 242 | elemsList3.map(a => { stream.writeObject(a) }) 243 | stream.close() 244 | } 245 | 246 | class javaServer3(port: Int) extends Thread { 247 | 248 | val server = new ServerSocket(port) 249 | var isRunning = true 250 | var startTime: Long = 0 251 | 252 | override def run() { 253 | 254 | val worker = server.accept() 255 | val ois = new ObjectInputStream(new BufferedInputStream(worker.getInputStream)) 256 | var newObject: Any = null 257 | while ({ newObject = ois.readObject(); (newObject != (-1, -1, -1)) }) { 258 | // println("Java server: received: " + newObject) 259 | } 260 | println("javaTime: " + (System.currentTimeMillis() - startTime) + "ms") 261 | this.stop() 262 | } 263 | } 264 | 265 | /** 266 | * akka Actors 267 | */ 268 | 269 | def actorsTuple3(quantity: Int) = { 270 | val elemsList = generateTuple3(quantity) 271 | println("###--- Akka actors ---###") 272 | 273 | val system3 = ActorSystem("CommBenchmark3") 274 | val receiver3 = system3.actorOf(Props(new ReceiverActor3), "receiver3") 275 | val sender3 = system3.actorOf(Props(new SenderActor3(receiver3)), "sender3") 276 | sender3 ! StartTuples3(elemsList) 277 | } 278 | 279 | def quadrupleActor3(quantity: Int) = { 280 | val elemsList = generateTuple3(quantity) 281 | val system4 = ActorSystem("QuadActors2") 282 | val receiver3 = system4.actorOf(Props(new ReceiverActor3), "receiver3") 283 | val middle1 = system4.actorOf(Props(new middleActor3(receiver3)), "middle1") 284 | val middle2 = system4.actorOf(Props(new middleActor3(middle1)), "middle2") 285 | val sender3 = system4.actorOf(Props(new SenderActor3(middle2)), "sender3") 286 | middle1 ! middle2 287 | middle2 ! sender3 288 | sender3 ! StartTuples3(elemsList) 289 | } 290 | 291 | case class StartTuples3(tuples: List[Tuple3[Int, Int, Int]]) 292 | 293 | class SenderActor3(receiver: ActorRef) extends Actor { 294 | var startTime: Long = 0 295 | def receive = { 296 | case StartTuples3(tuples) => { 297 | startTime = System.currentTimeMillis() 298 | tuples.map(x => receiver ! x); 299 | receiver ! "Stop" 300 | } 301 | 302 | case endTime: Long => { 303 | println("akka time: " + (endTime - startTime) + " ms.") 304 | context.system.shutdown() 305 | } 306 | } 307 | } 308 | 309 | class ReceiverActor3 extends Actor { 310 | def receive = { 311 | case Tuple3(a, b, c) => {} 312 | case "Stop" => sender ! System.currentTimeMillis() 313 | 314 | } 315 | } 316 | 317 | /** 318 | * Triple message passing 319 | */ 320 | 321 | def tripleActor3(quantity: Int) = { 322 | val elemsList = generateTuple3(quantity) 323 | val system3 = ActorSystem("TripleActor3") 324 | val receiver3 = system3.actorOf(Props(new ReceiverActor3), "receiver3") 325 | val middle3 = system3.actorOf(Props(new middleActor3(receiver3)), "middle3") 326 | val sender3 = system3.actorOf(Props(new SenderActor3(middle3)), "sender3") 327 | middle3 ! sender3 328 | sender3 ! StartTuples3(elemsList) 329 | } 330 | 331 | class middleActor3(dest: ActorRef) extends Actor { 332 | var source: ActorRef = null 333 | def receive = { 334 | case t: Tuple3[Int, Int, Int] => { 335 | dest ! t 336 | } 337 | case "Stop" => dest ! "Stop" 338 | case endTime: Long => source ! endTime 339 | case a: ActorRef => source = a 340 | } 341 | } 342 | 343 | } 344 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/scala/FetchActors.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.scala 2 | 3 | import akka.actor.{Actor, ActorRef, ActorSystem, Props} 4 | 5 | import scala.concurrent.duration.FiniteDuration 6 | import scala.io.Source 7 | 8 | abstract class TimedReporterActor(master: ActorRef, dest: List[ActorRef], source: String) extends Actor { 9 | val system = this.context.system 10 | import system.dispatcher 11 | 12 | def receive = { 13 | case c: Start => this.context.system.scheduler.scheduleOnce(FiniteDuration(c.offset, scala.concurrent.duration.MILLISECONDS))( 14 | self ! RunItNow 15 | //readAndSend 16 | ) 17 | case RunItNow => readAndSend 18 | } 19 | 20 | case class RunItNow() 21 | 22 | def readAndSend: Unit = { 23 | val startTime = System.currentTimeMillis 24 | f() 25 | val endTime = System.currentTimeMillis 26 | master ! Report(source, startTime, endTime) 27 | } 28 | 29 | def f(): Unit 30 | } 31 | 32 | object TimedReporterActor { 33 | def fileFetchActor(sys: ActorSystem, master: ActorRef, filename: String): (List[ActorRef] => ActorRef) = { 34 | (dest: List[ActorRef]) => sys.actorOf(Props(classOf[FileFetch], master, filename, dest)) 35 | } 36 | } 37 | 38 | class FileFetch(master: ActorRef, filename: String, dest: List[ActorRef]) extends TimedReporterActor(master, dest, "Generator") { 39 | override def f(): Unit = Source.fromFile(filename).getLines().foreach( 40 | s => { 41 | val l = s.split(",") 42 | // dest.map(_ ! Transaction(l(1).toDouble, l(2).toDouble, l(0).toLong, Currency.withName(l(3).toLowerCase), l(4), l(5))) 43 | } 44 | ) 45 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/scala/FileProcessBenchActor.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.scala 2 | 3 | import akka.actor.ActorRef 4 | import ch.epfl.ts.component.Component 5 | import ch.epfl.ts.data.Transaction 6 | 7 | object FileProcessBenchActor { 8 | def main(args: Array[String]) = { 9 | /* 10 | val system = ActorSystem("DataSourceSystem") 11 | val reporter = system.actorOf(Props[Reporter]) 12 | val printer = system.actorOf(Props(classOf[ConsumerActor], reporter)) 13 | 14 | val mainActor = new InStage[Transaction](system, List(printer)) 15 | .withFetcherActor(TimedReporterActor.fileFetchActor(system, reporter, "fakeData.csv")) 16 | .start 17 | 18 | mainActor ! new Start(0) 19 | 20 | 21 | implicit val builder = new ComponentBuilder("DataSourceSystem") 22 | 23 | val printer = builder.createRef(Props(classOf[Printer], "my-printer")) 24 | val persistor = builder.createRef(Props(classOf[TransactionPersistanceComponent], "btce-transaction-db")) 25 | val fetcher = builder.createRef(Props(classOf[BtceTransactionPullFetcherComponent], "my-fetcher")) 26 | 27 | fetcher.addDestination(printer, classOf[Transaction]) 28 | fetcher.addDestination(persistor, classOf[Transaction]) 29 | 30 | builder.start 31 | 32 | */ 33 | } 34 | } 35 | 36 | 37 | 38 | class Consumer(reporter: ActorRef) extends Component { 39 | var notInit = true 40 | var startTime: Long = 0 41 | var count: Long = 0 42 | override def receiver = { 43 | case t:Transaction => timeConsume 44 | case _ => 45 | } 46 | def timeConsume: Unit = { 47 | if (notInit) { 48 | notInit = false 49 | startTime = System.currentTimeMillis() 50 | } 51 | count += 1 52 | //println(count) 53 | if (count == 999477) { 54 | val endTime = System.currentTimeMillis() 55 | reporter ! Report("Consumer", startTime, endTime) 56 | } 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/scala/LoadFromFileBenchmark.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.scala 2 | 3 | import java.io._ 4 | 5 | import akka.actor.{Actor, ActorRef, ActorSystem, Props} 6 | 7 | import scala.io.Source 8 | import scala.util.Random 9 | 10 | /** 11 | * Benchmarking reading data from a csv file, parsing it and instantiating Transaction objects from it 12 | * 13 | */ 14 | object LoadFromFileBenchmark { 15 | 16 | def main(args: Array[String]) = { 17 | // generateFakeData 18 | val filename = "fakeData-999721.csv" 19 | 20 | println("#####----- Java: using BufferedReader -----#####") 21 | var br: BufferedReader = null; 22 | var line: String = ""; 23 | val cvsSplitBy: String = ","; 24 | var rawList: List[String] = List() 25 | 26 | br = new BufferedReader(new FileReader(filename)); 27 | print("read file and store into List: ") 28 | val startTime = System.currentTimeMillis() 29 | while ({ line = br.readLine(); line != null }) { 30 | rawList = line :: rawList 31 | } 32 | println((System.currentTimeMillis() - startTime) + "ms.") 33 | if (br != null) { 34 | br.close(); 35 | } 36 | 37 | var splitList: List[Array[String]] = List() 38 | br = new BufferedReader(new FileReader(filename)); 39 | print("read file, parse and store into List: ") 40 | val startTime2 = System.currentTimeMillis() 41 | while ({ line = br.readLine(); line != null }) { 42 | 43 | splitList = line.split(cvsSplitBy) :: splitList 44 | } 45 | println((System.currentTimeMillis() - startTime2) + " ms.") 46 | 47 | if (br != null) { 48 | br.close(); 49 | } 50 | println 51 | 52 | println("#####----- Scala: using scala.io.Source -----#####") 53 | var source = Source.fromFile(filename) 54 | println("###-- Unbuffered run ---###") 55 | print("read file and store into list: ") 56 | val entriesList = timed(source.getLines().toList) 57 | println("entries count: " + entriesList.size) 58 | print("from list: parse each element and instantiate Transaction objects: ") 59 | // timed( 60 | // entriesList.map(_.split(",")).map(l => Transaction(l(1).toDouble, l(2).toDouble, l(0).toLong, Currency.withName(l(3)), l(4), l(5)))) 61 | 62 | println("###--- Buffered run ---###") 63 | print("read from file, instantiate Iterator, parse and instantiate Transaction objects: ") 64 | source = Source.fromFile(filename) 65 | timed(source.getLines().foreach( 66 | s => { 67 | val l = s.split(",") 68 | // Transaction(l(1).toDouble, l(2).toDouble, l(0).toLong, Currency.withName(l(3)), l(4), l(5)) 69 | })) 70 | println 71 | 72 | 73 | println("#####----- Scala: using akka Actors and scala.io.Source -----#####") 74 | val system = ActorSystem("ReadingBenchmarking") 75 | val bufferedReader = system.actorOf(Props(new BufferedReaderActor(filename)), "bufferedReader") 76 | val simpleReader = system.actorOf(Props(new SimpleReaderActor(filename, bufferedReader)), "simpleReader") 77 | simpleReader ! "Start" 78 | } 79 | 80 | class SimpleReaderActor(filename: String, next: ActorRef) extends Actor { 81 | 82 | 83 | def receive = { 84 | case "Start" => timed({ 85 | //val start = System.currentTimeMillis() 86 | val source = Source.fromFile(filename) 87 | print("read file and store into list: ") 88 | // val entriesList = timed(source.getLines().toList) 89 | 90 | val entriesList = source.getLines().toList 91 | //println( (System.currentTimeMillis()-start) + " ms") 92 | // next ! "Start" 93 | context.system.shutdown() 94 | }) 95 | } 96 | } 97 | 98 | class BufferedReaderActor(filename: String) extends Actor { 99 | val source = Source.fromFile(filename) 100 | 101 | def receive = { 102 | case "Start" => { 103 | print("read from file, instantiate Iterator, parse and instantiate Transaction objects: ") 104 | 105 | timed(source.getLines().foreach( 106 | s => { 107 | val l = s.split(",") 108 | // Transaction(l(1).toDouble, l(2).toDouble, l(0).toLong, Currency.withName(l(3)), l(4), l(5)) 109 | })) 110 | 111 | context.system.shutdown() 112 | } 113 | } 114 | } 115 | 116 | def timed[A](block: => A) = { 117 | val t0 = System.currentTimeMillis 118 | val result = block 119 | println((System.currentTimeMillis - t0) + "ms") 120 | result 121 | } 122 | 123 | def generateFakeData = { 124 | val writer = new PrintWriter(new File("fakeData.csv")) 125 | val rnd = new Random() 126 | for (a <- 1 to 1000000) { 127 | writer.write(a + "," + (rnd.nextInt(150) + 100) + "," + (rnd.nextInt(30) + 1) + "," + "usd" + "," + "buyer" + "," + "seller" + "\n") 128 | } 129 | writer.close() 130 | } 131 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/benchmark/scala/PullFetchBenchmarkImpl.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.scala 2 | 3 | import ch.epfl.ts.component.fetch.PullFetch 4 | import ch.epfl.ts.data.Currency._ 5 | import ch.epfl.ts.data.Transaction 6 | 7 | import scala.io.Source 8 | 9 | class PullFetchBenchmarkImpl extends PullFetch[Transaction] { 10 | override def interval = 12000 * 1000 * 1000 11 | 12 | val filename = "fakeData.csv" 13 | var called = false 14 | 15 | override def fetch: List[Transaction] = { 16 | if (called) { 17 | List[Transaction]() 18 | } else { 19 | called = true 20 | val source = Source.fromFile(filename) 21 | val lines = source.getLines().toList 22 | // lines.map(_.split(",")).map( 23 | // l => Transaction(l(1).toDouble, l(2).toDouble, l(0).toLong, Currency.withName(l(3).toLowerCase), l(4), l(5)) 24 | // ) 25 | 26 | new Transaction(0, 1.0,1.0,1,USD, BTC, 1, 1, 1, 1) :: Nil 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/Component.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component 2 | 3 | import akka.actor.{Actor, ActorRef, ActorSystem} 4 | 5 | import scala.reflect.ClassTag 6 | 7 | import scala.collection.mutable.{HashMap => MHashMap} 8 | 9 | case class StartSignal() 10 | case class StopSignal() 11 | case class ComponentRegistration(ar: ActorRef, ct: Class[_], name: String) 12 | 13 | final class ComponentBuilder(name: String) { 14 | type ComponentProps = akka.actor.Props 15 | val system = ActorSystem(name) 16 | var graph = Map[ComponentRef, List[(ComponentRef, Class[_])]]() 17 | var instances = List[ComponentRef]() 18 | 19 | def add(src: ComponentRef, dest: ComponentRef, data: Class[_]) { 20 | println("Connecting " + src.ar + " to " + dest.ar + " for type " + data.getSimpleName) 21 | graph = graph + (src -> ((dest, data) :: graph.getOrElse(src, List[(ComponentRef, Class[_])]()))) 22 | src.ar ! ComponentRegistration(dest.ar, data, dest.name) 23 | } 24 | 25 | def add(src: ComponentRef, dest: ComponentRef) = (src, dest, classOf[Any]) 26 | 27 | def start = instances.map(cr => { 28 | cr.ar ! new StartSignal() 29 | println("Sending start Signal to " + cr.ar) 30 | }) 31 | 32 | def stop = instances.map { cr => { 33 | cr.ar ! new StopSignal() 34 | println("Sending stop Signal to " + cr.ar) 35 | } } 36 | 37 | def createRef(props: ComponentProps, name: String) = { 38 | instances = new ComponentRef(system.actorOf(props, name), props.clazz, name) :: instances 39 | instances.head 40 | } 41 | } 42 | 43 | class ComponentRef(val ar: ActorRef, val clazz: Class[_], val name: String) { 44 | // TODO: Verify clazz <: Component 45 | def addDestination(destination: ComponentRef, data: Class[_])(implicit cb: ComponentBuilder) = { 46 | cb.add(this, destination, data) 47 | } 48 | } 49 | 50 | 51 | trait Receiver extends Actor { 52 | def receive: PartialFunction[Any, Unit] 53 | 54 | def send[T: ClassTag](t: T): Unit 55 | def send[T: ClassTag](t: List[T]): Unit 56 | } 57 | 58 | abstract class Component extends Receiver { 59 | var dest = MHashMap[Class[_], List[ActorRef]]() 60 | var destName = MHashMap[String, ActorRef]() 61 | var stopped = true 62 | 63 | final def componentReceive: PartialFunction[Any, Unit] = { 64 | case ComponentRegistration(ar, ct, name) => 65 | dest += (ct -> (ar :: dest.getOrElse(ct, List()))) 66 | destName += (name -> ar) 67 | println("Received destination " + this.getClass.getSimpleName + ": from " + ar + " to " + ct.getSimpleName) 68 | case s: StartSignal => stopped = false 69 | receiver(s) 70 | println("Received Start " + this.getClass.getSimpleName) 71 | case s: StopSignal => context.stop(self) 72 | receiver(s) 73 | println("Received Stop " + this.getClass.getSimpleName) 74 | case y if stopped => println("Received data when stopped " + this.getClass.getSimpleName + " of type " + y.getClass ) 75 | } 76 | 77 | def receiver: PartialFunction[Any, Unit] 78 | 79 | /* TODO: Dirty hack, componentReceive giving back unmatched to rematch in receiver using a andThen */ 80 | override def receive = componentReceive orElse receiver 81 | 82 | final def send[T: ClassTag](t: T) = dest.get(t.getClass).map(_.map (_ ! t)) 83 | final def send[T](name: String, t: T) = destName.get(name).map(_ ! t) 84 | final def send[T: ClassTag](t: List[T]) = t.map( elem => dest.get(elem.getClass).map(_.map(_ ! elem))) 85 | final def send[T](name: String, t: List[T]) = destName.get(name).map(d => t.map(d ! _)) 86 | } 87 | 88 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/fetch/BitfinexFetcher.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | import ch.epfl.ts.data.Currency._ 4 | import ch.epfl.ts.data.{DelOrder, LimitOrder, LimitAskOrder, LimitBidOrder, Order, Transaction} 5 | import net.liftweb.json.parse 6 | import org.apache.http.client.fluent._ 7 | 8 | /** 9 | * Implementation of the Transaction Fetch API for Bitfinex 10 | */ 11 | class BitfinexTransactionPullFetcher extends PullFetch[Transaction] { 12 | val btce = new BitfinexAPI(USD, BTC) 13 | var count = 2000 14 | var latest = new Transaction(0, 0.0, 0.0, 0, BTC, USD, 0, 0, 0, 0) 15 | 16 | override def interval(): Int = 12000 17 | 18 | override def fetch(): List[Transaction] = { 19 | val trades = btce.getTrade(count) 20 | val idx = trades.indexOf(latest) 21 | count = if (idx < 0) 2000 else Math.min(10 * idx, 2000) 22 | latest = if (trades.length == 0) latest else trades.head 23 | 24 | if (idx > 0) 25 | trades.slice(0, idx) 26 | else 27 | trades 28 | } 29 | } 30 | 31 | /** 32 | * Implementation of the Orders Fetch API for Bitfinex 33 | */ 34 | class BitfinexOrderPullFetcher extends PullFetch[Order] { 35 | val bitstampApi = new BitfinexAPI(USD, BTC) 36 | var count = 2000 37 | // Contains the OrderId and The fetch timestamp 38 | var oldOrderBook = Map[Order, (Long, Long)]() 39 | var oid = 15000000000L 40 | 41 | override def interval(): Int = 12000 42 | 43 | override def fetch(): List[Order] = { 44 | val fetchTime = System.currentTimeMillis() 45 | 46 | // Fetch the new Orders 47 | val curOrderBook = bitstampApi.getDepth(count) 48 | 49 | // find which are new by computing the difference: newOrders = currentOrders - oldOrders 50 | val newOrders = curOrderBook diff oldOrderBook.keySet.toList 51 | val delOrders = oldOrderBook.keySet.toList diff curOrderBook 52 | 53 | // Indexes deleted orders and removes them from the map 54 | val indexedDelOrders = delOrders map { k => 55 | val oidts: (Long, Long) = oldOrderBook.get(k).get 56 | oldOrderBook -= k 57 | k match { 58 | case LimitBidOrder(o, u, ft, wac, wic, v, p) => DelOrder(oidts._1, oidts._1, oidts._2, wac, wic, v, p) 59 | case LimitAskOrder(o, u, ft, wac, wic, v, p) => DelOrder(oidts._1, oidts._1, oidts._2, wac, wic, v, p) 60 | } 61 | } 62 | 63 | // Indexes new orders and add them to the map 64 | val indexedNewOrders = newOrders map { k => 65 | oid += 1 66 | val order = k match { 67 | case LimitAskOrder(o, u, ft, wac, wic, v, p) => LimitAskOrder(oid, u, fetchTime, wac, wic, v, p) 68 | case LimitBidOrder(o, u, ft, wac, wic, v, p) => LimitBidOrder(oid, u, fetchTime, wac, wic, v, p) 69 | } 70 | oldOrderBook += (k ->(oid, fetchTime)) 71 | order 72 | } 73 | 74 | indexedNewOrders ++ indexedDelOrders 75 | } 76 | } 77 | 78 | private[this] case class BitfinexCaseTransaction(timestamp: Long, tid: Int, price: String, amount: String, exchange: String) 79 | 80 | private[this] case class BitfinexOrder(price: String, amount: String, timestamp: String) 81 | 82 | private[this] case class BitfinexDepth(bids: List[BitfinexOrder], asks: List[BitfinexOrder]) 83 | 84 | class BitfinexAPI(from: Currency, to: Currency) { 85 | implicit val formats = net.liftweb.json.DefaultFormats 86 | val serverBase = "https://api.bitfinex.com/v1/" 87 | val pair = pair2path 88 | 89 | /** 90 | * Fetches count transactions from Bitfinex's HTTP trade API 91 | * @param count number of Transactions to fetch 92 | * @return the fetched transactions 93 | */ 94 | def getTrade(count: Int): List[Transaction] = { 95 | val path = serverBase + "/trades/" + pair 96 | val json = Request.Get(path).execute().returnContent().asString() 97 | val o = parse(json).extract[List[BitfinexCaseTransaction]] 98 | 99 | if (o.length != 0) { 100 | o.map(f => new Transaction(0, f.price.toDouble, f.amount.toDouble, f.timestamp, BTC, USD, 0, 0, 0, 0)) 101 | } else { 102 | List[Transaction]() 103 | } 104 | } 105 | 106 | /** 107 | * Fetches count orders from Bitfinex's orderbook using the HTTP order API 108 | * @param count number of Order to fetch 109 | * @return the fetched orders 110 | */ 111 | def getDepth(count: Int): List[LimitOrder] = { 112 | var t = List[LimitOrder]() 113 | try { 114 | val path = serverBase + "/book/" + pair + "?limit_bids=" + count / 2 + "&limit_asks=2000" + count / 2 115 | val json = Request.Get(path).execute().returnContent().asString() 116 | val depth: BitfinexDepth = parse(json).extract[BitfinexDepth] 117 | 118 | val asks = depth.asks map { o => LimitAskOrder(0, MarketNames.BITFINEX_ID, o.timestamp.toLong, from, to, o.amount.toDouble, o.price.toDouble)} 119 | val bids = depth.bids map { o => LimitBidOrder(0, MarketNames.BITFINEX_ID, o.timestamp.toLong, from, to, o.amount.toDouble, o.price.toDouble)} 120 | 121 | t = asks ++ bids 122 | } catch { 123 | case e: Throwable => 124 | t = List[LimitOrder]() 125 | } 126 | t 127 | } 128 | 129 | private def pair2path = (from, to) match { 130 | case (USD, BTC) => "btcusd" 131 | case (BTC, USD) => "btcusd" 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/fetch/BitstampFetcher.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | import ch.epfl.ts.data.Currency._ 4 | import ch.epfl.ts.data.{DelOrder, LimitOrder, LimitAskOrder, LimitBidOrder, Order, Transaction} 5 | import net.liftweb.json.parse 6 | import org.apache.http.client.fluent._ 7 | 8 | /** 9 | * Implementation of the Transaction Fetch API for Bitstamp 10 | */ 11 | class BitstampTransactionPullFetcher extends PullFetch[Transaction] { 12 | val bitstamp = new BitstampAPI(USD, BTC) 13 | var count = 2000 14 | var latest = new Transaction(MarketNames.BITSTAMP_ID, 0.0, 0.0, 0, BTC, USD, 0, 0, 0, 0) 15 | 16 | override def interval(): Int = 12000 17 | 18 | override def fetch(): List[Transaction] = { 19 | val trades = bitstamp.getTrade(count) 20 | 21 | val idx = trades.indexOf(latest) 22 | count = if (idx < 0) 2000 else Math.min(10 * idx, 100) 23 | latest = if (trades.length == 0) latest else trades.head 24 | 25 | if (idx > 0) 26 | trades.slice(0, idx).reverse 27 | else 28 | trades.reverse 29 | } 30 | } 31 | 32 | /** 33 | * Implementation of the Orders Fetch API for Bitstamp 34 | */ 35 | class BitstampOrderPullFetcher extends PullFetch[Order] { 36 | val bitstampApi = new BitstampAPI(USD, BTC) 37 | var count = 2000 38 | // Contains the OrderId and The fetch timestamp 39 | var oldOrderBook = Map[Order, (Long, Long)]() 40 | var oid = 10000000000L 41 | 42 | override def interval(): Int = 12000 43 | 44 | override def fetch(): List[Order] = { 45 | val fetchTime = System.currentTimeMillis() 46 | 47 | // Fetch the new Orders 48 | val curOrderBook = bitstampApi.getDepth(count) 49 | 50 | // find which are new by computing the difference: newOrders = currentOrders - oldOrders 51 | val newOrders = curOrderBook diff oldOrderBook.keySet.toList 52 | val delOrders = oldOrderBook.keySet.toList diff curOrderBook 53 | 54 | // Indexes deleted orders and removes them from the map 55 | val indexedDelOrders = delOrders map { k => 56 | val oidts: (Long, Long) = oldOrderBook.get(k).get 57 | oldOrderBook -= k 58 | k match { 59 | case LimitBidOrder(o, u, ft, wac, wic, v, p) => DelOrder(oidts._1, oidts._1, oidts._2, wac, wic, v, p) 60 | case LimitAskOrder(o, u, ft, wac, wic, v, p) => DelOrder(oidts._1, oidts._1, oidts._2, wac, wic, v, p) 61 | } 62 | } 63 | 64 | // Indexes new orders and add them to the map 65 | val indexedNewOrders = newOrders map { k => 66 | oid += 1 67 | val order = k match { 68 | case LimitAskOrder(o, u, ft, wac, wic, v, p) => LimitAskOrder(oid, u, fetchTime, wac, wic, v, p) 69 | case LimitBidOrder(o, u, ft, wac, wic, v, p) => LimitBidOrder(oid, u, fetchTime, wac, wic, v, p) 70 | } 71 | oldOrderBook += (k ->(oid, fetchTime)) 72 | order 73 | } 74 | 75 | indexedNewOrders ++ indexedDelOrders 76 | } 77 | } 78 | 79 | private[this] case class BitstampTransaction(date: String, tid: Int, price: String, amount: String) 80 | 81 | private[this] case class BitstampDepth(timestamp: String, bids: List[List[String]], asks: List[List[String]]) 82 | 83 | class BitstampAPI(from: Currency, to: Currency) { 84 | implicit val formats = net.liftweb.json.DefaultFormats 85 | 86 | val serverBase = "https://www.bitstamp.net/api/" 87 | 88 | /** 89 | * Fetches count transactions from Bitstamp's HTTP trade API 90 | * @param count number of Transactions to fetch 91 | * @return the fetched transactions 92 | */ 93 | def getTrade(count: Int): List[Transaction] = { 94 | val path = serverBase + "transactions/" 95 | val json = Request.Get(path).execute().returnContent().asString() 96 | val t = parse(json).extract[List[BitstampTransaction]] 97 | 98 | t.map(f => Transaction(MarketNames.BITSTAMP_ID, f.price.toDouble, f.amount.toDouble, f.date.toLong * 1000, BTC, USD, 0, 0, 0, 0)) 99 | } 100 | 101 | /** 102 | * Fetches count orders from Bitstamp's orderbook using the HTTP order API 103 | * @param count number of Order to fetch 104 | * @return the fetched orders 105 | */ 106 | def getDepth(count: Int): List[LimitOrder] = { 107 | var t = List[LimitOrder]() 108 | try { 109 | val path = serverBase + "order_book/" 110 | val json = Request.Get(path).execute().returnContent().asString() 111 | val o = parse(json).extract[BitstampDepth] 112 | 113 | val asks = o.asks.map { e => LimitAskOrder(0, 0, 0L, USD, BTC, e.last.toDouble, e.head.toDouble)} 114 | val bids = o.bids.map { e => LimitBidOrder(0, 0, 0L, USD, BTC, e.last.toDouble, e.head.toDouble)} 115 | 116 | t = asks ++ bids 117 | } catch { 118 | case e: Throwable => 119 | t = List[LimitOrder]() 120 | } 121 | t 122 | } 123 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/fetch/BtceFetcher.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | import ch.epfl.ts.data.Currency._ 4 | import ch.epfl.ts.data.{DelOrder, LimitOrder, LimitAskOrder, LimitBidOrder, Order, Transaction} 5 | import net.liftweb.json.parse 6 | import org.apache.http.client.fluent._ 7 | 8 | /** 9 | * Implementation of the Transaction Fetch API for BTC-e 10 | */ 11 | class BtceTransactionPullFetcher extends PullFetch[Transaction] { 12 | val btce = new BtceAPI(USD, BTC) 13 | var count = 2000 14 | var latest = new Transaction(0, 0.0, 0.0, 0, BTC, USD, 0, 0, 0, 0) 15 | 16 | override def interval(): Int = 12000 17 | 18 | override def fetch(): List[Transaction] = { 19 | val trades = btce.getTrade(count) 20 | val idx = trades.indexOf(latest) 21 | count = if (idx < 0) 2000 else Math.min(10 * idx, 100) 22 | latest = if (trades.length == 0) latest else trades.head 23 | 24 | if (idx > 0) 25 | trades.slice(0, idx).reverse 26 | else 27 | trades.reverse 28 | } 29 | } 30 | 31 | /** 32 | * Implementation of the Orders Fetch API for BTC-e 33 | */ 34 | class BtceOrderPullFetcher extends PullFetch[Order] { 35 | val btceApi = new BtceAPI(USD, BTC) 36 | var count = 2000 37 | // Contains the OrderId and The fetch timestamp 38 | var oldOrderBook = Map[Order, (Long, Long)]() 39 | var oid = 5000000000L 40 | 41 | override def interval(): Int = 12000 42 | 43 | override def fetch(): List[Order] = { 44 | val fetchTime = System.currentTimeMillis() 45 | 46 | // Fetch the new Orders 47 | val curOrderBook = btceApi.getDepth(count) 48 | 49 | // new orders: newOrders = currentOrders - oldOrders 50 | // deleted orders: delOrders = oldOrders - currentOrders 51 | val newOrders = curOrderBook diff oldOrderBook.keySet.toList 52 | val delOrders = oldOrderBook.keySet.toList diff curOrderBook 53 | 54 | // Indexes deleted orders and removes them from the map 55 | val indexedDelOrders = delOrders map { k => 56 | val oidts: (Long, Long) = oldOrderBook.get(k).get 57 | oldOrderBook -= k 58 | k match { 59 | case LimitBidOrder(o, u, ft, wac, wic, v, p) => DelOrder(oidts._1, oidts._1, oidts._2, wac, wic, v, p) 60 | case LimitAskOrder(o, u, ft, wac, wic, v, p) => DelOrder(oidts._1, oidts._1, oidts._2, wac, wic, v, p) 61 | } 62 | } 63 | 64 | // Indexes new orders and add them to the map 65 | val indexedNewOrders = newOrders map { k => 66 | oid += 1 67 | val order = k match { 68 | case LimitAskOrder(o, u, ft, wac, wic, v, p) => LimitAskOrder(oid, u, fetchTime, wac, wic, v, p) 69 | case LimitBidOrder(o, u, ft, wac, wic, v, p) => LimitBidOrder(oid, u, fetchTime, wac, wic, v, p) 70 | } 71 | oldOrderBook += (k ->(oid, fetchTime)) 72 | order 73 | } 74 | 75 | indexedNewOrders ++ indexedDelOrders 76 | } 77 | } 78 | 79 | private[this] case class BTCeTransaction(date: Long, price: Double, amount: Double, tid: Int, price_currency: String, item: String, trade_type: String) 80 | 81 | private[this] case class BTCeDepth(asks: List[List[Double]], bids: List[List[Double]]) 82 | 83 | class BtceAPI(from: Currency, to: Currency) { 84 | implicit val formats = net.liftweb.json.DefaultFormats 85 | 86 | val serverBase = "https://btc-e.com/api/2/" 87 | val pair = pair2path 88 | 89 | /** 90 | * Fetches count transactions from BTC-e's HTTP trade API 91 | * @param count number of Transactions to fetch 92 | * @return the fetched transactions 93 | */ 94 | def getTrade(count: Int): List[Transaction] = { 95 | var t = List[BTCeTransaction]() 96 | try { 97 | val path = serverBase + pair + "/trades/" + count 98 | val json = Request.Get(path).execute().returnContent().asString() 99 | t = parse(json).extract[List[BTCeTransaction]] 100 | } catch { 101 | case _: Throwable => t = List[BTCeTransaction](); 102 | } 103 | 104 | if (t.length != 0) { 105 | t.map(f => new Transaction(MarketNames.BTCE_ID, f.price, f.amount, f.date * 1000, BTC, USD, 0, 0, 0, 0)) 106 | } else { 107 | List[Transaction]() 108 | } 109 | } 110 | 111 | /** 112 | * Fetches count orders from BTC-e's orderbook using the HTTP order API 113 | * @param count number of Order to fetch 114 | * @return the fetched orders 115 | */ 116 | def getDepth(count: Int): List[Order] = { 117 | var t = List[LimitOrder]() 118 | try { 119 | val path = serverBase + pair + "/depth/" + count 120 | val json = Request.Get(path).execute().returnContent().asString() 121 | val depth = parse(json).extract[BTCeDepth] 122 | 123 | val asks = depth.asks map { o => LimitAskOrder(0, MarketNames.BTCE_ID, 0L, from, to, o.last, o.head)} 124 | val bids = depth.bids map { o => LimitBidOrder(0, MarketNames.BTCE_ID, 0L, from, to, o.last, o.head)} 125 | 126 | t = asks ++ bids 127 | } catch { 128 | case _: Throwable => t = List[LimitOrder]() 129 | } 130 | t 131 | } 132 | 133 | private def pair2path = (from, to) match { 134 | case (USD, BTC) => "btc_usd" 135 | case (BTC, USD) => "btc_usd" 136 | } 137 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/fetch/CSVFetcher.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | import ch.epfl.ts.component.persist.OrderPersistor 4 | import ch.epfl.ts.data.Currency._ 5 | import ch.epfl.ts.data.{DelOrder, LimitAskOrder, LimitBidOrder, Order} 6 | 7 | import scala.io.Source 8 | 9 | /** 10 | * load finance.csv (provided by Milos Nikolic) in OrderPersistor 11 | */ 12 | object CSVFetcher { 13 | def main(args: Array[String]) { 14 | val fetcher = new CSVFetcher 15 | fetcher.loadInPersistor("finance.csv") 16 | } 17 | } 18 | 19 | class CSVFetcher { 20 | 21 | def loadInPersistor(filename: String) { 22 | // name the db as "[filename without extension].db" 23 | val ordersPersistor = new OrderPersistor(filename.replaceAll("\\.[^.]*$", "")) 24 | ordersPersistor.init() 25 | var counter = 0 26 | val startTime = System.currentTimeMillis() 27 | val source = Source.fromFile(filename) 28 | var line: Array[String] = new Array[String](5) 29 | 30 | println("Start reading in.") 31 | 32 | var orders = List[Order]() 33 | source.getLines().foreach { 34 | s => { 35 | if (counter % 1000 == 0) { 36 | ordersPersistor.save(orders) 37 | println(System.currentTimeMillis() - startTime + "\t" + counter) 38 | orders = List[Order]() 39 | } 40 | 41 | counter += 1 42 | line = s.split(",") 43 | line(2) match { 44 | case "B" => orders = LimitBidOrder(line(1).toLong, 0, line(0).toLong, USD, USD, line(3).toDouble, line(4).toDouble) :: orders 45 | case "S" => orders = LimitAskOrder(line(1).toLong, 0, line(0).toLong, USD, USD, line(3).toDouble, line(4).toDouble) :: orders 46 | case "D" => orders = DelOrder(line(1).toLong, 0, line(0).toLong, DEF, DEF, 0, 0) :: orders 47 | case _ => 48 | } 49 | } 50 | } 51 | 52 | println("Done!") 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/fetch/Fetch.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | import ch.epfl.ts.component.Component 4 | 5 | import scala.concurrent.duration.DurationInt 6 | import scala.reflect.ClassTag 7 | 8 | trait Fetch[T] 9 | 10 | /* Direction PULL */ 11 | abstract class PullFetch[T] extends Fetch[T] { 12 | def fetch(): List[T] 13 | 14 | def interval(): Int 15 | } 16 | 17 | /* Direction PUSH */ 18 | abstract class PushFetch[T] extends Fetch[T] { 19 | var callback: (T => Unit) 20 | } 21 | 22 | /* Actor implementation */ 23 | class PullFetchComponent[T: ClassTag](f: PullFetch[T]) extends Component { 24 | import context._ 25 | case object Fetch 26 | system.scheduler.schedule(10 milliseconds, f.interval() milliseconds, self, Fetch) 27 | 28 | override def receiver = { 29 | // pull and send to each listener 30 | case Fetch => 31 | println("PullFetchComponent Fetch " + System.currentTimeMillis()) 32 | f.fetch().map(t => send[T](t)) 33 | case _ => 34 | } 35 | } 36 | 37 | /* Actor implementation */ 38 | class PullFetchListComponent[T: ClassTag](f: PullFetch[T]) extends Component { 39 | import context._ 40 | case object Fetch 41 | system.scheduler.schedule(0 milliseconds, f.interval() milliseconds, self, Fetch) 42 | 43 | override def receiver = { 44 | // pull and send to each listener 45 | case Fetch => 46 | println("PullFetchListComponent Fetch " + System.currentTimeMillis()) 47 | send(f.fetch()) 48 | case _ => 49 | } 50 | } 51 | 52 | /* Actor implementation */ 53 | class PushFetchComponent[T: ClassTag] extends Component { 54 | override def receiver = { 55 | case _ => 56 | } 57 | 58 | def callback(data: T) = send(data) 59 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/fetch/MarketNames.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | object MarketNames { 4 | 5 | val BTCE_NAME = "BTC-e" 6 | val BTCE_ID = 1L 7 | val BITSTAMP_NAME = "Bitstamp" 8 | val BITSTAMP_ID = 2L 9 | val BITFINEX_NAME = "Bitfinex" 10 | val BITFINEX_ID = 3L 11 | val marketIdToName = Map(BTCE_ID -> BTCE_NAME, BITSTAMP_ID -> BITSTAMP_NAME, BITFINEX_ID -> BITFINEX_NAME) 12 | val marketNameToId = Map(BTCE_NAME -> BTCE_ID, BITSTAMP_NAME -> BITSTAMP_ID, BITFINEX_NAME -> BITFINEX_ID) 13 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/fetch/TwitterFetchComponent.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | import java.io.{BufferedReader, InputStreamReader} 4 | 5 | import ch.epfl.ts.data.Tweet 6 | import twitter4j._ 7 | 8 | 9 | /** 10 | * TODO: Should be refactored to only use PushFetchComponent[Tweet] and no more PushFetch[Tweet]. 11 | */ 12 | class TwitterFetchComponent extends PushFetchComponent[Tweet] { 13 | new TwitterPushFetcher(this.callback) 14 | } 15 | 16 | class TwitterPushFetcher(override var callback: (Tweet => Unit)) extends PushFetch[Tweet]() { 17 | 18 | val config = new twitter4j.conf.ConfigurationBuilder() 19 | .setOAuthConsumerKey("h7HL6oGtIOrCZN53TbWafg") 20 | .setOAuthConsumerSecret("irg8l38K4DUrqPV638dIfXvK0UjVHKC936IxbaTmqg") 21 | .setOAuthAccessToken("77774972-eRxDxN3hPfTYgzdVx99k2ZvFjHnRxqEYykD0nQxib") 22 | .setOAuthAccessTokenSecret("FjI4STStCRFLjZYhRZWzwTaiQnZ7CZ9Zrm831KUWTNZri") 23 | .build 24 | 25 | val twitterStream = new TwitterStreamFactory(config).getInstance 26 | twitterStream.addListener(simpleStatusListener) 27 | twitterStream.filter(new FilterQuery().track(Array("bitcoin", "cryptocurrency", "btc", "bitcoins"))) 28 | 29 | def simpleStatusListener = new StatusListener() { 30 | override def onStatus(status: Status) { 31 | if (status.getUser.getFollowersCount < 30) { 32 | return 33 | } 34 | val tweet = status.getText.replace('\n', ' ') 35 | 36 | // send stuff to datasource 37 | val commands = Array("python", "twitter-classifier/sentiment.py", tweet) 38 | val p = Runtime.getRuntime.exec(commands) 39 | 40 | val stdInput = new BufferedReader(new InputStreamReader(p.getInputStream)) 41 | val stdError = new BufferedReader(new InputStreamReader(p.getErrorStream)) 42 | 43 | val sentiment = stdInput.readLine() 44 | 45 | val intSentiment = sentiment match { 46 | case "positive" => 1 47 | case "negative" => -1 48 | case "neutral" => 0 49 | case _ => throw new RuntimeException("Undefined sentiment value") 50 | } 51 | 52 | if (intSentiment == 1) { 53 | println(tweet) 54 | } else if (intSentiment == -1) { 55 | System.err.println(tweet) 56 | } 57 | val imagesrc = status.getUser.getProfileImageURL 58 | val author = status.getUser.getScreenName 59 | val ts = status.getCreatedAt.getTime 60 | 61 | callback(new Tweet(ts, tweet, intSentiment, imagesrc, author)) 62 | 63 | } 64 | 65 | override def onDeletionNotice(statusDeletionNotice: StatusDeletionNotice) {} 66 | 67 | override def onTrackLimitationNotice(numberOfLimitedStatuses: Int) {} 68 | 69 | override def onException(ex: Exception) { 70 | ex.printStackTrace() 71 | } 72 | 73 | override def onScrubGeo(arg0: Long, arg1: Long) {} 74 | 75 | override def onStallWarning(warning: StallWarning) {} 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/persist/OrderPersistor.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.persist 2 | 3 | import ch.epfl.ts.data.Currency._ 4 | import ch.epfl.ts.data.{Currency, DelOrder, LimitAskOrder, LimitBidOrder, MarketAskOrder, MarketBidOrder, Order} 5 | import scala.slick.driver.SQLiteDriver.simple._ 6 | import scala.slick.jdbc.JdbcBackend.Database 7 | import scala.slick.jdbc.JdbcBackend.Database.dynamicSession 8 | import scala.slick.jdbc.meta.MTable 9 | import scala.slick.lifted.{Column, TableQuery, Tag} 10 | import scala.collection.mutable.ListBuffer 11 | 12 | object OrderType extends Enumeration { 13 | type OrderType = Value 14 | val LIMIT_BID = Value("LB") 15 | val LIMIT_ASK = Value("LA") 16 | val MARKET_BID = Value("MB") 17 | val MARKET_ASK = Value("MA") 18 | val DEL = Value("D") 19 | } 20 | import ch.epfl.ts.component.persist.OrderType._ 21 | 22 | 23 | case class PersistorOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double, val orderType: OrderType) extends Order(oid, uid, timestamp, whatC, withC, volume, price) 24 | 25 | /** 26 | * Implementation of the Persistance trait for Order 27 | */ 28 | class OrderPersistor(dbFilename: String) extends Persistance[Order] { 29 | 30 | val nullStringValue = "NULL" 31 | 32 | class Orders(tag: Tag) extends Table[(Int, Long, Long, Long, String, String, Double, Double, String)](tag, "ORDERS") { 33 | def * = (id, oid, uid, timestamp, whatC, withC, volume, price, orderType) 34 | def id: Column[Int] = column[Int]("ID", O.PrimaryKey, O.AutoInc) 35 | def oid: Column[Long] = column[Long]("ORDER_ID") 36 | def uid: Column[Long] = column[Long]("USER_ID") 37 | def timestamp: Column[Long] = column[Long]("TIMESTAMP") 38 | def whatC: Column[String] = column[String]("WHAT_C") 39 | def withC: Column[String] = column[String]("WITH_C") 40 | def volume: Column[Double] = column[Double]("VOLUME") 41 | def price: Column[Double] = column[Double]("PRICE") 42 | def orderType: Column[String] = column[String]("ORDER_TYPE") 43 | } 44 | 45 | type OrderEntry = (Int, Long, Long, Long, String, String, Double, Double, String) 46 | lazy val order = TableQuery[Orders] 47 | val db = Database.forURL("jdbc:sqlite:" + dbFilename + ".db", driver = "org.sqlite.JDBC") 48 | 49 | /** 50 | * create table if it does not exist 51 | */ 52 | def init() = { 53 | db.withDynSession { 54 | if (MTable.getTables("ORDERS").list.isEmpty) { 55 | order.ddl.create 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * save single entry 62 | */ 63 | def save(newOrder: Order) = { 64 | db.withDynSession { 65 | newOrder match { 66 | case la: LimitAskOrder => order += (1, la.oid, la.uid, la.timestamp, la.whatC.toString, la.withC.toString, la.volume, la.price, LIMIT_ASK.toString) 67 | case lb: LimitBidOrder => order += (1, lb.oid, lb.uid, lb.timestamp, lb.whatC.toString, lb.withC.toString, lb.volume, lb.price, LIMIT_BID.toString) 68 | case mb: MarketBidOrder => order += (1, mb.oid, mb.uid, mb.timestamp, mb.whatC.toString, mb.withC.toString, mb.volume, 0, MARKET_BID.toString) 69 | case ma: MarketAskOrder => order += (1, ma.oid, ma.uid, ma.timestamp, ma.whatC.toString, ma.withC.toString, ma.volume, 0, MARKET_ASK.toString) 70 | case del: DelOrder => order += (1, del.oid, del.uid, del.timestamp, nullStringValue, nullStringValue, 0, 0, DEL.toString) 71 | case _ => println(dbFilename + " Persistor: save error") 72 | } 73 | } 74 | } 75 | 76 | /** 77 | * save entries 78 | */ 79 | def save(os: List[Order]) = { 80 | db.withDynSession { 81 | order ++= os.toIterable.map { 82 | case la: LimitAskOrder => (1, la.oid, la.uid, la.timestamp, la.whatC.toString, la.withC.toString, la.volume, la.price, LIMIT_ASK.toString) 83 | case lb: LimitBidOrder => (1, lb.oid, lb.uid, lb.timestamp, lb.whatC.toString, lb.withC.toString, lb.volume, lb.price, LIMIT_BID.toString) 84 | case mb: MarketBidOrder => (1, mb.oid, mb.uid, mb.timestamp, mb.whatC.toString, mb.withC.toString, mb.volume, 0.0, MARKET_BID.toString) 85 | case ma: MarketAskOrder => (1, ma.oid, ma.uid, ma.timestamp, ma.whatC.toString, ma.withC.toString, ma.volume, 0.0, MARKET_ASK.toString) 86 | case del: DelOrder => (1, del.oid, del.uid, del.timestamp, nullStringValue, nullStringValue, 0.0, 0.0, DEL.toString) 87 | 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * load entry with id 94 | */ 95 | def loadSingle(id: Int): Order /*Option[Order]*/ = { 96 | db.withDynSession { 97 | val r = order.filter(_.id === id).invoker.firstOption.get 98 | OrderType.withName(r._9) match { 99 | case LIMIT_BID => return LimitBidOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, r._8) 100 | case LIMIT_ASK => return LimitAskOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, r._8) 101 | case MARKET_BID => return MarketBidOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, 0.0) 102 | case MARKET_ASK => return MarketAskOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, 0.0) 103 | case DEL => return DelOrder(r._2, r._3, r._4, DEF, DEF, 0.0, 0.0) 104 | case _ => println(dbFilename + " Persistor: loadSingle error"); return null 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * load entries with timestamp value between startTime and endTime (inclusive) 111 | */ 112 | def loadBatch(startTime: Long, endTime: Long): List[Order] = { 113 | var res: ListBuffer[Order] = new ListBuffer[Order]() 114 | db.withDynSession { 115 | val r = order.filter(e => (e.timestamp >= startTime) && (e.timestamp <= endTime)).invoker.foreach { r => 116 | OrderType.withName(r._9) match { 117 | case LIMIT_BID => res.append(LimitBidOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, r._8)) 118 | case LIMIT_ASK => res.append(LimitAskOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, r._8)) 119 | case MARKET_BID => res.append(MarketBidOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, 0.0)) 120 | case MARKET_ASK => res.append(MarketAskOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, 0.0)) 121 | case DEL => res.append(DelOrder(r._2, r._3, r._4, DEF, DEF, 0.0, 0.0)) 122 | case _ => println(dbFilename + " Persistor: loadBatch error") 123 | } 124 | } 125 | } 126 | res.toList 127 | } 128 | 129 | /** 130 | * loads the amount of entries provided in the function 131 | * argument at most. 132 | */ 133 | def loadBatch(count: Int): List[Order] = { 134 | var res: ListBuffer[Order] = new ListBuffer[Order]() 135 | db.withDynSession { 136 | val r = order.filter(e => e.id <= count).invoker.foreach { r => 137 | OrderType.withName(r._9) match { 138 | case LIMIT_BID => res.append(LimitBidOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, r._8)) 139 | case LIMIT_ASK => res.append(LimitAskOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, r._8)) 140 | case MARKET_BID => res.append(MarketBidOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, 0.0)) 141 | case MARKET_ASK => res.append(MarketAskOrder(r._2, r._3, r._4, Currency.withName(r._5), Currency.withName(r._6), r._7, 0.0)) 142 | case DEL => res.append(DelOrder(r._2, r._3, r._4, DEF, DEF, 0.0, 0.0)) 143 | case _ => println(dbFilename + " Persistor: loadBatch error") 144 | } 145 | } 146 | } 147 | res.toList 148 | } 149 | 150 | /** 151 | * delete entry with id 152 | */ 153 | def deleteSingle(id: Int) = { 154 | db.withDynSession { 155 | order.filter(_.id === id).delete 156 | } 157 | } 158 | 159 | /** 160 | * delete entries with timestamp values between startTime and endTime (inclusive) 161 | */ 162 | def deleteBatch(startTime: Long, endTime: Long) = { 163 | db.withDynSession { 164 | order.filter(e => e.timestamp >= startTime && e.timestamp <= endTime).delete 165 | } 166 | } 167 | 168 | /** 169 | * delete all entries 170 | */ 171 | def clearAll = { 172 | db.withDynSession { 173 | order.delete 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/persist/Persistance.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.persist 2 | 3 | import ch.epfl.ts.component.Component 4 | 5 | import scala.reflect.ClassTag 6 | 7 | /** 8 | * Defines the Persistance interface 9 | * @tparam T 10 | */ 11 | trait Persistance[T] { 12 | def save(t: T) 13 | def save(ts: List[T]) 14 | def loadSingle(id: Int): T 15 | def loadBatch(startTime: Long, endTime: Long): List[T] 16 | } 17 | 18 | /** 19 | * The Abstraction for the persistance actors 20 | */ 21 | class Persistor[T: ClassTag](p: Persistance[T]) 22 | extends Component { 23 | val clazz = implicitly[ClassTag[T]].runtimeClass 24 | 25 | override def receiver = { 26 | case d if clazz.isInstance(d) => p.save(d.asInstanceOf[T]) 27 | case x => println("Persistance got: " + x.getClass.toString) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/persist/TransactionPersistor.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.persist 2 | 3 | import ch.epfl.ts.data.{Currency, Transaction} 4 | import scala.slick.driver.SQLiteDriver.simple._ 5 | import scala.slick.jdbc.JdbcBackend.Database 6 | import scala.slick.jdbc.JdbcBackend.Database.dynamicSession 7 | import scala.slick.jdbc.meta.MTable 8 | import scala.slick.lifted.{Column, TableQuery, Tag} 9 | import scala.collection.mutable.ListBuffer 10 | 11 | /** 12 | * Implementation of the Persistance trait for Transaction 13 | */ 14 | class TransactionPersistor(dbFilename: String) extends Persistance[Transaction] { 15 | 16 | class Transactions(tag: Tag) extends Table[(Int, Long, Double, Double, Long, String, String, Long, Long, Long, Long)](tag, "TRANSACTIONS") { 17 | def * = (id, mid, price, volume, timestamp, whatC, withC, buyerId, buyerOrderId, sellerId, sellerOrderId) 18 | def id: Column[Int] = column[Int]("ID", O.PrimaryKey, O.AutoInc) 19 | def mid: Column[Long] = column[Long]("MARKET_ID") 20 | def price: Column[Double] = column[Double]("PRICE") 21 | def volume: Column[Double] = column[Double]("QUANTITY") 22 | def timestamp: Column[Long] = column[Long]("TIMESTAMP") 23 | def whatC: Column[String] = column[String]("WHAT_C") 24 | def withC: Column[String] = column[String]("WITH_C") 25 | def buyerId: Column[Long] = column[Long]("BUYER_ID") 26 | def buyerOrderId: Column[Long] = column[Long]("BUYER_ORDER_ID") 27 | def sellerId: Column[Long] = column[Long]("SELLER_ID") 28 | def sellerOrderId: Column[Long] = column[Long]("SELLER_ORDER_ID") 29 | } 30 | 31 | type TransactionEntry = (Int, Long, Double, Double, Long, String, String, Long, Long, Long, Long) 32 | lazy val transaction = TableQuery[Transactions] 33 | val db = Database.forURL("jdbc:sqlite:" + dbFilename + ".db", driver = "org.sqlite.JDBC") 34 | 35 | /** 36 | * create table if it does not exist 37 | */ 38 | def init() = { 39 | db.withDynSession { 40 | if (MTable.getTables("TRANSACTIONS").list.isEmpty) { 41 | transaction.ddl.create 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * save single entry 48 | */ 49 | def save(newTransaction: Transaction) = { 50 | db.withDynSession { 51 | transaction += (1, newTransaction.mid, newTransaction.price, newTransaction.volume, newTransaction.timestamp, newTransaction.whatC.toString, newTransaction.withC.toString, newTransaction.buyerId, newTransaction.buyOrderId, newTransaction.sellerId, newTransaction.sellOrderId) // AutoInc are implicitly ignored 52 | } 53 | } 54 | 55 | /** 56 | * save entries 57 | */ 58 | def save(ts: List[Transaction]) = { 59 | db.withDynSession { 60 | transaction ++= ts.toIterable.map { x => (1, x.mid, x.price, x.volume, x.timestamp, x.whatC.toString, x.withC.toString, x.buyerId, x.buyOrderId, x.sellerId, x.sellOrderId) } 61 | } 62 | } 63 | 64 | /** 65 | * load entry with id 66 | */ 67 | def loadSingle(id: Int): Transaction = { 68 | db.withDynSession { 69 | val r = transaction.filter(_.id === id).invoker.firstOption.get 70 | return Transaction(r._2, r._3, r._4, r._5, Currency.withName(r._6), Currency.withName(r._7), r._8, r._9, r._10, r._11) 71 | } 72 | } 73 | 74 | /** 75 | * load entries with timestamp value between startTime and endTime (inclusive) 76 | */ 77 | def loadBatch(startTime: Long, endTime: Long): List[Transaction] = { 78 | var res: ListBuffer[Transaction] = ListBuffer[Transaction]() 79 | db.withDynSession { 80 | val r = transaction.filter(e => e.timestamp >= startTime && e.timestamp <= endTime).invoker.foreach { r => res.append(Transaction(r._2, r._3, r._4, r._5, Currency.withName(r._6), Currency.withName(r._7), r._8, r._9, r._10, r._11)) } 81 | } 82 | res.toList 83 | } 84 | 85 | /** 86 | * delete entry with id 87 | */ 88 | def deleteSingle(id: Int) = { 89 | db.withDynSession { 90 | transaction.filter(_.id === id).delete 91 | } 92 | } 93 | 94 | /** 95 | * delete entries with timestamp values between startTime and endTime (inclusive) 96 | */ 97 | def deleteBatch(startTime: Long, endTime: Long) = { 98 | db.withDynSession { 99 | transaction.filter(e => e.timestamp >= startTime && e.timestamp <= endTime).delete 100 | } 101 | } 102 | 103 | /** 104 | * delete all entries 105 | */ 106 | def clearAll = { 107 | db.withDynSession { 108 | transaction.delete 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/persist/TweetPersistor.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.persist 2 | 3 | import ch.epfl.ts.data.Tweet 4 | import scala.slick.driver.SQLiteDriver.simple._ 5 | import scala.slick.jdbc.JdbcBackend.Database 6 | import scala.slick.jdbc.JdbcBackend.Database.dynamicSession 7 | import scala.slick.jdbc.meta.MTable 8 | import scala.slick.lifted.{Column, TableQuery, Tag} 9 | import scala.collection.mutable.ListBuffer 10 | 11 | /** 12 | * Implementation of the Persistance for Tweets storage 13 | */ 14 | class TweetPersistor(dbFilename: String) extends Persistance[Tweet] { 15 | 16 | class Tweets(tag: Tag) extends Table[(Int, Long, String, Int, String, String)](tag, "TWEETS") { 17 | def * = (id, timestamp, content, sentiment, imagesrc, author) 18 | def id: Column[Int] = column[Int]("TWEET_ID", O.PrimaryKey, O.AutoInc) 19 | def timestamp: Column[Long] = column[Long]("TIMESTAMP") 20 | def content: Column[String] = column[String]("CONTENT") 21 | def sentiment: Column[Int] = column[Int]("SENTIMENT") 22 | def imagesrc: Column[String] = column[String]("IMAGE_SRC") 23 | def author: Column[String] = column[String]("AUTHOR") 24 | } 25 | 26 | type TweetEntry = (Int, Long, String, Int, String, String) 27 | lazy val tweet = TableQuery[Tweets] 28 | val db = Database.forURL("jdbc:sqlite:" + dbFilename + ".db", driver = "org.sqlite.JDBC") 29 | 30 | /** 31 | * create table if it does not exist 32 | */ 33 | def init() = { 34 | db.withDynSession { 35 | if (MTable.getTables("TWEETS").list.isEmpty) { 36 | tweet.ddl.create 37 | } 38 | } 39 | } 40 | 41 | /** 42 | * save single entry 43 | */ 44 | def save(newTweet: Tweet) = { 45 | db.withDynSession { 46 | tweet +=(1, newTweet.timestamp, newTweet.content, newTweet.sentiment, newTweet.imagesrc, newTweet.author) // AutoInc are implicitly ignored 47 | } 48 | } 49 | 50 | /** 51 | * save entries 52 | */ 53 | def save(ts: List[Tweet]) = { 54 | db.withDynSession { 55 | tweet ++= ts.toIterable.map { x => (1, x.timestamp, x.content, x.sentiment, x.imagesrc, x.author)} 56 | } 57 | } 58 | 59 | /** 60 | * load entry with id 61 | */ 62 | def loadSingle(id: Int): Tweet /*Option[Order]*/ = { 63 | db.withDynSession { 64 | val r = tweet.filter(_.id === id).invoker.firstOption.get 65 | return Tweet(r._2, r._3, r._4, r._5, r._6) 66 | } 67 | } 68 | 69 | /** 70 | * load entries with timestamp value between startTime and endTime (inclusive) 71 | */ 72 | def loadBatch(startTime: Long, endTime: Long): List[Tweet] = { 73 | var res: ListBuffer[Tweet] = ListBuffer[Tweet]() 74 | db.withDynSession { 75 | val r = tweet.filter(e => e.timestamp >= startTime && e.timestamp <= endTime).invoker.foreach { r => res.append(Tweet(r._2, r._3, r._4, r._5, r._6)) } 76 | } 77 | res.toList 78 | } 79 | 80 | /** 81 | * delete entry with id 82 | */ 83 | def deleteSingle(id: Int) = { 84 | db.withDynSession { 85 | tweet.filter(_.id === id).delete 86 | } 87 | } 88 | 89 | /** 90 | * delete entries with timestamp values between startTime and endTime (inclusive) 91 | */ 92 | def deleteBatch(startTime: Long, endTime: Long) = { 93 | db.withDynSession { 94 | tweet.filter(e => e.timestamp >= startTime && e.timestamp <= endTime).delete 95 | } 96 | } 97 | 98 | /** 99 | * delete all entries 100 | */ 101 | def clearAll = { 102 | db.withDynSession { 103 | tweet.delete 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/replay/Replay.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.replay 2 | 3 | import akka.actor.Cancellable 4 | import ch.epfl.ts.component.persist.Persistance 5 | import ch.epfl.ts.component.{Component, StartSignal, StopSignal} 6 | 7 | import scala.concurrent.duration.DurationLong 8 | import scala.reflect.ClassTag 9 | 10 | case class ReplayConfig(initTimeMs: Long, compression: Double) 11 | 12 | class Replay[T: ClassTag](p: Persistance[T], conf: ReplayConfig) extends Component { 13 | import context._ 14 | case class Tick() 15 | var schedule: Cancellable = null 16 | var currentTime = conf.initTimeMs 17 | 18 | override def receiver = { 19 | case StopSignal() => 20 | schedule.cancel() 21 | case StartSignal() => 22 | schedule = start(conf.compression) 23 | case Tick() if sender == self => 24 | process() 25 | currentTime += 1000 26 | case r: ReplayConfig => 27 | schedule.cancel() 28 | currentTime = r.initTimeMs 29 | // TODO: discard waiting objects 30 | schedule = start(r.compression) 31 | case _ => 32 | } 33 | private def start(compression: Double) = { 34 | context.system.scheduler.schedule(10 milliseconds, Math.round(compression * 1000) milliseconds, self, new Tick) 35 | } 36 | private def process() = send[T](p.loadBatch(currentTime, currentTime + 999)) 37 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/utils/BackLoop.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.utils 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.component.persist.Persistance 5 | import ch.epfl.ts.data.{DelOrder, LimitAskOrder, LimitBidOrder, Transaction} 6 | 7 | /** 8 | * Backloop component, plugged as Market Simulator's output. Saves the transactions in a persistor. 9 | * distributes the transactions and delta orders to the trading agents 10 | */ 11 | class BackLoop(marketId: Long, p: Persistance[Transaction]) extends Component { 12 | 13 | override def receiver = { 14 | case t: Transaction => { 15 | send(t) 16 | p.save(t) 17 | } 18 | case la: LimitAskOrder => send(la) 19 | case lb: LimitBidOrder => send(lb) 20 | case d: DelOrder => send(d) 21 | case _ => println("Looper: received unknown") 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/utils/BatcherComponent.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.utils 2 | 3 | import ch.epfl.ts.component.Component 4 | 5 | import scala.reflect.ClassTag 6 | 7 | case class BatchSize(size: Int) 8 | 9 | /** 10 | * component used to buffer data and forward them in batches of a certain size defined in the constructor 11 | */ 12 | class BatcherComponent[T: ClassTag](var size: Int) extends Component { 13 | val clazz = implicitly[ClassTag[T]].runtimeClass 14 | var batch: List[T] = List() 15 | 16 | override def receiver = { 17 | case bs: BatchSize => size = bs.size 18 | case d if clazz.isInstance(d) => 19 | batch = d.asInstanceOf[T] :: batch 20 | if (batch.size >= size) 21 | send(batch.reverse) 22 | case _ => 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/component/utils/Printer.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.utils 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.data.{DelOrder, LimitAskOrder, LimitBidOrder, LimitOrder, OHLC, Transaction, Tweet} 5 | 6 | /** 7 | * Simple printer component. Prints what it receives 8 | * @param name The name of the printer. 9 | */ 10 | class Printer(val name: String) extends Component { 11 | override def receiver = { 12 | case t: Transaction => println("Printer " + name + ": Transaction\t" + System.currentTimeMillis + "\t" + t.toString) 13 | case t: Tweet => println("Printer " + name + ": Tweet\t" + System.currentTimeMillis + "\t" + t.toString) 14 | case lb: LimitBidOrder => println("Printer " + name + ": Limit Bid Order\t" + System.currentTimeMillis() + "\t" + lb.toString) 15 | case la: LimitAskOrder => println("Printer " + name + ": Limit Ask Order\t" + System.currentTimeMillis() + "\t" + la.toString) 16 | case del: DelOrder => println("Printer " + name + ": Delete Order\t" + System.currentTimeMillis() + "\t" + del.toString) 17 | case ohlc: OHLC => println("Printer " + name + ": OHLC\t" + System.currentTimeMillis() + "\t" + ohlc.toString) 18 | case _ => println("Printer " + name + ": received unknown") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/data/Messages.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.data 2 | 3 | /** 4 | * Enum for Currencies 5 | */ 6 | object Currency extends Enumeration { 7 | type Currency = Value 8 | val BTC = Value("btc") 9 | val LTC = Value("ltc") 10 | val USD = Value("usd") 11 | val CHF = Value("chf") 12 | val RUR = Value("rur") 13 | val DEF = Value("def") // default 14 | } 15 | 16 | import ch.epfl.ts.data.Currency._ 17 | 18 | 19 | /** 20 | * Definition of the System's internal messages. 21 | */ 22 | trait Streamable 23 | 24 | /** 25 | * Data Transfer Object representing a Transaction 26 | * @param mid market id 27 | * @param price 28 | * @param volume 29 | * @param timestamp 30 | * @param whatC 31 | * @param withC 32 | * @param buyerId buyer user id 33 | * @param buyOrderId buyer order id 34 | * @param sellerId seller user id 35 | * @param sellOrderId seller order id 36 | */ 37 | case class Transaction(mid: Long, price: Double, volume: Double, timestamp: Long, whatC: Currency, withC: Currency, buyerId: Long, buyOrderId: Long, sellerId: Long, sellOrderId: Long) extends Streamable 38 | 39 | 40 | trait AskOrder 41 | trait BidOrder 42 | 43 | /** 44 | * Data Transfer Object representing a Order 45 | * @param oid 46 | * @param uid 47 | * @param timestamp 48 | * @param whatC 49 | * @param withC 50 | * @param volume 51 | * @param price 52 | */ 53 | abstract class Order(val oid: Long, val uid: Long, val timestamp: Long, val whatC: Currency, val withC: Currency, val volume: Double, val price: Double) extends Streamable 54 | 55 | abstract class LimitOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 56 | extends Order(oid, uid, timestamp, whatC, withC, volume, price) 57 | 58 | case class LimitBidOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 59 | extends LimitOrder(oid, uid, timestamp, whatC, withC, volume, price) with BidOrder 60 | 61 | case class LimitAskOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 62 | extends LimitOrder(oid, uid, timestamp, whatC, withC, volume, price) with AskOrder 63 | 64 | abstract class MarketOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 65 | extends Order(oid, uid, timestamp, whatC, withC, volume, price) 66 | 67 | case class MarketBidOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 68 | extends MarketOrder(oid, uid, timestamp, whatC, withC, volume, price) with BidOrder 69 | 70 | case class MarketAskOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 71 | extends MarketOrder(oid, uid, timestamp, whatC, withC, volume, price) with AskOrder 72 | 73 | case class DelOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 74 | extends Order(oid, uid, timestamp, whatC, withC, volume, price) 75 | 76 | 77 | /** 78 | * represents an Open-High-Low-Close tick, with volume and timestamp (beginning of the tick) 79 | */ 80 | case class OHLC(marketId: Long, open: Double, high: Double, low: Double, close: Double, volume: Double, timestamp: Long, duration: Long) extends Streamable 81 | 82 | 83 | /** 84 | * Data Transfer Object representing a Tweet 85 | * @param timestamp 86 | * @param content 87 | * @param sentiment 88 | * @param imagesrc 89 | * @param author 90 | */ 91 | case class Tweet(timestamp: Long, content: String, sentiment: Int, imagesrc: String, author: String) extends Streamable 92 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/engine/Actors.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import akka.actor.ActorRef 4 | 5 | object Actors { 6 | type WalletManager = ActorRef 7 | type MatcherEngine = ActorRef 8 | type Client = ActorRef 9 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/engine/Controller.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import akka.actor.Actor 4 | import ch.epfl.ts.data.Order 5 | import ch.epfl.ts.engine.Actors._ 6 | 7 | 8 | class Controller(wm: WalletManager, me: MatcherEngine) extends Actor { 9 | type ClientId = Long 10 | var clients = Map[ClientId, Client]() 11 | 12 | override def receive: Receive = { 13 | case ro: RejectedOrder => deny(ro) 14 | case ao: AcceptedOrder => accept(ao) 15 | case o: Order => verify(o, sender) 16 | 17 | case ws: WalletState => askWalletState(ws, sender) 18 | case _ => {} // Where is the public data? 19 | } 20 | 21 | def verify(o: Order, s: Client): Unit = { 22 | clients = clients + (o.uid -> s) 23 | } 24 | 25 | def deny(o: Order): Unit = { 26 | clients.get(o.uid) match { 27 | case Some(c) => c ! o 28 | case None => 29 | } 30 | } 31 | 32 | def accept(o: Order): Unit = { 33 | me ! o 34 | clients.get(o.uid) match { 35 | case Some(c) => c ! o 36 | case None => 37 | } 38 | } 39 | 40 | def askWalletState(ws: WalletState, c: Client): Unit = { 41 | clients = clients + (ws.uid -> c) 42 | this.wm ! ws 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/engine/MarketRules.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import ch.epfl.ts.data.Currency._ 4 | import ch.epfl.ts.data.{DelOrder, LimitAskOrder, LimitBidOrder, MarketAskOrder, MarketBidOrder, Order, Streamable, Transaction} 5 | 6 | /** 7 | * represents the cost of placing a bid and market order 8 | */ 9 | case class Commission(limitOrderFee: Double, marketOrderFee: Double) 10 | 11 | /** 12 | * Market Simulator Configuration class. Defines the orders books priority implementation, the matching function and the commission costs of limit and market orders. 13 | * Extend this class and override its method(s) to customize Market rules for specific markets. 14 | * 15 | */ 16 | class MarketRules { 17 | val commission = Commission(0, 0) 18 | 19 | // when used on TreeSet, head() and iterator() provide increasing order 20 | def asksOrdering = new Ordering[Order] { 21 | def compare(first: Order, second: Order): Int = 22 | if (first.price > second.price) 1 23 | else if (first.price < second.price) -1 24 | else { 25 | if (first.timestamp < second.timestamp) 1 else if (first.timestamp > second.timestamp) -1 else 0 26 | } 27 | } 28 | 29 | // when used on TreeSet, head() and iterator() provide decreasing order 30 | def bidsOrdering = new Ordering[Order] { 31 | def compare(first: Order, second: Order): Int = 32 | if (first.price > second.price) -1 33 | else if (first.price < second.price) 1 34 | else { 35 | if (first.timestamp < second.timestamp) 1 else if (first.timestamp > second.timestamp) -1 else 0 36 | } 37 | } 38 | 39 | def alwaysTrue(a: Double, b: Double) = true 40 | 41 | def matchingFunction(marketId: Long, 42 | newOrder: Order, 43 | newOrdersBook: PartialOrderBook, 44 | bestMatchsBook: PartialOrderBook, 45 | send: Streamable => Unit, 46 | matchExists: (Double, Double) => Boolean = alwaysTrue, 47 | oldTradingPrice: Double, 48 | enqueueOrElse: (Order, PartialOrderBook) => Unit): Double = { 49 | 50 | println("MS: got new order: " + newOrder) 51 | 52 | if (bestMatchsBook.isEmpty) { 53 | println("MS: matching orders book empty") 54 | enqueueOrElse(newOrder, newOrdersBook) 55 | oldTradingPrice 56 | } else { 57 | 58 | val bestMatch = bestMatchsBook.head 59 | // check if a matching order exists when used with a limit order, if market order: matchExists = true 60 | if (matchExists(bestMatch.price, newOrder.price)) { 61 | 62 | bestMatchsBook delete bestMatch 63 | send(DelOrder(bestMatch.oid, bestMatch.uid, newOrder.timestamp, DEF, DEF, 0.0, 0.0)) 64 | 65 | // perfect match 66 | if (bestMatch.volume == newOrder.volume) { 67 | println("MS: volume match with " + bestMatch) 68 | println("MS: removing order: " + bestMatch + " from bestMatch orders book.") 69 | 70 | bestMatch match { 71 | case lbo: LimitBidOrder => 72 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.uid, bestMatch.oid, newOrder.uid, newOrder.oid)) 73 | case lao: LimitAskOrder => 74 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, newOrder.uid, newOrder.oid, bestMatch.uid, bestMatch.oid)) 75 | case _ => println("MarketRules: casting error") 76 | } 77 | } else if (bestMatch.volume > newOrder.volume) { 78 | println("MS: matched with " + bestMatch + ", new order volume inferior - cutting matched order.") 79 | println("MS: removing order: " + bestMatch + " from match orders book. enqueuing same order with " + (bestMatch.volume - newOrder.volume) + " volume.") 80 | bestMatch match { 81 | case lbo: LimitBidOrder => 82 | bestMatchsBook insert LimitBidOrder(bestMatch.oid, bestMatch.uid, bestMatch.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.volume - newOrder.volume, bestMatch.price) 83 | send(Transaction(marketId, bestMatch.price, newOrder.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.uid, bestMatch.oid, newOrder.uid, newOrder.oid)) 84 | send(LimitBidOrder(bestMatch.oid, bestMatch.uid, bestMatch.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.volume - newOrder.volume, bestMatch.price)) 85 | case lao: LimitAskOrder => 86 | bestMatchsBook insert LimitAskOrder(bestMatch.oid, bestMatch.uid, bestMatch.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.volume - newOrder.volume, bestMatch.price) 87 | send(Transaction(marketId, bestMatch.price, newOrder.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, newOrder.uid, newOrder.oid, bestMatch.uid, bestMatch.oid)) 88 | send(LimitAskOrder(bestMatch.oid, bestMatch.uid, bestMatch.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.volume - newOrder.volume, bestMatch.price)) 89 | case _ => println("MarketRules: casting error") 90 | } 91 | } else { 92 | println("MS: matched with " + bestMatch + ", new order volume superior - reiterate") 93 | println("MS: removing order: " + bestMatch + " from match orders book.") 94 | 95 | bestMatch match { 96 | case lbo: LimitBidOrder => 97 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, newOrder.uid, newOrder.oid, bestMatch.uid, bestMatch.oid)) 98 | matchingFunction(marketId, LimitBidOrder(newOrder.oid, newOrder.uid, newOrder.timestamp, newOrder.whatC, newOrder.withC, newOrder.volume - bestMatch.volume, bestMatch.price), newOrdersBook, bestMatchsBook, send, matchExists, oldTradingPrice, enqueueOrElse) 99 | case lao: LimitAskOrder => 100 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.uid, bestMatch.oid, newOrder.uid, newOrder.oid)) 101 | matchingFunction(marketId, LimitAskOrder(newOrder.oid, newOrder.uid, newOrder.timestamp, newOrder.whatC, newOrder.withC, newOrder.volume - bestMatch.volume, bestMatch.price), newOrdersBook, bestMatchsBook, send, matchExists, oldTradingPrice, enqueueOrElse) 102 | case mbo: MarketBidOrder => 103 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, newOrder.uid, newOrder.oid, bestMatch.uid, bestMatch.oid)) 104 | matchingFunction(marketId, MarketBidOrder(newOrder.oid, newOrder.uid, newOrder.timestamp, newOrder.whatC, newOrder.withC, newOrder.volume - bestMatch.volume, newOrder.price), newOrdersBook, bestMatchsBook, send, matchExists, oldTradingPrice, enqueueOrElse) 105 | case mao: MarketAskOrder => 106 | send(Transaction(marketId, bestMatch.price, bestMatch.volume, newOrder.timestamp, bestMatch.whatC, bestMatch.withC, bestMatch.uid, bestMatch.oid, newOrder.uid, newOrder.oid)) 107 | matchingFunction(marketId, MarketAskOrder(newOrder.oid, newOrder.uid, newOrder.timestamp, newOrder.whatC, newOrder.withC, newOrder.volume - bestMatch.volume, newOrder.price), newOrdersBook, bestMatchsBook, send, matchExists, oldTradingPrice, enqueueOrElse) 108 | case _ => println("MarketRules: casting error") 109 | } 110 | } 111 | 112 | // Update price 113 | bestMatch.price 114 | 115 | // no match found 116 | } else { 117 | println("MS: no match found - enqueuing") 118 | enqueueOrElse(newOrder, newOrdersBook) 119 | oldTradingPrice 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/engine/MarketSimulator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.data._ 5 | 6 | 7 | /** 8 | * message used to print the books contents (since we use PriotityQueues, it's the heap order) 9 | */ 10 | case class PrintBooks() 11 | 12 | class MarketSimulator(marketId: Long, rules: MarketRules) extends Component { 13 | 14 | val book = OrderBook(rules.bidsOrdering, rules.asksOrdering) 15 | /** 16 | * the price at which the last transaction was executed 17 | */ 18 | var tradingPrice: Double = 185000.0 // set for SobiTrader when using with finance.csv 19 | 20 | override def receiver = { 21 | case limitBid: LimitBidOrder => 22 | tradingPrice = rules.matchingFunction(marketId, limitBid, book.bids, book.asks, this.send[Streamable], (a, b) => a <= b, tradingPrice, (limitBid, bidOrdersBook) => { 23 | bidOrdersBook insert limitBid; send(limitBid); println("MS: order enqueued") 24 | }) 25 | case limitAsk: LimitAskOrder => 26 | tradingPrice = rules.matchingFunction(marketId, limitAsk, book.asks, book.bids, this.send[Streamable], (a, b) => a >= b, tradingPrice, (limitAsk, askOrdersBook) => { 27 | askOrdersBook insert limitAsk; send(limitAsk); println("MS: order enqueued") 28 | }) 29 | case marketBid: MarketBidOrder => 30 | tradingPrice = rules.matchingFunction(marketId, marketBid, book.bids, book.asks, this.send[Streamable], (a, b) => true, tradingPrice, (marketBid, bidOrdersBook) => println("MS: market order discarded")) 31 | case marketAsk: MarketAskOrder => 32 | tradingPrice = rules.matchingFunction(marketId, marketAsk, book.asks, book.bids, this.send[Streamable], (a, b) => true, tradingPrice, (marketAsk, askOrdersBook) => println("MS: market order discarded")) 33 | case del: DelOrder => 34 | println("MS: got Delete: " + del) 35 | send(del) 36 | book delete del 37 | case t: Transaction => 38 | tradingPrice = t.price 39 | case PrintBooks => 40 | // print shows heap order (binary tree) 41 | println("Ask Orders Book: " + book.bids) 42 | println("Bid Orders Book: " + book.asks) 43 | case _ => 44 | println("MS: got unknown") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/engine/Messages.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import ch.epfl.ts.data.Currency._ 4 | import ch.epfl.ts.data.Order 5 | 6 | /* 7 | * Definition of the Simulator's internal messages. 8 | */ 9 | 10 | 11 | /* ***************************** 12 | * Order 13 | */ 14 | 15 | /* Answer */ 16 | case class AcceptedOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 17 | extends Order(oid, uid, timestamp, whatC, withC, volume, price) 18 | 19 | object AcceptedOrder { 20 | def apply(o: Order): AcceptedOrder = AcceptedOrder(o.oid, o.uid, o.timestamp, o.whatC, o.withC, o.volume, o.price) 21 | } 22 | 23 | case class RejectedOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 24 | extends Order(oid, uid, timestamp, whatC, withC, volume, price) 25 | 26 | object RejectedOrder { 27 | def apply(o: Order): RejectedOrder = RejectedOrder(o.oid, o.uid, o.timestamp, o.whatC, o.withC, o.volume, o.price) 28 | } 29 | 30 | case class ExecutedOrder(override val oid: Long, override val uid: Long, override val timestamp: Long, override val whatC: Currency, override val withC: Currency, override val volume: Double, override val price: Double) 31 | extends Order(oid, uid, timestamp, whatC, withC, volume, price) 32 | 33 | object ExecutedOrder { 34 | def apply(o: Order): ExecutedOrder = ExecutedOrder(o.oid, o.uid, o.timestamp, o.whatC, o.withC, o.volume, o.price) 35 | } 36 | 37 | 38 | /* ***************************** 39 | * Wallet 40 | */ 41 | abstract class WalletState(val uid: Long) 42 | 43 | /* Getter */ 44 | case class GetWalletFunds(override val uid: Long) extends WalletState(uid) 45 | 46 | case class GetWalletAllOrders(override val uid: Long) extends WalletState(uid) 47 | 48 | case class GetWalletOpenOrder(override val uid: Long) extends WalletState(uid) 49 | 50 | case class GetWalletClosedOrder(override val uid: Long) extends WalletState(uid) 51 | 52 | case class GetWalletCanceledOrder(override val uid: Long) extends WalletState(uid) 53 | 54 | /* Answers */ 55 | case class WalletFunds(override val uid: Long, f: Map[Currency, Double]) extends WalletState(uid) 56 | 57 | case class WalletAllOrders(override val uid: Long, opO: List[Order], clO: List[Order], caO: List[Order]) extends WalletState(uid) 58 | 59 | case class WalletOpenOrders(override val uid: Long, opO: List[Order]) extends WalletState(uid) 60 | 61 | case class WalletClosedOrders(override val uid: Long, clO: List[Order]) extends WalletState(uid) 62 | 63 | case class WalletCanceledOrders(override val uid: Long, caO: List[Order]) extends WalletState(uid) 64 | 65 | /* Actions */ 66 | case class FundWallet(override val uid: Long, c: Currency, q: Double) extends WalletState(uid) 67 | 68 | 69 | /* ***************************** 70 | * Matcher 71 | */ 72 | abstract class MatcherState() 73 | 74 | /* Getter */ 75 | case class GetMatcherOrderBook(count: Int) extends MatcherState 76 | 77 | /* Answers */ 78 | case class MatcherOrderBook(bids: List[Order], asks: List[Order]) extends MatcherState -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/engine/OrderBook.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import ch.epfl.ts.data.Order 4 | 5 | import scala.collection.mutable.{HashMap => MHashMap, TreeSet => MTreeSet} 6 | 7 | /** 8 | * Container for the Order Book 9 | * The implementation has a HashMap with the orderIds matching the orders 10 | * because the naive implementation that required to execute a find() with 11 | * a predicate on the TreeSet to acquire the Order reference was executed 12 | * in linear time. 13 | */ 14 | class PartialOrderBook(val comparator: Ordering[Order]) { 15 | // orders ordered by the comparator 16 | val book = MTreeSet[Order]()(comparator) 17 | // key: orderId, value: order 18 | val bookMap = MHashMap[Long, Order]() 19 | 20 | /** 21 | * delete order from the book. 22 | */ 23 | def delete(o: Order): Boolean = bookMap remove o.oid map { r => book remove r; true} getOrElse { 24 | false 25 | } 26 | 27 | /** 28 | * insert order in book 29 | */ 30 | def insert(o: Order): Unit = { 31 | bookMap update(o.oid, o) 32 | book add o 33 | } 34 | 35 | def isEmpty = book.isEmpty 36 | 37 | def head = book.head 38 | 39 | def size = book.size 40 | } 41 | 42 | class OrderBook(val bids: PartialOrderBook, val asks: PartialOrderBook) { 43 | 44 | def delete(o: Order): Unit = if (!bids.delete(o)) asks.delete(o) 45 | 46 | def insertAskOrder(o: Order): Unit = asks.insert(o) 47 | 48 | def insertBidOrder(o: Order): Unit = bids.insert(o) 49 | 50 | //def getOrderById(oid: Long): Option[Order] = bids.bookMap.get(oid) 51 | } 52 | 53 | object OrderBook { 54 | def apply(bidComparator: Ordering[Order], askComparator: Ordering[Order]): OrderBook = 55 | new OrderBook(new PartialOrderBook(bidComparator), new PartialOrderBook(askComparator)) 56 | } 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/engine/RevenueCompute.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import ch.epfl.ts.component.{ Component, StartSignal } 4 | import ch.epfl.ts.data.Transaction 5 | 6 | import scala.concurrent.duration.DurationInt 7 | 8 | class RevenueCompute(pingIntervalMillis: Int, traderNames: Map[Long, String]) extends Component { 9 | 10 | import context._ 11 | 12 | case class Tick() 13 | case class Wallet(var shares: Double = 0.0, var money: Double = 0.0) 14 | 15 | var wallets = Map[Long, Wallet]() 16 | var oldTradingPrice: Double = 0.0 17 | var currentTradingPrice: Double = 0.0 18 | 19 | def receiver = { 20 | case StartSignal() => startScheduler() 21 | case Tick() => displayStats 22 | case t: Transaction => process(t) 23 | case _ => 24 | } 25 | 26 | def process(t: Transaction) = { 27 | currentTradingPrice = t.price 28 | // buyer has more shares but less money 29 | val buyerWallet = wallets.getOrElse(t.buyerId, Wallet()) 30 | buyerWallet.money = buyerWallet.money - t.volume * t.price 31 | buyerWallet.shares = buyerWallet.shares + t.volume 32 | wallets += (t.buyerId -> buyerWallet) 33 | 34 | // seller has more money but less shares 35 | val sellerWallet = wallets.getOrElse(t.sellerId, Wallet()) 36 | sellerWallet.money = sellerWallet.money + t.volume * t.price 37 | sellerWallet.shares = sellerWallet.shares - t.volume 38 | wallets += (t.sellerId -> sellerWallet) 39 | } 40 | 41 | def displayStats = { 42 | val changeInPrice: Double = computePriceEvolution 43 | var disp = new StringBuffer() 44 | // println(f"Stats: trading price=$currentTradingPrice%s, change=$changeInPrice%.2f %%") 45 | disp.append(f"Stats: trading price=$currentTradingPrice%s, change=$changeInPrice%.4f %% \n") 46 | // wallets.keys.map { x => println("Stats: trader: " + traderNames(x) + ", cash=" + wallets(x).money + ", shares=" + wallets(x).shares + " Revenue=" + (wallets(x).money + wallets(x).shares * currentTradingPrice))} 47 | wallets.keys.map { x => disp.append("Stats: trader: " + traderNames(x) + ", cash=" + wallets(x).money + ", shares=" + wallets(x).shares + " Revenue=" + (wallets(x).money + wallets(x).shares * currentTradingPrice) + "\n") } 48 | print(disp) 49 | oldTradingPrice = currentTradingPrice 50 | } 51 | 52 | def startScheduler() = system.scheduler.schedule(0 milliseconds, pingIntervalMillis milliseconds, self, Tick()) 53 | 54 | def computePriceEvolution: Double = { 55 | (currentTradingPrice - oldTradingPrice) / oldTradingPrice 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/engine/WalletManager.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import akka.actor.{Actor, ActorRef, Props} 4 | import ch.epfl.ts.data.Currency.Currency 5 | import ch.epfl.ts.data.Order 6 | 7 | /** 8 | * Manages multiple wallets 9 | */ 10 | class WalletDispatcher() extends Actor { 11 | // TODO: Multiple WalletChief Instances 12 | 13 | import context._ 14 | 15 | val wc = system.actorOf(Props(classOf[WalletChief])) 16 | 17 | override def receive = { 18 | case w: WalletState => wc ! w 19 | } 20 | } 21 | 22 | /* 23 | * 24 | * Manages the content of his wallets 25 | */ 26 | class WalletChief extends Actor { 27 | var wallets = Map[Long, Wallet]() 28 | 29 | override def receive = { 30 | case GetWalletFunds(uid) => answerGetWalletFunds(uid, sender()) 31 | case GetWalletAllOrders(uid) => answerGetWalletAllOrders(uid, sender()) 32 | case GetWalletOpenOrder(uid) => answerGetWalletOpenOrder(uid, sender()) 33 | case GetWalletClosedOrder(uid) => answerGetWalletClosedOrder(uid, sender()) 34 | case GetWalletCanceledOrder(uid) => answerGetWalletCanceledOrder(uid, sender()) 35 | case FundWallet(uid, c, q) => fundWallet(uid, c, q) 36 | } 37 | 38 | def answerGetWalletFunds(uid: Long, s: ActorRef): Unit = { 39 | sender ! new WalletFunds(uid, wallets.get(uid) match { 40 | case Some(w) => w.funds 41 | case None => val w = new Wallet(uid); wallets = wallets + (uid -> w); w.funds 42 | }) 43 | } 44 | 45 | def answerGetWalletAllOrders(uid: Long, s: ActorRef): Unit = { 46 | wallets.get(uid) match { 47 | case Some(w) => sender ! new WalletAllOrders(uid, w.openOrders, w.closedOrders, w.canceledOrders) 48 | case None => 49 | val w = new Wallet(uid) 50 | wallets = wallets + (uid -> w) 51 | sender ! new WalletAllOrders(uid, w.openOrders, w.closedOrders, w.canceledOrders) 52 | } 53 | } 54 | 55 | def answerGetWalletOpenOrder(uid: Long, s: ActorRef): Unit = { 56 | sender ! WalletOpenOrders(uid, wallets.get(uid) match { 57 | case Some(w) => w.openOrders 58 | case None => val w = new Wallet(uid); wallets = wallets + (uid -> w); w.openOrders 59 | }) 60 | } 61 | 62 | def answerGetWalletClosedOrder(uid: Long, s: ActorRef): Unit = { 63 | sender ! WalletClosedOrders(uid, wallets.get(uid) match { 64 | case Some(w) => w.closedOrders 65 | case None => val w = new Wallet(uid); wallets = wallets + (uid -> w); w.closedOrders 66 | }) 67 | } 68 | 69 | def answerGetWalletCanceledOrder(uid: Long, s: ActorRef): Unit = { 70 | sender ! WalletCanceledOrders(uid, wallets.get(uid) match { 71 | case Some(w) => w.canceledOrders 72 | case None => val w = new Wallet(uid); wallets = wallets + (uid -> w); w.canceledOrders 73 | }) 74 | } 75 | 76 | def fundWallet(uid: Long, c: Currency, q: Double): Unit = { 77 | wallets.get(uid) match { 78 | case Some(w) => w.funds.get(c) match { 79 | case Some(cq) => w.funds + (c -> (cq + q)) 80 | case None => w.funds + (c -> q) 81 | } 82 | case None => 83 | val w = new Wallet(uid) 84 | w.funds = w.funds + (c -> q) 85 | wallets = wallets + (uid -> w) 86 | } 87 | } 88 | } 89 | 90 | 91 | /* 92 | * Represents a wallet 93 | * @param userId 94 | */ 95 | sealed class Wallet(userId: Long) { 96 | var openOrders: List[Order] = Nil 97 | var closedOrders: List[Order] = Nil 98 | var canceledOrders: List[Order] = Nil 99 | 100 | var funds: Map[Currency, Double] = Map[Currency, Double]() 101 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/example/BTCArbitrage.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import akka.actor.Props 4 | import ch.epfl.ts.component.ComponentBuilder 5 | import ch.epfl.ts.component.fetch.{ BitstampOrderPullFetcher, BitstampTransactionPullFetcher, BtceOrderPullFetcher, BtceTransactionPullFetcher, MarketNames, PullFetchComponent } 6 | import ch.epfl.ts.component.persist.TransactionPersistor 7 | import ch.epfl.ts.component.utils.{ BackLoop, Printer } 8 | import ch.epfl.ts.data.{ DelOrder, LimitAskOrder, LimitBidOrder, OHLC, Order, Transaction, MarketAskOrder, MarketBidOrder } 9 | import ch.epfl.ts.engine.{ MarketRules, MarketSimulator } 10 | import ch.epfl.ts.indicators.OhlcIndicator 11 | import ch.epfl.ts.traders.Arbitrageur 12 | import scala.reflect.ClassTag 13 | import ch.epfl.ts.data.MarketBidOrder 14 | import ch.epfl.ts.data.MarketBidOrder 15 | 16 | /** 17 | * in this system, two fetchers gather orders and transaction 18 | * and orders data from BTC-e and Bitstamp exchanges and feed them 19 | * to the MarketSimulator. 20 | * An arbitrageur trader receives the data from the Backloop, 21 | * monitors the price difference and submits orders in order to 22 | * make revenue. 23 | */ 24 | object BTCArbitrage { 25 | 26 | def main(args: Array[String]) { 27 | implicit val builder = new ComponentBuilder("ArbitrageSystem") 28 | 29 | // Initialize the Interfaces to the DBs 30 | val btceXactPersit = new TransactionPersistor("btce-transaction-db2") 31 | btceXactPersit.init() 32 | val bitstampXactPersist = new TransactionPersistor("bitstamp-transaction-db2") 33 | bitstampXactPersist.init() 34 | 35 | // Instantiate Transaction fetchers for Bitcoin exchange markets 36 | val btceMarketId = MarketNames.BTCE_ID 37 | val bitstampMarketId = MarketNames.BITSTAMP_ID 38 | val btceTransactionPullFetcher = new BtceTransactionPullFetcher 39 | val btceOrderPullFetcher = new BtceOrderPullFetcher 40 | val bitstampTransactionPullFetcher = new BitstampTransactionPullFetcher 41 | val bitstampOrderPullFetcher = new BitstampOrderPullFetcher 42 | 43 | // Create Components 44 | 45 | // fetchers 46 | val btceTransactionFetcher = builder.createRef(Props(classOf[PullFetchComponent[Transaction]], btceTransactionPullFetcher, implicitly[ClassTag[Transaction]]), "btceTransactionsFetcher") 47 | val bitstampTransactionFetcher = builder.createRef(Props(classOf[PullFetchComponent[Transaction]], bitstampTransactionPullFetcher, implicitly[ClassTag[Transaction]]), "bitstampTransactionFetcher") 48 | val btceOrderFetcher = builder.createRef(Props(classOf[PullFetchComponent[Order]], btceOrderPullFetcher, implicitly[ClassTag[Order]]), "btceOrderFetcher") 49 | val bitstampOrderFetcher = builder.createRef(Props(classOf[PullFetchComponent[Order]], bitstampOrderPullFetcher, implicitly[ClassTag[Order]]), "bitstampOrderFetcher") 50 | // trading agents 51 | val arbitrageurId = 111L 52 | val tradingPriceDelta = 1.0 53 | val volume = 50.0 54 | val arbitrageur = builder.createRef(Props(classOf[Arbitrageur], 111L, tradingPriceDelta, volume), "arbitrageur") 55 | // markets 56 | val rules = new MarketRules() 57 | val btceMarket = builder.createRef(Props(classOf[MarketSimulator], btceMarketId, rules), MarketNames.BTCE_NAME) 58 | val bitstampMarket = builder.createRef(Props(classOf[MarketSimulator], bitstampMarketId, rules), MarketNames.BITSTAMP_NAME) 59 | // backloops 60 | val btceBackLoop = builder.createRef(Props(classOf[BackLoop], btceMarketId, btceXactPersit), "btceBackLoop") 61 | val bitstampBackLoop = builder.createRef(Props(classOf[BackLoop], bitstampMarketId, bitstampXactPersist), "bitstampBackLoop") 62 | 63 | // Create the connections 64 | // BTC-e 65 | // fetcher to market 66 | btceOrderFetcher.addDestination(btceMarket, classOf[LimitAskOrder]) 67 | btceOrderFetcher.addDestination(btceMarket, classOf[LimitBidOrder]) 68 | btceOrderFetcher.addDestination(btceMarket, classOf[DelOrder]) 69 | // fetcher to backloop 70 | btceTransactionFetcher.addDestination(btceBackLoop, classOf[Transaction]) 71 | // market to backloop 72 | btceMarket.addDestination(btceBackLoop, classOf[Transaction]) 73 | // backloop to arbitrageur 74 | btceBackLoop.addDestination(arbitrageur, classOf[Transaction]) 75 | // Bitstamp 76 | // fetcher to market 77 | bitstampOrderFetcher.addDestination(bitstampMarket, classOf[LimitAskOrder]) 78 | bitstampOrderFetcher.addDestination(bitstampMarket, classOf[LimitBidOrder]) 79 | bitstampOrderFetcher.addDestination(bitstampMarket, classOf[DelOrder]) 80 | // fetcher to backloop 81 | bitstampTransactionFetcher.addDestination(bitstampBackLoop, classOf[Transaction]) 82 | // market to backloop 83 | bitstampMarket.addDestination(bitstampBackLoop, classOf[Transaction]) 84 | // backloop to arbitrageur 85 | bitstampBackLoop.addDestination(arbitrageur, classOf[Transaction]) 86 | bitstampBackLoop.addDestination(arbitrageur, classOf[OHLC]) 87 | // Arbitrageur to markets 88 | arbitrageur.addDestination(btceMarket, classOf[MarketBidOrder]) 89 | arbitrageur.addDestination(btceMarket, classOf[MarketAskOrder]) 90 | arbitrageur.addDestination(bitstampMarket, classOf[MarketAskOrder]) 91 | arbitrageur.addDestination(bitstampMarket, classOf[MarketBidOrder]) 92 | // Start the system 93 | builder.start 94 | } 95 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/example/BtceTransactionFlowTesterWithStorage.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import akka.actor.Props 4 | import ch.epfl.ts.component.ComponentBuilder 5 | import ch.epfl.ts.component.fetch.{BitstampTransactionPullFetcher, BtceTransactionPullFetcher, MarketNames, PullFetchComponent} 6 | import ch.epfl.ts.component.persist.{Persistor, TransactionPersistor} 7 | import ch.epfl.ts.component.utils.Printer 8 | import ch.epfl.ts.data.Transaction 9 | 10 | import scala.reflect.ClassTag 11 | 12 | /** 13 | * Demonstration of fetching Live Bitcoin/USD trading data from BTC-e, 14 | * saving it to a SQLite Database and printing it. 15 | */ 16 | object BtceTransactionFlowTesterWithStorage { 17 | def main(args: Array[String]): Unit = { 18 | implicit val builder = new ComponentBuilder("DataSourceSystem") 19 | 20 | // Initialize the Interface to DB 21 | val btceXactPersit = new TransactionPersistor("btce-transaction-db-batch") 22 | btceXactPersit.init() 23 | val bitstampXactPersit = new TransactionPersistor("bitstamp-transaction-db-batch") 24 | bitstampXactPersit.init() 25 | 26 | // Instantiate a Transaction etcher for BTC-e and Bitstamp 27 | val btceMarketId = MarketNames.BTCE_ID 28 | val bitstampMarketId = MarketNames.BITSTAMP_ID 29 | val btcePullFetcher = new BtceTransactionPullFetcher 30 | val bitstampPullFetcher = new BitstampTransactionPullFetcher 31 | 32 | // Create Components 33 | val printer = builder.createRef(Props(classOf[Printer], "my-printer"), "printer") 34 | val btcePersistor = builder.createRef(Props(classOf[Persistor[Transaction]], btceXactPersit, implicitly[ClassTag[Transaction]]), "btcePersistor") 35 | val btceFetcher = builder.createRef(Props(classOf[PullFetchComponent[Transaction]], btcePullFetcher, implicitly[ClassTag[Transaction]]), "btceFetcher") 36 | val bitstampPersistor = builder.createRef(Props(classOf[Persistor[Transaction]], bitstampXactPersit, implicitly[ClassTag[Transaction]]), "bitstampPeristor") 37 | val bitstampFetcher = builder.createRef(Props(classOf[PullFetchComponent[Transaction]], bitstampPullFetcher, implicitly[ClassTag[Transaction]]), "bitstampFetcher") 38 | 39 | // Create the connections 40 | btceFetcher.addDestination(printer, classOf[Transaction]) 41 | btceFetcher.addDestination(btcePersistor, classOf[Transaction]) 42 | bitstampFetcher.addDestination(printer, classOf[Transaction]) 43 | bitstampFetcher.addDestination(bitstampPersistor, classOf[Transaction]) 44 | 45 | // Start the system 46 | builder.start 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/example/ReplayFlowTesterFromStorage.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import akka.actor.Props 4 | import ch.epfl.ts.component.ComponentBuilder 5 | import ch.epfl.ts.component.persist.TransactionPersistor 6 | import ch.epfl.ts.component.replay.{Replay, ReplayConfig} 7 | import ch.epfl.ts.component.utils.Printer 8 | import ch.epfl.ts.data.Transaction 9 | 10 | import scala.reflect.ClassTag 11 | 12 | /** 13 | * Demonstration of loading Bitcoin/USD transactions data from a transactions 14 | * persistor and printing it. 15 | */ 16 | object ReplayFlowTesterFromStorage { 17 | def main(args: Array[String]): Unit = { 18 | implicit val builder = new ComponentBuilder("ReplayFlowTesterSystem") 19 | 20 | // Initialize the Interface to DB 21 | val btceXactPersit = new TransactionPersistor("btce-transaction-db") 22 | btceXactPersit.init() 23 | 24 | // Configuration object for Replay 25 | val replayConf = new ReplayConfig(1418737788400L, 0.01) 26 | 27 | // Create Components 28 | val printer = builder.createRef(Props(classOf[Printer], "printer"), "printer") 29 | val replayer = builder.createRef(Props(classOf[Replay[Transaction]], btceXactPersit, replayConf, implicitly[ClassTag[Transaction]]), "replayer") 30 | 31 | // Create the connections 32 | replayer.addDestination(printer, classOf[Transaction]) 33 | 34 | // Start the system 35 | builder.start 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/example/ReplayOrdersLoop.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import akka.actor.Props 4 | import ch.epfl.ts.component.ComponentBuilder 5 | import ch.epfl.ts.component.persist.{OrderPersistor, TransactionPersistor} 6 | import ch.epfl.ts.component.replay.{Replay, ReplayConfig} 7 | import ch.epfl.ts.component.utils.{BackLoop, Printer} 8 | import ch.epfl.ts.data.{DelOrder, LimitAskOrder, LimitBidOrder, MarketAskOrder, MarketBidOrder, OHLC, Order, Transaction} 9 | import ch.epfl.ts.engine.{MarketRules, MarketSimulator, RevenueCompute} 10 | import ch.epfl.ts.indicators.{OhlcIndicator, SMA, SmaIndicator} 11 | import ch.epfl.ts.traders.{DoubleCrossoverTrader, DoubleEnvelopeTrader, SimpleTrader, SobiTrader, TransactionVwapTrader} 12 | 13 | import scala.reflect.ClassTag 14 | 15 | /** 16 | * Use case where orders are loaded from a persistor (previously filled with orders from 17 | * finance.csv) and fed into the MarketSimulator. 18 | * The transactions executed by the MS are saved in a TransactionsPersistor. 19 | * A Simple Trader, SOBI trader, VWAP trader, Double Envelope 20 | * trader and a Double Crossover trader are plugged in the system 21 | * and submit orders according to their defined strategies. 22 | * A RevenueCompute component periodically displays the revenue 23 | * of each trader. 24 | * 25 | */ 26 | object ReplayOrdersLoop { 27 | 28 | def main(args: Array[String]) { 29 | implicit val builder = new ComponentBuilder("ReplayFinanceSystem") 30 | 31 | // replayer params 32 | val initTime = 25210389L 33 | val compression = 0.001 34 | // market params 35 | val marketId = 0L 36 | val rules = new MarketRules() 37 | 38 | // Persistors 39 | // source 40 | val financePersistor = new OrderPersistor("finance") // requires to have run CSVFetcher on finance.csv (obtained by mail from Milos) 41 | financePersistor.init() 42 | // destination 43 | val transactionsPersistor = new TransactionPersistor("ReplayTransactions") 44 | transactionsPersistor.init() 45 | 46 | // Create components 47 | // market 48 | val market = builder.createRef(Props(classOf[MarketSimulator], marketId, rules), "market") 49 | // Replay 50 | val replayer = builder.createRef(Props(classOf[Replay[Order]], financePersistor, ReplayConfig(initTime, compression), implicitly[ClassTag[Order]]), "replayer") 51 | // Printer 52 | val printer = builder.createRef(Props(classOf[Printer], "ReplayLoopPrinter"), "printer") 53 | // backloop 54 | val backloop = builder.createRef(Props(classOf[BackLoop], marketId, transactionsPersistor), "backloop") 55 | // ohlc computation 56 | val shortTickSizeMillis = 5000L 57 | val ohlcShort = builder.createRef(Props(classOf[OhlcIndicator], marketId, shortTickSizeMillis), "ohlcShort") 58 | val longTickSizeMillis = 10000L 59 | val ohlclong = builder.createRef(Props(classOf[OhlcIndicator], marketId, longTickSizeMillis), "ohlcLong") 60 | // Indicators 61 | val shortPeriod = 5 62 | val longPeriod = 10 63 | val smaShort = builder.createRef(Props(classOf[SmaIndicator], shortPeriod), "smaShort") 64 | val smaLong = builder.createRef(Props(classOf[SmaIndicator], longPeriod), "smaLong") 65 | // Traders 66 | val traderNames: Map[Long, String] = Map(0L -> "Finance", 123L -> "SobiTrader", 132L -> "SimpleTrader", 333L -> "VwapTrader", 444L -> "DcTrader", 555L -> "DeTrader") 67 | val sobiTrader = builder.createRef(Props(classOf[SobiTrader], 123L, 3000, 2, 700.0, 50, 100.0, rules), "sobiTrader") 68 | val simpleTrader = builder.createRef(Props(classOf[SimpleTrader], 132L, 10000, 50.0), "simpleTrader") 69 | val transactionVwap = builder.createRef(Props(classOf[TransactionVwapTrader], 333L, longTickSizeMillis.toInt), "transactionVwapTrader") 70 | val dcTrader = builder.createRef(Props(classOf[DoubleCrossoverTrader], 444L, 5, 10, 50.0), "dcTrader") 71 | val deTrader = builder.createRef(Props(classOf[DoubleEnvelopeTrader], 555L, 0.025, 50.0), "deTrader") 72 | // Display 73 | val display = builder.createRef(Props(classOf[RevenueCompute], 5000, traderNames), "display") 74 | 75 | // Create connections 76 | // replay 77 | replayer.addDestination(market, classOf[Order]) 78 | replayer.addDestination(market, classOf[LimitAskOrder]) 79 | replayer.addDestination(market, classOf[LimitBidOrder]) 80 | replayer.addDestination(market, classOf[DelOrder]) 81 | // market 82 | market.addDestination(backloop, classOf[Transaction]) 83 | market.addDestination(backloop, classOf[LimitBidOrder]) 84 | market.addDestination(backloop, classOf[LimitAskOrder]) 85 | market.addDestination(backloop, classOf[DelOrder]) 86 | market.addDestination(display, classOf[Transaction]) 87 | // backLoop 88 | backloop.addDestination(sobiTrader, classOf[LimitAskOrder]) 89 | backloop.addDestination(sobiTrader, classOf[LimitBidOrder]) 90 | backloop.addDestination(sobiTrader, classOf[DelOrder]) 91 | backloop.addDestination(transactionVwap, classOf[Transaction]) 92 | backloop.addDestination(ohlcShort, classOf[Transaction]) 93 | backloop.addDestination(ohlclong, classOf[Transaction]) 94 | // ohlc 95 | ohlclong.addDestination(smaLong, classOf[OHLC]) 96 | ohlcShort.addDestination(smaShort, classOf[OHLC]) 97 | // moving averages 98 | smaLong.addDestination(deTrader, classOf[SMA]) 99 | smaShort.addDestination(dcTrader, classOf[SMA]) 100 | smaLong.addDestination(dcTrader, classOf[SMA]) 101 | // traders 102 | // simpleTrader 103 | simpleTrader.addDestination(market, classOf[MarketAskOrder]) 104 | simpleTrader.addDestination(market, classOf[MarketBidOrder]) 105 | // SobiTrader 106 | sobiTrader.addDestination(market, classOf[LimitBidOrder]) 107 | sobiTrader.addDestination(market, classOf[LimitAskOrder]) 108 | // Double Crossover Trader 109 | dcTrader.addDestination(market, classOf[MarketAskOrder]) 110 | dcTrader.addDestination(market, classOf[MarketBidOrder]) 111 | // Double Envelope Trader 112 | deTrader.addDestination(market, classOf[MarketBidOrder]) 113 | deTrader.addDestination(market, classOf[MarketAskOrder]) 114 | // VWAP trader 115 | transactionVwap.addDestination(market, classOf[MarketAskOrder]) 116 | transactionVwap.addDestination(market, classOf[MarketBidOrder]) 117 | 118 | builder.start 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/example/TwitterFlowTesterWithStorage.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import akka.actor._ 4 | import ch.epfl.ts.component.ComponentBuilder 5 | import ch.epfl.ts.component.fetch.TwitterFetchComponent 6 | import ch.epfl.ts.component.persist.{Persistor, TweetPersistor} 7 | import ch.epfl.ts.component.utils.Printer 8 | import ch.epfl.ts.data.Tweet 9 | 10 | import scala.reflect.ClassTag 11 | 12 | object TwitterFlowTesterWithStorage { 13 | def main(args: Array[String]): Unit = { 14 | implicit val builder = new ComponentBuilder("TwitterPrintSystem") 15 | 16 | // Initialize the Interface to DB 17 | val tweetPersistor = new TweetPersistor("twitter-db") 18 | 19 | // Create Components 20 | val printer = builder.createRef(Props(classOf[Printer]), "printer") 21 | val persistor = builder.createRef(Props(classOf[Persistor[Tweet]], tweetPersistor, implicitly[ClassTag[Tweet]]), "tweet-persistor") 22 | val fetcher = builder.createRef(Props(classOf[TwitterFetchComponent]), "twitter-fetcher") 23 | 24 | // Create the connections 25 | fetcher.addDestination(printer, classOf[Tweet]) 26 | fetcher.addDestination(persistor, classOf[Tweet]) 27 | 28 | // Start the system 29 | builder.start 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/indicators/EmaIndicator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.indicators 2 | 3 | case class EMA(override val value: Double, override val period: Int) extends MA(value: Double, period: Int) 4 | 5 | /** 6 | * Exponentially weighted moving average component. 7 | */ 8 | class EmaIndicator(val period: Int) extends MaIndicator(period) { 9 | 10 | var previousEma: Double = 0.0 11 | 12 | def computeMa: EMA = { 13 | val alpha = 2 / (1 + 2 * period) 14 | previousEma = (alpha * values.head.close) + previousEma * (1 - alpha) 15 | EMA(previousEma, period) 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/indicators/MaIndicator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.indicators 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.data.OHLC 5 | 6 | import scala.collection.mutable.MutableList 7 | 8 | /** 9 | * Moving Average value data 10 | */ 11 | abstract class MA(val value: Double, val period: Int) 12 | 13 | /** 14 | * Moving average superclass. To implement a moving average indicator, 15 | * extend this class and implement the computeMa() method. 16 | */ 17 | abstract class MaIndicator(period: Int) extends Component { 18 | 19 | var values: MutableList[OHLC] = MutableList[OHLC]() 20 | 21 | def receiver = { 22 | case o: OHLC => { 23 | println("maIndicator: received ohlc: " + o) 24 | values += o // 25 | if (values.size == period) { 26 | val ma = computeMa 27 | println("maIndicator: sending " + ma) 28 | send(ma) 29 | values.clear() 30 | } 31 | } 32 | case _ => 33 | } 34 | 35 | /** 36 | * compute moving average 37 | */ 38 | def computeMa : MA 39 | 40 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/indicators/OhlcIndicator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.indicators 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.data.{OHLC, Transaction} 5 | 6 | import scala.collection.mutable.MutableList 7 | 8 | /** 9 | * computes OHLC tick for a tick frame of the provided size, the OHLCs are identified with the provided marketId 10 | */ 11 | class OhlcIndicator(marketId: Long, tickSizeMillis: Long) extends Component { 12 | 13 | /** 14 | * stores transactions' price values 15 | */ 16 | var values: MutableList[Double] = MutableList[Double]() 17 | var volume: Double = 0.0 18 | var close: Double = 0.0 19 | var currentTick: Long = 0 20 | 21 | override def receiver = { 22 | case t: Transaction => { 23 | if (whichTick(t.timestamp) > currentTick) { 24 | // new tick, send OHLC with values stored until now, and reset accumulators (Transaction volume & prices) 25 | send(computeOHLC) 26 | currentTick = whichTick(t.timestamp) 27 | } 28 | values += t.price 29 | volume = volume + t.volume 30 | } 31 | case _ => println("OhlcIndicator: received unknown") 32 | } 33 | 34 | /** 35 | * computes to which tick an item with the provided timestamp belongs to 36 | */ 37 | private def whichTick(timestampMillis: Long): Long = { 38 | timestampMillis / tickSizeMillis 39 | } 40 | 41 | /** 42 | * compute OHLC if values have been received, otherwise send OHLC with all values set to the close of the previous non-empty OHLC 43 | */ 44 | private def computeOHLC: OHLC = { 45 | // set OHLC's timestamp 46 | val tickTimeStamp = currentTick * tickSizeMillis 47 | 48 | if (!values.isEmpty) { 49 | close = values.head 50 | val ohlc = OHLC(marketId, values.last, values.max(Ordering.Double), values.min(Ordering.Double), values.head, volume, tickTimeStamp, tickSizeMillis) 51 | // clean ancient vals 52 | volume = 0 53 | values.clear() 54 | println("OhlcIndicator: sending OHLC : " + ohlc) 55 | ohlc 56 | } else { 57 | OHLC(marketId, close, close, close, close, 0, tickTimeStamp, tickSizeMillis) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/indicators/SmaIndicator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.indicators 2 | 3 | case class SMA(override val value: Double, override val period: Int) extends MA(value, period) 4 | 5 | /** 6 | * Simple moving average indicator 7 | */ 8 | class SmaIndicator(period: Int) extends MaIndicator(period) { 9 | 10 | def computeMa: SMA = { 11 | var sma: Double = 0.0 12 | values.map { o => sma = sma + o.close } 13 | SMA(sma / values.size, period) 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/traders/Arbitrageur.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.traders 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.component.fetch.MarketNames.marketIdToName 5 | import ch.epfl.ts.data.{ OHLC, Transaction, MarketAskOrder, MarketBidOrder } 6 | import ch.epfl.ts.data.Currency._ 7 | 8 | /** 9 | * Arbitrageur trader: receives Transactions from multiple markets and sends market orders 10 | * to the exchanges when a certain delta price difference is reached. 11 | */ 12 | class Arbitrageur(uid: Long, priceDelta: Double, volume: Double) extends Component { 13 | 14 | var oid = 40000000L 15 | 16 | // key: marketId, value: tradingPrice 17 | var marketPrices = Map[Long, Double]() 18 | // key: (marketIdA, marketIdB), value: (tradingPriceA - tradingPriceB) 19 | var marketPriceDifferences = Map[(Long, Long), Double]() 20 | 21 | def receiver = { 22 | case t: Transaction => { 23 | marketPrices += (t.mid -> t.price) 24 | computePriceDifferences(t.mid, t.price) 25 | } 26 | case _ => println("Arbitrageur: received unknown") 27 | } 28 | 29 | def computePriceDifferences(mId: Long, price: Double) = { 30 | 31 | // message base (mainly for debugging) 32 | val priceDifferencesDisplay = new StringBuffer("Arbitrageur:\n") 33 | 34 | marketPrices.map(a => 35 | if (a._1 != mId) { 36 | // compute price difference 37 | val difference = price - a._2 38 | marketPriceDifferences += (mId, a._1) -> difference 39 | 40 | // send orders if price delta is reached 41 | if (difference > priceDelta) { 42 | // trading price of market with id=mId > trading price of market with id=a._1 43 | // sell mId shares, buy a._1 shares 44 | println("Arbitrageur: sending sell to " + marketIdToName(mId) + " and buy to " + marketIdToName(a._1)) 45 | send(marketIdToName(mId), MarketAskOrder(oid, uid, System.currentTimeMillis(), BTC, USD, volume, 0.0)) 46 | oid = oid + 1 47 | send(marketIdToName(a._1), MarketBidOrder(oid, uid, System.currentTimeMillis(), BTC, USD, volume, 0.0)) 48 | oid = oid + 1 49 | } else if (-difference > priceDelta) { 50 | // trading price of market with id=a._1 > trading price of market with id=mId 51 | // sell a._1 shares, buy mId shares 52 | println("Arbitrageur: sending sell to " + marketIdToName(a._1) + " and buy to " + marketIdToName(mId)) 53 | send(marketIdToName(a._1), MarketAskOrder(oid, uid, System.currentTimeMillis(), BTC, USD, volume, 0.0)) 54 | oid = oid + 1 55 | send(marketIdToName(mId), MarketBidOrder(oid, uid, System.currentTimeMillis(), BTC, USD, volume, 0.0)) 56 | oid = oid + 1 57 | } 58 | 59 | // compute price difference percentage (only for display) 60 | val percentageDifference = (price - a._2) / a._2 61 | priceDifferencesDisplay.append("market " + marketIdToName(mId) + ": price = " + price + ", market " + marketIdToName(a._1) + 62 | ": price = " + a._2 + ", difference = ") 63 | priceDifferencesDisplay.append(f"${difference}%.4f, ") 64 | priceDifferencesDisplay.append(f"(${percentageDifference}%.4f %%)\n") 65 | }) 66 | print(priceDifferencesDisplay) 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/traders/DoubleCrossoverTrader.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.traders 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.data.Currency._ 5 | import ch.epfl.ts.data.{MarketAskOrder, MarketBidOrder} 6 | import ch.epfl.ts.indicators.MA 7 | 8 | /** 9 | * Double Crossover Trader: receives moving average values of 2 different sizes (one must be a multiple of the other). 10 | * When the short MA crosses the long MA from above, a sell order is sent with the provided volume 11 | * When the short MA crosses the long MA from below, a buy order is sent with the provided volume 12 | */ 13 | class DoubleCrossoverTrader(val uid: Long, val shortPeriod: Int, val longPeriod: Int, val volume: Double) extends Component { 14 | 15 | var previousShortMa: Double = 0.0 16 | var previousLongMa: Double = 0.0 17 | var currentShortMa: Double = 0.0 18 | var currentLongMa: Double = 0.0 19 | val maSizeDiff: Int = longPeriod / shortPeriod 20 | var shortMaCount: Int = 0 21 | var isShortReady: Boolean = false 22 | var isLongReady: Boolean = false 23 | var oid = 876543 24 | 25 | def receiver = { 26 | case ma: MA => 27 | println("DoubleCrossoverTrader: received " + ma); ma.period match { 28 | case `shortPeriod` => { 29 | shortMaCount = shortMaCount + 1 30 | if (shortMaCount == maSizeDiff) { 31 | isShortReady = true 32 | previousShortMa = currentShortMa 33 | currentShortMa = ma.value 34 | makeOrder 35 | } 36 | } 37 | case `longPeriod` => { 38 | isLongReady = true 39 | previousLongMa = currentLongMa 40 | currentLongMa = ma.value 41 | makeOrder 42 | } 43 | } 44 | case _ => 45 | } 46 | 47 | def makeOrder = { 48 | if (isShortReady && isLongReady) { 49 | println("DoubleCrossoverTrader: both MAs received, checking possible orders:\n" + 50 | "oldShortMa=" + previousShortMa + ", oldLongMa=" + previousLongMa + "\n" + 51 | "currentShortMa=" + currentShortMa + ", currentLongMa=" + currentLongMa) 52 | if ((previousShortMa > previousLongMa) && (currentShortMa < currentLongMa)) { 53 | send(MarketAskOrder(oid, uid, System.currentTimeMillis(), USD, USD, volume, 0)) 54 | println("DoubleCrossoverTrader: sending sell") 55 | oid = oid + 1 56 | } else if ((previousShortMa < previousLongMa) && (currentShortMa > currentLongMa)) { 57 | send(MarketBidOrder(oid, uid, System.currentTimeMillis(), USD, USD, volume, 0)) 58 | println("DoubleCrossoverTrader: sending buy") 59 | oid = oid + 1 60 | } 61 | isShortReady = false 62 | isLongReady = false 63 | shortMaCount = 0 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/traders/DoubleEnvelopeTrader.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.traders 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.data.Currency._ 5 | import ch.epfl.ts.data.{MarketAskOrder, MarketBidOrder, OHLC} 6 | import ch.epfl.ts.indicators.MA 7 | 8 | /** 9 | * Double envelope Trader: 10 | * The Double envelope trader monitors the data from a single moving average. 11 | * It computes two values that are each slightly above and below the actual MA 12 | * by a factor alpha. When the current price crosses the upper bound, a market 13 | * ask order with the volume defined in the constructor is sent. When the current 14 | * price falls below the lower bound, a market bid order is sent. 15 | */ 16 | class DoubleEnvelopeTrader(uid: Long, alpha: Double, volume: Double) extends Component { 17 | 18 | var oid = 23467 19 | var envBelow: Double = 0.0 20 | var envAbove: Double = 0.0 21 | var currentPrice: Double = 0.0 22 | 23 | def receiver = { 24 | case ma: MA => { 25 | envBelow = ma.value * (1 - alpha) 26 | envAbove = ma.value * (1 + alpha) 27 | if (currentPrice > envAbove) { 28 | // sell 29 | send(MarketAskOrder(oid, uid, System.currentTimeMillis(), USD, USD, volume, 0)) 30 | oid = oid + 1 31 | } 32 | if (currentPrice < envBelow) { 33 | // buy 34 | send(MarketBidOrder(oid, uid, System.currentTimeMillis(), USD, USD, volume, 0)) 35 | oid = oid + 1 36 | } 37 | } 38 | case o: OHLC => currentPrice = o.close 39 | case _ => 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/traders/SimpleTrader.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.traders 2 | 3 | import ch.epfl.ts.component.{Component, StartSignal} 4 | import ch.epfl.ts.data.Currency._ 5 | import ch.epfl.ts.data.{MarketAskOrder, MarketBidOrder, Order} 6 | 7 | import scala.concurrent.duration.DurationInt 8 | 9 | case class SendMarketOrder() 10 | 11 | /** 12 | * Simple trader that periodically sends market ask and bid orders alternatively. 13 | */ 14 | class SimpleTrader(uid: Long, intervalMillis: Int, orderVolume: Double) extends Component { 15 | import context._ 16 | 17 | var orderId = 4567 18 | val initDelayMillis = 10000 19 | 20 | var alternate = 0 21 | 22 | override def receiver = { 23 | case StartSignal() => start 24 | case SendMarketOrder => { 25 | if (alternate % 2 == 0) { 26 | println("SimpleTrader: sending market bid order") 27 | send[Order](MarketBidOrder(orderId, uid, System.currentTimeMillis(), USD, USD, 50, 0)) 28 | } else { 29 | println("SimpleTrader: sending market ask order") 30 | send[Order](MarketAskOrder(orderId, uid, System.currentTimeMillis(), USD, USD, 50, 0)) 31 | } 32 | alternate = alternate + 1 33 | orderId = orderId + 1 34 | } 35 | case _ => println("SimpleTrader: received unknown") 36 | } 37 | 38 | def start = { 39 | 40 | system.scheduler.schedule(initDelayMillis milliseconds, intervalMillis milliseconds, self, SendMarketOrder) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/traders/SobiTrader.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.traders 2 | 3 | import ch.epfl.ts.component.{Component, StartSignal} 4 | import ch.epfl.ts.data.Currency._ 5 | import ch.epfl.ts.data.{DelOrder, LimitAskOrder, LimitBidOrder, Order, Transaction} 6 | import ch.epfl.ts.engine.{MarketRules, OrderBook} 7 | 8 | import scala.collection.mutable.TreeSet 9 | import scala.concurrent.duration.DurationInt 10 | 11 | /** 12 | * SOBI trader 13 | */ 14 | class SobiTrader(uid: Long, intervalMillis: Int, quartile: Int, theta: Double, orderVolume: Int, priceDelta: Double, rules: MarketRules) 15 | extends Component { 16 | import context._ 17 | 18 | case class PossibleOrder() 19 | 20 | val book = OrderBook(rules.bidsOrdering, rules.asksOrdering) 21 | var tradingPrice = 188700.0 // for finance.csv 22 | 23 | var bi: Double = 0.0 24 | var si: Double = 0.0 25 | var currentOrderId: Long = 456789 26 | 27 | override def receiver = { 28 | case StartSignal() => start 29 | 30 | case limitAsk: LimitAskOrder => book insertAskOrder limitAsk 31 | case limitBid: LimitBidOrder => book insertBidOrder limitBid 32 | case delete: DelOrder => removeOrder(delete) 33 | case transaction: Transaction => tradingPrice = transaction.price 34 | 35 | case b: PossibleOrder => { 36 | bi = computeBiOrSi(book.bids.book) 37 | si = computeBiOrSi(book.asks.book) 38 | if ((si - bi) > theta) { 39 | currentOrderId = currentOrderId + 1 40 | //"place an order to buy x shares at (lastPrice-p)" 41 | println("SobiTrader: making buy order: price=" + (tradingPrice - priceDelta) + ", volume=" + orderVolume) 42 | send[Order](LimitBidOrder(currentOrderId, uid, System.currentTimeMillis, USD, USD, orderVolume, tradingPrice - priceDelta)) 43 | } 44 | if ((bi - si) > theta) { 45 | currentOrderId = currentOrderId + 1 46 | //"place an order to sell x shares at (lastPrice+p)" 47 | println("SobiTrader: making sell order: price=" + (tradingPrice + priceDelta) + ", volume=" + orderVolume) 48 | send[Order](LimitAskOrder(currentOrderId, uid, System.currentTimeMillis(), USD, USD, orderVolume, tradingPrice + priceDelta)) 49 | } 50 | } 51 | 52 | case _ => println("SobiTrader: received unknown") 53 | } 54 | 55 | def start = { 56 | system.scheduler.schedule(0 milliseconds, intervalMillis milliseconds, self, PossibleOrder()) 57 | } 58 | 59 | def removeOrder(order: Order): Unit = book delete order 60 | 61 | /** 62 | * compute the volume-weighted average price of the top quartile*25% of the volume of the bids/asks orders book 63 | */ 64 | def computeBiOrSi[T <: Order](bids: TreeSet[T]): Double = { 65 | if (bids.size > 4) { 66 | val it = bids.iterator 67 | var bi: Double = 0.0 68 | var vol: Double = 0 69 | for (i <- 0 to ((bids.size * quartile) / 4)) { 70 | val currentBidOrder = it.next() 71 | bi = bi + currentBidOrder.price * currentBidOrder.volume 72 | vol = vol + currentBidOrder.volume 73 | } 74 | bi / vol 75 | } else { 76 | 0.0 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/main/scala/ch/epfl/ts/traders/TransactionVwapTrader.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.traders 2 | 3 | import ch.epfl.ts.component.{Component, StartSignal} 4 | import ch.epfl.ts.data.Currency._ 5 | import ch.epfl.ts.data.{MarketAskOrder, MarketBidOrder, Transaction} 6 | 7 | import scala.concurrent.duration.DurationInt 8 | 9 | /** 10 | * Transaction VWAP trader. 11 | */ 12 | class TransactionVwapTrader(uid: Long, timeFrameMillis: Int) extends Component { 13 | import context._ 14 | 15 | case class Tick() 16 | 17 | def priceOrdering = new Ordering[Transaction] { 18 | def compare(first: Transaction, second: Transaction): Int = 19 | if (first.price > second.price) 1 else if (first.price < second.price) -1 else 0 20 | } 21 | 22 | def timeOrdering = new Ordering[Transaction] { 23 | def compare(first: Transaction, second: Transaction): Int = 24 | if (first.timestamp > second.timestamp) 1 else if (first.timestamp < second.timestamp) -1 else 0 25 | } 26 | 27 | var transactions: List[Transaction] = Nil 28 | var cumulativeTPV: Double = 0.0; 29 | var cumulativeVolume: Double = 0.0; 30 | var vwap: Double = 0.0; 31 | var tradingPrice: Double = 0.0; 32 | 33 | var oid = uid 34 | val volumeToTrade = 50 35 | 36 | def receiver = { 37 | case StartSignal() => start 38 | case t: Transaction => transactions = t :: transactions 39 | case Tick() => { 40 | computeVWAP 41 | if (tradingPrice > vwap) { 42 | // sell 43 | println("TransactionVWAPTrader: sending market ask order") 44 | send(MarketAskOrder(oid, uid, System.currentTimeMillis(), USD, USD, volumeToTrade, 0)) 45 | oid = oid + 1 46 | } else { 47 | // buy 48 | println("TransactionVWAPTrader: sending market bid order") 49 | send(MarketBidOrder(oid, uid, System.currentTimeMillis(), USD, USD, volumeToTrade, 0)) 50 | oid = oid + 1 51 | } 52 | } 53 | case _ => println("vwapTrader: unknown message received") 54 | } 55 | 56 | def computeVWAP() = { 57 | if (!transactions.isEmpty) { 58 | val typicalPrice = (transactions.max(priceOrdering)).price * (transactions.min(priceOrdering)).price * (transactions.max(timeOrdering)).price / 3 59 | var frameVolume: Double = 0 60 | transactions.map { t => frameVolume = frameVolume + t.volume } 61 | cumulativeVolume = cumulativeVolume + frameVolume 62 | val TPV = typicalPrice * frameVolume 63 | cumulativeTPV = cumulativeTPV + TPV 64 | vwap = cumulativeTPV / cumulativeVolume 65 | tradingPrice = transactions.max(timeOrdering).price 66 | transactions = Nil 67 | } 68 | } 69 | 70 | def start = { 71 | println("TransactionVwapTrader: Started") 72 | system.scheduler.schedule(0 milliseconds, timeFrameMillis milliseconds, self, Tick()) 73 | } 74 | } -------------------------------------------------------------------------------- /twitter-classifier/fList.pickle: -------------------------------------------------------------------------------- 1 | (S'apple' 2 | p0 3 | S'google' 4 | p1 5 | S'iphone' 6 | p2 7 | S'android' 8 | p3 9 | S'exchange' 10 | p4 11 | S'free' 12 | p5 13 | S'price' 14 | p6 15 | S'china' 16 | p7 17 | S'liquidation' 18 | p8 19 | S'phone' 20 | p9 21 | S'nexus' 22 | p10 23 | S'cryptocurrency' 24 | p11 25 | S'samsung' 26 | p12 27 | S'news' 28 | p13 29 | S'time' 30 | p14 31 | S'bank' 32 | p15 33 | S'mtgox' 34 | p16 35 | S'money' 36 | p17 37 | S'siri' 38 | p18 39 | S'reddit' 40 | p19 41 | S'litecoin' 42 | p20 43 | S'files' 44 | p21 45 | S'mining' 46 | p22 47 | S'thanks' 48 | p23 49 | S'sandwich' 50 | p24 51 | S'exchanges' 52 | p25 53 | S'technology' 54 | p26 55 | S'store' 56 | p27 57 | S'dogecoin' 58 | p28 59 | S'court' 60 | p29 61 | S'sell' 62 | p30 63 | S'cream' 64 | p31 65 | S'update' 66 | p32 67 | S'using' 68 | p33 69 | S'reserve' 70 | p34 71 | S'market' 72 | p35 73 | S'payment' 74 | p36 75 | S'service' 76 | p37 77 | S'wallet' 78 | p38 79 | S'icecreamsandwich' 80 | p39 81 | S'galaxy' 82 | p40 83 | S'currency' 84 | p41 85 | S'live' 86 | p42 87 | S'federal' 88 | p43 89 | S'love' 90 | p44 91 | S'blockchain' 92 | p45 93 | S'nice' 94 | p46 95 | S'spend' 96 | p47 97 | S'liquidated' 98 | p48 99 | S'bitstamp' 100 | p49 101 | S'doge' 102 | p50 103 | S'coindesk' 104 | p51 105 | S'ipad' 106 | p52 107 | S'mins' 108 | p53 109 | S'power' 110 | p54 111 | S'uprising' 112 | p55 113 | S'miner' 114 | p56 115 | S'apps' 116 | p57 117 | S'tweet' 118 | p58 119 | S'icloud' 120 | p59 121 | S'online' 122 | p60 123 | S'banks' 124 | p61 125 | S'guide' 126 | p62 127 | S'szabo' 128 | p63 129 | S'dear' 130 | p64 131 | S'million' 132 | p65 133 | S'nick' 134 | p66 135 | S'battery' 136 | p67 137 | S'worth' 138 | p68 139 | S'people' 140 | p69 141 | S'shit' 142 | p70 143 | S'digital' 144 | p71 145 | S'liquidate' 146 | p72 147 | S'profits' 148 | p73 149 | S'terminal' 150 | p74 151 | S'pair' 152 | p75 153 | S'bankruptcy' 154 | p76 155 | S'users' 156 | p77 157 | S'brasserietivoli' 158 | p78 159 | S'network' 160 | p79 161 | S'ranging' 162 | p80 163 | S'fail' 164 | p81 165 | S'life' 166 | p82 167 | S'wait' 168 | p83 169 | S'spanning' 170 | p84 171 | S'yielding' 172 | p85 173 | S'business' 174 | p86 175 | S'opps' 176 | p87 177 | S'please' 178 | p88 179 | S'fucking' 180 | p89 181 | S'doesn' 182 | p90 183 | S'customer' 184 | p91 185 | S'oudenbosch' 186 | p92 187 | S'stop' 188 | p93 189 | S'times' 190 | p94 191 | S'holland' 192 | p95 193 | S'operational' 194 | p96 195 | S'virtual' 196 | p97 197 | S'itunes' 198 | p98 199 | S'btce' 200 | p99 201 | S'awesome' 202 | p100 203 | S'screen' 204 | p101 205 | S'play' 206 | p102 207 | S'video' 208 | p103 209 | S'fuck' 210 | p104 211 | S'lost' 212 | p105 213 | S'official' 214 | p106 215 | S'projectcoin' 216 | p107 217 | S'coins' 218 | p108 219 | S'wallets' 220 | p109 221 | S'current' 222 | p110 223 | S'plans' 224 | p111 225 | S'card' 226 | p112 227 | S'future' 228 | p113 229 | S'average' 230 | p114 231 | S'cnbc' 232 | p115 233 | S'calls' 234 | p116 235 | S'reuters' 236 | p117 237 | S'gold' 238 | p118 239 | S'reportedly' 240 | p119 241 | S'send' 242 | p120 243 | S'looks' 244 | p121 245 | S'changed' 246 | p122 247 | S'galaxynexus' 248 | p123 249 | S'amazing' 250 | p124 251 | S'president' 252 | p125 253 | S'days' 254 | p126 255 | S'amazon' 256 | p127 257 | S'support' 258 | p128 259 | S'sign' 260 | p129 261 | S'thank' 262 | p130 263 | S'hasn' 264 | p131 265 | S'help' 266 | p132 267 | S'plan' 268 | p133 269 | S'services' 270 | p134 271 | S'twitter' 272 | p135 273 | S'whitepaper' 274 | p136 275 | S'rehabilitation' 276 | p137 277 | S'local' 278 | p138 279 | S'hope' 280 | p139 281 | S'feature' 282 | p140 283 | S'mine' 284 | p141 285 | S'tech' 286 | p142 287 | S'security' 288 | p143 289 | S'post' 290 | p144 291 | S'skirting' 292 | p145 293 | S'name' 294 | p146 295 | S'author' 296 | p147 297 | S'central' 298 | p148 299 | S'crackdown' 300 | p149 301 | S'issues' 302 | p150 303 | S'deals' 304 | p151 305 | S'camera' 306 | p152 307 | S'data' 308 | p153 309 | S'taking' 310 | p154 311 | S'value' 312 | p155 313 | S'hate' 314 | p156 315 | S'officially' 316 | p157 317 | S'watch' 318 | p158 319 | S'check' 320 | p159 321 | S'billion' 322 | p160 323 | S'upgrade' 324 | p161 325 | S'troubled' 326 | p162 327 | S'seriously' 328 | p163 329 | S'kocherlakota' 330 | p164 331 | S'genius' 332 | p165 333 | S'trader' 334 | p166 335 | S'media' 336 | p167 337 | S'world' 338 | p168 339 | S'hours' 340 | p169 341 | S'accept' 342 | p170 343 | S'plant' 344 | p171 345 | S'accepting' 346 | p172 347 | S'look' 348 | p173 349 | S'facebook' 350 | p174 351 | S'narayana' 352 | p175 353 | S'administration' 354 | p176 355 | S'bter' 356 | p177 357 | S'tell' 358 | p178 359 | S'jgarzik' 360 | p179 361 | S'ebay' 362 | p180 363 | S'cryptsy' 364 | p181 365 | S'website' 366 | p182 367 | S'hunt' 368 | p183 369 | S'address' 370 | p184 371 | S'guys' 372 | p185 373 | S'didn' 374 | p186 375 | S'crypto' 376 | p187 377 | S'looking' 378 | p188 379 | S'researchers' 380 | p189 381 | S'blog' 382 | p190 383 | S'coinbase' 384 | p191 385 | S'discoverbitcoin' 386 | p192 387 | S'tries' 388 | p193 389 | S'creator' 390 | p194 391 | S'available' 392 | p195 393 | S'smart' 394 | p196 395 | S'doge_report' 396 | p197 397 | S'taps' 398 | p198 399 | S'based' 400 | p199 401 | S'computer' 402 | p200 403 | S'getting' 404 | p201 405 | S'music' 406 | p202 407 | S'priceofdogecoin' 408 | p203 409 | S'linguistic' 410 | p204 411 | S'blackberry' 412 | p205 413 | S'abandons' 414 | p206 415 | S'dogec' 416 | p207 417 | S'community' 418 | p208 419 | S'prices' 420 | p209 421 | S'mycelium' 422 | p210 423 | S'sent' 424 | p211 425 | S'giving' 426 | p212 427 | S'cloud' 428 | p213 429 | S'system' 430 | p214 431 | S'bought' 432 | p215 433 | S'restore' 434 | p216 435 | S'bitcoinmoney' 436 | p217 437 | S'software' 438 | p218 439 | S'replace' 440 | p219 441 | S'excellent' 442 | p220 443 | S'india' 444 | p221 445 | S'debit' 446 | p222 447 | S'bitfinex' 448 | p223 449 | S'macbook' 450 | p224 451 | S'deal' 452 | p225 453 | S'global' 454 | p226 455 | S'tokyo' 456 | p227 457 | S'half' 458 | p228 459 | S'profit' 460 | p229 461 | S'friend' 462 | p230 463 | S'article' 464 | p231 465 | S'rebuild' 466 | p232 467 | S'shopping' 468 | p233 469 | S'running' 470 | p234 471 | S'dark' 472 | p235 473 | S'chinese' 474 | p236 475 | S'social' 476 | p237 477 | S'hour' 478 | p238 479 | S'learn' 480 | p239 481 | S'paper' 482 | p240 483 | S'retrogaming' 484 | p241 485 | S'damn' 486 | p242 487 | S'game' 488 | p243 489 | S'signal' 490 | p244 491 | S'fundamentally' 492 | p245 493 | S'payments' 494 | p246 495 | S'bitcoinagile' 496 | p247 497 | S'credit' 498 | p248 499 | S'hold' 500 | p249 501 | S'report' 502 | p250 503 | S'earn' 504 | p251 505 | S'protection' 506 | p252 507 | S'games' 508 | p253 509 | S'betting' 510 | p254 511 | S'easy' 512 | p255 513 | S'denies' 514 | p256 515 | S'sale' 516 | p257 517 | S'alerts' 518 | p258 519 | S'trouble' 520 | p259 521 | S'laundering' 522 | p260 523 | S'road' 524 | p261 525 | S'worst' 526 | p262 527 | S'revival' 528 | p263 529 | S'fees' 530 | p264 531 | S'pretty' 532 | p265 533 | S'silk' 534 | p266 535 | S'iphones' 536 | p267 537 | S'else' 538 | p268 539 | S'bitpay' 540 | p269 541 | S'bloombergnews' 542 | p270 543 | S'steve' 544 | p271 545 | S'makes' 546 | p272 547 | S'decentralized' 548 | p273 549 | S'private' 550 | p274 551 | S'doing' 552 | p275 553 | S'ipod' 554 | p276 555 | S'satoshi' 556 | p277 557 | S'documentary' 558 | p278 559 | S'minutes' 560 | p279 561 | S'book' 562 | p280 563 | S'electricity' 564 | p281 565 | S'bloombergway' 566 | p282 567 | S'recovery' 568 | p283 569 | S'japan' 570 | p284 571 | S'trading' 572 | p285 573 | S'rejects' 574 | p286 575 | S'seen' 576 | p287 577 | S'bitundo' 578 | p288 579 | S'professor' 580 | p289 581 | S'release' 582 | p290 583 | S'save' 584 | p291 585 | S'thebitcoinnews' 586 | p292 587 | S'stuck' 588 | p293 589 | S'happy' 590 | p294 591 | S'paypal' 592 | p295 593 | S'eiffel' 594 | p296 595 | S'word' 596 | p297 597 | S'goes' 598 | p298 599 | S'change' 600 | p299 601 | S'discussion' 602 | p300 603 | S'product' 604 | p301 605 | S'quits' 606 | p302 607 | S'little' 608 | p303 609 | S'recover' 610 | p304 611 | S'btcnewsbot' 612 | p305 613 | S'awards' 614 | p306 615 | S'middle' 616 | p307 617 | S'real' 618 | p308 619 | S'tower' 620 | p309 621 | S'start' 622 | p310 623 | S'gone' 624 | p311 625 | S'dollar' 626 | p312 627 | S'design' 628 | p313 629 | S'version' 630 | p314 631 | S'search' 632 | p315 633 | S'finally' 634 | p316 635 | S'company' 636 | p317 637 | S'soon' 638 | p318 639 | S'hell' 640 | p319 641 | S'actually' 642 | p320 643 | S'week' 644 | p321 645 | S'hard' 646 | p322 647 | S'terrible' 648 | p323 649 | S'feel' 650 | p324 651 | S'heads' 652 | p325 653 | S'banking' 654 | p326 655 | S'lion' 656 | p327 657 | S'missing' 658 | p328 659 | S'annual' 660 | p329 661 | S'sweet' 662 | p330 663 | S'stolen' 664 | p331 665 | S'questions' 666 | p332 667 | S'excited' 668 | p333 669 | S'contacts' 670 | p334 671 | S'line' 672 | p335 673 | S'called' 674 | p336 675 | S'products' 676 | p337 677 | S'stock' 678 | p338 679 | S'submitted' 680 | p339 681 | S'april' 682 | p340 683 | S'http' 684 | p341 685 | S'freebitcoin' 686 | p342 687 | S'transaction' 688 | p343 689 | S'poor' 690 | p344 691 | S'cashclamber' 692 | p345 693 | S'blackcoin' 694 | p346 695 | S'recognition' 696 | p347 697 | S'placed' 698 | p348 699 | S'piece' 700 | p349 701 | S'consumed' 702 | p350 703 | S'commerce' 704 | p351 705 | S'warns' 706 | p352 707 | S'voice' 708 | p353 709 | S'type' 710 | p354 711 | S'silver' 712 | p355 713 | S'content' 714 | p356 715 | S'sahm' 716 | p357 717 | S'features' 718 | p358 719 | S'rates' 720 | p359 721 | S'talking' 722 | p360 723 | S'contact' 724 | p361 725 | S'transactions' 726 | p362 727 | S'result' 728 | p363 729 | S'trust' 730 | p364 731 | S'buysellbitco' 732 | p365 733 | S'claims' 734 | p366 735 | S'entrepreneur' 736 | p367 737 | S'amsterdam' 738 | p368 739 | S'hear' 740 | p369 741 | S'smartphone' 742 | p370 743 | S'sharing' 744 | p371 745 | S'instantly' 746 | p372 747 | S'unlock' 748 | p373 749 | S'rebuilding' 750 | p374 751 | S'coinkite' 752 | p375 753 | S'cryptomoms' 754 | p376 755 | S'allegedly' 756 | p377 757 | S'gotta' 758 | p378 759 | S'collapsed' 760 | p379 761 | S'indicted' 762 | p380 763 | S'campbx' 764 | p381 765 | S'jobs' 766 | p382 767 | S'costs' 768 | p383 769 | S'disappointed' 770 | p384 771 | S'storage' 772 | p385 773 | S'month' 774 | p386 775 | S'former' 776 | p387 777 | S'presentation' 778 | p388 779 | S'ticker' 780 | p389 781 | S'list' 782 | p390 783 | S'experience' 784 | p391 785 | S'glad' 786 | p392 787 | S'libertarian' 788 | p393 789 | S'coming' 790 | p394 791 | S'trade' 792 | p395 793 | S'idea' 794 | p396 795 | S'launch' 796 | p397 797 | S'leading' 798 | p398 799 | S'quote' 800 | p399 801 | S'mobile' 802 | p400 803 | S'failed' 804 | p401 805 | S'bars' 806 | p402 807 | S'expert' 808 | p403 809 | S'simple' 810 | p404 811 | S'keys' 812 | p405 813 | S'user' 814 | p406 815 | S'homeland' 816 | p407 817 | S'lacking' 818 | p408 819 | S'sprint' 820 | p409 821 | S'steve_hanke' 822 | p410 823 | S'ronpaul' 824 | p411 825 | S'step' 826 | p412 827 | S'forward' 828 | p413 829 | S'warning' 830 | p414 831 | S'prepares' 832 | p415 833 | S'capital' 834 | p416 835 | S'altcoin' 836 | p417 837 | S'nejbye' 838 | p418 839 | S'updating' 840 | p419 841 | S'apparently' 842 | p420 843 | S'able' 844 | p421 845 | S'goldrush' 846 | p422 847 | S'fixed' 848 | p423 849 | S'icecream' 850 | p424 851 | S'email' 852 | p425 853 | S'finance' 854 | p426 855 | S'usage' 856 | p427 857 | S'gambling' 858 | p428 859 | S'avoid' 860 | p429 861 | S'wish' 862 | p430 863 | S'sorry' 864 | p431 865 | S'risk' 866 | p432 867 | S'updates' 868 | p433 869 | S'bringing' 870 | p434 871 | S'reported' 872 | p435 873 | S'bookcase' 874 | p436 875 | S'story' 876 | p437 877 | S'safe' 878 | p438 879 | S'improvements' 880 | p439 881 | S'foundation' 882 | p440 883 | S'japanese' 884 | p441 885 | S'sound' 886 | p442 887 | S'coin' 888 | p443 889 | S'annoyed' 890 | p444 891 | S'photo' 892 | p445 893 | S'conference' 894 | p446 895 | S'chrome' 896 | p447 897 | S'launches' 898 | p448 899 | S'property' 900 | p449 901 | S'portal' 902 | p450 903 | S'buyers' 904 | p451 905 | S'spread' 906 | p452 907 | S'survival' 908 | p453 909 | S'rumor' 910 | p454 911 | S'dollars' 912 | p455 913 | S'weekend' 914 | p456 915 | S'voluntarism' 916 | p457 917 | S'charge' 918 | p458 919 | S'cool' 920 | p459 921 | S'useful' 922 | p460 923 | S'btcgbp' 924 | p461 925 | S'loving' 926 | p462 927 | S'watching' 928 | p463 929 | S'huge' 930 | p464 931 | S'mail' 932 | p465 933 | S'yeah' 934 | p466 935 | S'rational' 936 | p467 937 | S'america' 938 | p468 939 | S'anti' 940 | p469 941 | S'speech' 942 | p470 943 | S'totally' 944 | p471 945 | S'announcement' 946 | p472 947 | S'server' 948 | p473 949 | S'welcome' 950 | p474 951 | S'lands' 952 | p475 953 | S'trying' 954 | p476 955 | S'pull' 956 | p477 957 | S'sales' 958 | p478 959 | S'moves' 960 | p479 961 | S'investment' 962 | p480 963 | S'markets' 964 | p481 965 | S'beautiful' 966 | p482 967 | S'playing' 968 | p483 969 | S'mthompsoncnbc' 970 | p484 971 | S'pulpspot' 972 | p485 973 | S'major' 974 | p486 975 | S'infinite' 976 | p487 977 | S'ecommerce' 978 | p488 979 | S'redditbtc' 980 | p489 981 | S'theft' 982 | p490 983 | S'close' 984 | p491 985 | S'latin' 986 | p492 987 | S'secret' 988 | p493 989 | S'integration' 990 | p494 991 | S'btcfoundation' 992 | p495 993 | S'dealbook' 994 | p496 995 | S'wanna' 996 | p497 997 | S'block' 998 | p498 999 | S'question' 1000 | p499 1001 | S'fast' 1002 | p500 1003 | S'link' 1004 | p501 1005 | S'reservation' 1006 | p502 1007 | S'results' 1008 | p503 1009 | S'meet' 1010 | p504 1011 | S'hearn' 1012 | p505 1013 | S'panic' 1014 | p506 1015 | S'including' 1016 | p507 1017 | S'nakamoto' 1018 | p508 1019 | S'crash' 1020 | p509 1021 | S'investor' 1022 | p510 1023 | S'rising' 1024 | p511 1025 | S'bradnews' 1026 | p512 1027 | S'spreadshirt' 1028 | p513 1029 | S'beam' 1030 | p514 1031 | S'mike' 1032 | p515 1033 | S'demo' 1034 | p516 1035 | S'center' 1036 | p517 1037 | S'skill' 1038 | p518 1039 | S'adoption' 1040 | p519 1041 | S'government' 1042 | p520 1043 | S'home' 1044 | p521 1045 | S'transfer' 1046 | p522 1047 | S'thepriceofbtc' 1048 | p523 1049 | S'panama' 1050 | p524 1051 | S'items' 1052 | p525 1053 | S'study' 1054 | p526 1055 | S'reports' 1056 | p527 1057 | S'secure' 1058 | p528 1059 | S'recommend' 1060 | p529 1061 | S'provide' 1062 | p530 1063 | S'answer' 1064 | p531 1065 | S'wrong' 1066 | p532 1067 | S'purchase' 1068 | p533 1069 | S'keyboard' 1070 | p534 1071 | S'fidelity' 1072 | p535 1073 | S'personal' 1074 | p536 1075 | S'writing' 1076 | p537 1077 | S'merchants' 1078 | p538 1079 | S'engineer' 1080 | p539 1081 | S'btcusd' 1082 | p540 1083 | S'forensic' 1084 | p541 1085 | S'project' 1086 | p542 1087 | S'relayed' 1088 | p543 1089 | S'saying' 1090 | p544 1091 | S'unable' 1092 | p545 1093 | S'suggested' 1094 | p546 1095 | S'balboas' 1096 | p547 1097 | S'height' 1098 | p548 1099 | S'east' 1100 | p549 1101 | S'forum' 1102 | p550 1103 | S'publicly' 1104 | p551 1105 | S'cant' 1106 | p552 1107 | S'mouse' 1108 | p553 1109 | S'hand' 1110 | p554 1111 | S'mentioning' 1112 | p555 1113 | S'featured' 1114 | p556 1115 | S'dead' 1116 | p557 1117 | S'unmissable' 1118 | p558 1119 | S'marketing' 1120 | p559 1121 | S'peer' 1122 | p560 1123 | S'months' 1124 | p561 1125 | S'accepts' 1126 | p562 1127 | S'beta' 1128 | p563 1129 | S'congratulations' 1130 | p564 1131 | S'inside' 1132 | p565 1133 | S'bitcoint' 1134 | p566 1135 | S'physical' 1136 | p567 1137 | S'dying' 1138 | p568 1139 | S'concept' 1140 | p569 1141 | S'team' 1142 | p570 1143 | S'cost' 1144 | p571 1145 | S'french' 1146 | p572 1147 | S'panamabitcoins' 1148 | p573 1149 | S'insider' 1150 | p574 1151 | S'graphic' 1152 | p575 1153 | S'growing' 1154 | p576 1155 | S'heard' 1156 | p577 1157 | S'remember' 1158 | p578 1159 | S'talk' 1160 | p579 1161 | S'blocked' 1162 | p580 1163 | S'avid' 1164 | p581 1165 | S'civil' 1166 | p582 1167 | S'offers' 1168 | p583 1169 | S'lies' 1170 | p584 1171 | S'starts' 1172 | p585 1173 | S'shoppers' 1174 | p586 1175 | S'appreciated' 1176 | p587 1177 | S'upgrading' 1178 | p588 1179 | S'note' 1180 | p589 1181 | S'multibit' 1182 | p590 1183 | S'trace' 1184 | p591 1185 | S'shop' 1186 | p592 1187 | S'jpmorgan' 1188 | p593 1189 | S'aggressive' 1190 | p594 1191 | S'crashing' 1192 | p595 1193 | S'slow' 1194 | p596 1195 | S'black' 1196 | p597 1197 | S'prime' 1198 | p598 1199 | S'stupid' 1200 | p599 1201 | S'calendar' 1202 | p600 1203 | S'markkarpeles' 1204 | p601 1205 | S'according' 1206 | p602 1207 | S'quick' 1208 | p603 1209 | S'cash' 1210 | p604 1211 | S'itouch' 1212 | p605 1213 | S'verizon' 1214 | p606 1215 | S'bitcoinanomaly' 1216 | p607 1217 | S'read' 1218 | p608 1219 | S'miracle' 1220 | p609 1221 | S'happened' 1222 | p610 1223 | S'financial' 1224 | p611 1225 | S'hire' 1226 | p612 1227 | S'daily' 1228 | p613 1229 | S'imessage' 1230 | p614 1231 | S'decide' 1232 | p615 1233 | S'rate' 1234 | p616 1235 | S'appreciating' 1236 | p617 1237 | S'hacked' 1238 | p618 1239 | S'iras' 1240 | p619 1241 | S'monetize' 1242 | p620 1243 | S'allows' 1244 | p621 1245 | S'bitcoinnews' 1246 | p622 1247 | S'flaws' 1248 | p623 1249 | S'none' 1250 | p624 1251 | S'sucks' 1252 | p625 1253 | S'breaking' 1254 | p626 1255 | S'late' 1256 | p627 1257 | S'encouraging' 1258 | p628 1259 | S'found' 1260 | p629 1261 | S'connect' 1262 | p630 1263 | S'beyond' 1264 | p631 1265 | S'offline' 1266 | p632 1267 | S'faucet' 1268 | p633 1269 | S'drain' 1270 | p634 1271 | S'syncing' 1272 | p635 1273 | S'build' 1274 | p636 1275 | S'sponsored' 1276 | p637 1277 | S'jonwaller' 1278 | p638 1279 | S'waiting' 1280 | p639 1281 | S'express' 1282 | p640 1283 | S'restart' 1284 | p641 1285 | S'intelligence' 1286 | p642 1287 | S'retweet' 1288 | p643 1289 | S'cryptocurrencies' 1290 | p644 1291 | S'defunct' 1292 | p645 1293 | S'cryptiv' 1294 | p646 1295 | S'replaced' 1296 | p647 1297 | S'survey' 1298 | p648 1299 | S'currencies' 1300 | p649 1301 | S'moment' 1302 | p650 1303 | S'chronicles' 1304 | p651 1305 | S'absolutely' 1306 | p652 1307 | S'androidbeam' 1308 | p653 1309 | S'madbitcoins' 1310 | p654 1311 | S'johnehenderson' 1312 | p655 1313 | S'vendors' 1314 | p656 1315 | S'authored' 1316 | p657 1317 | S'searching' 1318 | p658 1319 | S'properly' 1320 | p659 1321 | S'youtube' 1322 | p660 1323 | S'shanghai' 1324 | p661 1325 | S'manipulated' 1326 | p662 1327 | S'click' 1328 | p663 1329 | S'busy' 1330 | p664 1331 | S'tools' 1332 | p665 1333 | S'butler' 1334 | p666 1335 | S'account' 1336 | p667 1337 | S'announcing' 1338 | p668 1339 | S'drops' 1340 | p669 1341 | S'control' 1342 | p670 1343 | S'located' 1344 | p671 1345 | S'move' 1346 | p672 1347 | S'perfect' 1348 | p673 1349 | S'salesman' 1350 | p674 1351 | S'retail' 1352 | p675 1353 | S'quality' 1354 | p676 1355 | S'management' 1356 | p677 1357 | S'text' 1358 | p678 1359 | S'agreed' 1360 | p679 1361 | S'national' 1362 | p680 1363 | S'addition' 1364 | p681 1365 | S'anomaly' 1366 | p682 1367 | S'prospects' 1368 | p683 1369 | S'decreased' 1370 | p684 1371 | S'means' 1372 | p685 1373 | S'fucked' 1374 | p686 1375 | S'capabilities' 1376 | p687 1377 | S'page' 1378 | p688 1379 | S'growth' 1380 | p689 1381 | S'mined' 1382 | p690 1383 | S'schedule' 1384 | p691 1385 | S'journal' 1386 | p692 1387 | S'petertoddbtc' 1388 | p693 1389 | S'biggest' 1390 | p694 1391 | S'victoriavaneyk' 1392 | p695 1393 | S'rumors' 1394 | p696 1395 | S'buysellbitcoinr' 1396 | p697 1397 | S'yellow' 1398 | p698 1399 | S'woods' 1400 | p699 1401 | S'updated' 1402 | p700 1403 | S'jacob' 1404 | p701 1405 | S'solution' 1406 | p702 1407 | S'convenience' 1408 | p703 1409 | S'street' 1410 | p704 1411 | S'hide' 1412 | p705 1413 | S'supplier' 1414 | p706 1415 | S'told' 1416 | p707 1417 | S'maxcoin' 1418 | p708 1419 | S'cointalks' 1420 | p709 1421 | S'total' 1422 | p710 1423 | S'haha' 1424 | p711 1425 | S'join' 1426 | p712 1427 | S'unmasked' 1428 | p713 1429 | S'bitcoinmarket' 1430 | p714 1431 | S'office' 1432 | p715 1433 | S'mastercoin' 1434 | p716 1435 | S'easier' 1436 | p717 1437 | S'split' 1438 | p718 1439 | S'started' 1440 | p719 1441 | S'estimate' 1442 | p720 1443 | S'messages' 1444 | p721 1445 | S'heck' 1446 | p722 1447 | S'primary' 1448 | p723 1449 | S'delicious' 1450 | p724 1451 | S'listen' 1452 | p725 1453 | S'abandoning' 1454 | p726 1455 | S'silly' 1456 | p727 1457 | S'heralds' 1458 | p728 1459 | S'recommended' 1460 | p729 1461 | S'ebook' 1462 | p730 1463 | S'peers' 1464 | p731 1465 | S'definitely' 1466 | p732 1467 | S'folks' 1468 | p733 1469 | S'folder' 1470 | p734 1471 | S'bitvegas' 1472 | p735 1473 | S'volatility' 1474 | p736 1475 | S'arrests' 1476 | p737 1477 | S'hortans' 1478 | p738 1479 | S'fair' 1480 | p739 1481 | S'typography' 1482 | p740 1483 | S'literally' 1484 | p741 1485 | S'downgrade' 1486 | p742 1487 | S'improve' 1488 | p743 1489 | S'tone' 1490 | p744 1491 | S'speak' 1492 | p745 1493 | S'cont' 1494 | p746 1495 | S'played' 1496 | p747 1497 | S'patent' 1498 | p748 1499 | S'syncs' 1500 | p749 1501 | S'updown' 1502 | p750 1503 | S'tops' 1504 | p751 1505 | S'evil' 1506 | p752 1507 | S'opportunity' 1508 | p753 1509 | S'coinlab' 1510 | p754 1511 | S'investments' 1512 | p755 1513 | S'wednesday' 1514 | p756 1515 | S'petersurda' 1516 | p757 1517 | S'cybersecurity' 1518 | p758 1519 | S'bottom' 1520 | p759 1521 | S'burn' 1522 | p760 1523 | S'plus' 1524 | p761 1525 | S'wasting' 1526 | p762 1527 | S'offer' 1528 | p763 1529 | S'spont' 1530 | p764 1531 | S'shaydiddy' 1532 | p765 1533 | S'tesla' 1534 | p766 1535 | S'roll' 1536 | p767 1537 | S'push' 1538 | p768 1539 | S'managed' 1540 | p769 1541 | S'battle' 1542 | p770 1543 | S'telegraph' 1544 | p771 1545 | S'vegas' 1546 | p772 1547 | S'anonymous' 1548 | p773 1549 | S'item' 1550 | p774 1551 | S'dealing' 1552 | p775 1553 | S'mely' 1554 | p776 1555 | S'constitution' 1556 | p777 1557 | S'international' 1558 | p778 1559 | S'women' 1560 | p779 1561 | S'extra' 1562 | p780 1563 | S'losing' 1564 | p781 1565 | S'introducing' 1566 | p782 1567 | S'jacobcze' 1568 | p783 1569 | S'claim' 1570 | p784 1571 | S'feathercoin' 1572 | p785 1573 | S'bitaddress' 1574 | p786 1575 | S'secures' 1576 | p787 1577 | S'dive' 1578 | p788 1579 | S'suck' 1580 | p789 1581 | S'maybe' 1582 | p790 1583 | S'seeing' 1584 | p791 1585 | S'btctalk' 1586 | p792 1587 | S'tweets' 1588 | p793 1589 | S'january' 1590 | p794 1591 | S'domain' 1592 | p795 1593 | S'believers' 1594 | p796 1595 | S'coin_artist' 1596 | p797 1597 | S'grandma' 1598 | p798 1599 | S'care' 1600 | p799 1601 | S'message' 1602 | p800 1603 | S'cyprus' 1604 | p801 1605 | S'released' 1606 | p802 1607 | S'lmao' 1608 | p803 1609 | S'transferred' 1610 | p804 1611 | S'rehab' 1612 | p805 1613 | S'cards' 1614 | p806 1615 | S'efficient' 1616 | p807 1617 | S'animation' 1618 | p808 1619 | S'paid' 1620 | p809 1621 | S'pages' 1622 | p810 1623 | S'powerlunch' 1624 | p811 1625 | S'installed' 1626 | p812 1627 | S'coindebate' 1628 | p813 1629 | S'innovative' 1630 | p814 1631 | S'wonder' 1632 | p815 1633 | S'outside' 1634 | p816 1635 | S'umber' 1636 | p817 1637 | S'bitcoingarden' 1638 | p818 1639 | S'west' 1640 | p819 1641 | S'bitcoin_dealer' 1642 | p820 1643 | S'photos' 1644 | p821 1645 | S'textbook' 1646 | p822 1647 | S'arbitrage' 1648 | p823 1649 | S'livepurafoods' 1650 | p824 1651 | S'retweeting' 1652 | p825 1653 | S'singapore' 1654 | p826 1655 | S'urdailybitcoin' 1656 | p827 1657 | S'fixing' 1658 | p828 1659 | S'touch' 1660 | p829 1661 | S'generator' 1662 | p830 1663 | S'gonna' 1664 | p831 1665 | S'accepted' 1666 | p832 1667 | S'faster' 1668 | p833 1669 | S'bitlive' 1670 | p834 1671 | S'bitcoinmining' 1672 | p835 1673 | S'greatly' 1674 | p836 1675 | S'alleged' 1676 | p837 1677 | S'bubble' 1678 | p838 1679 | S'complete' 1680 | p839 1681 | S'buying' 1682 | p840 1683 | S'happening' 1684 | p841 1685 | S'fraud' 1686 | p842 1687 | S'agree' 1688 | p843 1689 | S'event' 1690 | p844 1691 | S'includes' 1692 | p845 1693 | S'included' 1694 | p846 1695 | S'invest' 1696 | p847 1697 | S'websites' 1698 | p848 1699 | S'chain' 1700 | p849 1701 | S'crap' 1702 | p850 1703 | S'awful' 1704 | p851 1705 | S'difference' 1706 | p852 1707 | S'sync' 1708 | p853 1709 | S'learned' 1710 | p854 1711 | S'operating' 1712 | p855 1713 | S'difficulty' 1714 | p856 1715 | S'options' 1716 | p857 1717 | S'family' 1718 | p858 1719 | S'texts' 1720 | p859 1721 | S'pulls' 1722 | p860 1723 | S'evolved' 1724 | p861 1725 | S'history' 1726 | p862 1727 | S'mainstream' 1728 | p863 1729 | S'garavaglia' 1730 | p864 1731 | S'developer' 1732 | p865 1733 | S'systems' 1734 | p866 1735 | S'bummer' 1736 | p867 1737 | S'complain' 1738 | p868 1739 | S'funding' 1740 | p869 1741 | S'research' 1742 | p870 1743 | S'friday' 1744 | p871 1745 | S'verifacation' 1746 | p872 1747 | S'jmakamba' 1748 | p873 1749 | S'thread' 1750 | p874 1751 | S'dont' 1752 | p875 1753 | S'miss' 1754 | p876 1755 | S'selling' 1756 | p877 1757 | S'gleefully' 1758 | p878 1759 | S'option' 1760 | p879 1761 | S'magical' 1762 | p880 1763 | S'scrypt' 1764 | p881 1765 | S'built' 1766 | p882 1767 | S'zero' 1768 | p883 1769 | S'steal' 1770 | p884 1771 | S'gains' 1772 | p885 1773 | S'hourly' 1774 | p886 1775 | S'eggs' 1776 | p887 1777 | S'chart' 1778 | p888 1779 | S'resolve' 1780 | p889 1781 | S'currently' 1782 | p890 1783 | S'pickup' 1784 | p891 1785 | S'probably' 1786 | p892 1787 | S'upgrades' 1788 | p893 1789 | S'cryptofund' 1790 | p894 1791 | S'crack' 1792 | p895 1793 | S'understand' 1794 | p896 1795 | S'convo' 1796 | p897 1797 | S'error' 1798 | p898 1799 | S'thecryptofund' 1800 | p899 1801 | S'ready' 1802 | p900 1803 | S'anymore' 1804 | p901 1805 | S'dnotes' 1806 | p902 1807 | S'yesterday' 1808 | p903 1809 | S'recent' 1810 | p904 1811 | S'lower' 1812 | p905 1813 | S'whores' 1814 | p906 1815 | S'kraken' 1816 | p907 1817 | S'person' 1818 | p908 1819 | S'cest' 1820 | p909 1821 | S'location' 1822 | p910 1823 | S'auditing' 1824 | p911 1825 | S'easter' 1826 | p912 1827 | S'customers' 1828 | p913 1829 | S'matters' 1830 | p914 1831 | S'motorola' 1832 | p915 1833 | S'nicaragua' 1834 | p916 1835 | S'connecting' 1836 | p917 1837 | S'at&t' 1838 | p918 1839 | S'wealth' 1840 | p919 1841 | S'impressed' 1842 | p920 1843 | S'filed' 1844 | p921 1845 | S'info' 1846 | p922 1847 | S'regulations' 1848 | p923 1849 | S'truckstop' 1850 | p924 1851 | S'helps' 1852 | p925 1853 | S'friendly' 1854 | p926 1855 | S'magic' 1856 | p927 1857 | S'button' 1858 | p928 1859 | S'reaching' 1860 | p929 1861 | S'indian' 1862 | p930 1863 | S'following' 1864 | p931 1865 | S'honor' 1866 | p932 1867 | S'stevejobs' 1868 | p933 1869 | S'names' 1870 | p934 1871 | S'plateforme' 1872 | p935 1873 | S'clients' 1874 | p936 1875 | S'pour' 1876 | p937 1877 | S'struggles' 1878 | p938 1879 | S'instead' 1880 | p939 1881 | S'owns' 1882 | p940 1883 | S'light' 1884 | p941 1885 | S'newsweek' 1886 | p942 1887 | S'linguistics' 1888 | p943 1889 | S'mention' 1890 | p944 1891 | S'university' 1892 | p945 1893 | S'platform' 1894 | p946 1895 | S'announced' 1896 | p947 1897 | S'undo' 1898 | p948 1899 | S'bitco' 1900 | p949 1901 | S'perfectly' 1902 | p950 1903 | S'haven' 1904 | p951 1905 | S'clickbank' 1906 | p952 1907 | S'nytimes' 1908 | p953 1909 | S'btcglass' 1910 | p954 1911 | S'device' 1912 | p955 1913 | S'tired' 1914 | p956 1915 | S'sells' 1916 | p957 1917 | S'bring' 1918 | p958 1919 | S'controls' 1920 | p959 1921 | S'aliprobro' 1922 | p960 1923 | S'freeze' 1924 | p961 1925 | S'desperate' 1926 | p962 1927 | S'gmail' 1928 | p963 1929 | S'interview' 1930 | p964 1931 | S'importance' 1932 | p965 1933 | S'useless' 1934 | p966 1935 | S'btceur' 1936 | p967 1937 | S'pissed' 1938 | p968 1939 | S'atlanta' 1940 | p969 1941 | S'fault' 1942 | p970 1943 | S'wild' 1944 | p971 1945 | S'site' 1946 | p972 1947 | S'juan' 1948 | p973 1949 | S'ochocinco' 1950 | p974 1951 | S'identity' 1952 | p975 1953 | S'stores' 1954 | p976 1955 | S'creditcard' 1956 | p977 1957 | S'boom' 1958 | p978 1959 | S'howto' 1960 | p979 1961 | S'newsstand' 1962 | p980 1963 | S'facial' 1964 | p981 1965 | S'string' 1966 | p982 1967 | S'dubai' 1968 | p983 1969 | S'traders' 1970 | p984 1971 | S'james' 1972 | p985 1973 | S'shed' 1974 | p986 1975 | S'drop' 1976 | p987 1977 | S'tumblr' 1978 | p988 1979 | S'host' 1980 | p989 1981 | S'chris' 1982 | p990 1983 | S'champ' 1984 | p991 1985 | S'brand' 1986 | p992 1987 | S'wise' 1988 | p993 1989 | S'flix' 1990 | p994 1991 | S'education' 1992 | p995 1993 | S'corporation' 1994 | p996 1995 | S'details' 1996 | p997 1997 | S'sick' 1998 | p998 1999 | S'album' 2000 | p999 2001 | tp1000 2002 | . -------------------------------------------------------------------------------- /twitter-classifier/sentiment.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import random 3 | import nltk 4 | import re 5 | import pickle 6 | import sys 7 | from collections import Counter 8 | 9 | 10 | class SentimentUtils: 11 | 12 | @staticmethod 13 | def getFoldPartition(dataset, kfold): 14 | for k in xrange(kfold): 15 | training = [x for i, x in enumerate(dataset) if i % kfold != k] 16 | validation = [x for i, x in enumerate(dataset) if i % kfold == k] 17 | yield training, validation 18 | 19 | @staticmethod 20 | def getStopWordList(filePath): 21 | stopWords = [] 22 | fp = open(filePath, 'r') 23 | line = fp.readline() 24 | while line: 25 | word = line.strip() 26 | stopWords.append(word) 27 | line = fp.readline() 28 | fp.close() 29 | return stopWords 30 | 31 | 32 | class TwitterSentimentClassifier: 33 | 34 | def __init__(self,load=False, 35 | classifierPath="classifier.pickle", 36 | fListPath="fList.pickle"): 37 | 38 | if load: 39 | f = open(fListPath) 40 | self.featureList = pickle.load(f) 41 | f.close() 42 | f = open(classifierPath) 43 | self.classifier = pickle.load(f) 44 | f.close() 45 | else: 46 | self.featureList = [] 47 | self.classifier = None 48 | 49 | def get_feature_vec(self,tweets,stopWords): 50 | featureVector = [] 51 | words = tweets.split() 52 | for w in words: 53 | if(w in stopWords or len(w) <= 3): 54 | continue 55 | else: 56 | featureVector.append(w.lower()) 57 | return featureVector 58 | 59 | 60 | def clean_tweet(self, tweet): 61 | # Convert the text to lower case 62 | post = tweet.lower() 63 | # Remove all urls 64 | post = re.sub('(http(s?)://)([a-zA-Z0-9\/\.])*', ' ', post) 65 | # Remove everything that is not a word 66 | post = re.sub('[^(\w&\s)]|\(|\)|\d', ' ', post) 67 | return post 68 | 69 | def extract_features(self,tweet): 70 | tweet_words = set(tweet) 71 | features = {} 72 | for word in self.featureList: 73 | features['contains(%s)' % word] = (word in tweet_words) 74 | return features 75 | 76 | def classify_tweet(self, tweet): 77 | processedTweet = self.clean_tweet(tweet) 78 | stopWords = SentimentUtils.getStopWordList('stopwords.txt') 79 | featureVec = self.get_feature_vec(processedTweet, stopWords) 80 | return self.classifier.classify(self.extract_features(featureVec)) 81 | 82 | def train(self, filePath, classificationModel, crossValidation=False): 83 | tweetsRaw = list(csv.reader(open(filePath, 'rb'), delimiter=',', quotechar='"')) 84 | random.shuffle(tweetsRaw) 85 | stopWords = SentimentUtils.getStopWordList('stopwords.txt') 86 | balanceSentiment = {'positive':0,'negative':0,'neutral':0} 87 | 88 | tweets = [] 89 | for row in tweetsRaw: 90 | sentiment = row[1] 91 | if sentiment != 'positive' and sentiment != 'negative' and sentiment != 'neutral': 92 | continue 93 | #Want equal proportion of neg/pos/neutral tweets 94 | #Since we have only 691 neg tweets, it is the upperband 95 | if balanceSentiment[sentiment] >= 691: 96 | continue 97 | balanceSentiment[sentiment] += 1 98 | tweet = row[0] 99 | processedTweet = self.clean_tweet(tweet) 100 | featureVector = self.get_feature_vec(processedTweet, stopWords) 101 | self.featureList.extend(featureVector) 102 | tweets.append((featureVector, sentiment)); 103 | 104 | wordOccurences = Counter(self.featureList).most_common(1000) 105 | self.featureList = zip(*wordOccurences)[0] 106 | fvecs = nltk.classify.util.apply_features(self.extract_features, tweets) 107 | 108 | if not crossValidation: 109 | ratioTrainTest = 0.8 110 | lenTrain = int(ratioTrainTest*len(fvecs)) 111 | train_set = fvecs[0:lenTrain] 112 | test_set = fvecs[lenTrain:] 113 | 114 | self.classifier = classificationModel.train(train_set); 115 | print 'Training complete' 116 | accuracy = nltk.classify.accuracy(self.classifier, test_set) 117 | print 'Accuracy : \t%.2f%%' % round(accuracy*100.0,2) 118 | 119 | f = open('classifier.pickle', 'wb') 120 | pickle.dump(self.classifier, f) 121 | f.close() 122 | f = open('fList.pickle', 'wb') 123 | pickle.dump(self.featureList, f) 124 | f.close() 125 | else: 126 | kfold = 5 127 | current = 0 128 | print '########################' 129 | print '%d-fold crossvalidation' % kfold 130 | print '########################' 131 | print 'Iteration\tAccuracy' 132 | for (train_set, test_set) in SentimentUtils.getFoldPartition(fvecs, kfold): 133 | current += 1 134 | self.classifier = classificationModel.train(train_set); 135 | accuracy = nltk.classify.accuracy(self.classifier, test_set) 136 | print '%d\t\t%.2f%%' % (current, round(accuracy*100.0,2)) 137 | 138 | if __name__=='__main__': 139 | classificationModel = nltk.NaiveBayesClassifier 140 | sentimentClassifier = TwitterSentimentClassifier(load=True) 141 | print sentimentClassifier.classify_tweet(sys.argv[1]) 142 | #sentimentClassifier.train('mergedWithApple.csv', classificationModel, crossValidation=False) 143 | -------------------------------------------------------------------------------- /twitter-classifier/stopwords.txt: -------------------------------------------------------------------------------- 1 | 2 | a 3 | about 4 | above 5 | across 6 | after 7 | again 8 | against 9 | all 10 | almost 11 | alone 12 | along 13 | already 14 | also 15 | although 16 | always 17 | among 18 | an 19 | and 20 | another 21 | any 22 | anybody 23 | anyone 24 | anything 25 | anywhere 26 | are 27 | area 28 | areas 29 | around 30 | as 31 | ask 32 | asked 33 | asking 34 | asks 35 | at 36 | away 37 | b 38 | back 39 | backed 40 | backing 41 | backs 42 | be 43 | became 44 | because 45 | become 46 | becomes 47 | been 48 | before 49 | began 50 | behind 51 | being 52 | beings 53 | best 54 | better 55 | between 56 | big 57 | bitcoin 58 | bitcoins 59 | both 60 | but 61 | by 62 | c 63 | came 64 | can 65 | cannot 66 | case 67 | cases 68 | certain 69 | certainly 70 | clear 71 | clearly 72 | come 73 | could 74 | d 75 | did 76 | differ 77 | different 78 | differently 79 | do 80 | does 81 | done 82 | down 83 | downed 84 | downing 85 | downs 86 | during 87 | e 88 | each 89 | early 90 | either 91 | end 92 | ended 93 | ending 94 | ends 95 | enough 96 | even 97 | evenly 98 | ever 99 | every 100 | everybody 101 | everyone 102 | everything 103 | everywhere 104 | f 105 | face 106 | faces 107 | fact 108 | facts 109 | far 110 | felt 111 | few 112 | find 113 | finds 114 | first 115 | for 116 | four 117 | from 118 | full 119 | fully 120 | further 121 | furthered 122 | furthering 123 | furthers 124 | g 125 | gave 126 | general 127 | generally 128 | get 129 | gets 130 | give 131 | given 132 | gives 133 | go 134 | going 135 | good 136 | goods 137 | got 138 | great 139 | greater 140 | greatest 141 | group 142 | grouped 143 | grouping 144 | groups 145 | h 146 | had 147 | has 148 | have 149 | having 150 | he 151 | her 152 | here 153 | herself 154 | high 155 | higher 156 | highest 157 | him 158 | himself 159 | his 160 | how 161 | however 162 | i 163 | if 164 | important 165 | in 166 | interest 167 | interested 168 | interesting 169 | interests 170 | into 171 | is 172 | it 173 | its 174 | itself 175 | j 176 | just 177 | k 178 | keep 179 | keeps 180 | kind 181 | knew 182 | know 183 | known 184 | knows 185 | l 186 | large 187 | largely 188 | last 189 | later 190 | latest 191 | least 192 | less 193 | let 194 | lets 195 | like 196 | likely 197 | long 198 | longer 199 | longest 200 | m 201 | made 202 | make 203 | making 204 | man 205 | many 206 | may 207 | me 208 | member 209 | members 210 | men 211 | might 212 | more 213 | most 214 | mostly 215 | mr 216 | mrs 217 | much 218 | must 219 | my 220 | myself 221 | n 222 | necessary 223 | need 224 | needed 225 | needing 226 | needs 227 | never 228 | new 229 | newer 230 | newest 231 | next 232 | no 233 | nobody 234 | non 235 | noone 236 | not 237 | nothing 238 | now 239 | nowhere 240 | number 241 | numbers 242 | o 243 | of 244 | off 245 | often 246 | old 247 | older 248 | oldest 249 | on 250 | once 251 | one 252 | only 253 | open 254 | opened 255 | opening 256 | opens 257 | or 258 | order 259 | ordered 260 | ordering 261 | orders 262 | other 263 | others 264 | our 265 | out 266 | over 267 | p 268 | part 269 | parted 270 | parting 271 | parts 272 | per 273 | perhaps 274 | place 275 | places 276 | point 277 | pointed 278 | pointing 279 | points 280 | possible 281 | present 282 | presented 283 | presenting 284 | presents 285 | problem 286 | problems 287 | put 288 | puts 289 | q 290 | quite 291 | r 292 | rather 293 | really 294 | right 295 | room 296 | rooms 297 | s 298 | said 299 | same 300 | saw 301 | say 302 | says 303 | second 304 | seconds 305 | see 306 | seem 307 | seemed 308 | seeming 309 | seems 310 | sees 311 | several 312 | shall 313 | she 314 | should 315 | show 316 | showed 317 | showing 318 | shows 319 | side 320 | sides 321 | since 322 | small 323 | smaller 324 | smallest 325 | so 326 | some 327 | somebody 328 | someone 329 | something 330 | somewhere 331 | state 332 | states 333 | still 334 | such 335 | sure 336 | t 337 | take 338 | taken 339 | than 340 | that 341 | the 342 | their 343 | them 344 | then 345 | there 346 | therefore 347 | these 348 | they 349 | thing 350 | things 351 | think 352 | thinks 353 | this 354 | those 355 | though 356 | thought 357 | thoughts 358 | three 359 | through 360 | thus 361 | to 362 | today 363 | together 364 | too 365 | took 366 | toward 367 | turn 368 | turned 369 | turning 370 | turns 371 | two 372 | u 373 | under 374 | until 375 | up 376 | upon 377 | us 378 | use 379 | used 380 | uses 381 | v 382 | very 383 | w 384 | want 385 | wanted 386 | wanting 387 | wants 388 | was 389 | way 390 | ways 391 | we 392 | well 393 | wells 394 | went 395 | were 396 | what 397 | when 398 | where 399 | whether 400 | which 401 | while 402 | who 403 | whole 404 | whose 405 | why 406 | will 407 | with 408 | within 409 | without 410 | work 411 | worked 412 | working 413 | works 414 | would 415 | x 416 | y 417 | year 418 | years 419 | yet 420 | you 421 | young 422 | younger 423 | youngest 424 | your 425 | yours 426 | z 427 | -------------------------------------------------------------------------------- /wiki/figures/arbitrage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kebetsi/TradingSimulation/727142f48725f4c1d4404e07b8461bed424daf6e/wiki/figures/arbitrage.png -------------------------------------------------------------------------------- /wiki/figures/disp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kebetsi/TradingSimulation/727142f48725f4c1d4404e07b8461bed424daf6e/wiki/figures/disp.png -------------------------------------------------------------------------------- /wiki/figures/live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kebetsi/TradingSimulation/727142f48725f4c1d4404e07b8461bed424daf6e/wiki/figures/live.png -------------------------------------------------------------------------------- /wiki/figures/rep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kebetsi/TradingSimulation/727142f48725f4c1d4404e07b8461bed424daf6e/wiki/figures/rep.png -------------------------------------------------------------------------------- /wiki/figures/replay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kebetsi/TradingSimulation/727142f48725f4c1d4404e07b8461bed424daf6e/wiki/figures/replay.png -------------------------------------------------------------------------------- /wiki/figures/sobi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kebetsi/TradingSimulation/727142f48725f4c1d4404e07b8461bed424daf6e/wiki/figures/sobi.png -------------------------------------------------------------------------------- /wiki/figures/useCaseReplayTraders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kebetsi/TradingSimulation/727142f48725f4c1d4404e07b8461bed424daf6e/wiki/figures/useCaseReplayTraders.png -------------------------------------------------------------------------------- /wiki/figures/vwap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kebetsi/TradingSimulation/727142f48725f4c1d4404e07b8461bed424daf6e/wiki/figures/vwap.png --------------------------------------------------------------------------------