Welcome to StakeMachine’s documentation!¶
Basics¶
Setup¶
Installation¶
pip3 install stakemachine [--user]
If you install using the --user
flag, the binaries of
stakemachine
and uptick
are located in ~/.local/bin
.
Otherwise they should be globally reachable.
Adding Keys¶
It is important to install the private key of your
bot’s account into the pybitshares wallet. This can be done using
uptick
which is installed as a dependency of stakemachine
:
uptick addkey
Configuration¶
You will need to create configuration file in YAML format. The default
file name is config.yml
, otherwise you can specify a different
config file using the --configufile X
parameter of stakemachine
.
Read more about the Configuration.
Running¶
The bot can be run by:
stakemachine run
It will ask for your wallet passphrase (that you have provide when
adding your private key to pybitshares using uptick addkey
).
If you want to prevent the password dialog, you can predefine an
environmental variable UNLOCK
, if you understand the security
implications.
Configuration¶
The configuration of stakemachine
happens through a YAML formated
file and takes the following form:
# The BitShares endpoint to talk to
node: "wss://node.testnet.bitshares.eu"
# List of bots
bots:
# Name of the bot. This is mostly for logging and internal
# use to distinguish different bots
NAME_OF_BOT:
# Python module to look for the strategy (can be custom)
module: "stakemachine.strategies.echo"
# The bot class in that module to use
bot: Echo
# The market to subscribe to
market: GOLD:TEST
# The account to use for this bot
account: xeroc
# Custom bot configuration
foo: bar
Usig the configuration in custom strategies¶
The bot’s configuration is available to in each strategy as dictionary
in self.bot
. The whole configuration is avaialable in
self.config
. The name of your bot can be found in self.name
.
Strategies¶
Wall Strategy¶
This strategy simply places a buy and a sell wall into a specific market using a specified account.
Example Configuration¶
# BitShares end point
node: "wss://node.bitshares.eu"
# List of Bots
bots:
# Only a single Walls Bot
Walls:
# The Walls strategy module and class
module: stakemachine.strategies.walls
bot: Walls
# The market to serve
market: HERO:BTS
# The account to sue
account: hero-market-maker
# We shall bundle operations into a single transaction
bundle: True
# Test your conditions every x blocks
test:
blocks: 10
# Where the walls should be
target:
# They relate to the price feed
reference: feed
# There should be an offset
offsets:
buy: 2.5
sell: 2.5
# We'd like to use x amount of quote (here: HERO)
# in the walls
amount:
buy: 5.0
sell: 5.0
# When the price moves by more than 2%, update the walls
threshold: 2
Source Code¶
1 2 3 4 5 6 7 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | from math import fabs
from pprint import pprint
from collections import Counter
from bitshares.amount import Amount
from stakemachine.basestrategy import BaseStrategy
from stakemachine.errors import InsufficientFundsError
import logging
log = logging.getLogger(__name__)
class Walls(BaseStrategy):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define Callbacks
self.onMarketUpdate += self.test
self.ontick += self.tick
self.onAccount += self.test
self.error_ontick = self.error
self.error_onMarketUpdate = self.error
self.error_onAccount = self.error
# Counter for blocks
self.counter = Counter()
# Tests for actions
self.test_blocks = self.bot.get("test", {}).get("blocks", 0)
def error(self, *args, **kwargs):
self.disabled = True
self.cancelall()
pprint(self.execute())
def updateorders(self):
""" Update the orders
"""
log.info("Replacing orders")
# Canceling orders
self.cancelall()
# Target
target = self.bot.get("target", {})
price = self.getprice()
# prices
buy_price = price * (1 - target["offsets"]["buy"] / 100)
sell_price = price * (1 + target["offsets"]["sell"] / 100)
# Store price in storage for later use
self["feed_price"] = float(price)
# Buy Side
if float(self.balance(self.market["base"])) < buy_price * target["amount"]["buy"]:
InsufficientFundsError(Amount(target["amount"]["buy"] * float(buy_price), self.market["base"]))
self["insufficient_buy"] = True
else:
self["insufficient_buy"] = False
self.market.buy(
buy_price,
Amount(target["amount"]["buy"], self.market["quote"]),
account=self.account
)
# Sell Side
if float(self.balance(self.market["quote"])) < target["amount"]["sell"]:
InsufficientFundsError(Amount(target["amount"]["sell"], self.market["quote"]))
self["insufficient_sell"] = True
else:
self["insufficient_sell"] = False
self.market.sell(
sell_price,
Amount(target["amount"]["sell"], self.market["quote"]),
account=self.account
)
pprint(self.execute())
def getprice(self):
""" Here we obtain the price for the quote and make sure it has
a feed price
"""
target = self.bot.get("target", {})
if target.get("reference") == "feed":
assert self.market == self.market.core_quote_market(), "Wrong market for 'feed' reference!"
ticker = self.market.ticker()
price = ticker.get("quoteSettlement_price")
assert abs(price["price"]) != float("inf"), "Check price feed of asset! (%s)" % str(price)
return price
def tick(self, d):
""" ticks come in on every block
"""
if self.test_blocks:
if not (self.counter["blocks"] or 0) % self.test_blocks:
self.test()
self.counter["blocks"] += 1
def test(self, *args, **kwargs):
""" Tests if the orders need updating
"""
orders = self.orders
# Test if still 2 orders in the market (the walls)
if len(orders) < 2 and len(orders) > 0:
if (
not self["insufficient_buy"] and
not self["insufficient_sell"]
):
log.info("No 2 orders available. Updating orders!")
self.updateorders()
elif len(orders) == 0:
self.updateorders()
# Test if price feed has moved more than the threshold
if (
self["feed_price"] and
fabs(1 - float(self.getprice()) / self["feed_price"]) > self.bot["threshold"] / 100.0
):
log.info("Price feed moved by more than the threshold. Updating orders!")
self.updateorders()
|
Developing own Strategies¶
Base Strategy¶
All strategies should inherit
stakemachine.basestrategy.BaseStrategy
which simplifies and
unifies the development of new strategies.
API¶
-
class
stakemachine.basestrategy.
BaseStrategy
(config, name, onAccount=None, onOrderMatched=None, onOrderPlaced=None, onMarketUpdate=None, onUpdateCallOrder=None, ontick=None, bitshares_instance=None, *args, **kwargs)¶ Base Strategy and methods available in all Sub Classes that inherit this BaseStrategy.
BaseStrategy inherits:
stakemachine.storage.Storage
stakemachine.statemachine.StateMachine
Events
Available attributes:
basestrategy.bitshares
: instance of ´`bitshares.BitShares()``basestrategy.add_state
: Add a specific statebasestrategy.set_state
: Set finite state machinebasestrategy.get_state
: Change state of state machinebasestrategy.account
: The Account object of this botbasestrategy.market
: The market used by this botbasestrategy.orders
: List of open orders of the bot’s account in the bot’s marketbasestrategy.balance
: List of assets and amounts available in the bot’s account
Also, Base Strategy inherits
stakemachine.storage.Storage
which allows to permanently store data in a sqlite database using:basestrategy["key"] = "value"
Note
This applies a
json.loads(json.dumps(value))
!-
account
¶ Return the full account as
bitshares.account.Account
object!Can be refreshed by using
x.refresh()
-
balance
(asset)¶ Return the balance of your bot’s account for a specific asset
-
balances
¶ Return the balances of your bot’s account
-
cancelall
()¶ Cancel all orders of this bot
-
execute
()¶ Execute a bundle of operations
-
market
¶ Return the market object as
bitshares.market.Market
-
orders
¶ Return the bot’s open accounts in the current market
Storage¶
This class allows to permanently store bot-specific data in a sqlite
database (stakemachine.sqlite
) using:
self["key"] = "value"
Note
Here, self
refers to the instance of your bot’s strategy
when coding yaour own strategy.
The value is persistently stored and can be access later on using:
print(self["key"])
.
Note
This applies a json.loads(json.dumps(value))
!
SQLite database¶
The user’s data is stored in its OS protected user directory:
OSX:
- ~/Library/Application Support/<AppName>
Windows:
- C:Documents and Settings<User>Application DataLocal Settings<AppAuthor><AppName>
- C:Documents and Settings<User>Application Data<AppAuthor><AppName>
Linux:
- ~/.local/share/<AppName>
Where <AppName>
is stakemachine
and <AppAuthor>
is
ChainSquad GmbH
.
Simple example¶
1 2 3 4 5 6 7 8 9 10 11 12 | from stakemachine.basestrategy import BaseStrategy
class StorageDemo(BaseStrategy):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ontick += self.tick
def tick(self, i):
print("previous block: %s" % self["block"])
print("new block: %s" % i)
self["block"] = i
|
Example Output:
Current Wallet Passphrase:
previous block: None
new block: 008c4c2424e6394ad4bf5a9756ae2ee883b0e049
previous block: 008c4c2424e6394ad4bf5a9756ae2ee883b0e049
new block: 008c4c257a76671144fdba251e4ebbe61e4593a4
previous block: 008c4c257a76671144fdba251e4ebbe61e4593a4
new block: 008c4c2617851b31d0b872e32fbff6f8248663a3
Statemachine¶
The base strategy comes with a state machine that can be used by your strategy.
Similar to Storage, the methods of this class can be used in your
strategy directly, e.g., via self.get_state()
, since the class is
inherited by Base Strategy.
API¶
-
class
stakemachine.statemachine.
StateMachine
(*args, **kwargs)¶ Generic state machine
-
add_state
(state)¶ Add a new state to the state machine
Parameters: state (str) – Name of the state
-
get_state
()¶ Return state of state machine
-
set_state
(state)¶ Change state of the state machine
Parameters: state (str) – Name of the new state
-
Events¶
The websocket endpoint of BitShares has notifications that are
subscribed to and dispatched by stakemachine
. This uses python’s
native Events
. The following events are available in your
strategies and depend on the configuration of your bot/strategy:
onOrderMatched
: Called when orders in your market are matchedonOrderPlaced
: Called when a new order in your market is placedonUpdateCallOrder
: Called if one of the assets in your market is a market-pegged asset and someone updates his call positiononMarketUpdate
: Called whenever something happens in your market (includes matched orders, placed orders and call order updates!)ontick
: Called when a new block is receivedonAccount
: Called when your account’s statistics is updated (changes to2.6.xxxx
withxxxx
being your account id number)error_ontick
: Is called when an error happend when processingontick
error_onMarketUpdate
: Is called when an error happend when processingonMarketUpdate
error_onAccount
: Is called when an error happend when processingonAccount
Simple Example¶
class Simple(BaseStrategy):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
""" set call backs for events
"""
self.onOrderMatched += print
self.onOrderPlaced += print
self.onUpdateCallOrder += print
self.onMarketUpdate += print
self.ontick += print
self.onAccount += print
Simple Echo Strategy¶
API¶
-
class
stakemachine.strategies.echo.
Echo
(*args, **kwargs)¶ -
error
(*args, **kwargs)¶ What to do on an error
-
print_UpdateCallOrder
(i)¶ Is called when a call order for a market pegged asset is updated
A developer may want to filter those to identify own orders.
Parameters: i (bitshares.price.CallOrder) – Call order details
-
print_accountUpdate
(i)¶ This method is called when the bot’s account name receives any update. This includes anything that changes
2.6.xxxx
, e.g., any operation that affects your account.
-
print_marketUpdate
(i)¶ Is called when Something happens in your market.
This method is actually called by the backend and is dispatched to
onOrderMatched
,onOrderPlaced
andonUpdateCallOrder
.Parameters: i (object) – Can be instance of FilledOrder
,Order
, orCallOrder
-
print_newBlock
(i)¶ Is called when a block is received
Parameters: i (str) – The hash of the block Note
Unfortunately, it is currently not possible to identify the block number for
i
alone. If you need to know the most recent block number, you need to usebitshares.blockchain.Blockchain
-
print_orderMatched
(i)¶ Is called when an order in the market is matched
A developer may want to filter those to identify own orders.
Parameters: i (bitshares.price.FilledOrder) – Filled order details
-
print_orderPlaced
(i)¶ Is called when a new order in the market is placed
A developer may want to filter those to identify own orders.
Parameters: i (bitshares.price.Order) – Order details
-
Full Source Code¶
1 2 3 4 5 6 7 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | from stakemachine.basestrategy import BaseStrategy
import logging
log = logging.getLogger(__name__)
class Echo(BaseStrategy):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
""" set call backs for events
"""
self.onOrderMatched += self.print_orderMatched
self.onOrderPlaced += self.print_orderPlaced
self.onUpdateCallOrder += self.print_UpdateCallOrder
self.onMarketUpdate += self.print_marketUpdate
self.ontick += self.print_newBlock
self.onAccount += self.print_accountUpdate
self.error_ontick = self.error
self.error_onMarketUpdate = self.error
self.error_onAccount = self.error
def error(self, *args, **kwargs):
""" What to do on an error
"""
# Cancel all future execution
self.disabled = True
def print_orderMatched(self, i):
""" Is called when an order in the market is matched
A developer may want to filter those to identify
own orders.
:param bitshares.price.FilledOrder i: Filled order details
"""
print("order matched: %s" % i)
def print_orderPlaced(self, i):
""" Is called when a new order in the market is placed
A developer may want to filter those to identify
own orders.
:param bitshares.price.Order i: Order details
"""
print("order placed: %s" % i)
def print_UpdateCallOrder(self, i):
""" Is called when a call order for a market pegged asset is updated
A developer may want to filter those to identify
own orders.
:param bitshares.price.CallOrder i: Call order details
"""
print("call update: %s" % i)
def print_marketUpdate(self, i):
""" Is called when Something happens in your market.
This method is actually called by the backend and is
dispatched to ``onOrderMatched``, ``onOrderPlaced`` and
``onUpdateCallOrder``.
:param object i: Can be instance of ``FilledOrder``, ``Order``, or ``CallOrder``
"""
print("marketupdate: %s" % i)
def print_newBlock(self, i):
""" Is called when a block is received
:param str i: The hash of the block
.. note:: Unfortunately, it is currently not possible to
identify the block number for ``i`` alone. If you
need to know the most recent block number, you
need to use ``bitshares.blockchain.Blockchain``
"""
print("new block: %s" % i)
# raise ValueError("Testing disabling")
def print_accountUpdate(self, i):
""" This method is called when the bot's account name receives
any update. This includes anything that changes
``2.6.xxxx``, e.g., any operation that affects your account.
"""
print("account: %s" % i)
|