├── .gitignore ├── .travis.yml ├── README.md ├── activator-sbt-echo-akka-shim.sbt ├── benchmark-c ├── .gitignore └── readPerf.c ├── build.sbt ├── data └── .gitkeep ├── docs ├── code │ └── simple.scala ├── content │ ├── catalog.tex │ ├── components.tex │ ├── cover.tex │ ├── distributed-setup.tex │ ├── examples.tex │ ├── introduction.tex │ ├── preface.tex │ └── simulation.tex ├── guide.tex ├── img │ ├── epfl.png │ └── examples │ │ ├── btce.png │ │ ├── evaluation.png │ │ ├── forex-live.png │ │ ├── forex-replay.png │ │ ├── full-simulation.png │ │ ├── simulation-mad-buys-mm-sells.png │ │ ├── simulation-mad-sells-mm-buys.png │ │ └── simulation-sell-and-buy.png └── notes │ ├── hist-data-fetcher-performance.md │ └── run-on-cluster.md ├── frontend ├── .gitignore ├── LICENSE ├── app │ ├── actors │ │ ├── GlobalOhlc.scala │ │ ├── MessageToJson.scala │ │ └── TraderParameters.scala │ ├── assets │ │ ├── javascripts │ │ │ ├── app.js │ │ │ ├── controllers │ │ │ │ ├── evaluationReportController.js │ │ │ │ ├── lineGraphController.js │ │ │ │ ├── ohlcGraphController.js │ │ │ │ ├── traderController.js │ │ │ │ └── transactionController.js │ │ │ ├── filters │ │ │ │ └── percentageFilter.js │ │ │ ├── highstockTheme.js │ │ │ └── services │ │ │ │ ├── alertService.js │ │ │ │ ├── ohlcGraphService.js │ │ │ │ ├── ohlcGraphTransactionFlagsService.js │ │ │ │ ├── traderService.js │ │ │ │ └── transactionService.js │ │ └── stylesheets │ │ │ └── main.css │ ├── controllers │ │ └── Application.scala │ ├── utils │ │ ├── DoubleSerializer.scala │ │ ├── EnumJsonUtils.scala │ │ ├── JsonFormatters.scala │ │ ├── MapSerializer.scala │ │ └── TradingSimulationActorSelection.scala │ └── views │ │ ├── index.scala.html │ │ └── main.scala.html ├── conf │ ├── application.conf │ └── routes ├── public │ └── images │ │ └── favicon.png └── test │ └── ApplicationSpec.scala ├── project ├── activator-sbt-echo-shim.sbt ├── activator-sbt-eclipse-shim.sbt ├── activator-sbt-idea-shim.sbt ├── dependencies.scala └── plugins.sbt ├── scripts ├── get-data.sh ├── vm-prepare.sh └── workerctl.sh ├── ts ├── .gitignore ├── data └── src │ ├── main │ ├── resources │ │ ├── .gitkeep │ │ └── application.conf │ └── scala │ │ └── ch │ │ └── epfl │ │ └── ts │ │ ├── benchmark │ │ ├── marketSimulator │ │ │ ├── BenchmarkMarketRules.scala │ │ │ ├── BenchmarkOrderBookMarketSimulator.scala │ │ │ ├── BenchmarkTrader.scala │ │ │ ├── MarketSimulatorBenchmark.scala │ │ │ ├── OrderFeeder.scala │ │ │ ├── TimeCounter.scala │ │ │ └── TraderReactionBenchmark.scala │ │ └── scala │ │ │ ├── BenchmarkCommands.scala │ │ │ ├── CommunicationBenchmark.scala │ │ │ ├── FetchActors.scala │ │ │ ├── FileProcessBenchActor.scala │ │ │ ├── LoadFromFileBenchmark.scala │ │ │ └── PullFetchBenchmarkImpl.scala │ │ ├── brokers │ │ └── StandardBroker.scala │ │ ├── component │ │ ├── Component.scala │ │ ├── ComponentBuilder.scala │ │ ├── fetch │ │ │ ├── BitfinexFetcher.scala │ │ │ ├── BitstampFetcher.scala │ │ │ ├── BtceFetcher.scala │ │ │ ├── CSVFetcher.scala │ │ │ ├── Fetch.scala │ │ │ ├── HistDataCSVFetcher.scala │ │ │ ├── MarketNames.scala │ │ │ ├── TrueFxFetcher.scala │ │ │ └── TwitterFetchComponent.scala │ │ ├── persist │ │ │ ├── DummyPersistor.scala │ │ │ ├── OrderPersistor.scala │ │ │ ├── Persistance.scala │ │ │ ├── QuotePersistor.scala │ │ │ ├── TransactionPersistor.scala │ │ │ └── TweetPersistor.scala │ │ ├── replay │ │ │ └── Replay.scala │ │ └── utils │ │ │ ├── BackLoop.scala │ │ │ ├── BatcherComponent.scala │ │ │ ├── ParentActor.scala │ │ │ ├── Printer.scala │ │ │ ├── Reaper.scala │ │ │ └── Timekeeper.scala │ │ ├── config │ │ └── FundDistribution.scala │ │ ├── data │ │ ├── Currency.scala │ │ ├── Messages.scala │ │ └── StrategyParameters.scala │ │ ├── engine │ │ ├── Actors.scala │ │ ├── Controller.scala │ │ ├── ForexMarketRules.scala │ │ ├── HybridMarketSimulator.scala │ │ ├── MarketFXSimulator.scala │ │ ├── MarketRules.scala │ │ ├── MarketSimulator.scala │ │ ├── Messages.scala │ │ ├── OrderBook.scala │ │ ├── OrderBookMarketSimulator.scala │ │ ├── WalletManager.scala │ │ └── rules │ │ │ ├── FxMarketRulesWrapper.scala │ │ │ ├── MarketRulesWrapper.scala │ │ │ └── SimulationMarketRulesWrapper.scala │ │ ├── evaluation │ │ ├── EvaluationRunner.scala │ │ └── Evaluator.scala │ │ ├── example │ │ ├── AbstractExample.scala │ │ ├── AbstractOptimizationExample.scala │ │ ├── AbstractTraderShowcaseExample.scala │ │ ├── BTCArbitrage.scala │ │ ├── BrokerExamples.scala │ │ ├── BtceTransactionFlowTesterWithStorage.scala │ │ ├── DemoExample.scala │ │ ├── FullMarketSimulation.scala │ │ ├── HistDataFetcherExample.scala │ │ ├── MovingAverageTraderExample.scala │ │ ├── RangeTraderExample.scala │ │ ├── ReplayFlowTesterFromStorage.scala │ │ ├── RsiTraderExample.scala │ │ └── TwitterFlowTesterWithStorage.scala │ │ ├── indicators │ │ ├── EmaIndicator.scala │ │ ├── MaIndicator.scala │ │ ├── OhlcIndicator.scala │ │ ├── RangeIndicator.scala │ │ ├── RsiIndicator.scala │ │ └── SmaIndicator.scala │ │ ├── optimization │ │ ├── ForexStrategyFactory.scala │ │ ├── HostSystem.scala │ │ ├── OptimizationSupervisor.scala │ │ ├── RemotingMasterRunner.scala │ │ ├── RemotingWorkerRunner.scala │ │ ├── StrategyFactory.scala │ │ └── StrategyOptimizer.scala │ │ └── traders │ │ ├── Arbitrageur.scala │ │ ├── MadTrader.scala │ │ ├── MovingAverageTrader.scala │ │ ├── RangeTrader.scala │ │ ├── RsiTrader.scala │ │ ├── SimpleTraderWithBroker.scala │ │ ├── SobiTrader.scala │ │ ├── Trader.scala │ │ └── TransactionVwapTrader.scala │ └── test │ └── scala │ └── ch │ └── epfl │ └── ts │ └── test │ ├── HelloTest.scala │ ├── TestHelpers.scala │ ├── component │ ├── BrokerInteractionTest.scala │ ├── ComponentBuilder.scala │ ├── Timekeeper.scala │ ├── evaluation │ │ └── EvaluationReportTestSuite.scala │ └── fetch │ │ └── TrueFxFetcher.scala │ ├── config │ └── FundDistributionTest.scala │ ├── data │ ├── CurrencyTest.scala │ └── StrategyParameters.scala │ ├── optimization │ └── StrategyOptimizer.scala │ └── traders │ ├── MadTraderTest.scala │ ├── MovingAverageShortTest.scala │ ├── MovingAverageTraderTest.scala │ ├── RangeTraderTest.scala │ ├── RsiIndicatorTest.scala │ └── Trader.scala ├── twitter-classifier ├── classifier.pickle ├── fList.pickle ├── sentiment.py └── stopwords.txt └── wiki └── figures ├── btce.png ├── evaluation.png ├── forex-live.png ├── forex-replay.png └── full-simulation.png /.gitignore: -------------------------------------------------------------------------------- 1 | # ____ _ _ 2 | # | _ \ _ __ ___ (_) ___ ___| |_ 3 | # | |_) | '__/ _ \| |/ _ \/ __| __| 4 | # | __/| | | (_) | | __/ (__| |_ 5 | # |_| |_| \___// |\___|\___|\__| 6 | # |__/ 7 | 8 | ts/data/* 9 | data/* 10 | !data/.gitkeep 11 | ts/src/main/resources/application.conf 12 | ts/src/main/resources/names.txt 13 | ts/src/main/resources/names-shuffled.txt 14 | 15 | 16 | # ____ 17 | # / ___| ___ _ _ _ __ ___ ___ 18 | # \___ \ / _ \| | | | '__/ __/ _ \ 19 | # ___) | (_) | |_| | | | (_| __/ 20 | # |____/ \___/ \__,_|_| \___\___| 21 | 22 | # Java specific 23 | *.class 24 | *.log 25 | 26 | # sbt specific 27 | .cache/ 28 | .history/ 29 | .lib/ 30 | dist/* 31 | target/ 32 | lib_managed/ 33 | src_managed/ 34 | project/target 35 | project/boot/ 36 | project/plugins/project/ 37 | 38 | # Scala-IDE specific 39 | .scala_dependencies 40 | .worksheet 41 | **/*.sc 42 | 43 | # Eclipse specific 44 | .cache 45 | .classpath 46 | .project 47 | .settings 48 | .metadata 49 | 50 | # IntelliJ specific 51 | TradingSimulation.iml 52 | .idea/ 53 | 54 | # OS specific 55 | .DS_Store 56 | 57 | 58 | # _ _____ __ __ 59 | # | | __ |_ _|__\ \/ / 60 | # | | / _` || |/ _ \\ / 61 | # | |__| (_| || | __// \ 62 | # |_____\__,_||_|\___/_/\_\ 63 | 64 | 65 | ## Core latex/pdflatex auxiliary files: 66 | *.aux 67 | *.lof 68 | *.log 69 | *.lot 70 | *.fls 71 | *.out 72 | *.toc 73 | 74 | ## Intermediate documents: 75 | *.dvi 76 | # these rules might exclude image files for figures etc. 77 | # *.ps 78 | # *.eps 79 | *.pdf 80 | 81 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 82 | *.bbl 83 | *.bcf 84 | *.blg 85 | *-blx.aux 86 | *-blx.bib 87 | *.run.xml 88 | 89 | ## Build tool auxiliary files: 90 | *.fdb_latexmk 91 | *.synctex.gz 92 | *.synctex.gz(busy) 93 | *.pdfsync 94 | 95 | ## Auxiliary and intermediate files from other packages: 96 | 97 | # algorithms 98 | *.alg 99 | *.loa 100 | 101 | # amsthm 102 | *.thm 103 | 104 | # beamer 105 | *.nav 106 | *.snm 107 | *.vrb 108 | 109 | #(e)ledmac/(e)ledpar 110 | *.end 111 | *.[1-9] 112 | *.[1-9][0-9] 113 | *.[1-9][0-9][0-9] 114 | *.[1-9]R 115 | *.[1-9][0-9]R 116 | *.[1-9][0-9][0-9]R 117 | 118 | # glossaries 119 | *.acn 120 | *.acr 121 | *.glg 122 | *.glo 123 | *.gls 124 | 125 | # hyperref 126 | *.brf 127 | 128 | # listings 129 | *.lol 130 | 131 | # makeidx 132 | *.idx 133 | *.ilg 134 | *.ind 135 | *.ist 136 | 137 | # minitoc 138 | *.maf 139 | *.mtc 140 | *.mtc0 141 | 142 | # minted 143 | *.pyg 144 | 145 | # nomencl 146 | *.nlo 147 | 148 | # sagetex 149 | *.sagetex.sage 150 | *.sagetex.py 151 | *.sagetex.scmd 152 | 153 | # sympy 154 | *.sout 155 | *.sympy 156 | sympy-plots-for-*.tex/ 157 | 158 | # todonotes 159 | *.tdo 160 | 161 | # xindy 162 | *.xdy 163 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | install: true 3 | language: scala 4 | scala: 5 | - 2.11.2 6 | cache: 7 | directories: 8 | - $HOME/.ivy2/cache 9 | - $HOME/.sbt/boot/ 10 | script: 11 | - sbt ++$TRAVIS_SCALA_VERSION test 12 | # Tricks to avoid unnecessary cache updates 13 | - find $HOME/.sbt -name "*.lock" | xargs rm 14 | - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm 15 | notifications: 16 | slack: 17 | secure: h0QcxCkN1aYhEgEvrLi9eKM2OdVOSfnPkQfwCXspV5fDYyEh96rDDJ4kBWoW6M0Fp4Uq7TZY6tGzUAdbnTU/Yg2+T0+H4+lDsfPhPTmmeZkAaByXVN2gOrNft06yHvte7WaDfON1eIUweNvuP8RGSgzyMpgAgIfhu5L43an7m/c= 18 | email: false 19 | -------------------------------------------------------------------------------- /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 "project ts" run` displays a list of examples that can be run in the TradingSimulater backend 17 | 18 | - `sbt "project frontend" run` starts the frontend. The server is listening for HTTP on http://localhost:9000/ 19 | 20 | ### License 21 | 22 | Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 -------------------------------------------------------------------------------- /activator-sbt-echo-akka-shim.sbt: -------------------------------------------------------------------------------- 1 | echoSettings -------------------------------------------------------------------------------- /benchmark-c/.gitignore: -------------------------------------------------------------------------------- 1 | bench 2 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.Keys._ 2 | import sbt._ 3 | 4 | EclipseKeys.skipParents in ThisBuild := false 5 | 6 | name := "TradingSimProject" 7 | 8 | version in ThisBuild := "0.1" 9 | 10 | scalaVersion in ThisBuild := "2.11.6" 11 | 12 | autoScalaLibrary in ThisBuild := true 13 | 14 | ivyScala := ivyScala.value map { _.copy(overrideScalaVersion = true) } 15 | 16 | scalacOptions in ThisBuild ++= Seq("-deprecation", "-feature") 17 | 18 | resolvers in ThisBuild += "Akka Snapshot Repository" at "http://repo.akka.io/snapshots/" 19 | 20 | lazy val frontend = (project in file("frontend")) 21 | .enablePlugins(PlayScala) 22 | .settings( 23 | name := "frontend", 24 | libraryDependencies ++= (Dependencies.frontend ++ Seq(filters, cache)), 25 | pipelineStages := Seq(rjs, digest, gzip) 26 | ).dependsOn(ts).aggregate(ts) 27 | 28 | lazy val ts = (project in file("ts")) 29 | .settings( 30 | name := "ts", 31 | libraryDependencies ++= Dependencies.ts, 32 | // Add res directory to runtime classpath 33 | unmanagedClasspath in Runtime <+= (baseDirectory) map { bd => Attributed.blank(bd / "src/main/resources") } 34 | ) 35 | 36 | // Some of our tests require sequential execution 37 | parallelExecution in Test in ts := false 38 | 39 | parallelExecution in Test in frontend := false 40 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/data/.gitkeep -------------------------------------------------------------------------------- /docs/code/simple.scala: -------------------------------------------------------------------------------- 1 | implicit val builder = new ComponentBuilder("ReplayFlowTesterSystem") 2 | 3 | // Initialize the Interface to DB 4 | val btceXactPersit = new TransactionPersistor("btce-transaction-db") 5 | btceXactPersit.init() 6 | 7 | // Configuration object for Replay 8 | val replayConf = new ReplayConfig(1418737788400L, 0.01) 9 | 10 | // Create Components 11 | val printer = builder.createRef(Props(classOf[Printer], "printer"), "printer") 12 | val replayer = builder.createRef(Props(classOf[Replay[Transaction]], btceXactPersit, replayConf, implicitly[ClassTag[Transaction]]), "replayer") 13 | 14 | // Create the connections 15 | replayer->(printer, classOf[Transaction]) 16 | 17 | // Start the system 18 | builder.start 19 | -------------------------------------------------------------------------------- /docs/content/cover.tex: -------------------------------------------------------------------------------- 1 | %!TEX root = ../guide.tex 2 | 3 | \begin{titlepage} 4 | 5 | \begin{center} 6 | 7 | %% \vspace{20cm} 8 | \vspace*{3\baselineskip} 9 | % Title 10 | {\Large \bfseries Trading Simulation framework: Programming Guide\\[0.4cm] } 11 | 12 | % Authors and supervisor 13 | \noindent 14 | The Trading Simulation Developer Team \\[4cm] 15 | 16 | \begin{framed} 17 | Project of Big Data Course \\ 18 | Professor: Christoph Koch \\ 19 | Supervisor: Mohammad Dashti 20 | \end{framed} 21 | 22 | \noindent 23 | Lausanne, Academic Year 2014 - 2015 \\[0.3cm] 24 | 25 | % Upper part of the page. The '~' is needed because \\ 26 | % only works if a paragraph has started. 27 | \includegraphics[width=0.2\textwidth]{img/epfl}~\\[1cm] 28 | 29 | \vfill 30 | 31 | % Bottom of the page 32 | {\large \today} 33 | 34 | \end{center} 35 | 36 | \end{titlepage} 37 | -------------------------------------------------------------------------------- /docs/content/distributed-setup.tex: -------------------------------------------------------------------------------- 1 | %!TEX root = ../guide.tex 2 | 3 | \section{Distributed setup} 4 | 5 | One main characteristic of the Akka actor model is that each actor is solely responsible for maintaining its state, which is completely insulated from the external world. This way, we protect ourselves from synchronization problems and are allowed to run highly parallelized code.\\ 6 | Moreover, Akka has built-in \textit{remoting}\footnote{\url{http://doc.akka.io/docs/akka/snapshot/scala/remoting.html}} capabilities: actors can be instantiated remotely while maintaining referential transparency. Therefore, with minimal modifications to our initial codebase, we were able to run a distributed deployment of our system.\\ 7 | 8 | We demonstrate this feature using eight virtual machines (VM) from the \textit{Microsoft Azure}\footnote{\url{https://azure.microsoft.com/}} cloud. Each VM has very little requirements, as it only needs to be accessible from the exterior world through the network and be able to run the Java Runtime Environment. The short script \texttt{vm-prepare.sh} contains all that is needed to make a mint Ubuntu system ready to run our code. 9 | 10 | Finally, \texttt{workerctl.sh} provides useful commands for worker control, such as start / stop / restart. Executing \texttt{workerctl.sh start} will start a host actor system which will wait to receive commands from a master system. The latter can be started simply from your own machine, e.g. by running \texttt{ch.epfl.ts.optimization.RemotingMasterRunner}. Components are created and watched from the master onto the workers, which is responsible in this case for tracking the performance of each Trader instance and picking the best one at the end of the run. 11 | -------------------------------------------------------------------------------- /docs/content/preface.tex: -------------------------------------------------------------------------------- 1 | %!TEX root = ../guide.tex 2 | 3 | \subsection*{Preface} 4 | 5 | The targeted audience of this documentation is developers who want to use the TradingSimulation framework to experiment with algorithmic trading or contribute to the TradingSimulation project. The readers should have some background in algorithmic trading or have access to expertises of the field. The reader should also be familiar with programming in Scala. 6 | -------------------------------------------------------------------------------- /docs/guide.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} % default square logo 2 | 3 | \usepackage[margin=2.8cm]{geometry} 4 | \usepackage{setspace} 5 | \usepackage{mathptmx} % Times font 6 | \usepackage[utf8]{inputenc} 7 | 8 | % handles ~ in url 9 | \usepackage{hyperref} 10 | 11 | % center captions 12 | \usepackage[justification=centering]{caption} 13 | 14 | % code listing 15 | \usepackage{listings} 16 | \usepackage{xcolor} 17 | 18 | % packages for drawing 19 | \usepackage{tikz} 20 | \usetikzlibrary{graphs} % create graphs 21 | 22 | % for warning box 23 | \usepackage{pifont,mdframed} 24 | \usepackage{framed} 25 | 26 | % algorithms 27 | \usepackage[]{algorithm} 28 | \usepackage[]{algpseudocode} 29 | 30 | % tables 31 | \usepackage{tabularx} % full-width 32 | 33 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 34 | 35 | \onehalfspacing 36 | 37 | %% styles for filter graph 38 | \tikzstyle{filter} = [rectangle, draw, font=\small, text centered, rounded corners, minimum height=2em, node distance=3cm, minimum width=4em] 39 | 40 | %% set code styles 41 | 42 | \definecolor{dkgreen}{rgb}{0,0.6,0} 43 | \definecolor{gray}{rgb}{0.5,0.5,0.5} 44 | \definecolor{mauve}{rgb}{0.58,0,0.82} 45 | 46 | \lstset{ 47 | frame=tb, 48 | language=scala, 49 | aboveskip=3mm, 50 | belowskip=3mm, 51 | showstringspaces=false, 52 | columns=flexible, 53 | basicstyle={\small\ttfamily}, 54 | numbers=none, 55 | numberstyle=\tiny\color{gray}, 56 | keywordstyle=\color{blue}, 57 | commentstyle=\color{dkgreen}, 58 | stringstyle=\color{mauve}, 59 | breaklines=true, 60 | breakatwhitespace=true, 61 | tabsize=3, 62 | } 63 | 64 | %% warning box 65 | 66 | \newenvironment{warning} 67 | {\par\begin{mdframed}[linewidth=1pt,linecolor=red!20,backgroundcolor=yellow!40]% 68 | \begin{list}{}{\leftmargin=1cm 69 | \labelwidth=\leftmargin}\item[\Large\ding{43}]} 70 | {\end{list}\end{mdframed}\par} 71 | 72 | \newenvironment{info} 73 | {\par\begin{mdframed}[linewidth=1pt,backgroundcolor=blue!20!white]% 74 | \begin{list}{}{\leftmargin=1cm 75 | \labelwidth=\leftmargin}\item[\Large\ding{46}]} 76 | {\end{list}\end{mdframed}\par} 77 | 78 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 | \begin{document} 80 | 81 | \input{content/cover} 82 | \include{content/preface} 83 | 84 | %% \pagenumbering{Roman} 85 | \tableofcontents 86 | 87 | %% \pagenumbering{arabic} 88 | 89 | \include{content/introduction} 90 | \include{content/components} 91 | \include{content/catalog} 92 | \include{content/examples} 93 | \include{content/simulation} 94 | \include{content/distributed-setup} 95 | 96 | \end{document} 97 | -------------------------------------------------------------------------------- /docs/img/epfl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/docs/img/epfl.png -------------------------------------------------------------------------------- /docs/img/examples/btce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/docs/img/examples/btce.png -------------------------------------------------------------------------------- /docs/img/examples/evaluation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/docs/img/examples/evaluation.png -------------------------------------------------------------------------------- /docs/img/examples/forex-live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/docs/img/examples/forex-live.png -------------------------------------------------------------------------------- /docs/img/examples/forex-replay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/docs/img/examples/forex-replay.png -------------------------------------------------------------------------------- /docs/img/examples/full-simulation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/docs/img/examples/full-simulation.png -------------------------------------------------------------------------------- /docs/img/examples/simulation-mad-buys-mm-sells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/docs/img/examples/simulation-mad-buys-mm-sells.png -------------------------------------------------------------------------------- /docs/img/examples/simulation-mad-sells-mm-buys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/docs/img/examples/simulation-mad-sells-mm-buys.png -------------------------------------------------------------------------------- /docs/img/examples/simulation-sell-and-buy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/docs/img/examples/simulation-sell-and-buy.png -------------------------------------------------------------------------------- /docs/notes/hist-data-fetcher-performance.md: -------------------------------------------------------------------------------- 1 | On the performance of the Historical Data CSV Fetcher 2 | ----------------------------------------------------- 3 | The size of our data for one currency pair (e.g. EURCHF) is about 75M records or 3.2GB (1 record is about 43 Bytes when saved in an SQLite DB). It takes an average of 10us per record to put it into the DB on my system, i.e. about 750s = 12.5min for one currency pair. Let's make that 15min to account for uncertainty in estimations. That's about 4.3 MB/s of reading, parsing and writing to the DB. So for four currency pairs it will take about 1h to load them into a persistor with the current system. 4 | 5 | When using the fetcher as an actor and reading data directly from CSV for show or simulation purposes, it will start sending the first quotes as soon as they have been read. The fetcher keeps reading from disk lazily (i.e. only when needed) and only loads into memory what's necessary. 6 | 7 | To optimize performance in a distributed system one should probably use a DB as basis (not CSV, which needs to be parsed first). Also, one could distribute DBs for different currency pairs on different nodes, depending on the configuration and the network traffic this could be beneficial. 8 | -------------------------------------------------------------------------------- /docs/notes/run-on-cluster.md: -------------------------------------------------------------------------------- 1 | ## Running a trading simulator deployment in the "cloud" 2 | 3 | ### Basic concepts 4 | There is a master and several slave nodes. The master runs (_RemotingHostExample_) and the slaves must run (_RemotingActorExample_) which make them listen on TCP port 3333. The master can then sends jobs to the slaves. 5 | 6 | ### Setting up the slaves 7 | The setup procedure for each VM consisted of: 8 | 9 | 1. Aquire your VM with SSH and full access 10 | 2. Run a setup script something like https://github.com/merlinND/TradingSimulation/blob/run-on-cluster/scripts/vm-prepare.sh . 11 | 3. Run `screen` and press enter to get a shell 12 | 4. Run the project as you would normally: `sbt "project ts" run` and select the _RemotingActorExample_ class in the component selection. 13 | 5. The VM is now ready to communicate with _RemotingHostExample_ which you can run from your machine or from another node which you dedicate to be the master. 14 | 6. Press _CTRL+A+D_ to detach from this screen (you can reattach by running `screen -D -R`(connects to the first best session) or `screen -r 34863.pts-0.ts-1` (if you have noted down the identifier of your session after having detached before). 15 | 7. You can now disconnect form this machine. The process will keep running. 16 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | project/project 3 | project/target 4 | target 5 | tmp 6 | .history 7 | dist 8 | /.idea 9 | /*.iml 10 | /out 11 | /.idea_modules 12 | /.classpath 13 | /.project 14 | /RUNNING_PID 15 | /.settings 16 | /bin/ 17 | -------------------------------------------------------------------------------- /frontend/LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the Apache 2 license, quoted below. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with 4 | the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 5 | 6 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 7 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 8 | language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /frontend/app/actors/GlobalOhlc.scala: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import scala.language.postfixOps 4 | import akka.actor.Props 5 | import akka.actor.Actor 6 | import akka.actor.ActorRef 7 | import scala.concurrent.duration._ 8 | import akka.actor.ActorSystem 9 | import akka.actor.ActorPath 10 | import play.libs.Akka 11 | import ch.epfl.ts.data.OHLC 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | import ch.epfl.ts.component.ComponentRegistration 14 | import scala.reflect.ClassTag 15 | import net.liftweb.json._ 16 | import net.liftweb.json.Serialization.write 17 | import com.typesafe.config.ConfigFactory 18 | import ch.epfl.ts.data.Quote 19 | import ch.epfl.ts.indicators.OhlcIndicator 20 | import ch.epfl.ts.data.Currency 21 | import scala.collection.mutable.Set 22 | import ch.epfl.ts.data.TimeParameter 23 | import ch.epfl.ts.data.OHLC 24 | import scala.collection.mutable.HashMap 25 | import utils.TradingSimulationActorSelection 26 | 27 | /** 28 | * Computes OHLC for each currency based on the quote data fetched in the TradingSimulation backend 29 | * 30 | * Since each trader in the backend instantiates its own indicators, we simply compute a global 31 | * OHLC to display on a graph in the frontend 32 | * Starts a child actor for each symbol and converts the result to JSON which included the symbol 33 | */ 34 | class GlobalOhlc(out: ActorRef) extends Actor { 35 | implicit val formats = DefaultFormats 36 | 37 | type Symbol = (Currency, Currency) 38 | var workers = HashMap[Symbol, ActorRef]() 39 | val ohlcPeriod = 1 hour 40 | 41 | val fetchers = new TradingSimulationActorSelection(context, 42 | ConfigFactory.load().getString("akka.backend.fetchersActorSelection")).get 43 | 44 | fetchers ! ComponentRegistration(self, classOf[Quote], "frontendQuote") 45 | 46 | def receive() = { 47 | case q: Quote => 48 | val symbol: Symbol = (q.whatC, q.withC) 49 | val worker = workers.getOrElseUpdate(symbol, 50 | context.actorOf(Props(classOf[OhlcIndicator], q.marketId, symbol, ohlcPeriod))) 51 | worker ! q 52 | 53 | case ohlc: OHLC => 54 | workers.find(_._2 == sender) match { 55 | case Some((symbol: Symbol, _)) => 56 | out ! write(SymbolOhlc(symbol._1, symbol._2, ohlc)) 57 | case _ => 58 | } 59 | 60 | case _ => 61 | } 62 | 63 | } 64 | 65 | case class SymbolOhlc(whatC: Currency, withC: Currency, ohlc: OHLC) 66 | -------------------------------------------------------------------------------- /frontend/app/actors/MessageToJson.scala: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import akka.actor.Props 4 | import akka.actor.Actor 5 | import akka.actor.ActorRef 6 | import scala.concurrent.duration._ 7 | import akka.actor.ActorSystem 8 | import akka.actor.ActorPath 9 | import play.libs.Akka 10 | import ch.epfl.ts.data.OHLC 11 | import scala.concurrent.ExecutionContext.Implicits.global 12 | import ch.epfl.ts.component.ComponentRegistration 13 | import scala.reflect.ClassTag 14 | import net.liftweb.json._ 15 | import net.liftweb.json.Serialization.write 16 | import com.typesafe.config.ConfigFactory 17 | import utils.TradingSimulationActorSelection 18 | import utils.MapSerializer 19 | import utils.DoubleSerializer 20 | 21 | /** 22 | * Receives Messages of a given Class Tag from the Trading Simulation backend (ts) 23 | * and converts them to JSON in order to be passed to the client through a web socket 24 | * 25 | * Note: we are using lift-json since there is no easy way to use Play's json 26 | * library with generic type parameters. 27 | */ 28 | class MessageToJson[T <: AnyRef: ClassTag](out: ActorRef, actorSelection: String) extends Actor { 29 | val clazz = implicitly[ClassTag[T]].runtimeClass 30 | implicit val formats = DefaultFormats + MapSerializer + DoubleSerializer 31 | 32 | val actors = new TradingSimulationActorSelection(context, actorSelection).get 33 | 34 | actors ! ComponentRegistration(self, clazz, "frontend" + clazz) 35 | 36 | def receive() = { 37 | case msg: T => 38 | out ! write(msg) 39 | case _ => 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /frontend/app/actors/TraderParameters.scala: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import akka.actor.Props 4 | import akka.actor.Actor 5 | import akka.actor.ActorRef 6 | import scala.concurrent.duration._ 7 | import akka.actor.ActorSystem 8 | import akka.actor.ActorPath 9 | import play.libs.Akka 10 | import ch.epfl.ts.data.OHLC 11 | import scala.concurrent.ExecutionContext.Implicits.global 12 | import ch.epfl.ts.component.ComponentRegistration 13 | import scala.reflect.ClassTag 14 | import net.liftweb.json._ 15 | import net.liftweb.json.Serialization.write 16 | import com.typesafe.config.ConfigFactory 17 | import ch.epfl.ts.traders.Trader 18 | import ch.epfl.ts.engine.TraderMessage 19 | import ch.epfl.ts.engine.GetTraderParameters 20 | import ch.epfl.ts.engine.TraderIdentity 21 | import utils.TradingSimulationActorSelection 22 | 23 | class TraderParameters(out: ActorRef) extends Actor { 24 | implicit val formats = DefaultFormats 25 | 26 | val traders = new TradingSimulationActorSelection(context, 27 | ConfigFactory.load().getString("akka.backend.tradersActorSelection")).get 28 | 29 | traders ! ComponentRegistration(self, classOf[Trader], "frontend" + classOf[Trader]) 30 | traders ! ComponentRegistration(self, classOf[TraderIdentity], "frontend" + classOf[TraderIdentity]) 31 | 32 | def receive() = { 33 | case "getAllTraderParameters" => 34 | traders ! GetTraderParameters 35 | 36 | case t: TraderIdentity => 37 | out ! write(new SimpleTraderIdentity(t)) 38 | 39 | case _ => 40 | } 41 | 42 | } 43 | 44 | case class SimpleTraderIdentity(name: String, id: Long, strategy: String, parameters: List[String]) { 45 | def this(t: TraderIdentity) = { 46 | this(t.name, t.uid, t.strategy.toString(), t.parameters.parameters.map { case (k, v) => k + ": " + v.value() }.toList) 47 | } 48 | } -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/app.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | var app = angular.module('myApp', [ 'ngRoute', 'ui.bootstrap', 5 | 'highcharts-ng', 'ngTable', 'ngAnimate' ]); 6 | 7 | })(); -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/controllers/evaluationReportController.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('myApp').controller( 5 | 'EvaluationReportController', 6 | [ 7 | '$scope', 8 | '$filter', 9 | 'traderList', 10 | 'ngTableParams', 11 | 'ohlcGraphTransactionFlags', 12 | function($scope, $filter, traderList, ngTableParams, ohlcGraphTransactionFlags) { 13 | var traders = traderList.get(); 14 | var evaluationReports = {}; 15 | 16 | /** 17 | * Gets all the transactions for the selected trader ID and attaches 18 | * them as buy/sell flags to the global OHLC graph 19 | */ 20 | $scope.showTransactionsOnGraphFor = function(traderId) { 21 | ohlcGraphTransactionFlags.watchTrader(traders[traderId]); 22 | }; 23 | 24 | 25 | /** 26 | * Listens to EvaluationReport messages 27 | */ 28 | var ws = new WebSocket( 29 | 'ws://localhost:9000/trader/evaluation-report'); 30 | 31 | ws.onmessage = function(event) { 32 | var report = JSON.parse(event.data); 33 | evaluationReports[report.traderId] = report; 34 | 35 | if (!$scope.referenceCurrency) { 36 | $scope.referenceCurrency = report.currency.s; 37 | } 38 | $scope.tableParams.reload(); 39 | }; 40 | 41 | 42 | /** 43 | * ngTable configuration We tell jshint to ignore this part since 44 | * ngTableParams starts with a lowercase letter, which leads to a 45 | * jshint error 46 | */ 47 | /* jshint ignore:start */ 48 | $scope.tableParams = new ngTableParams({ 49 | count : evaluationReports.length, // no pager 50 | sorting : { 51 | totalReturns : 'desc' 52 | } 53 | }, { 54 | counts : [], // hide page size 55 | total : evaluationReports.length, 56 | getData : function($defer, params) { 57 | var data = []; 58 | for ( var report in evaluationReports) { 59 | data.push(evaluationReports[report]); 60 | } 61 | var orderedData = params.sorting() ? $filter('orderBy')(data, 62 | params.orderBy()) : data; 63 | $defer.resolve(orderedData); 64 | } 65 | }); 66 | /* jshint ignore:end */ 67 | 68 | } ]); 69 | 70 | })(); -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/controllers/lineGraphController.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('myApp').controller('LineGraphController', 5 | [ '$scope', 'alertService', function($scope, alertService) { 6 | $scope.alerts = alertService.get(); 7 | 8 | var ws = new WebSocket('ws://localhost:9000/fetchers/quote'); 9 | 10 | ws.onmessage = function(event) { 11 | var quote = JSON.parse(event.data); 12 | $scope.$apply(function() { 13 | var name = quote.whatC.s + " to " + quote.withC.s; 14 | 15 | var quoteSeries = $scope.chartSeries.filter(function(series) { 16 | return series.name == name; 17 | })[0]; 18 | 19 | if (!quoteSeries) { 20 | quoteSeries = { 21 | 'name' : name, 22 | 'visible' : false, 23 | 'data' : [] 24 | }; 25 | 26 | if ($scope.chartSeries.length === 0) { 27 | quoteSeries.visible = true; 28 | } 29 | 30 | $scope.chartSeries.push(quoteSeries); 31 | } 32 | 33 | quoteSeries.data.push([ quote.timestamp, quote.ask ]); 34 | 35 | if ($scope.chartConfig.loading) { 36 | $scope.chartConfig.loading = false; 37 | } 38 | 39 | }); 40 | }; 41 | 42 | ws.onclose = function(event) { 43 | $scope.$apply(function() { 44 | alertService.add('info', 'Closed connection to the backend'); 45 | }); 46 | }; 47 | 48 | ws.onerror = function(event) { 49 | $scope.$apply(function() { 50 | alertService.add('danger', 'Lost connection to the backend'); 51 | }); 52 | }; 53 | 54 | $scope.chartSeries = []; 55 | 56 | $scope.chartConfig = { 57 | options : { 58 | xAxis : [ { 59 | type : 'datetime' 60 | } ], 61 | navigator : { 62 | enabled : true, 63 | series : { 64 | data : $scope.chartSeries[0] 65 | } 66 | }, 67 | rangeSelector : { 68 | enabled : true, 69 | buttonTheme: { 70 | width: null, 71 | padding: 2 72 | }, 73 | buttons : [ { 74 | type : 'minute', 75 | count : 1, 76 | text : '1 min' 77 | }, { 78 | type : 'minute', 79 | count : 60, 80 | text : '1 hour' 81 | }, { 82 | type : 'day', 83 | count : 1, 84 | text : '1 day' 85 | }, { 86 | type : 'month', 87 | count : 1, 88 | text : '1 month' 89 | }, { 90 | type : 'month', 91 | count : 6, 92 | text : '6 months' 93 | }, { 94 | type : 'year', 95 | count : 1, 96 | text : '1 year' 97 | }, { 98 | type : 'all', 99 | text : 'All' 100 | } ] 101 | }, 102 | }, 103 | series : $scope.chartSeries, 104 | title : { 105 | text : 'Market price' 106 | }, 107 | credits : { 108 | enabled : false 109 | }, 110 | loading : true, 111 | size : {} 112 | }; 113 | 114 | } ]); 115 | 116 | })(); -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/controllers/ohlcGraphController.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('myApp').controller( 5 | 'OhlcGraphController', 6 | [ 7 | '$scope', 8 | 'alertService', 9 | 'ohlcGraph', 10 | function($scope, alertService, ohlcGraph) { 11 | $scope.alerts = alertService.get(); 12 | $scope.chartConfig = ohlcGraph.getChartConfig(); 13 | 14 | } ]); 15 | 16 | })(); -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/controllers/traderController.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Controller responsible for the table of traders in the system 6 | */ 7 | angular.module('myApp').controller( 8 | 'TraderController', 9 | [ 10 | '$scope', 11 | '$filter', 12 | 'traderList', 13 | 'transactionList', 14 | 'ohlcGraphTransactionFlags', 15 | 'ngTableParams', 16 | function($scope, $filter, traderList, transactionList, ohlcGraphTransactionFlags, 17 | ngTableParams) { 18 | var traders = traderList.get(); 19 | var transactions = transactionList.get(); 20 | 21 | /** 22 | * Gets all the transactions for the selected trader ID and attaches 23 | * them as buy/sell flags to the global OHLC graph 24 | */ 25 | $scope.showTransactionsOnGraphFor = function(trader) { 26 | ohlcGraphTransactionFlags.watchTrader(trader); 27 | }; 28 | 29 | /** 30 | * Updates the ngTable on data change 31 | */ 32 | $scope.$on('traders:updated', function(event, data) { 33 | $scope.tableParams.reload(); 34 | }); 35 | 36 | /** 37 | * ngTable configuration We tell jshint to ignore this part since 38 | * ngTableParams starts with a lowercase letter, which leads to a 39 | * jshint error 40 | */ 41 | /* jshint ignore:start */ 42 | $scope.tableParams = new ngTableParams({ 43 | count : traders.length, // no pager 44 | sorting : { 45 | id : 'asc' 46 | } 47 | }, { 48 | counts : [], // hide page size 49 | total : traders.length, 50 | getData : function($defer, params) { 51 | var data = []; 52 | for ( var report in traders) { 53 | data.push(traders[report]); 54 | } 55 | var orderedData = params.sorting() ? $filter('orderBy')( 56 | data, params.orderBy()) : data; 57 | $defer.resolve(orderedData); 58 | } 59 | }); 60 | /* jshint ignore:end */ 61 | 62 | } ]); 63 | 64 | })(); -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/controllers/transactionController.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('myApp').controller( 5 | 'TransactionController', 6 | [ 7 | '$scope', 8 | '$filter', 9 | 'alertService', 10 | 'transactionList', 11 | 'ngTableParams', 12 | function($scope, $filter, alertService, transactionList, 13 | ngTableParams) { 14 | $scope.alerts = alertService.get(); 15 | var transactions = transactionList.get(); 16 | 17 | $scope.$on('transactions:updated', function(event, data) { 18 | $scope.tableParams.reload(); 19 | }); 20 | 21 | /* jshint ignore:start */ 22 | $scope.tableParams = new ngTableParams({ 23 | count : transactions.length, // no pager 24 | sorting : { 25 | timestamp : 'desc' 26 | } 27 | }, { 28 | counts : [], // hide page size 29 | total : transactions.length, 30 | getData : function($defer, params) { 31 | var orderedData = params.sorting() ? $filter('orderBy')( 32 | transactions, params.orderBy()) : transactions; 33 | $defer.resolve(orderedData); 34 | } 35 | }); 36 | /* jshint ignore:end */ 37 | 38 | } ]); 39 | 40 | })(); -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/filters/percentageFilter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Filter to format a number as a percentage 3 | * It assumes that the input is in decimal form, i.e. 15% is 0.15 4 | */ 5 | angular.module('myApp').filter('percentage', ['$filter', function ($filter) { 6 | return function (input, decimals) { 7 | return $filter('number')(input * 100, decimals) + '%'; 8 | }; 9 | }]); -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/services/alertService.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('myApp').factory('alertService', function() { 5 | var service = {}; 6 | var alerts = []; 7 | 8 | service.get = function() { 9 | return alerts; 10 | }; 11 | 12 | service.clearAll = function() { 13 | alerts = []; 14 | }; 15 | 16 | service.add = function(type, msg) { 17 | return alerts.push({ 18 | type : type, 19 | msg : msg, 20 | close : function() { 21 | return close(this); 22 | } 23 | }); 24 | }; 25 | 26 | service.close = function(alert) { 27 | return closeByIndex(alerts.indexOf(alert)); 28 | }; 29 | 30 | service.closeByIndex = function(index) { 31 | return alerts.splice(index, 1); 32 | }; 33 | 34 | return service; 35 | }); 36 | 37 | })(); -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/services/traderService.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | var app = angular.module('myApp'); 5 | 6 | /** 7 | * Service to build a collection of known traders and their parameters 8 | */ 9 | angular.module('myApp').factory('traderList', 10 | [ '$rootScope', function($rootScope) { 11 | var service = {}; 12 | var traders = {}; 13 | 14 | /** 15 | * Listens to traderParameter messages and updates the trader in the 16 | * collection of known traders with the parameter 17 | */ 18 | var ws = new WebSocket('ws://localhost:9000/trader/parameters'); 19 | ws.onmessage = function(message) { 20 | var traderParameters = JSON.parse(message.data); 21 | traders[traderParameters.id] = traderParameters; 22 | $rootScope.$broadcast('traders:updated', traderParameters); 23 | }; 24 | 25 | /** 26 | * Returns the collection of known traders 27 | */ 28 | service.get = function() { 29 | return traders; 30 | }; 31 | 32 | /** 33 | * Adds a new trader to the collection of known traders If the trader is 34 | * not already known, it asks for its parameters 35 | */ 36 | service.add = function(traderId) { 37 | if (!traders[traderId]) { 38 | traders[traderId] = { 39 | id : traderId 40 | }; 41 | ws.send('getAllTraderParameters'); 42 | } 43 | return traders; 44 | }; 45 | 46 | return service; 47 | } ]); 48 | 49 | })(); -------------------------------------------------------------------------------- /frontend/app/assets/javascripts/services/transactionService.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | var app = angular.module('myApp'); 5 | 6 | angular.module('myApp').factory('transactionList', 7 | [ '$rootScope', 'traderList', function($rootScope, traderList) { 8 | var service = {}; 9 | var transactions = []; 10 | 11 | var ws = new WebSocket('ws://localhost:9000/market/transaction'); 12 | 13 | ws.onmessage = function(event) { 14 | var transaction = JSON.parse(event.data); 15 | if (transaction.buyerId >= 0) { 16 | traderList.add(transaction.buyerId); 17 | } else { 18 | transaction.buyerId = 'external'; 19 | } 20 | 21 | if (transaction.sellerId >= 0) { 22 | traderList.add(transaction.sellerId); 23 | } else { 24 | transaction.sellerId = 'external'; 25 | } 26 | 27 | transactions.push(transaction); 28 | $rootScope.$broadcast('transactions:updated', transaction); 29 | }; 30 | 31 | service.get = function() { 32 | return transactions; 33 | }; 34 | 35 | return service; 36 | } ]); 37 | 38 | })(); -------------------------------------------------------------------------------- /frontend/app/assets/stylesheets/main.css: -------------------------------------------------------------------------------- 1 | /** 2 | * ngTable styles 3 | **/ 4 | .ng-table th.sortable { 5 | cursor: pointer; 6 | } 7 | 8 | .ng-table th.sortable.sort-asc:before { 9 | content: '\25B2'; 10 | } 11 | 12 | .ng-table th.sortable.sort-desc:before { 13 | content: '\25BC'; 14 | } 15 | 16 | .ng-table th.sortable div { 17 | display: inline 18 | } 19 | 20 | /** 21 | * Animations 22 | **/ 23 | .animate--fade-in.ng-enter { 24 | -webkit-transition: 500ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 25 | -moz-transition: 500ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 26 | -ms-transition: 500ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 27 | -o-transition: 500ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 28 | transition: 500ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; 29 | opacity: 0; 30 | } 31 | 32 | .animate--fade-in.ng-enter-active, .animate--fade-in { 33 | opacity: 1; 34 | } -------------------------------------------------------------------------------- /frontend/app/controllers/Application.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api._ 4 | import play.api.Play.current 5 | import play.api.libs.iteratee.Iteratee 6 | import play.api.mvc._ 7 | import play.api.libs.json.JsValue 8 | import akka.actor.Props 9 | import actors.MessageToJson 10 | import scala.reflect.ClassTag 11 | import ch.epfl.ts.data.OHLC 12 | import ch.epfl.ts.indicators.SMA 13 | import ch.epfl.ts.data.Quote 14 | import ch.epfl.ts.data.Transaction 15 | import ch.epfl.ts.data.Register 16 | import actors.TraderParameters 17 | import actors.GlobalOhlc 18 | import akka.actor.Actor 19 | import akka.actor.ActorSystem 20 | import akka.actor.Props 21 | import com.typesafe.config.ConfigFactory 22 | import ch.epfl.ts.evaluation.EvaluationReport 23 | 24 | object Application extends Controller { 25 | val config = ConfigFactory.load() 26 | 27 | def index = Action { 28 | Ok(views.html.index("hello")) 29 | } 30 | 31 | def quote = WebSocket.acceptWithActor[String, String] { request => 32 | out => 33 | Props(classOf[MessageToJson[Quote]], out, 34 | config.getString("akka.backend.fetchersActorSelection"), implicitly[ClassTag[Quote]]) 35 | } 36 | 37 | def globalOhlc = WebSocket.acceptWithActor[String, String] { request => 38 | out => Props(classOf[GlobalOhlc], out) 39 | } 40 | 41 | def transaction = WebSocket.acceptWithActor[String, String] { request => 42 | out => 43 | Props(classOf[MessageToJson[Transaction]], out, 44 | config.getString("akka.backend.marketsActorSelection"), implicitly[ClassTag[Transaction]]) 45 | } 46 | 47 | def traderRegistration = WebSocket.acceptWithActor[String, String] { request => 48 | out => 49 | Props(classOf[MessageToJson[Register]], out, 50 | config.getString("akka.backend.tradersActorSelection"), implicitly[ClassTag[Register]]) 51 | } 52 | 53 | def traderParameters = WebSocket.acceptWithActor[String, String] { request => 54 | out => Props(classOf[TraderParameters], out) 55 | } 56 | 57 | def evaluationReport = WebSocket.acceptWithActor[String, String] { request => 58 | out => 59 | Props(classOf[MessageToJson[EvaluationReport]], out, 60 | config.getString("akka.backend.evaluatorsActorSelection"), implicitly[ClassTag[EvaluationReport]]) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /frontend/app/utils/DoubleSerializer.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import net.liftweb.json._ 4 | 5 | /** 6 | * Extends Lift JSON serialization to convert scala Infinity and NaN to a string 7 | * since JSON does not have any support for Infinity or NaN 8 | */ 9 | object DoubleSerializer extends Serializer[Double] { 10 | def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { 11 | case d: Double if d.isPosInfinity=> JString("inf") 12 | case d: Double if d.isNegInfinity => JString("-inf") 13 | case d: Double if d.isNaN => JString("NaN") 14 | } 15 | 16 | def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Double] = { 17 | sys.error("Not interested.") 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /frontend/app/utils/EnumJsonUtils.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import play.api.libs.json.JsSuccess 4 | import play.api.libs.json.Reads 5 | import play.api.libs.json.Writes 6 | import play.api.libs.json.JsError 7 | import play.api.libs.json.JsResult 8 | import play.api.libs.json.JsString 9 | import play.api.libs.json.JsValue 10 | import play.api.libs.json.Format 11 | import scala.language.implicitConversions 12 | 13 | /** 14 | * Small helper to deal with enumerations in JSON 15 | */ 16 | object EnumJsonUtils { 17 | def enumReads[E <: Enumeration](enum: E): Reads[E#Value] = new Reads[E#Value] { 18 | def reads(json: JsValue): JsResult[E#Value] = json match { 19 | case JsString(s) => { 20 | try { 21 | JsSuccess(enum.withName(s)) 22 | } catch { 23 | case _: NoSuchElementException => JsError(s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$s'") 24 | } 25 | } 26 | case _ => JsError("String value expected") 27 | } 28 | } 29 | 30 | implicit def enumWrites[E <: Enumeration]: Writes[E#Value] = new Writes[E#Value] { 31 | def writes(v: E#Value): JsValue = JsString(v.toString) 32 | } 33 | 34 | implicit def enumFormat[E <: Enumeration](enum: E): Format[E#Value] = { 35 | Format(EnumJsonUtils.enumReads(enum), EnumJsonUtils.enumWrites) 36 | } 37 | } -------------------------------------------------------------------------------- /frontend/app/utils/JsonFormatters.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ch.epfl.ts.data.OHLC 4 | import play.api.libs.json._ 5 | 6 | object JsonFormatters { 7 | implicit val ohlcFormat: Format[OHLC] = Json.format[OHLC] 8 | } -------------------------------------------------------------------------------- /frontend/app/utils/MapSerializer.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import net.liftweb.json._ 4 | 5 | /** 6 | * Extends Lift JSON serialization to support arbitrary maps 7 | * instead of only maps that have strings as keys 8 | */ 9 | object MapSerializer extends Serializer[Map[Any, Any]] { 10 | def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { 11 | case m: Map[_, _] => JObject(m.map({ 12 | case (k, v) => JField( 13 | k match { 14 | case ks: String => ks 15 | case ks: Symbol => ks.name 16 | case ks: Any => ks.toString 17 | }, 18 | Extraction.decompose(v)) 19 | }).toList) 20 | } 21 | 22 | def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Map[Any, Any]] = { 23 | sys.error("Not interested.") 24 | } 25 | } -------------------------------------------------------------------------------- /frontend/app/utils/TradingSimulationActorSelection.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import akka.actor.ActorContext 5 | 6 | class TradingSimulationActorSelection(context: ActorContext, actorSelection: String = "/user/*") { 7 | val config = ConfigFactory.load() 8 | val name = config.getString("akka.backend.systemName") 9 | val hostname = config.getString("akka.backend.hostname") 10 | val port = config.getString("akka.backend.port") 11 | val actors = context.actorSelection("akka.tcp://" + name + "@" + hostname + ":" + port + actorSelection) 12 | 13 | def get = actors 14 | } -------------------------------------------------------------------------------- /frontend/app/views/index.scala.html: -------------------------------------------------------------------------------- 1 | @(message: String) 2 | 3 | @main("Welcome to Play") { 4 | 5 | 6 |
7 | 8 | {{ alert.msg }} 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
{{ t.timestamp | date:'MM.dd.yyyy HH:mm:ss' }}{{ t.whatC.s | uppercase }} / {{ t.withC.s | uppercase }}{{ t.price }}{{ t.volume }}{{ t.buyerId }}{{ t.sellerId }}
26 |
27 | 28 | 29 | All currencies converted to {{ referenceCurrency }} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 44 | 45 |
{{ report.traderId }}{{ report.traderName }}{{ report.initial | number: 2 }}{{ report.current | number: 2 }}{{ report.totalReturns | percentage: 2 }}{{ report.volatility | number: 2 }}{{ report.drawdown | percentage: 2 }} 40 |
    41 |
  • {{ currency }} : {{ value | number: 2 }}
  • 42 |
43 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 59 | 62 | 63 |
{{ trader.id }}{{ trader.name }}{{ trader.strategy }} 55 |
    56 |
  • {{ param }}
  • 57 |
58 |
60 | Show transactions on graph 61 |
64 |
65 |
66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /frontend/app/views/main.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(content: Html) 2 | 3 | 4 | 5 | 6 | 7 | @title 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 44 | @content 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /frontend/conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # 8 | # This must be changed for production, but we recommend not changing it in this file. 9 | # 10 | # See http://www.playframework.com/documentation/latest/ApplicationSecret for more details. 11 | application.secret="R`8a_8j8kGe " 12 | exit 13 | fi 14 | 15 | SERVER=$1 # set server here to a constant if you use the script often 16 | PAIR=$2 # set the currency pair to constant here (lowercase, e.g. "eurchf") 17 | 18 | for j in `seq 2003 2015`; do # Years you want to fetch (min 2003) 19 | for i in `seq 1 12`; do # Months you want to fetch (max in running year is running month) 20 | for TYPE in tick-last-quotes tick-ask-quotes tick-bid-quotes; do 21 | # Helper variables 22 | YEAR=$j 23 | MONTH=`printf "%02d" $i` 24 | TK=`curl http://${SERVER}/download-free-forex-historical-data/?/ninjatrader/${TYPE}/${PAIR}/${YEAR}/${MONTH} --silent | perl -n -e '/id=\"tk\" value=\"([a-z0-9]+)\"/ && print "$1\n"' | head -n 1`; 25 | case $TYPE in 26 | tick-last-quotes) TYPECODE="T_LAST";; 27 | tick-ask-quotes) TYPECODE="T_ASK";; 28 | tick-bid-quotes) TYPECODE="T_BID";; 29 | *) echo "What's that type? Did not recognize type ${TYPE}"; exit 1; 30 | esac 31 | 32 | echo "Covert Ops tick data downloader: fetching month $MONTH of year $YEAR" 33 | 34 | # Preparing the request's fields 35 | FILENAME="${YEAR}.${MONTH}.zip" 36 | ORIGIN="Origin: http://${SERVER}" 37 | REFERER="Referer: http://${SERVER}/download-free-forex-historical-data/?/ninjatrader/${TYPE}/${PAIR}/${YEAR}/${MONTH}" 38 | DATA="tk=${TK}&date=${YEAR}&datemonth=${YEAR}${MONTH}&platform=NT&timeframe=${TYPECODE}&fxpair=${PAIR^^}" 39 | 40 | # Getting and saving the data 41 | mkdir -p data 42 | CMD="curl 'http://${SERVER}/get.php' -o 'data/$FILENAME' -H '$ORIGIN' -H '$REFERER' --data '$DATA'" 43 | echo "$CMD" 44 | eval "$CMD" 45 | unzip data/$FILENAME -d data 46 | rm data/*.zip 47 | done 48 | done 49 | done 50 | -------------------------------------------------------------------------------- /scripts/vm-prepare.sh: -------------------------------------------------------------------------------- 1 | sudo apt-get update 2 | sudo apt-get install git wget unzip default-jdk 3 | git clone https://github.com/merlinND/TradingSimulation.git 4 | wget https://dl.bintray.com/sbt/native-packages/sbt/0.13.8/sbt-0.13.8.zip 5 | unzip sbt-0.13.8.zip 6 | rm sbt-0.13.8.zip 7 | sudo ln -s ~/sbt/bin/sbt /usr/bin/sbt 8 | cd TradingSimulation/ 9 | 10 | sbt compile 11 | sbt test 12 | -------------------------------------------------------------------------------- /scripts/workerctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # EPFL Bigdata Course 2015, remote actor starter/stopper script V.1 4 | 5 | # Start and stop the remote actor system on our worker nodes. 6 | # The script logs into the workers via ssh, it assumes your 7 | # public key is in ~/.ssh/authorized_key on every worker. 8 | 9 | if [ $# -ne "1" ]; then 10 | echo "Usage: ./workerctl.sh " 11 | exit 12 | fi 13 | 14 | # Which workers to control 15 | DOMAINNAME=".cloudapp.net" 16 | HOSTS=(ts-1-021qv44y ts-2 ts-3 ts-4 ts-5 ts-6 ts-7 ts-8) 17 | USERS=(merlin dennis ts3 jakub ts-5 jakob ts-7 ts-8) 18 | PORTS=(22 22 22 22 58575 22 65530 52640) 19 | 20 | # Which git branch to run on workers 21 | BRANCH="master" 22 | 23 | # Which class to run on workers 24 | CLASS="ch.epfl.ts.optimization.RemotingWorkerRunner" 25 | 26 | # Which logfile to write to on workers 27 | LOGFILE_ON_WORKER="~/RemotingWorkerRunner.log" 28 | 29 | bold=$(tput bold) ; normal=$(tput sgr0) # Text formatting 30 | 31 | # Commands 32 | # The start command does: 33 | # 1. "ssh -f -n " connects to that server and executes the specified command 34 | # 2. "sh -c "" executes command2 in a shell environment 35 | # 3. "nohup " makes sure that command3 is not killed when its parent is killed 36 | # (in our case: sh will be killed as soon as ssh has sent its command and disconnects, 37 | # but nohup assures this doesn't happen to sbt) 38 | # 4. "sbt 'project ts' 'runMain ch.epfl.ts.remoting.RemotingWorkerRunner'" runs our remote actor system 39 | # 5. " > ~/RemoteActor.log 2>&1" redirects all sbt output to ~/RemoteActor.log 40 | START_CMD="sh -c \"\cd ~/TradingSimulation; \ 41 | nohup sbt 'project ts' 'runMain $CLASS' \ 42 | > $LOGFILE_ON_WORKER 2>&1 &\"" 43 | 44 | STOP_CMD="kill \$(ps -ef | grep '$CLASS' | grep -v grep | awk '{print \$2}')" 45 | 46 | STATUS_CMD="ps -ef | grep '$CLASS' | grep -v grep" 47 | 48 | UPDATE_CMD="sh -c 'cd ~/TradingSimulation; git fetch; git checkout origin/$BRANCH'" 49 | 50 | for i in "${!HOSTS[@]}"; do 51 | HOST=${HOSTS[$i]} 52 | USER=${USERS[$i]} 53 | PORT=${PORTS[$i]} 54 | echo "${bold}Connecting to $USER@$HOST$DOMAINNAME port $PORT${normal}" 55 | 56 | case "$1" in 57 | start) 58 | ssh $USER@$HOST$DOMAINNAME -p $PORT "$START_CMD" 59 | echo "Remote actor started. Logs are at $HOST$DOMAINNAME:$LOGFILE_ON_WORKER" 60 | ;; 61 | stop) 62 | ssh $USER@$HOST$DOMAINNAME -p $PORT "$STOP_CMD" 63 | echo "Killed remote actors." 64 | ;; 65 | status) 66 | echo "${bold}ps -ef | grep '$CLASS'${normal} on $HOST$DOMAINNAME:" 67 | ssh $USER@$HOST$DOMAINNAME -p $PORT "$STATUS_CMD" 68 | ;; 69 | update) 70 | echo "Updating from git branch $BRANCH" 71 | ssh $USER@$HOST$DOMAINNAME -p $PORT "$UPDATE_CMD" 72 | ;; 73 | update-restart) 74 | echo "Updating and restarting..." 75 | ssh $USER@$HOST$DOMAINNAME -p $PORT "$UPDATE_CMD; $STOP_CMD; $START_CMD" 76 | ;; 77 | *) 78 | echo "Unknown action $1" 79 | ;; 80 | esac 81 | done 82 | 83 | -------------------------------------------------------------------------------- /ts/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | -------------------------------------------------------------------------------- /ts/data: -------------------------------------------------------------------------------- 1 | ../data -------------------------------------------------------------------------------- /ts/src/main/resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/ts/src/main/resources/.gitkeep -------------------------------------------------------------------------------- /ts/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | 3 | # Expected name for the actor system (used by 4 | # the frontend to lookup the backend actor system) 5 | systemName = "TradingSimulation" 6 | 7 | loglevel = "INFO" 8 | 9 | actor { 10 | provider = "akka.remote.RemoteActorRefProvider" 11 | 12 | // TODO: remove when in a production context 13 | serialize-creators = on 14 | serialize-messages = on 15 | } 16 | remote { 17 | # log-sent-messages = on 18 | # log-received-messages = on 19 | enabled-transports = ["akka.remote.netty.tcp"] 20 | netty.tcp { 21 | hostname = "localhost" 22 | bind-hostname = "0.0.0.0" 23 | port = 5353 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/benchmark/marketSimulator/BenchmarkOrderBookMarketSimulator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.benchmark.marketSimulator 2 | 3 | import ch.epfl.ts.data._ 4 | import ch.epfl.ts.engine.{MarketRules, OrderBookMarketSimulator} 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 BenchmarkOrderBookMarketSimulator(marketId: Long, rules: MarketRules) extends OrderBookMarketSimulator(marketId, rules) { 12 | 13 | override def receiver = { 14 | case last: LastOrder => 15 | send(FinishedProcessingOrders(book.asks.size, book.bids.size)); 16 | 17 | case limitBid: LimitBidOrder => 18 | val currentPrice = tradingPrices((limitBid.withC, limitBid.whatC)) 19 | val newBidPrice = rules.matchingFunction( 20 | marketId, limitBid, book.bids, book.asks, 21 | this.send[Streamable], 22 | (a, b) => a <= b, 23 | currentPrice._1, 24 | (limitBid, bidOrdersBook) => { bidOrdersBook insert limitBid; send(limitBid) }) 25 | tradingPrices((limitBid.withC, limitBid.whatC)) = (newBidPrice, currentPrice._2) 26 | 27 | case limitAsk: LimitAskOrder => 28 | // TODO: check that the currencies have not been swapped by mistake 29 | val currentPrice = tradingPrices((limitAsk.withC, limitAsk.whatC)) 30 | val newAskPrice = rules.matchingFunction( 31 | marketId, limitAsk, book.asks, book.bids, 32 | this.send[Streamable], 33 | (a, b) => a >= b, 34 | currentPrice._2, 35 | (limitAsk, askOrdersBook) => { askOrdersBook insert limitAsk; send(limitAsk) }) 36 | tradingPrices((limitAsk.withC, limitAsk.whatC)) = (currentPrice._1, newAskPrice) 37 | 38 | case marketBid: MarketBidOrder => 39 | val currentPrice = tradingPrices((marketBid.withC, marketBid.whatC)) 40 | val newBidPrice = rules.matchingFunction( 41 | marketId, marketBid, book.bids, book.asks, 42 | this.send[Streamable], 43 | (a, b) => true, 44 | currentPrice._1, 45 | (marketBid, bidOrdersBook) => ()) 46 | tradingPrices((marketBid.withC, marketBid.whatC)) = (newBidPrice, currentPrice._2) 47 | 48 | case marketAsk: MarketAskOrder => 49 | val currentPrice = tradingPrices((marketAsk.withC, marketAsk.whatC)) 50 | val newAskPrice = rules.matchingFunction( 51 | marketId, marketAsk, book.asks, book.bids, 52 | this.send[Streamable], 53 | (a, b) => true, 54 | currentPrice._2, 55 | (marketAsk, askOrdersBook) => ()) 56 | tradingPrices((marketAsk.withC, marketAsk.whatC)) = (currentPrice._1, newAskPrice) 57 | 58 | case del: DelOrder => 59 | send(del) 60 | book delete del 61 | 62 | case t: Transaction => 63 | // TODO: how to know which currency of the two was bought? (Which to update, bid or ask price?) 64 | tradingPrices((t.withC, t.whatC)) = (???, ???) 65 | 66 | case _ => 67 | println("MS: got unknown") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ts/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, Currency.BTC, Currency.USD, 0.0, 0.0)) 15 | case _ => 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ts/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 4 | import ch.epfl.ts.data.Currency 5 | import ch.epfl.ts.data.Order 6 | 7 | case class LastOrder(val oid: Long, val uid: Long, val timestamp: Long, val whatC: Currency, val withC: Currency, val volume: Double, val price: Double) extends Order 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 | def receiver = { 16 | case _ => 17 | } 18 | 19 | override def start = { 20 | val ordersSent = orders :+ LastOrder(0L, 0L, System.currentTimeMillis(), Currency.DEF, Currency.DEF, 0.0, 0.0) 21 | send(StartSending(orders.size)) 22 | ordersSent.map { o => send(o) } 23 | } 24 | } -------------------------------------------------------------------------------- /ts/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 | } -------------------------------------------------------------------------------- /ts/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(), Currency.BTC, Currency.USD, 50.0, 0.0) :: orders 20 | orders = LimitBidOrder(0L, 0L, System.currentTimeMillis(), Currency.BTC, Currency.USD, 50.0, 50.0) :: orders 21 | 22 | // create factory 23 | 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[BenchmarkOrderBookMarketSimulator], 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->(market, classOf[LimitAskOrder], classOf[LimitBidOrder], 39 | classOf[MarketAskOrder], classOf[MarketBidOrder], classOf[DelOrder], classOf[LastOrder]) 40 | // used to test without the backloop 41 | // market->(trader, classOf[Transaction]) 42 | market->(backloop, classOf[Transaction]) 43 | backloop->(trader, classOf[Transaction]) 44 | trader->(market, classOf[LastOrder]) 45 | // start and end signals 46 | orderFeeder->(timeCounter, classOf[StartSending]) 47 | market->(timeCounter, classOf[FinishedProcessingOrders]) 48 | 49 | // start the benchmark 50 | builder.start 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ts/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 object 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 | } -------------------------------------------------------------------------------- /ts/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 object 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 | } -------------------------------------------------------------------------------- /ts/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 | -------------------------------------------------------------------------------- /ts/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, Currency.USD, Currency.BTC, 1, 1, 1, 1) :: Nil 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/component/Component.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component 2 | 3 | import scala.collection.mutable.{HashMap => MHashMap} 4 | import scala.language.existentials 5 | import scala.language.postfixOps 6 | import scala.reflect.ClassTag 7 | import akka.actor.Actor 8 | import akka.actor.ActorRef 9 | import akka.actor.actorRef2Scala 10 | import akka.actor.ActorLogging 11 | 12 | trait Receiver extends Actor with ActorLogging { 13 | def receive: PartialFunction[Any, Unit] 14 | 15 | def send[T: ClassTag](t: T): Unit 16 | def send[T: ClassTag](t: List[T]): Unit 17 | } 18 | 19 | abstract class Component extends Receiver { 20 | var dest = MHashMap[Class[_], List[ActorRef]]() 21 | var stopped = true 22 | 23 | final def componentReceive: PartialFunction[Any, Unit] = { 24 | case ComponentRegistration(ar, ct, name) => 25 | connect(ar, ct, name) 26 | log.debug("Received destination " + this.getClass.getSimpleName + ": from " + ar + " to " + ct.getSimpleName) 27 | case StartSignal => stopped = false 28 | start 29 | log.debug("Received Start " + this.getClass.getSimpleName) 30 | case StopSignal => context.stop(self) 31 | stop 32 | log.debug("Received Stop " + this.getClass.getSimpleName) 33 | stopped = true 34 | case y if stopped => log.debug("Received data when stopped " + this.getClass.getSimpleName + " of type " + y.getClass ) 35 | } 36 | 37 | /** 38 | * Connects two compoenents 39 | * 40 | * Normally subclass don't need to override this method. 41 | * */ 42 | def connect(ar: ActorRef, ct: Class[_], name: String): Unit = { 43 | dest += (ct -> (ar :: dest.getOrElse(ct, List()))) 44 | } 45 | 46 | /** 47 | * Starts the component 48 | * 49 | * Subclass can override do initialization here 50 | * */ 51 | def start: Unit = {} 52 | 53 | /** 54 | * Stops the component 55 | * 56 | * Subclass can override do release resources here 57 | * */ 58 | def stop: Unit = {} 59 | 60 | def receiver: PartialFunction[Any, Unit] 61 | 62 | /* TODO: Dirty hack, componentReceive giving back unmatched to rematch in receiver using a andThen */ 63 | override def receive = componentReceive orElse receiver 64 | 65 | def send[T: ClassTag](t: T) = dest.get(t.getClass).map(_.map (_ ! t)) //TODO(sygi): support superclasses 66 | def send[T: ClassTag](t: List[T]) = t.map( elem => dest.get(elem.getClass).map(_.map(_ ! elem))) 67 | } 68 | 69 | /** Encapsulates [[akka.actor.ActorRef]] to facilitate connection of components 70 | * TODO(sygi): support sending messages to ComponentRefs through ! 71 | */ 72 | class ComponentRef(val ar: ActorRef, val clazz: Class[_], val name: String, cb: ComponentBuilder) extends Serializable { 73 | /** Connects current component to the destination component 74 | * 75 | * @param destination the destination component 76 | * @param types the types of messages that the destination expects to receive 77 | */ 78 | def ->(destination: ComponentRef, types: Class[_]*) = { 79 | types.map(cb.add(this, destination, _)) 80 | } 81 | 82 | /** Connects current component to the specified components 83 | * 84 | * @param refs the destination components 85 | * @param types the types of messages that the destination components expect to receive 86 | */ 87 | def ->(refs: Seq[ComponentRef], types: Class[_]*) = { 88 | for (ref <- refs; typ <- types) cb.add(this, ref, typ) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ts/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, Currency.USD, Currency.USD, line(3).toDouble, line(4).toDouble) :: orders 45 | case "S" => orders = LimitAskOrder(line(1).toLong, 0, line(0).toLong, Currency.USD, Currency.USD, line(3).toDouble, line(4).toDouble) :: orders 46 | case "D" => orders = DelOrder(line(1).toLong, 0, line(0).toLong, Currency.DEF, Currency.DEF, 0, 0) :: orders 47 | case _ => 48 | } 49 | } 50 | } 51 | 52 | println("Done!") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/component/fetch/Fetch.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | import scala.concurrent.duration.DurationInt 4 | import scala.language.postfixOps 5 | import scala.reflect.ClassTag 6 | 7 | import ch.epfl.ts.component.Component 8 | 9 | trait Fetch[T] 10 | 11 | /* Direction PULL */ 12 | abstract class PullFetch[T] extends Fetch[T] { 13 | def fetch(): List[T] 14 | 15 | // TODO: which unit is that? 16 | def interval(): Int 17 | } 18 | 19 | /* Direction PUSH */ 20 | abstract class PushFetch[T] extends Fetch[T] { 21 | var callback: (T => Unit) 22 | } 23 | 24 | /* Actor implementation */ 25 | abstract class FetchingComponent extends Component 26 | 27 | class PullFetchComponent[T: ClassTag](f: PullFetch[T]) extends FetchingComponent { 28 | import context._ 29 | 30 | system.scheduler.schedule(10 milliseconds, f.interval() milliseconds, self, 'DoFetchNow) 31 | 32 | override def receiver = { 33 | // pull and send to each listener 34 | case 'DoFetchNow => 35 | f.fetch().map(t => send[T](t)) 36 | case _ => 37 | } 38 | } 39 | 40 | /* Actor implementation */ 41 | class PullFetchListComponent[T: ClassTag](f: PullFetch[T]) extends FetchingComponent { 42 | import context._ 43 | 44 | system.scheduler.schedule(0 milliseconds, f.interval() milliseconds, self, 'DoFetchNow) 45 | 46 | override def receiver = { 47 | // pull and send to each listener 48 | case 'DoFetchNow => 49 | send(f.fetch()) 50 | case _ => 51 | } 52 | } 53 | 54 | /* Actor implementation */ 55 | /** 56 | * To implement your own PushFetchComponent: 57 | * 1. Create your class C, C extends PushFetchComponent 58 | * 2. Inside your class use callback(what you want to send) 59 | * to send data to components connected to your fetcher 60 | */ 61 | class PushFetchComponent[T: ClassTag] extends FetchingComponent { 62 | override def receiver = { 63 | case _ => 64 | } 65 | 66 | def callback(data: T) = send(data) 67 | } 68 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/component/fetch/MarketNames.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | // TODO: refactor to a package which makes more sense 4 | object MarketNames { 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 FOREX_NAME = "Forex" 12 | val FOREX_ID = 4L; 13 | val marketIdToName = Map(BTCE_ID -> BTCE_NAME, BITSTAMP_ID -> BITSTAMP_NAME, BITFINEX_ID -> BITFINEX_NAME, FOREX_ID->FOREX_NAME) 14 | val marketNameToId = Map(BTCE_NAME -> BTCE_ID, BITSTAMP_NAME -> BITSTAMP_ID, BITFINEX_NAME -> BITFINEX_ID, FOREX_NAME->FOREX_ID) 15 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/component/fetch/TrueFxFetcher.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.fetch 2 | 3 | import ch.epfl.ts.data.Currency 4 | import ch.epfl.ts.data.Quote 5 | import org.apache.http.client.fluent.Request 6 | import ch.epfl.ts.data.OHLC 7 | 8 | /** 9 | * Fetcher for the TrueFX HTTP API, which provides live Forex quotes for free 10 | * 11 | * @param symbols [optional] List of the symbols to fetch. Leave empty to get all available symbols. 12 | * 13 | * @see TrueFX dev documentation: http://www.truefx.com/dev/data/TrueFX_MarketDataWebAPI_DeveloperGuide.pdf 14 | */ 15 | class TrueFxFetcher(symbols: List[(Currency, Currency)] = List()) 16 | extends PullFetch[Quote] 17 | with Serializable { 18 | 19 | val serverBase = "http://webrates.truefx.com/rates/connect.html" + "?f=csv" 20 | 21 | val marketId = MarketNames.FOREX_ID 22 | 23 | def fetch(): List[Quote] = { 24 | val csv = Request.Get(serverBase).execute().returnContent().asString() 25 | 26 | /** Parse a single line of TrueFX CSV format to Quote */ 27 | def parseLine(line: String): Quote = { 28 | val fields = line.split(',') 29 | val currencies = fields(0).split('/').map(s => Currency.fromString(s.toLowerCase)) 30 | val timestamp = fields(1).toLong 31 | val values = fields.drop(1).map(s => s.toDouble) 32 | 33 | /** 34 | * Prices are separated in "big figure" and "points". 35 | * We can simply concatenate them to obtain the full price. 36 | */ 37 | val bid = (fields(2) + fields(3)).toDouble 38 | val ask = (fields(4) + fields(5)).toDouble 39 | 40 | Quote(marketId, timestamp, currencies(0), currencies(1), bid, ask) 41 | } 42 | 43 | 44 | for { 45 | line <- csv.split('\n').toList 46 | if (line.length() > 1) // Eliminate the last empty line 47 | quote = parseLine(line) 48 | 49 | // Filter on the currencies that the user is interested in 50 | if (symbols.isEmpty || symbols.contains((quote.whatC, quote.withC))) 51 | } yield quote 52 | } 53 | 54 | def interval(): Int = 5000 55 | } 56 | -------------------------------------------------------------------------------- /ts/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 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/component/persist/DummyPersistor.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.persist 2 | 3 | import ch.epfl.ts.data.Transaction 4 | 5 | /** 6 | * a Persistor that does nothing. 7 | * Created by sygi on 23.03.15. 8 | */ 9 | class DummyPersistor extends Persistance[Transaction]{ 10 | override def save(t: Transaction): Unit = { 11 | 12 | } 13 | override def loadBatch(startTime: Long, endTime: Long): List[Transaction] = { 14 | return List() 15 | } 16 | override def loadSingle(id: Int): Transaction = { 17 | return null 18 | } 19 | override def save(ts: List[Transaction]): Unit = { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ts/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 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/component/persist/QuotePersistor.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.persist 2 | 3 | import ch.epfl.ts.data.Quote 4 | import ch.epfl.ts.data.Currency 5 | import ch.epfl.ts.data.Currency 6 | import ch.epfl.ts.component.fetch.MarketNames 7 | import scala.slick.driver.SQLiteDriver.simple._ 8 | import scala.slick.jdbc.JdbcBackend.Database.dynamicSession 9 | import scala.slick.jdbc.meta.MTable 10 | import scala.slick.lifted.{Column, TableQuery, Tag} 11 | 12 | /** 13 | * Provides methods to save or load a set of quotes into an SQLite database 14 | * 15 | * @param dbFilename The database this persistor works on. The actual file accessed 16 | * will be at data/.db 17 | */ 18 | class QuotePersistor(dbFilename: String) extends Persistance[Quote]{ 19 | 20 | // Define the QUOTES table format 21 | class Quotes(tag: Tag) extends Table[(Int, Long, String, String, Double, Double)](tag, "QUOTES") { 22 | def * = (id, timestamp, whatC, withC, bid, ask) 23 | def id: Column[Int] = column[Int]("QUOTE_ID", O.PrimaryKey, O.AutoInc) 24 | def timestamp: Column[Long] = column[Long]("TIMESTAMP") 25 | def whatC: Column[String] = column[String]("WHAT_C") 26 | def withC: Column[String] = column[String]("WITH_C") 27 | def bid: Column[Double] = column[Double]("BID") 28 | def ask: Column[Double] = column[Double]("ASK") 29 | } 30 | 31 | // Open DB session and query handler on the QUOTES table 32 | val db = Database.forURL("jdbc:sqlite:data/" + dbFilename + ".db", driver = "org.sqlite.JDBC") 33 | lazy val QuotesTable = TableQuery[Quotes] 34 | db.withDynSession { 35 | if (MTable.getTables("QUOTES").list.isEmpty) { 36 | QuotesTable.ddl.create 37 | } 38 | } 39 | 40 | 41 | override def loadBatch(startTime: Long, endTime: Long): List[Quote] = db.withDynSession { 42 | val res = QuotesTable.filter(e => e.timestamp >= startTime && e.timestamp <= endTime).invoker 43 | res.list.map( r => Quote(MarketNames.FOREX_ID, r._2, Currency.fromString(r._3), Currency.fromString(r._4), r._5, r._6)) 44 | } 45 | // TODO 46 | def loadSingle(id: Int): ch.epfl.ts.data.Quote = ??? 47 | 48 | override def save(newQuotes: List[Quote]): Unit = db.withDynSession { 49 | // The first field of the quote (QUOTE_ID) is set to -1 but this will be 50 | // ignored and auto incremented by jdbc:sqlite in the actual DB table. 51 | QuotesTable ++= newQuotes.map(q => (-1, q.timestamp, q.whatC.toString, q.withC.toString, q.bid, q.ask)) 52 | } 53 | def save(q: ch.epfl.ts.data.Quote): Unit = save(List(q)) 54 | } 55 | -------------------------------------------------------------------------------- /ts/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 | } -------------------------------------------------------------------------------- /ts/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 6 | 7 | import scala.concurrent.duration.DurationLong 8 | import scala.language.postfixOps 9 | import scala.reflect.ClassTag 10 | 11 | case class ReplayConfig(initTimeMs: Long, compression: Double) 12 | 13 | class Replay[T: ClassTag](p: Persistance[T], conf: ReplayConfig) extends Component { 14 | import context._ 15 | case object Tick 16 | 17 | var schedule: Cancellable = null 18 | var currentTime = conf.initTimeMs 19 | 20 | override def receiver = { 21 | case Tick if sender == self => 22 | process() 23 | currentTime += 1000 24 | case r: ReplayConfig => 25 | schedule.cancel() 26 | currentTime = r.initTimeMs 27 | // TODO: discard waiting objects 28 | schedule = startScheduler(r.compression) 29 | case _ => 30 | } 31 | 32 | private def startScheduler(compression: Double) = { 33 | context.system.scheduler.schedule(10 milliseconds, Math.round(compression * 1000) milliseconds, self, Tick) 34 | } 35 | 36 | override def stop = { 37 | schedule.cancel() 38 | } 39 | 40 | override def start = { 41 | schedule = startScheduler(conf.compression) 42 | } 43 | 44 | private def process() = send[T](p.loadBatch(currentTime, currentTime + 999)) 45 | } -------------------------------------------------------------------------------- /ts/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._ 6 | import ch.epfl.ts.data.Currency 7 | import ch.epfl.ts.data.Transaction 8 | import ch.epfl.ts.data.LimitBidOrder 9 | import ch.epfl.ts.data.DelOrder 10 | import ch.epfl.ts.data.LimitAskOrder 11 | 12 | /** 13 | * Backloop component, plugged as Market Simulator's output. Saves the transactions in a persistor. 14 | * distributes the transactions and delta orders to the trading agents 15 | */ 16 | class BackLoop(marketId: Long, p: Persistance[Transaction]) extends Component { 17 | 18 | override def receiver = { 19 | case t: Transaction => { 20 | send(t) 21 | p.save(t) 22 | } 23 | case la: LimitAskOrder => send(la) //WhereTF is this being used? 24 | case lb: LimitBidOrder => send(lb) 25 | case d: DelOrder => send(d) 26 | case _ => println("Looper: received unknown") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ts/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 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/component/utils/ParentActor.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.utils 2 | 3 | import akka.actor.Actor 4 | import akka.actor.Props 5 | import akka.actor.ActorRef 6 | import ch.epfl.ts.data.Streamable 7 | 8 | object ParentActor { 9 | abstract class ParentActorMessage extends Streamable 10 | 11 | case class Create(props: Props, name: String) extends ParentActorMessage 12 | case class Done(ref: ActorRef) extends ParentActorMessage 13 | } 14 | 15 | /** 16 | * Empty actor who's only goal is to be parents to other actors. 17 | * It only serves as a sub-root in the actor hierarchy. 18 | */ 19 | class ParentActor extends Actor { 20 | import context.dispatcher 21 | import ParentActor._ 22 | 23 | def receive = { 24 | case Create(props, name) => { 25 | val ref = context.actorOf(props, name) 26 | sender ! Done(ref) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /ts/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, Quote } 5 | import ch.epfl.ts.evaluation.EvaluationReport 6 | import ch.epfl.ts.data.EndOfFetching 7 | /** 8 | * Simple printer component. Prints what it receives 9 | * @param name The name of the printer. 10 | */ 11 | class Printer(val name: String) extends Component { 12 | override def receiver = { 13 | case t: Transaction => println("Printer " + name + ": Transaction\t" + System.currentTimeMillis + "\t" + t.toString) 14 | case t: Tweet => println("Printer " + name + ": Tweet\t" + System.currentTimeMillis + "\t" + t.toString) 15 | case lb: LimitBidOrder => println("Printer " + name + ": Limit Bid Order\t" + System.currentTimeMillis() + "\t" + lb.toString) 16 | case la: LimitAskOrder => println("Printer " + name + ": Limit Ask Order\t" + System.currentTimeMillis() + "\t" + la.toString) 17 | case del: DelOrder => println("Printer " + name + ": Delete Order\t" + System.currentTimeMillis() + "\t" + del.toString) 18 | case ohlc: OHLC => println("Printer " + name + ": OHLC\t" + System.currentTimeMillis() + "\t" + ohlc.toString) 19 | case quote: Quote => println("Printer " + name + ": Quote\t" + System.currentTimeMillis() + "\t" + quote.toString) 20 | case evalReport: EvaluationReport => println("Printer " + name + ": EvalReport\t" + System.currentTimeMillis() + "\t" + evalReport.toString) 21 | case endSignal: EndOfFetching => println("Printer " + name + ": EndOfFetching\t" + System.currentTimeMillis() + "\t" + endSignal) 22 | case _ => println("Printer " + name + ": received unknown") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/component/utils/Reaper.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.utils 2 | 3 | import scala.collection.mutable.ArrayBuffer 4 | import akka.actor.Terminated 5 | import akka.actor.ActorLogging 6 | import akka.actor.Actor 7 | import akka.actor.ActorRef 8 | import scala.concurrent.Promise 9 | import akka.actor.PoisonPill 10 | 11 | /** 12 | * @param references List of components that need to be killed & watched 13 | */ 14 | case class StartKilling(references: List[ActorRef]) 15 | 16 | /** 17 | * Supervising actor that waits for Actors' termination 18 | * @see http://letitcrash.com/post/30165507578/shutdown-patterns-in-akka-2 19 | */ 20 | class Reaper extends Actor with ActorLogging { 21 | import scala.concurrent.ExecutionContext.Implicits.global 22 | 23 | var watched = ArrayBuffer.empty[ActorRef] 24 | var promise: Option[Promise[Unit]] = None 25 | 26 | def onDone = promise match { 27 | case None => log.warning("Reaper tried to complete a non-existing promise") 28 | case Some(p) => p.success(Unit) 29 | } 30 | 31 | override def receive = { 32 | case StartKilling(bodies) => { 33 | bodies.foreach(c => { 34 | context.watch(c) 35 | c ! PoisonPill 36 | 37 | watched += c 38 | }) 39 | 40 | // This promise will be completed when all watched have died 41 | val p = Promise[Unit] 42 | val respondTo = sender 43 | p.future.onSuccess({ case _ => 44 | respondTo ! Unit 45 | }) 46 | promise = Some(p) 47 | 48 | if(bodies.isEmpty) onDone 49 | } 50 | 51 | case Terminated(ref) => { 52 | watched -= ref 53 | if(watched.isEmpty) onDone 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/component/utils/Timekeeper.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.component.utils 2 | 3 | import akka.actor.Actor 4 | import scala.concurrent.duration.FiniteDuration 5 | import ch.epfl.ts.component.StopSignal 6 | import java.util.Timer 7 | import ch.epfl.ts.data.TheTimeIs 8 | import akka.actor.ActorRef 9 | 10 | /** 11 | * When switching from Replay to "full simulation" mode, historical data 12 | * had timestamps from the past; and then suddenly our simulation must take 13 | * charge of generating quotes. These new quotes emitted from the simulation 14 | * can't just jump immediately to the present time. We thus introduce this component 15 | * to bridge the time gap. 16 | * 17 | * We use the last known time from historical data and add real physical time elapsed from 18 | * here. Note that physical time will always elapse at 1x speed. 19 | * 20 | * This component is instantiated with a fixed beginning date (timestamp) and will then emit 21 | * periodically `TheTimeIs` messages. 22 | * 23 | * @param parent The actor to send `TheTimeIs` messages 24 | * @param timeBase Timestamp (milliseconds) from which to start 25 | * @param period Delay between two `TimeIsNow` messages. 26 | * Note this also defines the granularity of time in the system. 27 | */ 28 | // TODO: replace the `parent` argument by simply using `context.parent`? 29 | class Timekeeper(val parent: ActorRef, val timeBase: Long, val period: FiniteDuration) extends Actor { 30 | 31 | private val timeOffset: Long = System.currentTimeMillis() 32 | 33 | private val timer = new Timer() 34 | private class SendTheTime extends java.util.TimerTask { 35 | def run() { 36 | val current = timeBase + (System.currentTimeMillis() - timeOffset) 37 | parent ! TheTimeIs(current) 38 | } 39 | } 40 | timer.scheduleAtFixedRate(new SendTheTime, 0L, period.toMillis) 41 | 42 | def receive = PartialFunction.empty 43 | 44 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/data/Currency.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.data 2 | 3 | import akka.util.HashCode 4 | 5 | /** 6 | * Enum for Currencies 7 | */ 8 | object Currency extends Serializable { 9 | def apply(s: String) = new Currency(s) 10 | 11 | // Cryptocurrencies 12 | val BTC = Currency("btc") 13 | val LTC = Currency("ltc") 14 | 15 | // Real-life currencies 16 | val USD = Currency("usd") 17 | val CHF = Currency("chf") 18 | val RUR = Currency("rur") 19 | val EUR = Currency("eur") 20 | val JPY = Currency("jpy") 21 | val GBP = Currency("gbp") 22 | val AUD = Currency("aud") 23 | val CAD = Currency("cad") 24 | 25 | /** Fallback currency ("default") */ 26 | val DEF = Currency("def") 27 | 28 | def values = Seq(BTC, LTC, USD, CHF, RUR, EUR, JPY, GBP, AUD, CAD, DEF) 29 | def supportedCurrencies(): Set[Currency] = values.toSet 30 | 31 | def fromString(s: String): Currency = { 32 | this.values.find(v => v.toString().toLowerCase() == s.toLowerCase()) match { 33 | case Some(currency) => currency 34 | case None => throw new UnsupportedOperationException("Currency " + s + " is not supported.") 35 | } 36 | } 37 | 38 | 39 | /** 40 | * Creates a tuple of currencies given a string 41 | * 42 | * @param s Input string of length six, three characters for each currency. Case insensitive. 43 | * Example: "EURCHF" returns (Currency.EUR, Currency.CHF) 44 | */ 45 | def pairFromString(s: String): (Currency, Currency) = { 46 | ( Currency.fromString(s.slice(0, 3)), Currency.fromString(s.slice(3,6)) ) 47 | } 48 | } 49 | 50 | /** 51 | * Need this (as a top level class) to help serializability 52 | */ 53 | class Currency(val s: String) extends Serializable { 54 | override def toString() = s.toString() 55 | override def equals(other: Any) = other match { 56 | case c: Currency => c.s == this.s 57 | case _ => false 58 | } 59 | override def hashCode: Int = s.hashCode() 60 | } 61 | -------------------------------------------------------------------------------- /ts/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 | } -------------------------------------------------------------------------------- /ts/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 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/engine/ForexMarketRules.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 | import ch.epfl.ts.component.fetch.MarketNames 6 | import ch.epfl.ts.data.MarketBidOrder 7 | import ch.epfl.ts.data.MarketAskOrder 8 | import akka.actor.ActorLogging 9 | 10 | /** 11 | * represents the cost of placing a bid and market order 12 | */ 13 | case class CommissionFX(limitOrderFee: Double, marketOrderFee: Double) 14 | 15 | /** 16 | * Market Simulator Configuration class. Defines the orders books priority implementation, the matching function and the commission costs of limit and market orders. 17 | * Extend this class and override its method(s) to customize Market rules for specific markets. 18 | * 19 | */ 20 | class ForexMarketRules extends MarketRules { 21 | 22 | def matchingFunction(marketId: Long, 23 | newOrder: Order, 24 | newOrdersBook: PartialOrderBook, 25 | bestMatchsBook: PartialOrderBook, 26 | send: Streamable => Unit, 27 | currentTradingPrice: Double): Unit = { 28 | 29 | newOrder match { 30 | case mbid: MarketBidOrder => 31 | // TODO: meaningful seller order & trader ids 32 | val sellOrderId = -1 33 | val sellerTraderId = -1 34 | send(Transaction( 35 | marketId, currentTradingPrice, 36 | newOrder.volume, newOrder.timestamp, 37 | newOrder.whatC, newOrder.withC, 38 | newOrder.uid, newOrder.oid, 39 | sellerTraderId, sellOrderId)) 40 | send(ExecutedBidOrder.apply(mbid,currentTradingPrice)) 41 | 42 | case mask: MarketAskOrder => 43 | // TODO: meaningful buyer order & trader ids 44 | val buyOrderId = -1 45 | val buyerTraderId = -1 46 | send(Transaction(marketId, currentTradingPrice, 47 | newOrder.volume, newOrder.timestamp, 48 | newOrder.whatC, newOrder.withC, 49 | buyerTraderId, buyOrderId, 50 | newOrder.uid, newOrder.oid)) 51 | send(ExecutedAskOrder.apply(mask,currentTradingPrice)) 52 | 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/engine/MarketFXSimulator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import ch.epfl.ts.data._ 4 | import akka.actor.ActorLogging 5 | import ch.epfl.ts.data.MarketBidOrder 6 | import ch.epfl.ts.data.Quote 7 | import ch.epfl.ts.data.LimitBidOrder 8 | import scala.Some 9 | import ch.epfl.ts.data.MarketAskOrder 10 | import ch.epfl.ts.data.LimitAskOrder 11 | import ch.epfl.ts.engine.rules.FxMarketRulesWrapper 12 | 13 | class MarketFXSimulator(marketId: Long, val rulesWrapper: FxMarketRulesWrapper = new FxMarketRulesWrapper()) 14 | extends MarketSimulator(marketId, rulesWrapper.rules) with ActorLogging { 15 | override def receiver = { 16 | case o: Order => 17 | rulesWrapper.processOrder(o, marketId, book, tradingPrices, this.send[Streamable]) 18 | case q: Quote => 19 | send(q) 20 | //log.debug("FxMS: got quote: " + q) 21 | tradingPrices((q.withC, q.whatC)) = (q.bid, q.ask) 22 | rulesWrapper.checkPendingOrders(marketId, book, tradingPrices, this.send[Streamable]) 23 | case _ => 24 | println("FxMS: got unknown") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/engine/MarketSimulator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import scala.collection.mutable.{ HashMap => MHashMap } 4 | import ch.epfl.ts.component.Component 5 | import ch.epfl.ts.data.Currency 6 | 7 | /** 8 | * Base class for all market simulators. 9 | * Defines the interface and common behaviors. 10 | */ 11 | abstract class MarketSimulator(marketId: Long, rules: MarketRules) extends Component { 12 | 13 | /** 14 | * Format: 15 | * (currency sold, currency bought) --> (bid price, ask price) 16 | */ 17 | type Prices = MHashMap[(Currency, Currency), (Double, Double)] 18 | 19 | /** 20 | * Last price at which a transaction was executed for each currency. 21 | */ 22 | // TODO: need to set initial trading price? 23 | var tradingPrices: Prices = MHashMap[(Currency, Currency), (Double, Double)]() 24 | 25 | val book = OrderBook(rules.bidsOrdering, rules.asksOrdering) 26 | } 27 | -------------------------------------------------------------------------------- /ts/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 | import scala.collection.mutable 7 | 8 | /** 9 | * Container for the Order Book. 10 | * It contains the ordered set of pending Order for one symbol, for either bid or ask. 11 | * The implementation has a HashMap with the orderIds matching the orders 12 | * because the naive implementation that required to execute a find() with 13 | * a predicate on the TreeSet to acquire the Order reference was executed 14 | * in linear time. 15 | */ 16 | class PartialOrderBook(val comparator: Ordering[Order]) { 17 | // orders ordered by the comparator 18 | val book = MTreeSet[Order]()(comparator) 19 | // key: orderId, value: order 20 | val bookMap = MHashMap[Long, Order]() 21 | 22 | /** 23 | * delete order from the book. 24 | */ 25 | def delete(o: Order): Boolean = bookMap remove o.oid map { r => book remove r; true} getOrElse { 26 | false 27 | } 28 | 29 | /** 30 | * insert order in book. 31 | */ 32 | def insert(o: Order): Unit = { 33 | bookMap update(o.oid, o) 34 | book add o 35 | } 36 | 37 | def isEmpty = book.isEmpty 38 | 39 | def head = book.head 40 | 41 | def size = book.size 42 | 43 | override def toString :String = { 44 | val sb = new mutable.StringBuilder 45 | for(i <- book){ 46 | sb.append(i.toString + "\n") 47 | } 48 | sb.toString() 49 | } 50 | } 51 | 52 | class OrderBook(val bids: PartialOrderBook, val asks: PartialOrderBook) { 53 | 54 | def delete(o: Order): Unit = if (!bids.delete(o)) asks.delete(o) 55 | 56 | def insertAskOrder(o: Order): Unit = asks.insert(o) 57 | 58 | def insertBidOrder(o: Order): Unit = bids.insert(o) 59 | 60 | //def getOrderById(oid: Long): Option[Order] = bids.bookMap.get(oid) 61 | } 62 | 63 | object OrderBook { 64 | def apply(bidComparator: Ordering[Order], askComparator: Ordering[Order]): OrderBook = 65 | new OrderBook(new PartialOrderBook(bidComparator), new PartialOrderBook(askComparator)) 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/engine/OrderBookMarketSimulator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import scala.collection.mutable.{ HashMap => MHashMap } 4 | import ch.epfl.ts.component.Component 5 | import ch.epfl.ts.data._ 6 | 7 | 8 | /** 9 | * Message used to print the books contents (since we use PriotityQueues, it's the heap order) 10 | */ 11 | case object PrintBooks 12 | 13 | //TODO(sygi): this can (and should) be rewritten in terms of SimulationMarketRulesWrapper 14 | class OrderBookMarketSimulator(marketId: Long, rules: MarketRules) extends MarketSimulator(marketId, rules) { 15 | 16 | override def receiver = { 17 | case limitBid: LimitBidOrder => 18 | val currentPrice = tradingPrices((limitBid.withC, limitBid.whatC)) 19 | val newBidPrice = rules.matchingFunction( 20 | marketId, limitBid, book.bids, book.asks, 21 | this.send[Streamable], 22 | (a, b) => a <= b, currentPrice._1, 23 | (limitBid, bidOrdersBook) => { 24 | bidOrdersBook insert limitBid 25 | send(limitBid) 26 | println("MS: order enqueued") 27 | }) 28 | tradingPrices((limitBid.withC, limitBid.whatC)) = (newBidPrice, currentPrice._2) 29 | 30 | case limitAsk: LimitAskOrder => 31 | val currentPrice = tradingPrices((limitAsk.withC, limitAsk.whatC)) 32 | val newAskPrice = rules.matchingFunction( 33 | marketId, limitAsk, book.asks, book.bids, 34 | this.send[Streamable], 35 | (a, b) => a >= b, currentPrice._2, 36 | (limitAsk, askOrdersBook) => { 37 | askOrdersBook insert limitAsk 38 | send(limitAsk) 39 | println("MS: order enqueued") 40 | }) 41 | tradingPrices((limitAsk.withC, limitAsk.whatC)) = (currentPrice._1, newAskPrice) 42 | 43 | case marketBid: MarketBidOrder => 44 | val currentPrice = tradingPrices((marketBid.withC, marketBid.whatC)) 45 | val newBidPrice = rules.matchingFunction( 46 | marketId, marketBid, book.bids, book.asks, 47 | this.send[Streamable], 48 | (a, b) => true, 49 | currentPrice._1, 50 | (marketBid, bidOrdersBook) => println("MS: market order discarded - there is no matching order")) 51 | tradingPrices((marketBid.withC, marketBid.whatC)) = (newBidPrice, currentPrice._2) 52 | 53 | case marketAsk: MarketAskOrder => 54 | // TODO: check currencies haven't been swapped here by mistake 55 | val currentPrice = tradingPrices((marketAsk.withC, marketAsk.whatC)) 56 | val newAskPrice = rules.matchingFunction( 57 | marketId, marketAsk, book.asks, book.bids, 58 | this.send[Streamable], 59 | (a, b) => true, 60 | currentPrice._2, 61 | (marketAsk, askOrdersBook) => println("MS: market order discarded - there is no matching order")) 62 | tradingPrices((marketAsk.withC, marketAsk.whatC)) = (currentPrice._1, newAskPrice) 63 | 64 | case del: DelOrder => 65 | println("MS: got Delete: " + del) 66 | send(del) 67 | book delete del 68 | 69 | case t: Transaction => 70 | // TODO: how to know which currency of the two was bought? (Which to update, bid or ask price?) 71 | tradingPrices((t.withC, t.whatC)) = (???, ???) 72 | 73 | case q: Quote => 74 | throw new UnsupportedOperationException("OrderBook MarketSimulator should never receive and handle quotes. It generates them by itself.") 75 | 76 | case PrintBooks => 77 | // print shows heap order (binary tree) 78 | println("Ask Orders Book: " + book.bids) 79 | println("Bid Orders Book: " + book.asks) 80 | 81 | case _ => 82 | println("MS: got unknown") 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/engine/WalletManager.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine 2 | 3 | import akka.actor.{ ActorLogging, Actor } 4 | import ch.epfl.ts.data.Currency 5 | 6 | /** 7 | * Wallet actor's companion object 8 | */ 9 | object Wallet { 10 | /** Data representation of the funds in multiple currencies */ 11 | type Type = Map[Currency, Double] 12 | } 13 | 14 | /* 15 | * Represents an one trader's wallet 16 | * TODO(sygi): remove user id from those communicates (ch.epfl.ts.engine.Messages) 17 | */ 18 | class Wallet extends Actor with ActorLogging { 19 | var funds: Wallet.Type = Map[Currency, Double]() 20 | 21 | override def receive = { 22 | case GetWalletFunds(uid, ref) => answerGetWalletFunds(uid) 23 | case FundWallet(uid, c, q, aneg) => fundWallet(uid, c, q, aneg) 24 | } 25 | 26 | def answerGetWalletFunds(uid: Long): Unit = { 27 | sender ! WalletFunds(uid, funds) 28 | } 29 | 30 | /** 31 | * Method to add given amount of currency to the wallet. Negative amount corresponds to charging a wallet. 32 | * Will return success/failure message to the sender. 33 | * @param uid - id of a wallet, will probably be removed at some point 34 | * @param c - currency name 35 | * @param q - amount to be added to the wallet. 36 | * @param allowNegative - Allow negative amount of money (for example in case of short orders) 37 | * 38 | */ 39 | def fundWallet(uid: Long, c: Currency, q: Double, allowNegative: Boolean): Unit = { 40 | funds.get(c) match { 41 | case None => { 42 | log.debug("adding a new currency") 43 | funds = funds + (c -> 0.0) 44 | fundWallet(uid, c, q, allowNegative) 45 | } 46 | case Some(status) => { 47 | log.debug("adding " + q + " to currency " + c) 48 | if (q + status >= 0.0 || allowNegative) { 49 | funds = funds + (c -> (q + status)) 50 | log.debug("Confirming") 51 | sender ! WalletConfirm(uid) 52 | } else 53 | sender ! WalletInsufficient(uid) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/engine/rules/FxMarketRulesWrapper.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine.rules 2 | 3 | import ch.epfl.ts.engine.{OrderBook, ForexMarketRules} 4 | import ch.epfl.ts.data._ 5 | import ch.epfl.ts.data.Currency 6 | import scala.collection.mutable 7 | import scala.Some 8 | import ch.epfl.ts.data.MarketBidOrder 9 | import ch.epfl.ts.data.LimitBidOrder 10 | import scala.Some 11 | import ch.epfl.ts.data.MarketAskOrder 12 | import ch.epfl.ts.data.LimitAskOrder 13 | import java.util.logging.Logger 14 | 15 | /** 16 | * Wrapper around ForexMarketRules, to have ALL the information needed to proceed the order in one method. 17 | */ 18 | class FxMarketRulesWrapper(val rules :ForexMarketRules = new ForexMarketRules()) extends MarketRulesWrapper(rules) { 19 | override def processOrder(o: Order, marketId: Long, 20 | book: OrderBook, tradingPrices: mutable.HashMap[(Currency, Currency), (Double, Double)], 21 | send: Streamable => Unit): Unit = 22 | o match { 23 | case limitBid: LimitBidOrder => 24 | book.bids.insert(limitBid) 25 | checkPendingOrders(marketId, book, tradingPrices, send) 26 | 27 | case limitAsk: LimitAskOrder => 28 | book.asks.insert(limitAsk) 29 | checkPendingOrders(marketId, book, tradingPrices, send) 30 | 31 | case marketBid: MarketBidOrder => 32 | tradingPrices.get((marketBid.withC, marketBid.whatC)) match { 33 | case Some(t) => rules.matchingFunction(marketId, marketBid, book.bids, book.asks, send, t._2) 34 | case None => 35 | } 36 | case marketAsk: MarketAskOrder => 37 | tradingPrices.get((marketAsk.withC, marketAsk.whatC)) match { 38 | case Some(t) => rules.matchingFunction(marketId, marketAsk, book.bids, book.asks, send, t._1) 39 | //TODO(sygi): does it actually work at all, when there are multiple currencies? 40 | case None => 41 | } 42 | case _ => println("FMRW: got unknown order") 43 | } 44 | 45 | //TODO(sygi): refactor common code 46 | def checkPendingOrders(marketId: Long, 47 | book: OrderBook, tradingPrices: mutable.HashMap[(Currency, Currency), (Double, Double)], 48 | send: (Streamable) => Unit) { 49 | if (!book.bids.isEmpty) { 50 | val topBid = book.bids.head 51 | tradingPrices.get((topBid.withC, topBid.whatC)) match { 52 | case Some(t) => { 53 | val askPrice = t._2 54 | if (askPrice <= topBid.price) { 55 | //execute order right now 56 | //TODO(sygi): we put the topBid price, but it won't be used, as we ALWAYS sell/buy at the market price (from Quote)) 57 | //TODO(sygi): Order type casting 58 | val order = MarketBidOrder(topBid.oid, topBid.uid, topBid.timestamp, topBid.whatC, topBid.withC, topBid.volume, topBid.price) 59 | book.bids.delete(topBid) 60 | processOrder(order, marketId, book, tradingPrices, send) 61 | checkPendingOrders(marketId, book, tradingPrices, send) 62 | } 63 | } 64 | case None => println("FMRW: dont have info about topBid " + topBid.withC + " " + topBid.whatC + " pair price") 65 | } 66 | } 67 | if (!book.asks.isEmpty) { 68 | val topAsk = book.asks.head 69 | tradingPrices.get((topAsk.withC, topAsk.whatC)) match { 70 | case Some(t) => { 71 | val bidPrice = t._2 72 | if (bidPrice >= topAsk.price) { 73 | val order = MarketAskOrder(topAsk.oid, topAsk.uid, topAsk.timestamp, topAsk.whatC, topAsk.withC, topAsk.volume, topAsk.price) 74 | book.asks.delete(topAsk) 75 | processOrder(order, marketId, book, tradingPrices, send) 76 | checkPendingOrders(marketId, book, tradingPrices, send) 77 | } 78 | } 79 | case None => println("FMRW: dont have info about " + topAsk.withC + " " + topAsk.whatC + " pair price") 80 | } 81 | } 82 | } 83 | 84 | override def initQuotes(q: Quote): Unit = {} 85 | } 86 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/engine/rules/MarketRulesWrapper.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine.rules 2 | 3 | import ch.epfl.ts.data.{Currency, Streamable, Order, Quote} 4 | import ch.epfl.ts.engine.{OrderBook, PartialOrderBook, MarketRules} 5 | import scala.collection.mutable.HashMap 6 | 7 | /** 8 | * A wrapper around the MarketRules, so that all the information needed to proceed with the order is in one place. 9 | * Needed to be able to change between strategies without changing the MarketSimulator. 10 | */ 11 | abstract class MarketRulesWrapper(rules: MarketRules) extends Serializable { 12 | def processOrder(o: Order, marketId: Long, 13 | book: OrderBook, tradingPrices: HashMap[(Currency, Currency), (Double, Double)], //TODO(sygi): get type from original class 14 | send: Streamable => Unit) 15 | def getRules = rules 16 | 17 | /** give the first quote to be able to generate them periodically right from the start */ 18 | def initQuotes(q: Quote): Unit 19 | } 20 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/engine/rules/SimulationMarketRulesWrapper.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.engine.rules 2 | 3 | import ch.epfl.ts.engine.{OrderBook, MarketRules} 4 | import ch.epfl.ts.data._ 5 | import ch.epfl.ts.data.Currency 6 | import ch.epfl.ts.data.MarketBidOrder 7 | import ch.epfl.ts.data.LimitBidOrder 8 | import ch.epfl.ts.data.MarketAskOrder 9 | import ch.epfl.ts.data.LimitAskOrder 10 | import scala.collection.mutable.HashMap 11 | 12 | class SimulationMarketRulesWrapper(val rules: MarketRules = new MarketRules()) extends MarketRulesWrapper(rules){ 13 | override def processOrder(o: Order, marketId: Long, 14 | book: OrderBook, tradingPrices: HashMap[(Currency, Currency), (Double, Double)], 15 | send: Streamable => Unit) = { 16 | o match { 17 | case limitBid: LimitBidOrder => 18 | val currentPrice = tradingPrices((limitBid.withC, limitBid.whatC)) 19 | val newBidPrice = rules.matchingFunction( 20 | marketId, limitBid, book.bids, book.asks, 21 | send, 22 | (a, b) => a <= b, currentPrice._1, 23 | (limitBid, bidOrdersBook) => { 24 | bidOrdersBook insert limitBid 25 | }) 26 | tradingPrices((limitBid.withC, limitBid.whatC)) = (newBidPrice, currentPrice._2) 27 | 28 | case limitAsk: LimitAskOrder => 29 | val currentPrice = tradingPrices((limitAsk.withC, limitAsk.whatC)) 30 | val newAskPrice = rules.matchingFunction( 31 | marketId, limitAsk, book.asks, book.bids, 32 | send, 33 | (a, b) => a >= b, currentPrice._2, 34 | (limitAsk, askOrdersBook) => { 35 | askOrdersBook insert limitAsk 36 | }) 37 | tradingPrices((limitAsk.withC, limitAsk.whatC)) = (currentPrice._1, newAskPrice) 38 | 39 | case marketBid: MarketBidOrder => 40 | val currentPrice = tradingPrices((marketBid.withC, marketBid.whatC)) 41 | val newBidPrice = rules.matchingFunction( 42 | marketId, marketBid, book.bids, book.asks, 43 | send, 44 | (a, b) => true, 45 | currentPrice._1, 46 | (marketBid, bidOrdersBook) => println("SMRW: market order discarded - there is no matching order")) 47 | tradingPrices((marketBid.withC, marketBid.whatC)) = (newBidPrice, currentPrice._2) 48 | 49 | case marketAsk: MarketAskOrder => 50 | val currentPrice = tradingPrices((marketAsk.withC, marketAsk.whatC)) 51 | val newAskPrice = rules.matchingFunction( 52 | marketId, marketAsk, book.asks, book.bids, 53 | send, 54 | (a, b) => true, 55 | currentPrice._2, 56 | (marketAsk, askOrdersBook) => println("SMRW: market order discarded - there is no matching order")) 57 | tradingPrices((marketAsk.withC, marketAsk.whatC)) = (currentPrice._1, newAskPrice) 58 | } 59 | } 60 | override def initQuotes(q: Quote) = rules.initQuotes(q) 61 | } 62 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/example/AbstractExample.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import scala.concurrent.ExecutionContext.Implicits.global 4 | import scala.concurrent.duration.FiniteDuration 5 | import ch.epfl.ts.component.ComponentBuilder 6 | import ch.epfl.ts.optimization.SystemDeployment 7 | import akka.actor.ActorRef 8 | import ch.epfl.ts.component.ComponentRef 9 | import ch.epfl.ts.optimization.StrategyFactory 10 | import ch.epfl.ts.data.Currency 11 | import ch.epfl.ts.component.fetch.MarketNames 12 | import ch.epfl.ts.optimization.HostActorSystem 13 | import ch.epfl.ts.optimization.RemoteHost 14 | 15 | 16 | /** 17 | * Common behaviors observed in typical deployments of our systems. 18 | */ 19 | abstract class AbstractExample { 20 | 21 | implicit val builder: ComponentBuilder = new ComponentBuilder 22 | 23 | /** The default host is a simple local actor system (no remoting) */ 24 | def localHost: HostActorSystem = new HostActorSystem() 25 | 26 | /** Factory to use in order to create a deployment */ 27 | def factory: StrategyFactory 28 | 29 | /** List of IDs of the market being traded on */ 30 | def marketIds: Seq[Long] 31 | 32 | /** 33 | * Connect the various components of a system deployment 34 | */ 35 | def makeConnections(d: SystemDeployment): Unit 36 | 37 | /** 38 | * Optional supervisor actor 39 | * @example [[OptimizationSupervisor]] 40 | */ 41 | lazy val supervisorActor: Option[ComponentRef] = None 42 | 43 | 44 | /** 45 | * Main runnable method 46 | */ 47 | def main(args: Array[String]): Unit 48 | 49 | /** 50 | * Use this if we need to terminate early regardless of the data being fetched 51 | */ 52 | import scala.concurrent.ExecutionContext.Implicits.global 53 | def terminateAfter(delay: FiniteDuration)(implicit builder: ComponentBuilder) = { 54 | builder.system.scheduler.scheduleOnce(delay) { 55 | println("---------- Terminating system after a fixed duration of " + delay) 56 | builder.shutdownManagedActors(delay) onComplete { 57 | _ => builder.system.terminate() 58 | } 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * Use this trait if your example uses evaluation 65 | */ 66 | trait TraderEvaluation { 67 | /** Emit evaluation reports every `evaluationPeriod` */ 68 | def evaluationPeriod: FiniteDuration 69 | /** Assess the value of traders' wallet using this currency */ 70 | def referenceCurrency: Currency 71 | } 72 | 73 | /** 74 | * Use this trait if your example uses remoting 75 | */ 76 | trait RemotingDeployment { 77 | /** 78 | * List of hosts on which to deploy the systems 79 | */ 80 | def availableHosts: Seq[RemoteHost] 81 | } 82 | 83 | abstract class AbstractForexExample extends AbstractExample { 84 | 85 | lazy val marketIds = Seq(MarketNames.FOREX_ID) 86 | 87 | /** Main symbol (currency pair) being traded */ 88 | def symbol: (Currency, Currency) 89 | } 90 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/example/AbstractOptimizationExample.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import scala.concurrent.ExecutionContext.Implicits.global 4 | import scala.concurrent.duration.FiniteDuration 5 | 6 | import akka.actor.ActorRef 7 | import akka.actor.Props 8 | import akka.actor.actorRef2Scala 9 | import ch.epfl.ts.component.ComponentRef 10 | import ch.epfl.ts.component.StartSignal 11 | import ch.epfl.ts.data._ 12 | import ch.epfl.ts.engine.ExecutedAskOrder 13 | import ch.epfl.ts.engine.ExecutedBidOrder 14 | import ch.epfl.ts.engine.FundWallet 15 | import ch.epfl.ts.engine.GetWalletFunds 16 | import ch.epfl.ts.evaluation.EvaluationReport 17 | import ch.epfl.ts.optimization.OptimizationSupervisor 18 | import ch.epfl.ts.optimization.StrategyOptimizer 19 | import ch.epfl.ts.optimization.SystemDeployment 20 | 21 | abstract class AbstractOptimizationExample extends AbstractTraderShowcaseExample { 22 | 23 | /** Set this if you want to end the run after a fixed duration */ 24 | def maximumRunDuration: Option[FiniteDuration] = None 25 | 26 | // Supervisor 27 | override lazy val supervisorActor: Option[ComponentRef] = Some({ 28 | builder.createRef(Props(classOf[OptimizationSupervisor]), "MasterSupervisor") 29 | }) 30 | 31 | // Traders 32 | /** 33 | * Target number of Trader instances to deploy (may not be respected exactly) 34 | */ 35 | def maxInstances: Int 36 | /** Which of this strategy's parameter do we want to optimize */ 37 | def parametersToOptimize: Set[String] 38 | /** Provide values for the required parameters that we do not optimize for */ 39 | def otherParameterValues: Map[String, Parameter] 40 | /** Generate parameterizations using the previous fields */ 41 | override lazy val parameterizations = { 42 | StrategyOptimizer.generateParameterizations(strategy, parametersToOptimize, 43 | otherParameterValues, maxInstances).toSet 44 | } 45 | 46 | override def makeConnections(d: SystemDeployment): Unit = { 47 | val master = supervisorActor.get 48 | 49 | d.fetcher -> (d.market, classOf[Quote]) 50 | d.fetcher -> (Seq(d.market, master), classOf[EndOfFetching]) 51 | d.market -> (d.broker, classOf[Quote], classOf[ExecutedBidOrder], classOf[ExecutedAskOrder]) 52 | // TODO: make sure to support all order types 53 | d.broker -> (d.market, classOf[LimitBidOrder], classOf[LimitAskOrder], classOf[MarketBidOrder], classOf[MarketAskOrder]) 54 | 55 | for(e <- d.evaluators) { 56 | d.fetcher -> (e, classOf[EndOfFetching]) 57 | e -> (d.broker, classOf[Register], classOf[FundWallet], classOf[GetWalletFunds]) 58 | e -> (d.broker, classOf[LimitBidOrder], classOf[LimitAskOrder], classOf[MarketBidOrder], classOf[MarketAskOrder]) 59 | d.market -> (e, classOf[Quote], classOf[ExecutedBidOrder], classOf[ExecutedAskOrder]) 60 | d.market -> (e, classOf[Transaction]) 61 | 62 | e -> (master, classOf[EvaluationReport]) 63 | } 64 | 65 | for(printer <- d.printer) { 66 | d.market -> (printer, classOf[Transaction]) 67 | d.fetcher -> (printer, classOf[EndOfFetching]) 68 | 69 | for(e <- d.evaluators) e -> (printer, classOf[EvaluationReport]) 70 | } 71 | } 72 | 73 | 74 | /** 75 | * Use this if we need to terminate early regardless of the data being fetched 76 | */ 77 | def terminateOptimizationAfter(delay: FiniteDuration, supervisor: ActorRef) = { 78 | import scala.concurrent.ExecutionContext.Implicits.global 79 | builder.system.scheduler.scheduleOnce(delay) { 80 | println("---------- Terminating optimization after a fixed duration of " + delay) 81 | supervisor ! EndOfFetching(System.currentTimeMillis()) 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/example/AbstractTraderShowcaseExample.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import scala.concurrent.duration.DurationInt 4 | import scala.language.postfixOps 5 | 6 | import ch.epfl.ts.data.Currency 7 | import ch.epfl.ts.data.EndOfFetching 8 | import ch.epfl.ts.data.MarketAskOrder 9 | import ch.epfl.ts.data.MarketBidOrder 10 | import ch.epfl.ts.data.Quote 11 | import ch.epfl.ts.data.Register 12 | import ch.epfl.ts.data.StrategyParameters 13 | import ch.epfl.ts.data.Transaction 14 | import ch.epfl.ts.engine.ExecutedAskOrder 15 | import ch.epfl.ts.engine.ExecutedBidOrder 16 | import ch.epfl.ts.engine.FundWallet 17 | import ch.epfl.ts.engine.GetWalletFunds 18 | import ch.epfl.ts.evaluation.EvaluationReport 19 | import ch.epfl.ts.optimization.ForexLiveStrategyFactory 20 | import ch.epfl.ts.optimization.ForexReplayStrategyFactory 21 | import ch.epfl.ts.optimization.SystemDeployment 22 | import ch.epfl.ts.traders.TraderCompanion 23 | 24 | /** 25 | * Typical runnable class that showcases a trading strategy 26 | * on either live or historical data. 27 | */ 28 | abstract class AbstractTraderShowcaseExample extends AbstractForexExample with TraderEvaluation { 29 | 30 | // ----- Data 31 | def useLiveData: Boolean 32 | /** If using historical data, use this replay speed */ 33 | def replaySpeed: Double 34 | /** If using historical data, fetch between these dates (format: YYYYMM) */ 35 | def startDate: String 36 | def endDate: String 37 | 38 | // ----- Evaluation 39 | def evaluationPeriod = (10 seconds) 40 | def referenceCurrency = symbol._2 41 | 42 | lazy val factory = { 43 | if(useLiveData) new ForexLiveStrategyFactory(evaluationPeriod, referenceCurrency) 44 | else new ForexReplayStrategyFactory(evaluationPeriod, referenceCurrency, symbol, replaySpeed, startDate, endDate) 45 | } 46 | 47 | def makeConnections(d: SystemDeployment) = { 48 | d.fetcher -> (d.market, classOf[Quote]) 49 | d.broker -> (d.market, classOf[MarketAskOrder], classOf[MarketBidOrder]) 50 | d.market -> (d.broker, classOf[Quote], classOf[ExecutedBidOrder], classOf[ExecutedAskOrder]) 51 | 52 | d.evaluators.foreach(e => { 53 | d.fetcher -> (e, classOf[EndOfFetching]) 54 | d.market -> (e, classOf[Quote], classOf[Transaction]) 55 | e -> (d.broker, classOf[Register], classOf[FundWallet], classOf[GetWalletFunds], classOf[MarketAskOrder], classOf[MarketBidOrder]) 56 | }) 57 | 58 | // ----- Print things for debug 59 | d.evaluators.foreach(_ -> (d.printer.get, classOf[EvaluationReport])) 60 | d.fetcher -> (d.printer.get, classOf[EndOfFetching]) 61 | d.market -> (d.printer.get, classOf[Transaction]) 62 | } 63 | 64 | /** The single strategy to showcase */ 65 | def strategy: TraderCompanion 66 | 67 | /** Parameterizations to instantiate for this strategy (may be a singleton) */ 68 | def parameterizations: Set[StrategyParameters] 69 | 70 | /** Optional: give names to your traders */ 71 | def traderNames: Set[String] = Set() 72 | 73 | def main(args: Array[String]): Unit = { 74 | // ----- Creating actors 75 | val d = factory.createDeployment(localHost, strategy, parameterizations, traderNames) 76 | 77 | // ----- Connecting actors 78 | makeConnections(d) 79 | 80 | // ----- Start 81 | builder.start 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/example/BrokerExamples.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import akka.actor.Props 5 | import ch.epfl.ts.component.ComponentBuilder 6 | import ch.epfl.ts.traders.SimpleTraderWithBroker 7 | import ch.epfl.ts.data.{MarketAskOrder, Order, Register, StrategyParameters} 8 | import com.typesafe.config.ConfigFactory 9 | import ch.epfl.ts.brokers.StandardBroker 10 | import ch.epfl.ts.data.WalletParameter 11 | import ch.epfl.ts.data.Currency 12 | import ch.epfl.ts.component.fetch.MarketNames 13 | 14 | /** 15 | * Example main with a broker and wallet-aware trader 16 | */ 17 | object BrokerExamples { 18 | def main(args: Array[String]): Unit = { 19 | implicit val builder = new ComponentBuilder("TestingBroker", ConfigFactory.parseString("akka.loglevel = \"DEBUG\"")) 20 | val broker = builder.createRef(Props(classOf[StandardBroker]), "Broker") 21 | 22 | val tId = 15L 23 | val marketIds = List(MarketNames.FOREX_ID) 24 | val parameters = new StrategyParameters( 25 | SimpleTraderWithBroker.INITIAL_FUNDS -> WalletParameter(Map(Currency.CHF -> 1000.0)) 26 | ) 27 | val trader = SimpleTraderWithBroker.getInstance(tId, marketIds, parameters, "BrokerAwareTrader") 28 | 29 | trader->(broker, classOf[Register]) 30 | trader->(broker, classOf[MarketAskOrder]) 31 | 32 | builder.start 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ts/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->(Seq(printer, btcePersistor), classOf[Transaction]) 41 | bitstampFetcher->(Seq(printer, bitstampPersistor), classOf[Transaction]) 42 | 43 | // Start the system 44 | builder.start 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/example/DemoExample.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import scala.language.postfixOps 4 | import scala.io.Source 5 | import scala.concurrent.duration.DurationInt 6 | import ch.epfl.ts.component.ComponentBuilder 7 | import ch.epfl.ts.data.Currency 8 | import ch.epfl.ts.traders.RangeTrader 9 | import ch.epfl.ts.data.StrategyParameters 10 | import scala.concurrent.duration.FiniteDuration 11 | import ch.epfl.ts.traders.MovingAverageTrader 12 | import ch.epfl.ts.engine.Wallet 13 | import ch.epfl.ts.data.CurrencyPairParameter 14 | import ch.epfl.ts.data.RealNumberParameter 15 | import ch.epfl.ts.data.WalletParameter 16 | import ch.epfl.ts.data.TimeParameter 17 | import ch.epfl.ts.component.StartSignal 18 | import ch.epfl.ts.data.NaturalNumberParameter 19 | import ch.epfl.ts.data.BooleanParameter 20 | import ch.epfl.ts.data.CoefficientParameter 21 | 22 | /** 23 | * Class used for a live demo of the project 24 | */ 25 | object DemoExample extends AbstractOptimizationExample { 26 | 27 | override val maximumRunDuration: Option[FiniteDuration] = None 28 | 29 | val symbol = (Currency.EUR, Currency.CHF) 30 | 31 | // Historical data 32 | val useLiveData = false 33 | val replaySpeed = 3600.0 34 | val startDate = "201304" 35 | val endDate = "201505" 36 | 37 | // Evaluation 38 | override val evaluationPeriod = (10 seconds) 39 | 40 | /** Names for our trader instances */ 41 | override lazy val traderNames = { 42 | val urlToNames = getClass.getResource("/names-shuffled.txt") 43 | val names = Source.fromFile(urlToNames.toURI()).getLines() 44 | names.toSet 45 | } 46 | 47 | // Trading strategy 48 | val maxInstances = traderNames.size 49 | val strategy = RangeTrader 50 | val parametersToOptimize = Set( 51 | RangeTrader.ORDER_WINDOW) 52 | val otherParameterValues = { 53 | val initialWallet: Wallet.Type = Map(symbol._1 -> 0, symbol._2 -> 5000.0) 54 | Map(RangeTrader.INITIAL_FUNDS -> WalletParameter(initialWallet), 55 | RangeTrader.SYMBOL -> CurrencyPairParameter(symbol)) 56 | } 57 | 58 | override def main(args: Array[String]): Unit = { 59 | println("Going to create " + parameterizations.size + " traders on localhost") 60 | 61 | // ----- Create instances 62 | val d = factory.createDeployment(localHost, strategy, parameterizations, traderNames) 63 | 64 | // ----- Connecting actors 65 | makeConnections(d) 66 | 67 | // ----- Start 68 | // Give an early start to important components 69 | supervisorActor.get.ar ! StartSignal 70 | d.broker.ar ! StartSignal 71 | d.market.ar ! StartSignal 72 | builder.start 73 | 74 | // ----- Registration to the supervisor 75 | // Register each new trader to the master 76 | for (e <- d.evaluators) { 77 | supervisorActor.get.ar ! e.ar 78 | } 79 | 80 | // ----- Controlled duration (optional) 81 | maximumRunDuration match { 82 | case Some(duration) => terminateOptimizationAfter(duration, supervisorActor.get.ar) 83 | case None => 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/example/HistDataFetcherExample.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.{HistDataCSVFetcher, PushFetchComponent} 6 | import ch.epfl.ts.component.utils.Printer 7 | import ch.epfl.ts.data.Quote 8 | import ch.epfl.ts.data.EndOfFetching 9 | import scala.reflect.ClassTag 10 | 11 | /** 12 | * This system should instantiate a histDataCSVFetcher and 13 | * display the fetched data live on the command line 14 | */ 15 | object HistDataFetcherExample { 16 | def main(args: Array[String]) { 17 | implicit val builder = new ComponentBuilder("HistFetcherExample") 18 | 19 | // variables for the fetcher 20 | val speed = 60.0 21 | val dateFormat = new java.text.SimpleDateFormat("yyyyMM") 22 | val startDate = dateFormat.parse("201304"); 23 | val endDate = dateFormat.parse("201304"); 24 | val workingDir = "./data"; 25 | val currencyPair = "EURCHF"; 26 | 27 | // Create Components 28 | // build fetcher 29 | val fetcher = builder.createRef(Props(classOf[HistDataCSVFetcher], workingDir, currencyPair, startDate, endDate, speed),"HistFetcher") 30 | // build printer 31 | val printer = builder.createRef(Props(classOf[Printer], "Printer"), "Printer") 32 | 33 | // Create the connection 34 | fetcher->(printer, classOf[Quote], classOf[EndOfFetching]) 35 | 36 | // Start the system 37 | builder.start 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/example/MovingAverageTraderExample.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import scala.concurrent.duration.DurationInt 4 | import scala.language.postfixOps 5 | 6 | import ch.epfl.ts.data.BooleanParameter 7 | import ch.epfl.ts.data.CoefficientParameter 8 | import ch.epfl.ts.data.Currency 9 | import ch.epfl.ts.data.CurrencyPairParameter 10 | import ch.epfl.ts.data.NaturalNumberParameter 11 | import ch.epfl.ts.data.RealNumberParameter 12 | import ch.epfl.ts.data.StrategyParameters 13 | import ch.epfl.ts.data.TimeParameter 14 | import ch.epfl.ts.data.WalletParameter 15 | import ch.epfl.ts.engine.Wallet 16 | import ch.epfl.ts.traders.MovingAverageTrader 17 | import ch.epfl.ts.traders.TraderCompanion 18 | 19 | object MovingAverageTraderExample extends AbstractTraderShowcaseExample { 20 | 21 | val useLiveData = false 22 | val replaySpeed = 8000.0 23 | val startDate = "201304" 24 | val endDate = "201404" 25 | 26 | val symbol = (Currency.EUR, Currency.CHF) 27 | 28 | val strategy: TraderCompanion = MovingAverageTrader 29 | val parameterizations = Set({ 30 | val periods = List(2, 6) 31 | val initialFunds: Wallet.Type = Map(Currency.CHF -> 5000.0) 32 | new StrategyParameters( 33 | MovingAverageTrader.INITIAL_FUNDS -> WalletParameter(initialFunds), 34 | MovingAverageTrader.SYMBOL -> CurrencyPairParameter(symbol), 35 | 36 | MovingAverageTrader.OHLC_PERIOD -> new TimeParameter(1 hour), 37 | MovingAverageTrader.SHORT_PERIODS -> NaturalNumberParameter(periods(0)), 38 | MovingAverageTrader.LONG_PERIODS -> NaturalNumberParameter(periods(1)), 39 | MovingAverageTrader.TOLERANCE -> RealNumberParameter(0.0002), 40 | MovingAverageTrader.WITH_SHORT -> BooleanParameter(true), 41 | MovingAverageTrader.SHORT_PERCENT -> CoefficientParameter(0.2)) 42 | }) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/example/RangeTraderExample.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import scala.language.postfixOps 4 | 5 | import ch.epfl.ts.data.CoefficientParameter 6 | import ch.epfl.ts.data.Currency 7 | import ch.epfl.ts.data.CurrencyPairParameter 8 | import ch.epfl.ts.data.RealNumberParameter 9 | import ch.epfl.ts.data.StrategyParameters 10 | import ch.epfl.ts.data.WalletParameter 11 | import ch.epfl.ts.engine.Wallet 12 | import ch.epfl.ts.traders.RangeTrader 13 | import ch.epfl.ts.traders.TraderCompanion 14 | 15 | object RangeTraderExample extends AbstractTraderShowcaseExample { 16 | 17 | val useLiveData = false 18 | val replaySpeed = 86400.0 19 | val startDate = "201304" 20 | val endDate = "201404" 21 | 22 | val symbol = (Currency.EUR, Currency.CHF) 23 | 24 | val strategy: TraderCompanion = RangeTrader 25 | val parameterizations = Set({ 26 | val initialFunds: Wallet.Type = Map(symbol._1 -> 5000.0, symbol._2 -> 10000.0) 27 | new StrategyParameters( 28 | RangeTrader.INITIAL_FUNDS -> WalletParameter(initialFunds), 29 | RangeTrader.SYMBOL -> CurrencyPairParameter(symbol), 30 | RangeTrader.ORDER_WINDOW -> CoefficientParameter(0.20) 31 | ) 32 | }) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /ts/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->(printer, classOf[Transaction]) 33 | 34 | // Start the system 35 | builder.start 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/example/RsiTraderExample.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.example 2 | 3 | import scala.concurrent.duration.DurationInt 4 | import scala.language.postfixOps 5 | 6 | import ch.epfl.ts.data.BooleanParameter 7 | import ch.epfl.ts.data.Currency 8 | import ch.epfl.ts.data.CurrencyPairParameter 9 | import ch.epfl.ts.data.NaturalNumberParameter 10 | import ch.epfl.ts.data.RealNumberParameter 11 | import ch.epfl.ts.data.StrategyParameters 12 | import ch.epfl.ts.data.TimeParameter 13 | import ch.epfl.ts.data.WalletParameter 14 | import ch.epfl.ts.engine.Wallet 15 | import ch.epfl.ts.traders.RsiTrader 16 | import ch.epfl.ts.traders.TraderCompanion 17 | 18 | object RsiTraderExample extends AbstractTraderShowcaseExample { 19 | 20 | val useLiveData = false 21 | val replaySpeed = 4000.0 22 | val startDate = "201304" 23 | val endDate = "201304" 24 | 25 | val symbol = (Currency.EUR, Currency.CHF) 26 | 27 | val strategy: TraderCompanion = RsiTrader 28 | val parameterizations = Set({ 29 | val initialFunds: Wallet.Type = Map(Currency.CHF -> 100000.0) 30 | new StrategyParameters( 31 | RsiTrader.INITIAL_FUNDS -> WalletParameter(initialFunds), 32 | RsiTrader.SYMBOL -> CurrencyPairParameter(symbol), 33 | RsiTrader.OHLC_PERIOD -> new TimeParameter(1 hour), 34 | RsiTrader.RSI_PERIOD->new NaturalNumberParameter(12), 35 | RsiTrader.HIGH_RSI -> RealNumberParameter(80), 36 | RsiTrader.LOW_RSI -> RealNumberParameter(20), 37 | RsiTrader.WITH_SMA_CONFIRMATION->BooleanParameter(true), 38 | RsiTrader.LONG_SMA_PERIOD->new NaturalNumberParameter(20) 39 | ) 40 | }) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /ts/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->(Seq(printer, persistor), classOf[Tweet]) 26 | 27 | // Start the system 28 | builder.start 29 | } 30 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/indicators/EmaIndicator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.indicators 2 | 3 | import scala.collection.mutable.MutableList 4 | case class EMA(override val value: Map[Int, Double]) extends MovingAverage(value) with Serializable 5 | 6 | class EmaIndicator(periods: List[Int]) extends MaIndicator(periods: List[Int]) { 7 | val multipliers = periods.map(p => p -> 2.0 / (p + 1)).toMap 8 | var previousMas = periods.map(p => (p -> 0.0)).toMap 9 | var hasStarted = periods.map(p => p -> false).toMap 10 | 11 | def computeMa: EMA = { 12 | 13 | def auxCompute(period: Int): Double = { 14 | //EMA = SMA at initialization 15 | if (!hasStarted(period)) { 16 | var sma: Double = 0.0 17 | values.takeRight(period.toInt).map { o => sma = sma + o.close } 18 | sma = sma / period 19 | previousMas += period -> sma 20 | hasStarted += period -> true 21 | sma 22 | } else { 23 | val lastPrice = values.last.close 24 | val previousMa = previousMas(period) 25 | val ema = multipliers.get(period).get * (lastPrice - previousMa) + previousMa 26 | previousMas+=period->ema 27 | ema 28 | } 29 | } 30 | EMA(periods.map(p => (p -> auxCompute(p))).toMap) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ts/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 | import ch.epfl.ts.data.Quote 6 | import scala.collection.mutable.MutableList 7 | import akka.actor.Actor 8 | import akka.event.Logging 9 | import akka.actor.ActorLogging 10 | import ch.epfl.ts.data.Streamable 11 | 12 | /** 13 | * Moving Average value data 14 | */ 15 | abstract class MovingAverage(val value: Map[Int, Double]) extends Streamable 16 | 17 | /** 18 | * Moving average superclass. To implement a moving average indicator, 19 | * extend this class and implement the computeMa() method. 20 | * @param periods List of periods (expressed in "Number of OHLC" unit) 21 | */ 22 | abstract class MaIndicator(periods: List[Int]) extends Actor with ActorLogging { 23 | 24 | var values: MutableList[OHLC] = MutableList[OHLC]() 25 | 26 | val sortedPeriods = periods.sorted 27 | val maxPeriod = sortedPeriods.last 28 | 29 | def receive = { 30 | 31 | case o: OHLC => { 32 | log.debug("Moving Average Indicator received an OHLC: " + o) 33 | values += o 34 | if(values.size == maxPeriod) { 35 | val ma = computeMa 36 | sender ! ma 37 | values = values.tail 38 | } 39 | } 40 | case m => log.debug("MaIndicator: received unknown: " + m) 41 | } 42 | 43 | /** 44 | * Compute moving average 45 | * Needs to be implemented by concrete subclasses 46 | */ 47 | def computeMa: MovingAverage 48 | 49 | } 50 | -------------------------------------------------------------------------------- /ts/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, Quote } 5 | import ch.epfl.ts.data.Currency 6 | import scala.collection.mutable.MutableList 7 | import akka.actor.Actor 8 | import akka.event.Logging 9 | import akka.actor.ActorLogging 10 | import scala.concurrent.duration.FiniteDuration 11 | 12 | /** 13 | * Computes an OHLC tick for a tick frame of the specified duration 14 | * The OHLCs are identified with the provided marketId. 15 | */ 16 | 17 | class OhlcIndicator(marketId: Long, symbol: (Currency,Currency), tickDuration: FiniteDuration) 18 | extends Actor with ActorLogging { 19 | 20 | val tickSizeMillis = tickDuration.toMillis 21 | 22 | /** 23 | * Stores transactions' price values 24 | */ 25 | var values: MutableList[Double] = MutableList[Double]() 26 | var volume: Double = 0.0 27 | var close: Double = 0.0 28 | var currentTick: Long = 0 29 | val (whatC, withC) = symbol 30 | 31 | override def receive = { 32 | 33 | // We either receive the price from quote (Backtesting/realtime trading) or from transaction (for simulation) 34 | 35 | case t: Transaction => { 36 | if (whichTick(t.timestamp) > currentTick) { 37 | // New tick, send OHLC with values stored until now, and reset accumulators (Transaction volume & prices) 38 | sender() ! computeOHLC 39 | currentTick = whichTick(t.timestamp) 40 | } 41 | values += t.price 42 | volume = volume + t.volume 43 | } 44 | 45 | case q: Quote => { 46 | if(currentTick == 0){ 47 | currentTick = whichTick(q.timestamp) 48 | } 49 | if (q.whatC == whatC && q.withC == withC) { 50 | if (whichTick(q.timestamp) > currentTick) { 51 | sender() ! computeOHLC 52 | 53 | currentTick = whichTick(q.timestamp) 54 | } 55 | values += q.bid //we consider the price as the bid price 56 | } 57 | } 58 | case _ => println("OhlcIndicator: received unknown") 59 | } 60 | 61 | /** 62 | * computes to which tick an item with the provided timestamp belongs to 63 | */ 64 | private def whichTick(timestampMillis: Long): Long = { 65 | timestampMillis / tickSizeMillis 66 | } 67 | 68 | /** 69 | * compute OHLC if values have been received, otherwise send OHLC with all values set to the close of the previous non-empty OHLC 70 | */ 71 | private def computeOHLC: OHLC = { 72 | // set OHLC's timestamp 73 | val tickTimeStamp = currentTick * tickSizeMillis 74 | 75 | if (!values.isEmpty) { 76 | close = values.head 77 | val ohlc = OHLC(marketId, values.last, values.max(Ordering.Double), values.min(Ordering.Double), values.head, volume, tickTimeStamp, tickSizeMillis) 78 | // Clean old values 79 | volume = 0 80 | values.clear() 81 | ohlc 82 | } else { 83 | OHLC(marketId, close, close, close, close, 0, tickTimeStamp, tickSizeMillis) 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/indicators/RangeIndicator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.indicators 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.data.OHLC 5 | import scala.collection.mutable.MutableList 6 | import akka.actor.Actor 7 | import akka.actor.ActorLogging 8 | 9 | case class RangeIndic(val support : Double, val resistance : Double, val period : Int ) 10 | 11 | /** 12 | * This indicator will define a range that contains most of the prices in the given period. 13 | * A range is defined by two value a resistance that can be seen as the ceiling and a support which can be seen as a floor. 14 | * 15 | * Note : tolerance should be set to 1 to have the classical range trading strategy. However if you want x 16 | * to have a more aggressive strategy you can increase the tolerance. 17 | * 18 | * @param tolerance the number of extremum value we discarded set to 1 to have the "classical" range. 19 | */ 20 | class RangeIndicator(timePeriod : Int, tolerance : Int) extends Actor with ActorLogging{ 21 | 22 | // resistance will be the period^th highest prices 23 | var support : Double = 0.0 24 | 25 | // support will be the period^th lowest prices 26 | var resistance : Double = 0.0 27 | 28 | var price : Double = 0.0 29 | 30 | var pricesInPeriod : MutableList[Double] = MutableList[Double]() 31 | 32 | //boolean variable use to detect when the initialization phase is done, when we receive enough info to compute first period 33 | var initializationDone : Boolean = false 34 | 35 | /** 36 | * We need to make sure that we receive enough ohlc before computing the ranges. 37 | */ 38 | var countOHLC : Int = 0 39 | 40 | override def receive = { 41 | 42 | case o: OHLC => { 43 | price = o.close 44 | if(!initializationDone){ 45 | countOHLC += 1 46 | pricesInPeriod = pricesInPeriod :+ price 47 | if(countOHLC == timePeriod) { 48 | initializationDone = true 49 | } 50 | } 51 | 52 | else { 53 | pricesInPeriod = pricesInPeriod.tail :+ price 54 | resistance = getResistance(pricesInPeriod) 55 | support = getSupport(pricesInPeriod) 56 | var rangeMessage = RangeIndic(support, resistance, timePeriod) 57 | sender() ! rangeMessage 58 | } 59 | } 60 | 61 | case somethingElse => log.debug("RangeIndicator : received unknown : "+somethingElse) 62 | } 63 | 64 | /** 65 | * @param list the list of prices of size timePeriod 66 | * @return the support for list of prices and the tolerance 67 | */ 68 | def getSupport(list : MutableList[Double]) : Double = { 69 | 70 | var sortedPrices : MutableList[Double] = list.sorted 71 | sortedPrices.get(tolerance) match { 72 | case Some(value) => value 73 | case None => -1.0 74 | } 75 | } 76 | /** 77 | * @param list the list of prices of size timePeriod 78 | * @return the support for list of prices and the tolerance 79 | */ 80 | def getResistance(list : MutableList[Double]) : Double = { 81 | 82 | var sortedPrices : MutableList[Double] = list.sorted 83 | sortedPrices.get(sortedPrices.size - tolerance) match { 84 | case Some(value) => value 85 | case None => -1.0 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/indicators/RsiIndicator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.indicators 2 | 3 | import akka.actor.ActorLogging 4 | import akka.actor.Actor 5 | import scala.collection.mutable.MutableList 6 | import ch.epfl.ts.data._ 7 | 8 | case class RSI(value : Double) 9 | 10 | class RsiIndicator(period : Int) extends Actor with ActorLogging { 11 | 12 | var previousPrice : Double = 0.0 13 | var U : MutableList[Double] = MutableList[Double]() 14 | var D : MutableList[Double] = MutableList[Double]() 15 | var countPeriod = period 16 | var currentPrice : Double = 0.0 17 | 18 | override def receive = { 19 | case ohlc : OHLC => { 20 | log.debug("receive OHLC") 21 | currentPrice = ohlc.close 22 | if(previousPrice != 0.0) { 23 | if(countPeriod == 0){ 24 | U = U.tail 25 | D = D.tail 26 | if(currentPrice - previousPrice >= 0) { 27 | U += (currentPrice - previousPrice) 28 | D += 0.0 29 | } 30 | else{ 31 | D += -(currentPrice - previousPrice) 32 | U += 0.0 33 | } 34 | log.debug("send RSI") 35 | sender() ! RSI(computeRsi) 36 | } 37 | else{ 38 | log.debug("building datas") 39 | if(currentPrice - previousPrice >= 0) { 40 | U += currentPrice -previousPrice 41 | D += 0.0 42 | } 43 | else { 44 | D += -(currentPrice-previousPrice) 45 | U += 0.0 46 | } 47 | countPeriod = countPeriod - 1 48 | } 49 | } 50 | previousPrice = currentPrice 51 | } 52 | case somethingElse => log.debug("RSI indicator receive the following unknow message "+somethingElse) 53 | } 54 | 55 | def computeRsi: Double = { 56 | 100 - 100/(1 + (computeSma(U) / computeSma(D) )) 57 | } 58 | 59 | def computeSma(data : MutableList[Double]) = { 60 | var sma: Double = 0.0 61 | data.map { o => sma = sma + o } 62 | sma / period 63 | } 64 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/indicators/SmaIndicator.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.indicators 2 | 3 | case class SMA(override val value: Map[Int, Double]) extends MovingAverage(value) with Serializable 4 | 5 | /** 6 | * Simple moving average indicator 7 | */ 8 | class SmaIndicator(periods: List[Int]) extends MaIndicator(periods) { 9 | 10 | def computeMa: SMA = { 11 | def auxCompute(period: Long): Double = { 12 | var sma: Double = 0.0 13 | values.takeRight(period.toInt).map { o => sma = sma + o.close } 14 | sma / period 15 | } 16 | 17 | SMA(periods.map(p => (p -> auxCompute(p))).toMap) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/optimization/ForexStrategyFactory.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.optimization 2 | 3 | import akka.actor.Props 4 | import ch.epfl.ts.brokers.StandardBroker 5 | import ch.epfl.ts.component.ComponentBuilder 6 | import ch.epfl.ts.component.ComponentRef 7 | import ch.epfl.ts.component.fetch.MarketNames 8 | import ch.epfl.ts.component.fetch.PullFetchComponent 9 | import ch.epfl.ts.component.utils.Printer 10 | import ch.epfl.ts.data.StrategyParameters 11 | import ch.epfl.ts.engine.MarketFXSimulator 12 | import ch.epfl.ts.engine.rules.FxMarketRulesWrapper 13 | import ch.epfl.ts.traders.TraderCompanion 14 | import scala.concurrent.duration.FiniteDuration 15 | import ch.epfl.ts.data.Currency 16 | import ch.epfl.ts.component.fetch.TrueFxFetcher 17 | 18 | /** 19 | * StrategyFactory allowing to use Forex traders. 20 | */ 21 | abstract class ForexStrategyFactory(override val evaluationPeriod: FiniteDuration, 22 | override val referenceCurrency: Currency) 23 | extends StrategyFactory { 24 | 25 | protected abstract class ForexCommonProps extends CommonProps { 26 | def marketIds = List(MarketNames.FOREX_ID) 27 | 28 | // Market 29 | def market = { 30 | val rules = new FxMarketRulesWrapper() 31 | Props(classOf[MarketFXSimulator], marketIds(0), rules) 32 | } 33 | 34 | def broker = { 35 | Props(classOf[StandardBroker]) 36 | } 37 | 38 | // Printer 39 | override def printer = Some(Props(classOf[Printer], "MyPrinter")) 40 | } 41 | } 42 | 43 | class ForexLiveStrategyFactory(evaluationPeriod: FiniteDuration, referenceCurrency: Currency) 44 | extends ForexStrategyFactory(evaluationPeriod, referenceCurrency) { 45 | override def commonProps = new ForexCommonProps with LiveFetcher { 46 | def getFetcherInstance = new TrueFxFetcher 47 | } 48 | } 49 | 50 | class ForexReplayStrategyFactory(evaluationPeriod: FiniteDuration, referenceCurrency: Currency, 51 | val symbol: (Currency, Currency), 52 | val speed: Double, val start: String, val end: String, 53 | val workingDirectory: String = "./data") 54 | extends ForexStrategyFactory(evaluationPeriod, referenceCurrency) { self => 55 | 56 | override def commonProps = new ForexCommonProps with HistoricalFetcher { 57 | val symbol = self.symbol 58 | val speed = self.speed 59 | val start = self.start 60 | val end = self.end 61 | override val workingDirectory = self.workingDirectory 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/optimization/HostSystem.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.optimization 2 | 3 | import akka.actor.Deploy 4 | import akka.actor.Props 5 | import ch.epfl.ts.component.ComponentBuilder 6 | import ch.epfl.ts.component.ComponentRef 7 | import akka.actor.Address 8 | import akka.remote.RemoteScope 9 | 10 | /** 11 | * Abstraction for "something that lets you create components". 12 | * This is mostly useful in its overriden form: 13 | * @see {@link RemoteHost} 14 | * 15 | * @param namingPrefix Prefix that will precede the name of each actor on this remote 16 | * Can be left empty if you are sure that there will be no naming conflict 17 | */ 18 | abstract class AnyHost(val namingPrefix: String = "") { 19 | def actualName(name: String) = namingPrefix match { 20 | case "" => name 21 | case _ => namingPrefix + '-' + name 22 | } 23 | 24 | def actorOf(props: Props, name: String)(implicit builder: ComponentBuilder): ComponentRef 25 | } 26 | 27 | /** 28 | * Typical local actor system (not using remote deployment) 29 | */ 30 | class HostActorSystem(namingPrefix: String = "") extends AnyHost(namingPrefix) { 31 | override def actorOf(props: Props, name: String)(implicit builder: ComponentBuilder): ComponentRef = 32 | builder.createRef(props, actualName(name)) 33 | } 34 | 35 | /** 36 | * @param systemName Name of the actor system being run on this remote 37 | */ 38 | class RemoteHost(val hostname: String, val port: Int, 39 | namingPrefix: String = "", val systemName: String = "remote") 40 | extends AnyHost(namingPrefix) { 41 | 42 | val address = Address("akka.tcp", systemName, hostname, port) 43 | val deploy = Deploy(scope = RemoteScope(address)) 44 | 45 | override def actorOf(props: Props, name: String)(implicit builder: ComponentBuilder): ComponentRef = { 46 | // TODO: use `log.debug` 47 | println("Creating remotely component " + actualName(name) + " at host " + hostname) 48 | builder.createRef(props.withDeploy(deploy), actualName(name)) 49 | } 50 | 51 | override def toString(): String = systemName + "@" + hostname + ":" + port 52 | } -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/optimization/RemotingMasterRunner.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.optimization 2 | 3 | import scala.concurrent.duration.DurationInt 4 | import scala.language.postfixOps 5 | import ch.epfl.ts.data.Currency 6 | import ch.epfl.ts.data.CurrencyPairParameter 7 | import ch.epfl.ts.data.RealNumberParameter 8 | import ch.epfl.ts.data.TimeParameter 9 | import ch.epfl.ts.data.WalletParameter 10 | import ch.epfl.ts.engine.Wallet 11 | import ch.epfl.ts.example.AbstractOptimizationExample 12 | import ch.epfl.ts.traders.MovingAverageTrader 13 | import ch.epfl.ts.example.RemotingDeployment 14 | import ch.epfl.ts.component.StartSignal 15 | import scala.concurrent.duration.FiniteDuration 16 | 17 | /** 18 | * Runs a main() method that creates all remote systems 19 | * as well as an `OptimizationSupervisor` to watch them all. 20 | * It assumes that there is a `RemotingWorker` class running and listening 21 | * on every availableWorker. 22 | * 23 | * @see {@link ch.epfl.ts.optimization.RemotingWorker} 24 | */ 25 | object RemotingMasterRunner extends AbstractOptimizationExample with RemotingDeployment { 26 | 27 | override val maximumRunDuration: Option[FiniteDuration] = None 28 | 29 | val availableHosts = { 30 | val availableWorkers = List( 31 | "ts-1-021qv44y.cloudapp.net", 32 | "ts-2.cloudapp.net", 33 | "ts-3.cloudapp.net", 34 | "ts-4.cloudapp.net", 35 | "ts-5.cloudapp.net", 36 | "ts-6.cloudapp.net", 37 | "ts-7.cloudapp.net", 38 | "ts-8.cloudapp.net" 39 | ) 40 | val port = 3333 41 | // TODO: lookup in configuration 42 | val systemName = "remote" 43 | 44 | availableWorkers.map(hostname => { 45 | val prefix = hostname.substring(0, 4) 46 | new RemoteHost(hostname, port, prefix, systemName) 47 | }) 48 | } 49 | 50 | val symbol = (Currency.USD, Currency.CHF) 51 | 52 | val useLiveData = false 53 | val replaySpeed = 4000.0 54 | val startDate = "201304" 55 | val endDate = "201304" 56 | 57 | // ----- Trading strategy 58 | val maxInstances = (10 * availableHosts.size) 59 | val strategy = MovingAverageTrader 60 | val parametersToOptimize = Set( 61 | MovingAverageTrader.SHORT_PERIODS, 62 | MovingAverageTrader.LONG_PERIODS 63 | ) 64 | val otherParameterValues = { 65 | val initialWallet: Wallet.Type = Map(symbol._1 -> 0, symbol._2 -> 5000.0) 66 | Map(MovingAverageTrader.INITIAL_FUNDS -> WalletParameter(initialWallet), 67 | MovingAverageTrader.SYMBOL -> CurrencyPairParameter(symbol), 68 | MovingAverageTrader.OHLC_PERIOD -> new TimeParameter(1 day), 69 | MovingAverageTrader.TOLERANCE -> RealNumberParameter(0.0002)) 70 | } 71 | 72 | override def main(args: Array[String]): Unit = { 73 | println("Going to distribute " + parameterizations.size + " traders over " + availableHosts.size + " worker machines.") 74 | 75 | // ----- Create instances over many hosts 76 | def distributed = factory.distributeOverHosts(availableHosts, parameterizations) 77 | lazy val deployments = distributed.map({ case (host, parameters) => 78 | println("Creating " + parameters.size + " instances of " + strategy.getClass.getSimpleName + " on host " + host) 79 | factory.createDeployment(host, strategy, parameterizations, traderNames) 80 | }) 81 | 82 | // ----- Connections 83 | deployments.foreach(makeConnections(_)) 84 | 85 | // ----- Start 86 | // Make sure brokers are started before the traders 87 | supervisorActor.get.ar ! StartSignal 88 | for(d <- deployments) d.broker.ar ! StartSignal 89 | builder.start 90 | 91 | // ----- Registration to the supervisor 92 | // Register each new trader to the master 93 | for(d <- deployments; e <- d.evaluators) { 94 | supervisorActor.get.ar ! e.ar 95 | } 96 | 97 | // ----- Controlled duration (optional) 98 | maximumRunDuration match { 99 | case Some(duration) => terminateOptimizationAfter(duration, supervisorActor.get.ar) 100 | case None => 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/optimization/RemotingWorkerRunner.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.optimization 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import akka.actor.Actor 5 | import akka.actor.ActorSelection.toScala 6 | import akka.actor.ActorSystem 7 | import akka.actor.Props 8 | import akka.actor.ActorRef 9 | 10 | object RemotingWorkerRunner { 11 | def main(args: Array[String]): Unit = { 12 | 13 | // `akka.remote.netty.tcp.hostname` is specified on a per-machine basis in the `application.conf` file 14 | val remotingConfig = ConfigFactory.parseString( 15 | """ 16 | akka.actor.provider = "akka.remote.RemoteActorRefProvider" 17 | akka.remote.enabled-transports = ["akka.remote.netty.tcp"] 18 | akka.remote.netty.tcp.bind-hostname = "0.0.0.0" 19 | akka.remote.netty.tcp.port = 3333 20 | """).withFallback(ConfigFactory.load()); 21 | 22 | implicit val system = ActorSystem("remote", remotingConfig) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/optimization/StrategyOptimizer.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.optimization 2 | 3 | import ch.epfl.ts.component.Component 4 | import ch.epfl.ts.traders.TraderCompanion 5 | import akka.actor.Props 6 | import ch.epfl.ts.data.Parameter 7 | import ch.epfl.ts.data.StrategyParameters 8 | import ch.epfl.ts.data.StrategyParameters 9 | import ch.epfl.ts.traders.TraderCompanion 10 | 11 | /** 12 | * Provides helper methods for paralllel strategy optimization. 13 | */ 14 | object StrategyOptimizer { 15 | private type GridPoint = Map[String, Parameter] 16 | 17 | /** 18 | * 19 | * @param strategyToOptimize 20 | * @param parametersToOptimize Set of parameter names (as defined in the TraderCompanion) to optimize on 21 | * @param otherParameterValues Values for each parameter we are not optimizing on 22 | * @param maxInstances Maximum number of traders to instantiate. It might not be perfectly respected 23 | * depending on the number of parameters to optimize for. 24 | */ 25 | def generateParameterizations(strategyToOptimize: TraderCompanion, 26 | parametersToOptimize: Set[String], 27 | otherParameterValues: Map[String, Parameter], 28 | maxInstances: Int = 50) = { 29 | 30 | // Check that all keys passed actually exist for this strategy 31 | parametersToOptimize.foreach { 32 | key => assert(strategyToOptimize.parameters.contains(key), "Strategy " + strategyToOptimize + " doesn't have a parameter " + key) 33 | } 34 | 35 | val dimensions = parametersToOptimize.size 36 | val maxPerAxis = Math.pow(maxInstances.toDouble, 1.0 / dimensions.toDouble).ceil.toInt 37 | 38 | if(maxInstances < dimensions) { 39 | throw new IllegalArgumentException("Given " + maxInstances + " instances, we cannot optimize for " + parametersToOptimize.size + " parameters") 40 | } 41 | 42 | 43 | val grid = generateGrid(strategyToOptimize, parametersToOptimize, maxPerAxis) 44 | val parameterizations = generateParametersWith(grid, otherParameterValues) 45 | parameterizations.foreach(p => strategyToOptimize.verifyParameters(p)) 46 | 47 | parameterizations 48 | } 49 | 50 | private def generateGrid(strategyToOptimize: TraderCompanion, parametersToOptimize: Set[String], nPerDimension: Int): List[GridPoint] = { 51 | // TODO: find a clever way to balance over parameter space 52 | // TODO: some dimensions may have very small size, balance out with the other ones 53 | val axes = parametersToOptimize.map(key => { 54 | // TODO: randomize somehow, instead of always taking the first (wrong values) 55 | val parameter = strategyToOptimize.parameters.get(key).get 56 | val values = parameter.validValues.take(nPerDimension) 57 | val parameterInstances: Iterable[Parameter] = values.map((v: parameter.T) => parameter.getInstance(v)) 58 | (key -> parameterInstances.toSet) 59 | }).toList 60 | 61 | subgrid(axes) 62 | } 63 | 64 | /** Generate all points of our `dimensions`-dimensional grid */ 65 | private def subgrid(l: List[(String, Set[Parameter])]): List[GridPoint]= { 66 | if(l.isEmpty) List() 67 | else if(l.length == 1) { 68 | l.head._2.toList.map(v => Map(l.head._1 -> v)) 69 | } 70 | else { 71 | // Enumerate all subgrids from one axis 72 | val key = l.head._1 73 | val values = l.head._2 74 | 75 | values.toList.flatMap(v => { 76 | // TODO: there is likely a lot of repeated work, should use dynamic programming? 77 | subgrid(l.tail).map(m => m + (key -> v)) 78 | }) 79 | } 80 | } 81 | 82 | /** 83 | * Provided values for the parameters we are *not* optimizing on, 84 | * generate and verify actual `StrategyParameters` 85 | */ 86 | private def generateParametersWith(grid: List[GridPoint], otherParameterValues: Map[String, Parameter]): List[StrategyParameters] = { 87 | val merged = grid.map(m => m ++ otherParameterValues) 88 | 89 | merged.map(m => { new StrategyParameters(m.toList : _*) }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/traders/SimpleTraderWithBroker.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.traders 2 | 3 | import scala.concurrent.duration.DurationInt 4 | import scala.language.postfixOps 5 | import akka.actor.ActorLogging 6 | import akka.actor.ActorRef 7 | import akka.pattern.ask 8 | import akka.util.Timeout 9 | import ch.epfl.ts.data.ConfirmRegistration 10 | import ch.epfl.ts.data.Currency 11 | import ch.epfl.ts.data.MarketBidOrder 12 | import ch.epfl.ts.data.MarketOrder 13 | import ch.epfl.ts.data.Order 14 | import ch.epfl.ts.data.Quote 15 | import ch.epfl.ts.data.Register 16 | import ch.epfl.ts.data.StrategyParameters 17 | import ch.epfl.ts.engine.{GetWalletFunds, WalletConfirm, FundWallet, WalletFunds, ExecutedAskOrder, AcceptedOrder, RejectedOrder} 18 | import ch.epfl.ts.engine.ExecutedBidOrder 19 | import ch.epfl.ts.engine.Wallet 20 | 21 | /** 22 | * SimpleTraderWithBroker companion object 23 | */ 24 | object SimpleTraderWithBroker extends TraderCompanion { 25 | type ConcreteTrader = SimpleTraderWithBroker 26 | override protected val concreteTraderTag = scala.reflect.classTag[SimpleTraderWithBroker] 27 | 28 | override def strategyRequiredParameters = Map() 29 | } 30 | 31 | /** 32 | * Dummy broker-aware trader. 33 | */ 34 | 35 | class SimpleTraderWithBroker(uid: Long, marketIds: List[Long], parameters: StrategyParameters) 36 | extends Trader(uid, marketIds, parameters) { 37 | 38 | // Allows the usage of ask pattern in an Actor 39 | import context.dispatcher 40 | 41 | override def companion = SimpleTraderWithBroker 42 | 43 | var broker: ActorRef = null 44 | var registered = false 45 | var oid = 1L 46 | 47 | override def receiver = { 48 | case q: Quote => { 49 | log.debug("TraderWithB receided a quote: " + q) 50 | } 51 | case ConfirmRegistration => { 52 | broker = sender() 53 | registered = true 54 | log.debug("TraderWithB: Broker confirmed") 55 | } 56 | case WalletFunds(uid, funds: Wallet.Type) => { 57 | log.debug("TraderWithB: money I have: ") 58 | for(i <- funds.keys) yield {log.debug(i + " -> " + funds.get(i))} 59 | } 60 | 61 | case WalletConfirm(tid) => { 62 | if (uid != tid) 63 | log.error("TraderWithB: Broker replied to the wrong trader") 64 | log.debug("TraderWithB: Got a wallet confirmation") 65 | } 66 | 67 | case _: ExecutedAskOrder => { 68 | log.debug("TraderWithB: Got an executed order") 69 | } 70 | case _: ExecutedBidOrder => { 71 | log.debug("TraderWithB: Got an executed order") 72 | } 73 | 74 | case 'sendTooBigOrder => { 75 | val order = MarketBidOrder(oid, uid, currentTimeMillis, Currency.CHF, Currency.USD, 1000.0, 100000.0) 76 | placeOrder(order) 77 | oid = oid + 1 78 | } 79 | case 'sendMarketOrder => { 80 | val order = MarketBidOrder(oid, uid, currentTimeMillis, Currency.CHF, Currency.USD, 3.0, 14.0) 81 | placeOrder(order) 82 | oid = oid + 1 83 | } 84 | case 'addFunds => { 85 | log.debug("TraderWithB: trying to add 100 bucks") 86 | send(FundWallet(uid, Currency.USD, 100)) 87 | } 88 | case 'knowYourWallet => { 89 | send(GetWalletFunds(uid,this.self)) 90 | } 91 | case p => { 92 | println("TraderWithB: received unknown: " + p) 93 | } 94 | } 95 | 96 | def placeOrder(order: MarketOrder) = { 97 | implicit val timeout = new Timeout(500 milliseconds) 98 | val future = (broker ? order).mapTo[Order] 99 | future onSuccess { 100 | case _: AcceptedOrder => log.debug("TraderWithB: order placement succeeded") 101 | case _: RejectedOrder => log.debug("TraderWithB: order failed") 102 | case _ => log.debug("TraderWithB: unknown order response") 103 | } 104 | future onFailure { 105 | case p => log.debug("Wallet command failed: " + p) 106 | } 107 | } 108 | 109 | override def init = { 110 | log.debug("TraderWithB received startSignal") 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ts/src/main/scala/ch/epfl/ts/traders/TransactionVwapTrader.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.traders 2 | 3 | import scala.language.postfixOps 4 | import scala.concurrent.duration.FiniteDuration 5 | import scala.concurrent.duration.DurationInt 6 | import ch.epfl.ts.component.Component 7 | import ch.epfl.ts.data.Currency 8 | import ch.epfl.ts.data.{MarketAskOrder, MarketBidOrder, Transaction} 9 | import ch.epfl.ts.data.{StrategyParameters, TimeParameter, NaturalNumberParameter, ParameterTrait} 10 | 11 | /** 12 | * Transaction VWAP trader companion object 13 | */ 14 | object TransactionVwapTrader extends TraderCompanion { 15 | type ConcreteTrader = TransactionVwapTrader 16 | override protected val concreteTraderTag = scala.reflect.classTag[TransactionVwapTrader] 17 | 18 | /** Time frame */ 19 | val TIME_FRAME = "TimeFrame" 20 | /** Volume to trade */ 21 | val VOLUME = "Volume" 22 | 23 | override def strategyRequiredParameters = Map( 24 | TIME_FRAME -> TimeParameter, 25 | VOLUME -> NaturalNumberParameter 26 | ) 27 | } 28 | 29 | /** 30 | * Transaction VWAP trader. 31 | */ 32 | class TransactionVwapTrader(uid: Long, marketIds: List[Long], parameters: StrategyParameters) extends Trader(uid, marketIds, parameters) { 33 | import context._ 34 | override def companion = TransactionVwapTrader 35 | 36 | val timeFrame = parameters.get[FiniteDuration](TransactionVwapTrader.TIME_FRAME) 37 | val volumeToTrade = parameters.get[Int](TransactionVwapTrader.VOLUME) 38 | 39 | var transactions: List[Transaction] = Nil 40 | var cumulativeTPV: Double = 0.0; 41 | var cumulativeVolume: Double = 0.0; 42 | var vwap: Double = 0.0; 43 | var tradingPrice: Double = 0.0; 44 | 45 | var oid = uid 46 | 47 | def priceOrdering = new Ordering[Transaction] { 48 | def compare(first: Transaction, second: Transaction): Int = 49 | if (first.price > second.price) 1 else if (first.price < second.price) -1 else 0 50 | } 51 | 52 | def timeOrdering = new Ordering[Transaction] { 53 | def compare(first: Transaction, second: Transaction): Int = 54 | if (first.timestamp > second.timestamp) 1 else if (first.timestamp < second.timestamp) -1 else 0 55 | } 56 | 57 | def receiver = { 58 | case t: Transaction => transactions = t :: transactions 59 | case 'Tick => { 60 | computeVWAP 61 | if (tradingPrice > vwap) { 62 | // sell 63 | println("TransactionVWAPTrader: sending market ask order") 64 | send(MarketAskOrder(oid, uid, currentTimeMillis, Currency.USD, Currency.USD, volumeToTrade, 0)) 65 | oid = oid + 1 66 | } else { 67 | // buy 68 | println("TransactionVWAPTrader: sending market bid order") 69 | send(MarketBidOrder(oid, uid, currentTimeMillis, Currency.USD, Currency.USD, volumeToTrade, 0)) 70 | oid = oid + 1 71 | } 72 | } 73 | case _ => println("vwapTrader: unknown message received") 74 | } 75 | 76 | def computeVWAP() = { 77 | if (!transactions.isEmpty) { 78 | val typicalPrice = (transactions.max(priceOrdering)).price * (transactions.min(priceOrdering)).price * (transactions.max(timeOrdering)).price / 3 79 | var frameVolume: Double = 0 80 | transactions.map { t => frameVolume = frameVolume + t.volume } 81 | cumulativeVolume = cumulativeVolume + frameVolume 82 | val TPV = typicalPrice * frameVolume 83 | cumulativeTPV = cumulativeTPV + TPV 84 | vwap = cumulativeTPV / cumulativeVolume 85 | tradingPrice = transactions.max(timeOrdering).price 86 | transactions = Nil 87 | } 88 | } 89 | 90 | override def init = { 91 | println("TransactionVwapTrader: Started") 92 | system.scheduler.schedule(0 milliseconds, timeFrame, self, 'Tick) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/HelloTest.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test 2 | 3 | import org.junit.runner.RunWith 4 | import org.scalatest.junit.JUnitRunner 5 | import org.scalatest.FunSuite 6 | 7 | @RunWith(classOf[JUnitRunner]) 8 | class HelloTest extends FunSuite { 9 | 10 | test("Tests are being executed") { 11 | assert(2 + 2 == 4) 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/TestHelpers.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test 2 | 3 | import scala.language.postfixOps 4 | import scala.concurrent.duration.DurationInt 5 | import scala.reflect.ClassTag 6 | 7 | import org.scalatest.BeforeAndAfterAll 8 | import org.scalatest.BeforeAndAfterEach 9 | import org.scalatest.WordSpecLike 10 | 11 | import com.typesafe.config.ConfigFactory 12 | 13 | import akka.actor.ActorRef 14 | import akka.actor.ActorSystem 15 | import akka.actor.actorRef2Scala 16 | import akka.testkit.TestKit 17 | import akka.util.Timeout 18 | import ch.epfl.ts.brokers.StandardBroker 19 | import ch.epfl.ts.component.ComponentBuilder 20 | import scala.concurrent.Await 21 | import ch.epfl.ts.engine.rules.FxMarketRulesWrapper 22 | import ch.epfl.ts.engine.{MarketFXSimulator, ForexMarketRules} 23 | 24 | 25 | object TestHelpers { 26 | def makeTestActorSystem(name: String = "TestActorSystem") = 27 | ActorSystem(name, ConfigFactory.parseString( 28 | """ 29 | akka.loglevel = "DEBUG" 30 | akka.loggers = ["akka.testkit.TestEventListener"] 31 | """ 32 | ).withFallback(ConfigFactory.load())) 33 | } 34 | 35 | /** 36 | * Common superclass for testing actors 37 | * @param name Name of the actor system 38 | */ 39 | abstract class ActorTestSuite(val name: String) 40 | extends TestKit(TestHelpers.makeTestActorSystem(name)) 41 | with WordSpecLike 42 | with BeforeAndAfterAll 43 | with BeforeAndAfterEach { 44 | 45 | implicit val builder = new ComponentBuilder(system) 46 | 47 | val shutdownTimeout = 3 seconds 48 | 49 | /** After all tests have run, shut down the system */ 50 | override def afterAll() = { 51 | TestKit.shutdownActorSystem(system, shutdownTimeout) 52 | } 53 | 54 | } 55 | 56 | /** 57 | * A bit dirty hack to allow ComponentRef-like communication between components, while having them in Test ActorSystem 58 | */ 59 | class SimpleBrokerWrapped(market: ActorRef) extends StandardBroker { 60 | override def send[T: ClassTag](t: T) { 61 | market ! t 62 | } 63 | 64 | override def send[T: ClassTag](t: List[T]) = t.map(market ! _) 65 | } 66 | 67 | /** 68 | * A bit dirty hack to allow ComponentRef-like communication between components, while having them in Test ActorSystem 69 | */ 70 | class FxMarketWrapped(uid: Long, rules: ForexMarketRules) extends MarketFXSimulator(uid, new FxMarketRulesWrapper(rules)) { 71 | import context.dispatcher 72 | override def send[T: ClassTag](t: T) { 73 | val brokerSelection = context.actorSelection("/user/brokers/*") 74 | implicit val timeout = new Timeout(100 milliseconds) 75 | val broker = Await.result(brokerSelection.resolveOne(), timeout.duration) 76 | println("Tried to get Broker: " + broker) 77 | println("Market sent to Broker ONLY: " + t) 78 | broker ! t 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/component/ComponentBuilder.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test.component 2 | 3 | import scala.concurrent.Await 4 | import scala.concurrent.duration.DurationInt 5 | import scala.language.postfixOps 6 | import scala.reflect.ClassTag 7 | import org.scalatest.junit.JUnitRunner 8 | import org.junit.runner.RunWith 9 | import akka.actor.Props 10 | import ch.epfl.ts.component.fetch.PullFetchComponent 11 | import ch.epfl.ts.component.fetch.TrueFxFetcher 12 | import ch.epfl.ts.component.utils.Printer 13 | import ch.epfl.ts.data.Currency 14 | import ch.epfl.ts.data.Quote 15 | import ch.epfl.ts.data.StrategyParameters 16 | import ch.epfl.ts.data.WalletParameter 17 | import ch.epfl.ts.evaluation.Evaluator 18 | import ch.epfl.ts.test.ActorTestSuite 19 | import ch.epfl.ts.traders.SimpleTraderWithBroker 20 | import ch.epfl.ts.component.ComponentRef 21 | import ch.epfl.ts.component.fetch.FetchingComponent 22 | import ch.epfl.ts.component.fetch.FetchingComponent 23 | 24 | @RunWith(classOf[JUnitRunner]) 25 | class ComponentBuilderTestSuite extends ActorTestSuite("ComponentBuilderTestSuite") { 26 | 27 | "A component builder" should { 28 | 29 | "Terminate actors should complete immediatly when there are no managed components" in { 30 | val components = Set() 31 | 32 | assert(builder.instances.toSet === components) 33 | val f = builder.shutdownManagedActors() 34 | Await.result(f, 100 milliseconds) 35 | assert(builder.instances === List()) 36 | } 37 | 38 | "Terminate actors and clean its `instances` list when done" in { 39 | val components = Set(builder.createRef(Props(classOf[Printer], "Printer"), "DummyActor1"), 40 | builder.createRef(Props(classOf[Printer], "Printer"), "DummyActor2"), 41 | builder.createRef(Props(classOf[Printer], "Printer"), "DummyActor3")) 42 | 43 | assert(builder.instances.toSet === components) 44 | val f = builder.shutdownManagedActors() 45 | Await.result(f, 3 seconds) 46 | assert(builder.instances === List()) 47 | } 48 | 49 | "Create components under the correct root" in { 50 | val printer = builder.createRef(Props(classOf[Printer], "Printer"), "printer") 51 | val fetcher = builder.createRef(Props(classOf[PullFetchComponent[Quote]], new TrueFxFetcher, implicitly[ClassTag[Quote]]), "fetcher") 52 | 53 | val traderId = 42L 54 | val parameters = new StrategyParameters( 55 | SimpleTraderWithBroker.INITIAL_FUNDS -> WalletParameter(Map(Currency.CHF -> 1000.0)) 56 | ) 57 | val trader = SimpleTraderWithBroker.getInstance(traderId, List(1L), parameters, "trader") 58 | 59 | val period = 10 seconds 60 | val referenceCurrency = Currency.CHF 61 | val evaluator = builder.createRef(Props(classOf[Evaluator], trader.ar, traderId, trader.name, referenceCurrency, period), "evaluator") 62 | 63 | def testName(root: String, component: ComponentRef) = { 64 | val protocol = component.ar.path.address.protocol 65 | assert(component.ar.path.toString() === protocol + "://" + system.name + "/user/" + root + "/" + component.name) 66 | } 67 | 68 | testName("other", printer) 69 | testName("fetchers", fetcher) 70 | testName("traders", trader) 71 | testName("evaluators", evaluator) 72 | } 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/component/Timekeeper.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test.component 2 | 3 | import scala.concurrent.duration.DurationInt 4 | import scala.language.postfixOps 5 | import org.junit.runner.RunWith 6 | import org.scalatest.junit.JUnitRunner 7 | import akka.actor.Props 8 | import ch.epfl.ts.component.utils.Timekeeper 9 | import ch.epfl.ts.data.TheTimeIs 10 | import ch.epfl.ts.test.ActorTestSuite 11 | import ch.epfl.ts.component.Component 12 | 13 | @RunWith(classOf[JUnitRunner]) 14 | class TimekeeperTestSuite extends ActorTestSuite("TimekeeperTestSuite") { 15 | 16 | "A Timekeeper" should { 17 | val base = 42L; 18 | val timePeriod = (250 milliseconds) 19 | 20 | val test = system.actorOf(Props(classOf[Timekeeper], testActor, base, timePeriod), "Timekeeper") 21 | val offset = now 22 | 23 | "Send out a first timestamp right away, using the given base" in { 24 | val timeout = (100 milliseconds) 25 | within(timeout) { 26 | val received = expectMsgType[TheTimeIs] 27 | assert(received.now >= base, "Timestamps should never be smaller than the base timestamp") 28 | assert(received.now <= base + timeout.toMillis, "Timestamps should never be larger than base + physical time") 29 | } 30 | } 31 | 32 | "Send messages periodically" in { 33 | val timeout = (2100 milliseconds) 34 | within(timeout) { 35 | val received = receiveN(8).toList 36 | assert(received.forall { m => m.isInstanceOf[TheTimeIs] }) 37 | val times = received.map { m => m.asInstanceOf[TheTimeIs].now } 38 | assert(times.sorted === times, "Timestamps should be increasing") 39 | assert(times.forall { t => t >= base}, "Timestamps should never be smaller than the base timestamp") 40 | assert(times.forall { t => t <= base + timeout.toMillis }, "Timestamps should never be larger than base + physical time") 41 | 42 | } 43 | } 44 | 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/component/evaluation/EvaluationReportTestSuite.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test.component.evaluation 2 | 3 | import ch.epfl.ts.evaluation.EvaluationReport 4 | import org.junit.runner.RunWith 5 | import org.scalatest.junit.JUnitRunner 6 | import org.scalatest.FunSuite 7 | import ch.epfl.ts.data.Currency 8 | 9 | 10 | @RunWith(classOf[JUnitRunner]) 11 | class EvaluationReportTestSuite extends FunSuite { 12 | val a = EvaluationReport(100L, "a", Map(), Currency.EUR, 10.0, 50.0, 2.4, 0.3, 20, 20) 13 | val b = EvaluationReport(101L, "b", Map(), Currency.EUR, 10.0, 50.0, 2.5, 0.3, 20, 10) 14 | 15 | test("compare report a < b") { 16 | assert(a < b) 17 | } 18 | 19 | test("compare report a > b") { 20 | assert(b > a) 21 | } 22 | 23 | test("compare report a = b") { 24 | val a = EvaluationReport(101L, "b", Map(), Currency.EUR, 10.0, 50.0, 2.4, 0.3, 20, 10) 25 | val b = EvaluationReport(100L, "a", Map(), Currency.EUR, 10.0, 50.0, 2.4, 0.3, 20, 10) 26 | assert(a >= b && a <= b) 27 | } 28 | 29 | test("compare with sorted") { 30 | val seq = Seq(a, b) 31 | assert(seq.sorted[EvaluationReport] == Seq(a, b)) 32 | } 33 | 34 | test("compare with sortBy") { 35 | val seq = Seq(a, b) 36 | assert(seq.sortBy(_.sharpeRatio) == Seq(b, a)) 37 | } 38 | 39 | test("compare with sortWith") { 40 | val seq = Seq(a, b) 41 | assert(seq.sortWith(_.sharpeRatio > _.sharpeRatio) == Seq(a, b)) 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/component/fetch/TrueFxFetcher.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test.component.fetch 2 | 3 | import org.junit.runner.RunWith 4 | import org.scalatest.junit.JUnitRunner 5 | import org.scalatest.FunSuite 6 | import ch.epfl.ts.component.fetch.TrueFxFetcher 7 | import ch.epfl.ts.data.Quote 8 | import scala.tools.nsc.interpreter.Power 9 | import ch.epfl.ts.data.Currency 10 | import org.scalatest.WordSpec 11 | import org.scalatest.WordSpecLike 12 | import java.util.Calendar 13 | 14 | 15 | @RunWith(classOf[JUnitRunner]) 16 | class TrueFxFetcherTestSuite extends WordSpec { 17 | 18 | val fetcher: TrueFxFetcher = new TrueFxFetcher() 19 | val currencyPairs = List((Currency.EUR, Currency.CHF), (Currency.EUR, Currency.USD)) 20 | val focusedFetcher: TrueFxFetcher = new TrueFxFetcher(currencyPairs) 21 | val nRequests = 10 22 | /** Delay between two requests in milliseconds */ 23 | val delay = 250L 24 | 25 | val today = Calendar.getInstance.get(Calendar.DAY_OF_WEEK) 26 | val isWeekend = (today == Calendar.SATURDAY || today == Calendar.SUNDAY) 27 | 28 | if(!isWeekend) { 29 | "TrueFX API on weekdays" should { 30 | "be reachable on weekdays" in { 31 | val fetched = fetcher.fetch() 32 | assert(fetched.length > 0 || isWeekend) 33 | } 34 | 35 | "allows several consecutive requests" in { 36 | for(i <- 1 to nRequests) { 37 | val fetched = fetcher.fetch() 38 | assert(fetched.length > 0) 39 | Thread.sleep(delay) 40 | } 41 | } 42 | } 43 | 44 | "TrueFXFetcher on weekdays" should { 45 | "give out all significant digits" in { 46 | val fetched = fetcher.fetch() 47 | val significantDigits = 6 48 | 49 | def extractStrings(quotes: List[Quote]): List[String] = fetched.flatMap(q => q match { 50 | case Quote(_, _, _, _, bid, ask) => List(bid.toString(), ask.toString()) 51 | }) 52 | 53 | /** 54 | * To verify we are given enough digits, check the last digit 55 | * is different for the various prices. 56 | * Although it may report a false negative with extremely low 57 | * probability, it will always fail if less significant digits 58 | * are given. 59 | * Warning Prices are on different scales, we should take care that: 60 | * e.g. JPY 128.634 has as many significant digits as CHF 1.06034 61 | */ 62 | val strings = extractStrings(fetched) 63 | 64 | // +1 takes into account the '.' separator, +2 is too much 65 | assert(strings.exists { s => s.length() == significantDigits + 1 }) 66 | assert(strings.forall { s => s.length() < significantDigits + 2 }) 67 | } 68 | 69 | "focus on user-specified currency pairs" in { 70 | val fetched = focusedFetcher.fetch() 71 | fetched.map(q => { 72 | assert(currencyPairs.contains((q.whatC, q.withC)), q + " was not of interest to the user") 73 | }) 74 | } 75 | 76 | "not contain all (valid) currency pairs specified by the user" in { 77 | val fetched = focusedFetcher.fetch() 78 | 79 | assert(fetched.length == currencyPairs.length, "User is interested in " + currencyPairs.length + " currency pairs") 80 | currencyPairs.map(p => { 81 | assert(fetched.exists(q => q.whatC == p._1 && q.withC == p._2), "User is interested in " + p + " but it was not mentionned") 82 | }) 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/config/FundDistributionTest.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test 2 | 3 | import org.junit.runner.RunWith 4 | import org.scalatest.junit.JUnitRunner 5 | import org.scalatest.FunSuite 6 | import ch.epfl.ts.config._ 7 | 8 | @RunWith(classOf[JUnitRunner]) 9 | class FundDistributionTest extends FunSuite { 10 | 11 | test("Local fund distribution test") { 12 | val fd_USD = new FundsUSA 13 | // val fd_CHF = new FundsSwitzerland 14 | // val fd_RUR = new FundsRussia 15 | // val fd_EUR = new FundsGermany 16 | // val fd_JPY = new FundsJapan 17 | // val fd_GBP = new FundsUK 18 | // val fd_AUD = new FundsAustralia 19 | // val fd_CAD = new FundsCanada 20 | 21 | // 1 22 | var distrInPercent = List( 23 | (1, 50.0), 24 | (2, 50.0)) 25 | var maxVal = distrInPercent.maxBy(_._1) 26 | var distr = fd_USD.percentListToDistribution(distrInPercent) 27 | 28 | var num1s = distr.foldRight(0)((elem, acc) => if (elem == 1) acc + 1 else acc) 29 | var num2s = distr.foldRight(0)((elem, acc) => if (elem >= 2) acc + 1 else acc) 30 | 31 | assert(num1s == 1) 32 | assert(num2s == 1) 33 | 34 | 35 | // 2 36 | distrInPercent = List( 37 | (1, 20.0), 38 | (2, 20.0), 39 | (3, 60.0)) // 60 % of the people own more than 2 40 | maxVal = distrInPercent.maxBy(_._1) 41 | distr = fd_USD.percentListToDistribution(distrInPercent) 42 | 43 | num1s = distr.foldRight(0)((elem, acc) => if (elem == 1) acc + 1 else acc) 44 | num2s = distr.foldRight(0)((elem, acc) => if (elem == 2) acc + 1 else acc) 45 | var num3s = distr.foldRight(0)((elem, acc) => if (elem >= 3) acc + 1 else acc) 46 | 47 | assert(num1s == 1) 48 | assert(num2s == 1) 49 | assert(num3s == 3) 50 | 51 | // 3 52 | distrInPercent = List( 53 | (1, 1.0), 54 | (2, 95.0), 55 | (3, 4.0)) // 4 % of the people own more than 2 56 | maxVal = distrInPercent.maxBy(_._1) 57 | distr = fd_USD.percentListToDistribution(distrInPercent) 58 | 59 | num1s = distr.foldRight(0)((elem, acc) => if (elem == 1) acc + 1 else acc) 60 | num2s = distr.foldRight(0)((elem, acc) => if (elem == 2) acc + 1 else acc) 61 | num3s = distr.foldRight(0)((elem, acc) => if (elem >= 3) acc + 1 else acc) 62 | 63 | assert(num1s == 1) 64 | assert(num2s == 95) 65 | assert(num3s == 4) 66 | 67 | // 4 68 | distrInPercent = List( 69 | (1, 3.0), 70 | (2, 93.0), 71 | (3, 4.0)) // 60 % of the people own more than 2 72 | maxVal = distrInPercent.maxBy(_._1) 73 | distr = fd_USD.percentListToDistribution(distrInPercent) 74 | 75 | num1s = distr.foldRight(0)((elem, acc) => if (elem == 1) acc + 1 else acc) 76 | num2s = distr.foldRight(0)((elem, acc) => if (elem == 2) acc + 1 else acc) 77 | num3s = distr.foldRight(0)((elem, acc) => if (elem >= 3) acc + 1 else acc) 78 | 79 | assert(num1s == 1) 80 | assert(num2s == 31) 81 | assert(num3s == 1) 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/data/CurrencyTest.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test.data 2 | 3 | import org.junit.runner.RunWith 4 | import org.scalatest.junit.JUnitRunner 5 | import org.scalatest.FunSuite 6 | import ch.epfl.ts.data.Currency 7 | 8 | @RunWith(classOf[JUnitRunner]) 9 | class CurrencyTestSuite extends FunSuite { 10 | 11 | test("toString and fromString are inverting each other") { 12 | val currencies = Currency.values; 13 | currencies.foreach(c => { 14 | assert(Currency.fromString(c.toString) == c, c + " fromString(toString) should be the same currency") 15 | }) 16 | } 17 | 18 | test("pairFromString handles different case input") { 19 | val lowercase = "eurchf" 20 | val uppercase = "EURCHF" 21 | val mixedcase = "EurChf" 22 | assert(Currency.pairFromString(lowercase) == (Currency.EUR, Currency.CHF), "Lower case") 23 | assert(Currency.pairFromString(uppercase) == (Currency.EUR, Currency.CHF), "Upper case") 24 | assert(Currency.pairFromString(mixedcase) == (Currency.EUR, Currency.CHF), "Mixed case") 25 | } 26 | } -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/traders/MadTraderTest.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test.traders 2 | 3 | import scala.concurrent.duration.DurationLong 4 | import scala.language.postfixOps 5 | import org.junit.runner.RunWith 6 | import org.scalatest.junit.JUnitRunner 7 | import org.scalatest.WordSpecLike 8 | import akka.testkit.TestKit 9 | import akka.testkit.EventFilter 10 | import ch.epfl.ts.component.ComponentBuilder 11 | import ch.epfl.ts.data.CoefficientParameter 12 | import ch.epfl.ts.data.Currency 13 | import ch.epfl.ts.data.CurrencyPairParameter 14 | import ch.epfl.ts.data.NaturalNumberParameter 15 | import ch.epfl.ts.data.StrategyParameters 16 | import ch.epfl.ts.data.TimeParameter 17 | import ch.epfl.ts.test.TestHelpers 18 | import ch.epfl.ts.traders.MadTrader 19 | import ch.epfl.ts.engine.GetWalletFunds 20 | import ch.epfl.ts.data.WalletParameter 21 | import ch.epfl.ts.engine.Wallet 22 | import ch.epfl.ts.test.ActorTestSuite 23 | import ch.epfl.ts.component.fetch.MarketNames 24 | 25 | @RunWith(classOf[JUnitRunner]) 26 | class MadTraderTest 27 | extends ActorTestSuite("MadTraderTest") { 28 | 29 | val traderId = 42L 30 | val currencies = (Currency.EUR, Currency.CHF) 31 | val initialFunds: Wallet.Type = Map(currencies._2 -> 1000.0) 32 | val initialDelay = 100 milliseconds 33 | val interval = 50 milliseconds 34 | val volume = 100 35 | val volumeVariation = 0.1 36 | val marketId = MarketNames.FOREX_ID 37 | 38 | /** Give a little margin of error in our timing assumptions */ 39 | val gracePeriod = (10 milliseconds) 40 | 41 | val parameters = new StrategyParameters( 42 | MadTrader.INITIAL_FUNDS -> WalletParameter(initialFunds), 43 | MadTrader.CURRENCY_PAIR -> CurrencyPairParameter(currencies), 44 | MadTrader.INITIAL_DELAY -> new TimeParameter(initialDelay), 45 | MadTrader.INTERVAL -> new TimeParameter(interval), 46 | MadTrader.ORDER_VOLUME -> NaturalNumberParameter(volume), 47 | MadTrader.ORDER_VOLUME_VARIATION -> CoefficientParameter(volumeVariation) 48 | ) 49 | 50 | // TODO: refactor generic strategy testing from `StrategyParameter` test suite? 51 | val trader = MadTrader.getInstance(traderId, List(marketId), parameters, "MadTrader") 52 | 53 | "A MadTrader" should { 54 | "send its first order within the given initial delay" in { 55 | within(initialDelay + gracePeriod) { 56 | // TODO 57 | assert(true) 58 | } 59 | } 60 | 61 | "send orders regularly based on the given interval" in { 62 | within(initialDelay + interval + 2 * gracePeriod) { 63 | // TODO 64 | assert(true) 65 | } 66 | } 67 | 68 | "respect respect the given volume" in { 69 | // TODO 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /ts/src/test/scala/ch/epfl/ts/test/traders/RsiIndicatorTest.scala: -------------------------------------------------------------------------------- 1 | package ch.epfl.ts.test.traders 2 | 3 | import org.junit.runner.RunWith 4 | import akka.actor.Props 5 | import ch.epfl.ts.indicators.RsiIndicator 6 | import ch.epfl.ts.test.ActorTestSuite 7 | import ch.epfl.ts.data.Quote 8 | import org.scalatest.junit.JUnitRunner 9 | import ch.epfl.ts.data.OHLC 10 | import akka.testkit.EventFilter 11 | import scala.language.postfixOps 12 | import scala.concurrent.duration.FiniteDuration 13 | import scala.concurrent.duration.DurationInt 14 | 15 | @RunWith(classOf[JUnitRunner]) 16 | class RsiIndicatorTest extends ActorTestSuite("RsiIndicator") { 17 | 18 | val period = 5 19 | val rsiIndicator = system.actorOf(Props(classOf[RsiIndicator], period),"RsiIndicator") 20 | 21 | 22 | "An indicator " should { 23 | 24 | "receive a quote " in { 25 | within(1 second) { 26 | EventFilter.debug(message = "receive OHLC", occurrences = 1) intercept { 27 | rsiIndicator ! OHLC(1L, 1.0, 1.0,1.0,1.0,1.0,0L,0L) 28 | 29 | } 30 | } 31 | } 32 | "wait to receive period + 1 OHLC" in { 33 | within(1 second){ 34 | EventFilter.debug(message = "building datas", occurrences = 5) intercept { 35 | rsiIndicator ! OHLC(1L, 2.0, 2.0,2.0,2.0,2.0,0L,0L) 36 | rsiIndicator ! OHLC(1L, 1.0, 1.0,1.0,1.0,1.0,0L,0L) 37 | rsiIndicator ! OHLC(1L, 2.0, 2.0,2.0,2.0,2.0,0L,0L) 38 | rsiIndicator ! OHLC(1L, 1.0, 1.0,1.0,1.0,1.0,0L,0L) 39 | rsiIndicator ! OHLC(1L, 2.0, 2.0,2.0,2.0,2.0,0L,0L) 40 | } 41 | } 42 | } 43 | 44 | "send RSI when available " in { 45 | within(1 second) { 46 | EventFilter.debug(message = "send RSI", occurrences = 1) intercept { 47 | rsiIndicator ! OHLC(1L, 1.0, 1.0,1.0,1.0,1.0,0L,0L) 48 | } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /wiki/figures/btce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/wiki/figures/btce.png -------------------------------------------------------------------------------- /wiki/figures/evaluation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/wiki/figures/evaluation.png -------------------------------------------------------------------------------- /wiki/figures/forex-live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/wiki/figures/forex-live.png -------------------------------------------------------------------------------- /wiki/figures/forex-replay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/wiki/figures/forex-replay.png -------------------------------------------------------------------------------- /wiki/figures/full-simulation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merlinND/TradingSimulation/0dde3971f26714d203c66dbfdb5af8abe55a03b6/wiki/figures/full-simulation.png --------------------------------------------------------------------------------