Skip to content

Commit

Permalink
cleanup sample code
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Dekkers committed Aug 18, 2022
1 parent 8aad2f8 commit bc0a126
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 118 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
jbake_sample_sqlite.db
output
build
target
*.dylib
.DS_Store
*.class
Expand Down
14 changes: 12 additions & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@ image:https://img.shields.io/github/issues/neurallayer/roboquant.org/enhancement
image:https://img.shields.io/github/last-commit/neurallayer/roboquant.org[GitHub last commit]
image:https://img.shields.io/github/commit-activity/m/neurallayer/roboquant.org[GitHub commit activity]

This repo contains the content for the roboquant.org website. It uses JBake as the generator for the static website and AsciiDoc for most of the documentation. All the Kotlin code samples used in the documentation are in the samples directory and are compiled to make sure they are correct and still up to date.
This repo contains the content for the roboquant.org website. It uses JBake as the generator for the static website and AsciiDoc for most of the documentation. All the Kotlin code samples used in the documentation are in the samples directory and can be compiled to make sure they are correct and still up to date.

Check the roboquant site at https://roboquant.org[roboquant.org]

== Build
To generate the website (output will be in the ./build directory), run:
To generate the website (output will be in the `./build directory)`, run:

[source,shell]
----
mvn jbake:generate
----

If you also want to validate if the included code snippets (found in the `./samples` directory) still compile, you can run:
[source,shell]
----
mvn compile
----

NOTE: If you are on Apple Silicon (the M1 processor), the default JBake installation won't work. But the JBake Maven plugin used in this project has an upgraded version of the OrientDB library that should work (but not very well tested).

To interactively develop, you can run:
Expand All @@ -38,5 +44,9 @@ To interactively develop, you can run:
mvn jbake:inline
----

== Deploy
The https://roboquant.org site is actually a GitHub Pages website. Using the action as defined in `.github/workflows/gh-pages.yml`, the static content for the website is automatically generated and deployed after each new push on the main branch. So there is no need for a manual step to update the website after a change has been committed to this repository.


== Other
If you want to fork this for your own website, please be aware that some templates still have hard coded content in there that you want to replace.
2 changes: 1 addition & 1 deletion content/community.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ _Roboquant_ has its own Discord server with various channels to discuss algo-tra

Since the _roboquant_ documentation is still lacking in certain areas, this is a great place to ask questions and find out more about the platform.

Invite link: https://discord.gg/Vt9wgNjSzw[join roboquant, window=_target]
Invite link: https://discord.gg/Vt9wgNjSzw[join roboquant Discord server, window=_target]

Goto the *#general* channel: https://discord.com/channels/954650958300856340/954650958300856343[general discussions, window=_target]

Expand Down
12 changes: 5 additions & 7 deletions content/tutorial/feed.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ TIP: Convert the CSV files to an AvroFeed, that way you have the best of both wo


== Avro Feed

Apache Avro™ is a data serialization system. Avro provides:

* Rich data structures.
Expand All @@ -118,7 +117,7 @@ Apache Avro™ is a data serialization system. Avro provides:

Avro files make it easy to store large amount of data in a single file and efficiently use it during back testing. The AvroFeed implementation of _roboquant_ only loads data in memory when it is required, so very large datasets can be used without having to worry about memory issues.

Also since the Avro Feed uses a single file, it is easier to distribute than for example directories full of CSV files.
Also since the Avro Feed uses a single file, it is easier to distribute it than for example directories full of CSV files.

=== Using an AvroFeed
To use any compatible Avro file as a feed is straight forward.You just provide the file location to the constructor as a parameter and the feed will be ready to use.
Expand All @@ -130,21 +129,20 @@ include::{sourcefile}[tag=avro]

A single feed can contain mixed price actions if required.So you can have Trade Prices and Price Bars in the same feed.


[#_convert_to_an_avrofeed]
=== Convert to an AvroFeed
You can also convert other feeds into Avro files that you can then later use for back-testing.This works with both historic data feeds as wel as live data feeds.The current limitation is that it only works with price actions and not other type of actions like image data or social media post.
You can also convert other feeds into Avro files that you can then later use for back-testing. This works with both historic data feeds as wel as live data feeds. The current limitation is that it only works with price actions and not other type of actions like image data or social media post.

[source,kotlin,indent=0]
----
include::{sourcefile}[tag=avrocapture]
----

TIP: If you work a lot with directories full of CSV files, you can convert them to a single Avro file and use that from then on.This is both faster and has lower memory consumption.
TIP: If you work a lot with directories full of CSV files, you can convert them to a single Avro file and use that from then on (using the above code snippet as an example). This is both faster and has lower memory consumption.

The nice thing is that the recording also works with live feeds.So you can record a live feed for a specified timeframe and then use it from then on in your own back test.
The nice thing is that the recording also works with live feeds. So you can record a live feed for a specified timeframe and then use it from then on in your own back test.

The following example shows how you can record 4 hours of Bitcoin price-bars from Binance.
The following example shows how you can record 4 hours worth of Bitcoin-USDT price-bars from the Binance exchange.

[source, kotlin, indent=0]
----
Expand Down
10 changes: 5 additions & 5 deletions content/tutorial/policy.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
== What is a policy
A `Policy` is responsible for creating the orders that will be placed at the broker. The most common use-case is that a `Policy` does this based on the signals it receives from a `Strategy`, but there are other use-cases.

A good `Policy` implementation is key in making your solution as robust as possible. Key things to consider when implementing a new policy:
A good `Policy` implementation is key in making your solution as robust as possible. Important things to consider when implementing a policy:

* What is a good allocation strategy, so how much of your cash do you allocate to a certain asset
* What is a good allocation strategy, so how much of your buying power do you allocate to a certain asset
* What order types to create
* How to handle new orders when there are still open orders (for the same asset)
* How to limit the maximum number of orders send to a broker (circuit breaker)
* How to deal with conflicting signals from strategies
* How to handle yo-yo signals (buy-sell-buy-sell) in a short timeframe
* How to manage risk and exposure
* How to ensure there is still enough balance left to place an order
* How to manage overall risk and exposure when volatility changes
* How to ensure there is still enough buying power left to avoid margin calls

****
CAUTION: If there is one thing that prevents many from going live with a strategy, it is that there is not a robust policy in place that handles all the possible edge-cases. The logic required in a robust policy is anything but trivial and should incorporate an extensive testing period.
Expand Down Expand Up @@ -63,7 +63,7 @@ include::{sourcefile}[tag=naive2]
----


The following example is more realistic and shows an implementation that calculates the ATR (Average True Range) that is than used to set the limit amount in a Limit Order.
The following example is more realistic and shows an implementation that calculates the ATR (Average True Range) that is than used to set the limit amount in a Limit Order. This example use the `DefaultPolicy` as its base class that will take care of much of the logic like sizing and dealing with concurrent orders.
[source, kotlin,indent=0]
----
include::{sourcefile}[tag=custom3]
Expand Down
2 changes: 1 addition & 1 deletion content/tutorial/roboquant.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The following default values will then be used:
* The `DefaultPolicy` will be the policy used
* The `MemoryLogger` for logging the metrics (although without any metrics to capture, there is not much to log)

Each of these defaults can be overwritten with a different implementation when you instantiate a `Roboquant`, as the following code snippet shows:
Each of these defaults can be overwritten with a different implementation when you instantiate a `Roboquant`, as the following code snippet demonstrates:

[source, kotlin, indent=0]
----
Expand Down
2 changes: 1 addition & 1 deletion content/tutorial/simbroker.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Out of the box _roboquant_ comes with several models that cather for common acco
=== Cash Accounts
Cash accounts can be modelled using the `CashAccount`. This model is also the default if you don't provide a BuyingPowerModel when instantiating a xref:simbroker.adoc[SimBroker].

`CashBuyingPowerModel` only base the buying power on the available cash, and no margin is calculated and no leverage is used. So cash and buyng power are equal, however cash can contain multiple currencies while the buying power is always converted to the base currency of the account.
`CashBuyingPowerModel` only base the buying power on the available cash, and no margin is calculated and no leverage is used. So cash and buying power are equal, however cash can contain multiple currencies while the buying power is always converted to the base currency of the account.

The below table shows an example using the default CashBuyingPowerModel when trading in different currencies, EUR and USD in this case.

Expand Down
11 changes: 3 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@
</repositories>

<build>
<directory>${project.basedir}/build</directory>
<sourceDirectory>${project.basedir}/samples</sourceDirectory>
<directory>${project.basedir}/target</directory>
<plugins>
<plugin>
<groupId>org.jbake</groupId>
<artifactId>jbake-maven-plugin</artifactId>
<version>0.3.4</version>
<configuration>
<inputDirectory>${project.basedir}</inputDirectory>
<outputDirectory>${project.build.directory}</outputDirectory>
<outputDirectory>${project.basedir}/build</outputDirectory>
</configuration>
<executions>
<execution>
Expand All @@ -55,12 +56,6 @@
<version>3.1.16</version>
</dependency>

<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj</artifactId>
<version>2.4.3</version>
</dependency>

<!-- AsciiDoc content support -->
<dependency>
<groupId>org.asciidoctor</groupId>
Expand Down
2 changes: 1 addition & 1 deletion samples/metrics.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@file:Suppress("unused", "UNUSED_VARIABLE")
@file:Suppress("unused")

import org.roboquant.Roboquant
import org.roboquant.RunInfo
Expand Down
122 changes: 63 additions & 59 deletions samples/policy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,82 +47,86 @@ class MyDefaultPolicy(private val percentage:Double = 0.05) : DefaultPolicy() {
}
// end::default[]


// tag::naive[]
class MyNaivePolicy : Policy {

override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
val orders = mutableListOf<Order>()
for (signal in signals) {
val qty = if (signal.rating.isPositive) 100 else -100
val order = MarketOrder(signal.asset, qty)
orders.add(order)
fun naivePolicy1() {
// tag::naive[]
class MyNaivePolicy : Policy {

override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
val orders = mutableListOf<Order>()
for (signal in signals) {
val qty = if (signal.rating.isPositive) 100 else -100
val order = MarketOrder(signal.asset, qty)
orders.add(order)
}
return orders
}
return orders
}
// end::naive[]
}
// end::naive[]

// tag::naive2[]
class MyNaivePolicy2 : Policy {
fun naivePolicy2() {
// tag::naive2[]
class MyNaivePolicy : Policy {

override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
return signals.map {
val qty = if (it.rating.isPositive) 100 else -100
MarketOrder(it.asset, qty)
override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
return signals.map {
val qty = if (it.rating.isPositive) 100 else -100
MarketOrder(it.asset, qty)
}
}
}
// end::naive2[]
}
// end::naive2[]


// tag::custom3[]
class MyPolicy3(
private val atrPercentage: Double = 0.01,
private val atrPeriod : Int = 5
) : DefaultPolicy() {

// map that contains the ATR per asset
private val atrs = mutableMapOf<Asset, ATR>()
fun customPolicy() {
// tag::custom3[]
class MyPolicy(
private val atrPercentage: Double = 0.01,
private val atrPeriod: Int = 5
) : DefaultPolicy() {

// map that contains the ATR per asset
private val atrs = mutableMapOf<Asset, ATR>()

/**
* Update the ATR for all the assets in the event
*/
private fun updateAtrs(event: Event) {
event.actions.filterIsInstance<PriceBar>().forEach {
val atr = atrs.getOrPut(it.asset) { ATR(atrPeriod) }
atr.add(it)
}
}

/**
* Update the ATR for all the assets in the event
*/
private fun updateAtrs(event: Event) {
event.actions.filterIsInstance<PriceBar>().forEach {
val atr = atrs.getOrPut(it.asset) { ATR(atrPeriod) }
atr.add(it)
override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
updateAtrs(event)
return super.act(signals, account, event)
}
}

override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
updateAtrs(event)
return super.act(signals, account, event)
}
/**
* Create limit BUY and SELL orders with the limit based on the ATR of the asset
*/
override fun createOrder(signal: Signal, size: Size, price: Double): Order? {
val atr = atrs[signal.asset]

// Only return an order if we know the ATR
return if (atr != null && atr.isReady()) {
val direction = if (size > 0) 1 else -1
val limit = price - direction * atr.calc() * atrPercentage
LimitOrder(signal.asset, size, limit)
} else {
null
}
}

/**
* Create limit BUY and SELL orders with the limit based on the ATR of the asset
*/
override fun createOrder(signal: Signal, size: Size, price: Double): Order? {
val atr = atrs[signal.asset]

// Only return an order if we know the ATR
return if (atr != null && atr.isReady()) {
val direction = if (size > 0) 1 else -1
val limit = price - direction * atr.calc() * atrPercentage
LimitOrder(signal.asset, size, limit)
} else {
null
override fun reset() {
atrs.clear()
super.reset()
}
}

override fun reset() {
atrs.clear()
super.reset()
}

// end::custom3[]
}
// end::custom3[]

fun noStrategy(myAdvancedPolicy:Policy) {
// tag::advanced[]
Expand Down
Loading

0 comments on commit bc0a126

Please sign in to comment.