Coderunner Documentation
Coderunner is currently in dev/testing and not available to all Cryptowatch clients.
We run your code so you don't have to. Go to https://cryptowat.ch/coderunner to try.

Quick start

  1. 1.
    Name your script
  2. 2.
    Click the "New Script" button next to the input to create it
  3. 3.
    Copy/paste the code below these steps
  4. 4.
    Click Save
  5. 5.
    In the generated form at the bottom type your name
  6. 6.
    Click "Run Greet"
1
def greet(name: str):
2
if not name:
3
name = "World"
4
print(f"Hello {name}!")
Copied!

Introduction

Scripting is a new service we are building that will allow users to create and execute their own custom trading scripts on top of CW’s trading infrastructure.
Cryptowatch has public APIs. Currently if a trader wants to automate a task, perhaps create a custom series of orders with complex conditions, they have the ability to write the code to access these APIs and run it on their computer.
While running it locally is great while developing, it is not a long term solution for serious automated trading. For that you would need to find a hosting provider and set up a server (or cluster for redundancy) set up monitoring, failovers, and generally invest a lot of time learning dev-ops, or hiring someone.
There is a steep learning curve to dev-ops, which takes time away from developing strategies, hosting providers will cost money, and downtime can cause trades to be missed.
Coderunner will remove all the tedious and expensive infrastructure costs and allow traders to focus on the part they care about, their strategies.
You can access the demo here: https://cryptowat.ch/coderunner
  1. 1.
    Users create a trading script in their language of choice while leveraging CW SDKs. For now, only Python will be enabled.
  2. 2.
    Scripts generate dynamic forms that create fields for customizable script params.
  3. 3.
    Users can preview and execute the script.

Cryptowatch Python SDK

Fetching market data via public API

The Python SDK matches Cryptowatch's Public API 1 to 1.
All the public API endpoints can be accessed by replacing the slashes (/) in the URL with dots (.) if using the object properties or using brackets ([ ]) if using dictionary lookups.
Both styles can be used together if it makes sense in your code.

Curl example

1
2
$ curl "<https://api.cryptowat.ch/markets/kraken/btceur/price>"
Copied!

Examples of making the same request via CW Python SDK

1
from cryptowatch import Cryptowatch
2
import typing
3
c = Cryptowatch()
4
5
# Let's define the types used below
6
PAIRS = typing.Annotated[
7
typing.Literal["btceur", "btcusd", "dogebtc"],
8
'{"title": "Pair","type": "string"}']
9
10
EXCHANGES = typing.Annotated[
11
typing.Literal["kraken", "bitstamp"],
12
'{"title": "Exchange","type": "string"}']
13
14
15
16
def get_kraken_btceur_price():
17
""" simply hard code the market you want"""
18
return c.markets.kraken.btceur.price()
19
20
21
def get_kraken_price(pair:PAIRS):
22
"""Get the pair from the parameters for kraken only"""
23
return c.markets.kraken[pair].price()
24
25
26
def get_price(exchange:EXCHANGES, pair:PAIRS):
27
"""Get price for any pair on any exchange"""
28
return c.markets[exchange][pair].price()
Copied!

Fetching Balances, Orders, & Trading via the Private API

Get Balances

Get the balances of all connected exchanges.
1
from cryptowatch import Cryptowatch
2
c = Cryptowatch()
3
c.balances()
4
5
{
6
"bitfinex": {
7
"exchangeName": "Bitfinex",
8
"balances": {
9
"spot": [
10
{
11
"currency": "Bitcoin",
12
"slug": "btc",
13
"amountTotal": "0.00165737",
14
"amountAvailable": "0.00135737"
15
},
16
{
17
"currency": "OmiseGo",
18
"slug": "omg",
19
"amountTotal": "0.00000068",
20
"amountAvailable": "0.00000068"
21
}
22
]
23
}
24
}
25
}
Copied!

Get Orders

Get all the orders of that user for any connected exchanges or by market.
1
from cryptowatch import Cryptowatch
2
c = Cryptowatch()
3
c.orders(**params)
Copied!

Get Positions

Get all the positions of that user for any connected exchanges.
1
from cryptowatch import Cryptowatch
2
c = Cryptowatch()
3
c.positions(**params)
Copied!

Create Order

Create a new order for this user.
1
from cryptowatch import Cryptowatch
2
c = Cryptowatch()
3
c.create_order(**params)
Copied!

Cancel Order

Cancel a specific order.
1
from cryptowatch import Cryptowatch
2
c = Cryptowatch()
3
c.cancel_order(**params)
Copied!

Cancel All Orders

Cancel all the orders of a market.
1
from cryptowatch import Cryptowatch
2
c = Cryptowatch()
3
c.cancel_all_orders(**params)
Copied!

Wallet Transfer

Transfer funds between wallets inside an exchange.
1
from cryptowatch import Cryptowatch
2
c = Cryptowatch()
3
c.wallet_transfer(**params)
Copied!

Simple Script Examples

Simple Market Order

1
import typing
2
from cryptowatch import Cryptowatch
3
4
5
c = Cryptowatch()
6
7
SIDE = typing.Annotated[
8
typing.Literal["buy", "sell"],
9
'{"title": "Side","type": "string"}']
10
11
EXCHANGES = typing.Annotated[
12
typing.Literal["kraken", "bitstamp"],
13
'{"title": "Exchange","type": "string"}']
14
15
16
def get_market(exchange: EXCHANGES, base: str, quote: str):
17
return c.v2.markets(exchange=exchange, base=base, quote=quote)
18
19
20
c = Cryptowatch()
21
22
def simple_market_order(
23
exchange: EXCHANGES,
24
side:SIDE,
25
base: str,
26
quote: str,
27
amount: float):
28
29
markets = get_market(exchange=exchange, base=base, quote=quote)
30
31
if markets is None:
32
return "no market found"
33
34
if len(markets) > 1:
35
return "too many markets"
36
37
38
39
market_id = markets[0]["id"]
40
exchange_id = markets[0]["exchange"]["id"]
41
42
43
return c.create_order(
44
exchangeId=exchange_id,
45
type="market",
46
side=side,
47
marketId=market_id,
48
amount=str(amount)
49
)
Copied!

Getting Account Balance

1
from cryptowatch import Cryptowatch
2
3
c = Cryptowatch()
4
5
def fetch_balances():
6
return c.balances()
Copied!

Simple Limit Script with Exchange Parameter

1
import typing
2
from cryptowatch import Cryptowatch
3
4
5
EXCHANGES = typing.Literal["kraken", "bitfinex"]
6
SIDE = typing.Literal["buy", "sell"]
7
8
c = Cryptowatch()
9
10
11
def get_market(exchange: EXCHANGES, base: str, quote: str):
12
return c.v2.markets(exchange=exchange, base=base, quote=quote)
13
14
def simple_limit_order(
15
exchange: EXCHANGES,
16
side:SIDE,
17
base: str,
18
quote: str,
19
amount: float,
20
price: float):
21
22
markets = get_market(exchange=exchange, base=base, quote=quote)
23
if len(markets) > 1:
24
print("too many markets")
25
return None
26
27
if len(markets) < 0:
28
print("no market found")
29
return None
30
31
market_id = markets[0]["id"]
32
exchange_id = markets[0]["exchange"]["id"]
33
34
35
return c.create_order(
36
exchangeId=exchange_id,
37
type="limit",
38
side=side,
39
marketId=market_id,
40
amount=str(amount),
41
price=price,
42
)
Copied!

Latest price & OHLC bars

1
from cryptowatch import Cryptowatch
2
import typing
3
4
5
c = Cryptowatch()
6
7
def get_kraken_btceur_price():
8
""" simply hard code the market you want"""
9
return c.markets.kraken.btceur.price()
10
11
12
# Coderunner extracts type annotations and uses it to build the forms in the client
13
PAIRS = typing.Literal["btceur", "btcusd", "dogebtc"]
14
15
def get_kraken_price(pair:PAIRS):
16
"""Get the pair from the parameters for kraken only"""
17
return c.markets.kraken[pair].price()
18
19
20
EXCHANGES = typing.Literal["kraken", "bitstamp"]
21
22
def get_price(exchange:EXCHANGES, pair:PAIRS):
23
"""Get price for any pair on any exchange"""
24
return c.markets[exchange][pair].price()
25
26
27
PERIODS = typing.Literal[60,180,300,900,1800,3600,7200,14400,21600,43200,86400,259200,604800]
28
29
def get_ohlc(exchange:EXCHANGES, pair:PAIRS, before:int, after:int, periods:PERIODS):
30
"""Get candles for any pair on any exchange"""
31
return c.markets[exchange][pair].ohlc(before=before, after=after, periods=periods)
Copied!

Advanced Script Examples

Scaled Order

1
# vim: set ft=python:
2
"""
3
Scaled limit and stop-loss orders
4
5
kraken_btcusd = c.v2.markets(exchange="kraken", base="btc", quote="usd")[0]
6
print(f'DEBUG - kraken exchange id {kraken_btcusd["exchange"]["id"]}')
7
print(f'DEBUG - btcusd market id {kraken_btcusd["id"]}')
8
9
=== FUNCTION NAME TO CALL ===
10
scaled_order
11
12
=== FUNCTION KEYWORD ARGUMENTS ===
13
{
14
"exchange_id": 4,
15
"market_id": 87,
16
"side": "buy",
17
"amount": 1,
18
"num_orders": 3,
19
"order_type": "limit",
20
"price_start": 1,
21
"price_stop": 10,
22
"price_scale_method": "linear"
23
}
24
25
"""
26
from cryptowatch import Cryptowatch
27
import numpy as np
28
import typing
29
from decimal import Decimal
30
31
from responses import RequestsMock, urlencoded_params_matcher
32
33
SCALE_MAPPING = {"linear": np.linspace, "exponential": np.geomspace}
34
SCALE_METHOD_TYPE = typing.Literal["linear", "exponential"]
35
SIDE_TYPE = typing.Literal["buy", "sell"]
36
ORDER_TYPE = typing.Literal["limit", "stop-loss"]
37
38
39
c = Cryptowatch()
40
41
42
def preview_create_order(**kwargs: typing.Any):
43
return {"command": "create_order", "parameters": kwargs}
44
45
46
def scaled_order_percent_offset(
47
exchange_id: int,
48
market_id: int,
49
side: SIDE_TYPE,
50
order_type: ORDER_TYPE,
51
amount: float,
52
num_orders: int,
53
price_start_pct: float,
54
price_stop_pct: float,
55
price_scale_method: SCALE_METHOD_TYPE,
56
dry_run: bool = False,
57
):
58
price_data = c.v2.markets[market_id].price()
59
price = Decimal(price_data["price"])
60
61
price_start = (Decimal(price_start_pct) * price) + price
62
price_stop = (Decimal(price_stop_pct) * price) + price
63
return scaled_order(
64
exchange_id,
65
market_id,
66
side,
67
order_type,
68
amount,
69
num_orders,
70
price_start,
71
price_stop,
72
price_scale_method,
73
dry_run,
74
)
75
76
77
def scaled_order(
78
exchange_id: int,
79
market_id: int,
80
side: SIDE_TYPE,
81
order_type: ORDER_TYPE,
82
amount: float,
83
num_orders: int,
84
price_start: float,
85
price_stop: float,
86
price_scale_method: SCALE_METHOD_TYPE,
87
dry_run: bool = False,
88
):
89
if dry_run:
90
create_order = preview_create_order
91
else:
92
create_order = c.create_order
93
94
order_amount = Decimal(amount) / Decimal(num_orders)
95
if price_scale_method not in SCALE_MAPPING:
96
return {
97
"error": f"Invalid price_scale_method: {price_scale_method}, should be one of [{ ', '.join(SCALE_MAPPING.keys())}]",
98
"result": None,
99
}
100
101
ret = {"error": None, "result": []}
102
for order_price in SCALE_MAPPING[price_scale_method](
103
float(price_start), float(price_stop), num_orders
104
):
105
ret["result"].append(
106
create_order(
107
exchangeId=exchange_id,
108
type=order_type,
109
side=side,
110
marketId=market_id,
111
amount=str(order_amount),
112
price=order_price,
113
)
114
)
115
return ret
Copied!

Typing

Available Parameter Types

1
typing.Literal
2
typing.List
3
typing.Dict
4
typing.Union
5
typing.Annotated
6
typing.Any
7
int
8
list
9
str
10
dict
Copied!

List of ints

The variable "LIST_OF_INTS" is a "type".
1
import typing
2
3
LIST_OF_INTS = list[int]
4
5
def list_of_ints(my_list: LIST_OF_INTS):
6
return my_list
Copied!
Rendered form from list_of_ints

List of ints with description and title

Here you can see that by using "typing.Annotated" and wrapping the original "LIST_OF_INTS" type we can add a title to the list of items and a description.
1
import typing
2
3
LIST_OF_INTS = list[int]
4
DESCRIPTIVE_LIST_OF_INTS = typing.Annotated[LIST_OF_INTS, '{"title": "My numbers","description": "Add your favorite numbers"}']
5
6
def descriptive_ints(i: DESCRIPTIVE_LIST_OF_INTS):
7
return i
Copied!
Using "typing.Literal" let's allow it to create a dropdown input that has a list to choose from.
For now, it is required to add the "{"type": "string"}" annotation, but it should be automatic in the future.
1
import typing
2
3
SPECIFIC_THINGS = typing.Annotated[
4
typing.Literal["item 1", "item 2"],
5
'{"type": "string"}'
6
]
7
8
def my_thing(thing: SPECIFIC_THINGS):
9
return thing
Copied!
Example of a dropdown.

Error Messages/Debugging

Standard Out is captured and returned.
Unhandled Exceptions are also returned with the type, message, and line number if possible.

Example:

Code

1
def make_error():
2
print("I will attempt the impossible")
3
1/0
Copied!

Error

1
{
2
"line": 14,
3
"column": 4,
4
"msg": "division by zero",
5
"type": "ZeroDivisionError",
6
"line_helper": " 1/0"
7
}
Copied!

Std out

1
2
I will attempt the impossible
Copied!

Importable modules

1
numpy.arange
2
numpy.linspace
3
numpy.geomspace
4
numpy.logspace
5
numpy.sin
6
numpy.cos
7
numpy.exp
8
numpy.log
9
numpy.repeat
10
numpy.digitize
11
numpy.nan
12
responses.RequestsMock
13
responses.urlencoded_params_matcher
14
decimal.Decimal
Copied!

Disabled Python Features

  • Class definitions
  • Network access
  • Filesystem access
  • Importing arbitrary modules
  • Some builtins that are likely ok, but have not been whitelisted yet
  • Python generators

Community Scripts

(No scripts yet)
Create your own script and share it with w/ the Discord group.
Our favourite scripts will be vetted and published to end-users once CW scripting is live.

Feature Requests

All feature requests can go to [email protected] or DM via Discord.

Known Issues (Bugs)

  • Some errors are not rendered in the browser correctly, and appear there was no response from the server.
  • Broker does not (yet) handle validation of order sizes causing some exchanges to return precision errors.
Last modified 3mo ago