├── .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 | {{ t.timestamp | date:'MM.dd.yyyy HH:mm:ss' }} |
19 | {{ t.whatC.s | uppercase }} / {{ t.withC.s | uppercase }} |
20 | {{ t.price }} |
21 | {{ t.volume }} |
22 | {{ t.buyerId }} |
23 | {{ t.sellerId }} |
24 |
25 |
26 |
27 |
28 |
29 | All currencies converted to {{ referenceCurrency }}
30 |
31 |
32 | {{ report.traderId }} |
33 | {{ report.traderName }} |
34 | {{ report.initial | number: 2 }} |
35 | {{ report.current | number: 2 }} |
36 | {{ report.totalReturns | percentage: 2 }} |
37 | {{ report.volatility | number: 2 }} |
38 | {{ report.drawdown | percentage: 2 }} |
39 |
40 |
41 | - {{ currency }} : {{ value | number: 2 }}
42 |
43 | |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {{ trader.id }} |
52 | {{ trader.name }} |
53 | {{ trader.strategy }} |
54 |
55 |
58 | |
59 |
60 | Show transactions on graph
61 | |
62 |
63 |
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
--------------------------------------------------------------------------------