How to Make Strategies
Python, known for its straightforward syntax and widespread popularity, is used for creating customized strategies.
π Basic Knowledge
Symbol
refers to a market symbol that binds trading targets. A representative example is BTCUSDT
.
Creating Your Own Strategy
You can create your own strategy in the Strategize
tab. Set strategy properties by clicking Edit basic info
button.
Script Editor
If writing a script from scratch is burdensome, it`s a good idea to start with a sample script.
You can indent or outdent multiple lines at once while writing a script. Select all the lines you want and press Tab
or Shift+Tab
.
If the script contains some broken code, a function that is executed periodically, such as graph update, repeatedly generates an error. In this case, the graph may not be updated or the simulation calculation may stop quickly. In that case, find out the cause in the Logs
inside the Manage
tab.
Strategy's Basic Info
If parallel calculation is used, the entire period is cut by a certain length during simulation calculation, calculated separately, and then combined. This has the advantage of speeding up simulation calculations, but also has the disadvantage that it does not result in continuous asset calculations. For example, if you divide by 7 days, the asset and position status will return to the origin every 7 days.
In the internal calculation, the asset status is returned to the origin for each split length, but the final graph and result display show the corrected values ββas if they were calculated continuously as if they were all added together. Since this calibration process refers to the input value of Chunk division
, changing the Chunk division
in the state that there is already calculated data may cause the graph and result display to be very strange.
It is recommended to set the Chunk division
of parallel computation appropriately. Splitting by more than the number of child processes visible in the Status
of the Manage
tab does not contribute to the speedup. Be careful not to make the chunk division too short so that the asset's state doesn't change to origin too often.
Basic simulation calculations cover the entire year, which is a slow operation that takes minutes to tens of minutes. If you want to experiment with that strategy a little faster, try performing a temporary calculation on the visible range.
π Writing the Indicators Script
Indicators script is used to create indicators used for graph display and decision.
API
Variables provided by default are as follows. You can use these without any import statements.
target_symbols
(list
): The symbols being observed.candle_data
(pandas.DataFrame
): Candle data. Extra 28 days of data before desired calculation range is included.new_indicators
(dict
): An object that holds newly created indicators.
Warning
Though these variables are still accessible in version 8, they will be removed in version 9. Use the explicit import
statements to use these modules.
ta
(module
): Python packagepandas-ta
. Used for making indicators.pd
(module
): Python packagepandas
.np
(module
): Python packagenumpy
.
Basic Syntax
Candle data exists internally in the form of a tabular pandas.DataFrame
.
MATICUSDT LTCUSDT
Close High Low Open Volume Close High Low Open Volume
2022-02-24 15:28:30+00:00 1.339 1.339 1.338 1.339 31966.0 96.98 96.99 96.85 96.93 669.532
2022-02-24 15:28:40+00:00 1.338 1.339 1.337 1.339 47233.0 96.89 97.00 96.85 96.99 395.509
2022-02-24 15:28:50+00:00 1.339 1.340 1.338 1.338 49107.0 96.97 96.99 96.88 96.89 803.709
2022-02-24 15:29:00+00:00 1.338 1.340 1.337 1.339 62917.0 96.85 97.00 96.85 96.96 541.428
2022-02-24 15:29:10+00:00 1.335 1.338 1.334 1.338 99874.0 96.71 96.88 96.71 96.86 797.188
2022-02-24 15:29:20+00:00 1.337 1.337 1.335 1.335 74435.0 96.83 96.85 96.71 96.71 174.285
2022-02-24 15:29:30+00:00 1.337 1.338 1.336 1.337 121574.0 96.81 96.86 96.81 96.84 110.111
2022-02-24 15:29:40+00:00 1.337 1.338 1.336 1.337 37174.0 96.88 96.91 96.80 96.81 795.653
2022-02-24 15:29:50+00:00 1.336 1.339 1.335 1.338 204744.0 96.75 96.94 96.71 96.87 1580.639
2022-02-24 15:30:00+00:00 1.334 1.336 1.334 1.336 104203.0 96.64 96.74 96.64 96.74 536.685
... ... ... ... ... ... ... ... ... ... ...
2022-02-26 01:27:40+00:00 1.571 1.572 1.571 1.572 3565.0 112.72 112.77 112.72 112.77 10.731
2022-02-26 01:27:50+00:00 1.572 1.572 1.571 1.571 45791.0 112.72 112.77 112.72 112.73 628.011
2022-02-26 01:28:00+00:00 1.572 1.572 1.572 1.572 1842.0 112.74 112.75 112.72 112.72 153.902
2022-02-26 01:28:10+00:00 1.572 1.572 1.572 1.572 2983.0 112.72 112.74 112.71 112.74 50.900
2022-02-26 01:28:20+00:00 1.572 1.572 1.571 1.572 2424.0 112.71 112.71 112.70 112.71 31.024
2022-02-26 01:28:30+00:00 1.573 1.573 1.572 1.572 4587.0 112.75 112.77 112.70 112.70 140.505
2022-02-26 01:28:40+00:00 1.574 1.574 1.573 1.573 36205.0 112.76 112.77 112.74 112.76 37.946
2022-02-26 01:28:50+00:00 1.574 1.574 1.574 1.574 2143.0 112.76 112.76 112.76 112.76 0.000
2022-02-26 01:29:00+00:00 1.574 1.574 1.574 1.574 6228.0 112.77 112.77 112.76 112.77 96.222
2022-02-26 01:29:10+00:00 1.572 1.573 1.572 1.573 3848.0 112.73 112.76 112.72 112.76 67.306
You can extract partial pandas.Series
data from candle_data
.
for symbol in target_symbols:
# Each of these is a `pandas.Series` column object
open_sr = candle_data[(symbol, "Open")]
high_sr = candle_data[(symbol, "High")]
low_sr = candle_data[(symbol, "Low")]
close_sr = candle_data[(symbol, "Close")]
volume_sr = candle_data[(symbol, "Volume")]
The pandas.Series
object has the following form. A one-dimensional array containing values ββover time.
2020-01-01 00:00:00+00:00 129.11
2020-01-01 00:00:10+00:00 129.03
2020-01-01 00:00:20+00:00 129.03
2020-01-01 00:00:30+00:00 128.94
2020-01-01 00:00:40+00:00 128.91
2020-01-01 00:00:50+00:00 128.97
2020-01-01 00:00:00+00:00 128.98
2020-01-01 00:01:10+00:00 129.03
2020-01-01 00:01:20+00:00 129.04
2020-01-01 00:01:30+00:00 129.01
...
2022-02-20 10:41:20+00:00 2633.99
2022-02-20 10:41:30+00:00 2633.07
2022-02-20 10:41:40+00:00 2633.43
2022-02-20 10:41:50+00:00 2633.04
2022-02-20 10:42:00+00:00 2633.24
2022-02-20 10:42:10+00:00 2632.45
2022-02-20 10:42:20+00:00 2630.84
2022-02-20 10:42:30+00:00 2631.55
2022-02-20 10:42:40+00:00 2630.69
Kolie uses the pandas-ta
package. Creation of dozens of basic indicators is available with this, including moving average, bollinger band, double exponential moving average, triple exponential moving average, stochastic, and parabolic. For more information, check the official documentation of pandas-ta
π.
for symbol in target_symbols:
close_sr = candle_data[(symbol, "Close")]
sma_sr = ta.sma(close_sr, 60) # Returns `pandas.Series` object
# 60 of 10 second candles means 600 seconds(10 minutes)
Once you have created the indicators, simply put them in a dict
object called new_indicators
as appropriate tuple keys. After that, multiple pandas.Series
objects inside this object are merged into a single indicators object. After writing this and saving it, you will see the indicator in the graph view. It can also be used in strategic decisions.
for symbol in target_symbols:
close_sr = candle_data[(symbol, "Close")]
sma_sr = ta.sma(close_sr, 60)
# Here, the tuple key is `(symbol, "Price", "SMA")`
new_indicators[(symbol, "Price", "SMA")] = sma_sr
The tuple key has 3 values. The first value represents a symbol and the third value would be any name you want. The second value determines which scale graph to draw on. It should be one of Price
, Volume
, or Abstract
.
- Graph 1:
Price
indicators - Graph 2:
Volume
indcators - Graph 3:
Abstract
indicators - Graph 4: Only asset information
Taking the
BTCUSDT
symbol as an example, prices move in tens of thousands of dollars, volume moves in tens, and abstract indicators move in single digit units. Of course, you can't show these three different scales in one graph. That's why we let you choose the graph to be drawn with the second value of the tuple key.
You can set the color drawn on the graph as you wish. Just put parentheses next to the name and color code it. Color codes can be chosen on a color combination siteπ.
for symbol in target_symbols:
close_sr = candle_data[(symbol, "Close")]
sma_sr = ta.sma(close_sr, 60)
new_indicators[(symbol, "Price", "SMA (#649CFF)")] = sma_sr # Blue
Up to this point, indicators generation has been completed using the pandas.Series
object and the ta
module. However, the flexibility of the way metrics are generated by means of coding comes from now on. The pandas.Series
object can be manipulated in a variety of ways, including addition, subtraction, division, and conditional transformations. The pandas
official documentationπ has a more detailed explanation.
Below is the code that creates the average of two different moving averages.
for symbol in target_symbols:
close_sr = candle_data[(symbol, "Close")]
sma_one = ta.sma(close_sr, 60)
sma_two = ta.sma(close_sr, 360)
average_sma = (sma_one + sma_two) / 2
new_indicators[(symbol, "Price", "Average SMA")] = average_sma
Below is the code that creates a market overheating indicator with two different moving averages and limits the value to not exceed 0.8. In the picture, you can see that everything above 0.8 is cut off.
for symbol in target_symbols:
volume_sr = candle_data[(symbol, "Volume")]
sma_one = ta.sma(volume_sr, 360)
sma_two = ta.sma(volume_sr, 2160)
wildness = sma_one / sma_two # Division operation
wildness[wildness > 0.8] = 0.8 # Set the limit
new_indicators[(symbol, "Abstract", "Wildness")] = wildness
Below is the code that simply creates an indicator that delays the closing price by 10 minutes.
for symbol in target_symbols:
close_sr = candle_data[(symbol, "Close")]
shifted_sr = close_sr.shift(60) # `pandas.Series` method
new_indicators[(symbol, "Price", "Shifted")] = shifted_sr
As demonstrated, many variations are possible for indicators generation through coding.
βοΈ Writing the Decision Script
The decision script is executed repeatedly every 10 seconds, which is the time length of a single candle. It is used to determine whether to place an order or, if so, which order to place.
API
Variables provided by default are as follows. You can use these without any import statements.
target_symbols
(list
): The symbols being observed.-
current_moment
(datetime.datetime
): The current time rounded down to the base time. For example, if the current exact time is 14:03:22.335 on January 3, 2022, thencurrent_moment
appears as 14:03:20 on January 3, 2022 in the 10-second interval. -
current_candle_data
(numpy.record
): Only the most recent row is truncated from the observation data recorded up to the current time. Contains price and volume information from the market. -
current_indicators
(numpy.record
): Only the most recent row up to the current time is provided. It contains different indicator information depending on the indicators script. -
account_state
(dict
): Contains current account status information. An object for reading. Writing something inside has no effect. -
scribbles
(dict
): Free writing space where you can write anything. After making a strategic decision, you can put whatever you want to remember inside this object. -
decision
(dict
): This is the core object that contains the strategic judgment.
Warning
Though these variables are still accessible in version 8, they will be removed in version 9. Use the explicit import
statements to use these modules.
datetime
(class
): part of the Python standard library.timezone
(class
): part of the Python standard library.timedelta
(class
): part of the Python standard library.math
(module
): part of the Python standard library.
Basic Syntax
You can extract a Series
column from the candle DataFrame
like this.
open_price = current_candle_data[str(("BTCUSDT", "Open"))]
high_price = current_candle_data[str(("BTCUSDT", "High"))]
low_price = current_candle_data[str(("BTCUSDT", "Low"))]
close_price = current_candle_data[str(("BTCUSDT", "Close"))]
sum_volume = current_candle_data[str(("BTCUSDT", "Volume"))]
account_state
object provided by the API has a structure like this.
{
"observed_until": datetime.datetime(...),
"wallet_balance": 1523.130323872, # total assets, in dollars
"positions": {
"BTCUSDT": {
"margin": 95.262900110, # Amount invested, in dollars
"direction": "short", # current position direction, one of none/long/short
"entry_price": 32126.275370193, # average price
"update_time": datetime.datetime(...), # Time of the last trade in this position
}
...
},
"open_orders": {
"BTCUSDT:"{
532563634: { # Order ID
"command": "later_down_buy", # what kind of decision
"boundary": 28260.0, # price activated
"left_margin": 226.321665911, # Amount to be input, in dollars
}
...
}
...
},
}
scribbles
are saved as pickle
files. So it can hold almost any type of Python objects.
# When retrieving, it is recommended to use the `get` method of `dict` in case it is not already stored.
did_i_do_it_before = scribbles.get("did_i_do_something", False)
You can check the indicator's current value with str
ized tuple
key.
# Creating an indicator named SMA in the Price category with the indicators script
sma_value = current_indicators[str(("BTCUSDT", "Price", "SMA"))]
Decision Types
There are 12 possible order commands, each corresponding to a specific order type on Binance. Simplified commands for easy and intuitive strategy development are used equally for automatic transaction and simulation.
Numbers do not have to be in
float
type. Other similar types, such asnumpy.float64
, also work well.
Command cancel_all
cancels all open orders of the symbol market.
Command now_close
closes the position immediately. Corresponds to Market Buy
or Market Sell
orders on Binance with the maximum quantity and Reduce Only
.
Command now_buy
buys directly at market price. Corresponds to Binance order Market Buy
.
wallet_balance = account_state["wallet_balance"]
decision[symbol]["now_buy"] = {
"margin": wallet_balance * 0.08,
}
Command now_sell
sells directly at market price. Corresponds to Binance order Market Sell
.
wallet_balance = account_state["wallet_balance"]
decision[symbol]["now_sell"] = {
"margin": wallet_balance * 0.08,
}
Command later_up_close
puts an order that will close the position when the price goes up to that boundary. Corresponds to Binance order Stop Market Buy
or Take Profit Market Sell
with Close Position
enabled.
current_price = current_candle_data[str((symbol, "Close"))]
decision[symbol]["later_up_close"] = {
"boundary": current_price * 1.05,
}
Command later_down_close
puts an order that will close the position when the price goes down to that boundary. Corresponds to Binance order Stop Market Sell
or Take Profit Market Buy
with Close Position
enabled.
current_price = current_candle_data[str((symbol, "Close"))]
decision[symbol]["later_down_close"] = {
"boundary": current_price * 0.95,
}
Command later_up_buy
puts an order to buy when the price goes up to that boundary. Corresponds to Binance order Stop Market Buy
.
current_price = current_candle_data[str((symbol, "Close"))]
wallet_balance = account_state["wallet_balance"]
decision[symbol]["later_up_buy"] = {
"boundary": current_price * 1.05,
"margin": wallet_balance * 0.08,
}
Command later_down_buy
puts an order to buy when the price goes down to that boundary. Corresponds to Binance order Take Profit Market Buy
.
current_price = current_candle_data[str((symbol, "Close"))]
wallet_balance = account_state["wallet_balance"]
decision[symbol]["later_down_buy"] = {
"boundary": current_price * 0.95,
"margin": wallet_balance * 0.08,
}
Command later_up_sell
puts an order to buy sell when the price goes up to that boundary. Corresponds to Binance order Take Profit Market Sell
.
current_price = current_candle_data[str((symbol, "Close"))]
wallet_balance = account_state["wallet_balance"]
decision[symbol]["later_up_sell"] = {
"boundary": current_price * 1.05,
"margin": wallet_balance * 0.08,
}
Command later_down_sell
puts an order to sell when the price goes down to that boundary.Corresponds to Binance order Stop Market Sell
.
current_price = current_candle_data[str((symbol, "Close"))]
wallet_balance = account_state["wallet_balance"]
decision[symbol]["later_down_sell"] = {
"boundary": current_price * 0.95,
"margin": wallet_balance * 0.08,
}
Command book_buy
puts a limit buy order that is added to the order book. Corresponds to Binance order Limit Buy
.
current_price = current_candle_data[str((symbol, "Close"))]
wallet_balance = account_state["wallet_balance"]
decision[symbol]["book_buy"] = {
"boundary": current_price * 0.95,
"margin": wallet_balance * 0.08,
}
Command book_sell
puts a limit sell order that is added to the order book. Corresponds to Binance order Limit Sell
.
current_price = current_candle_data[str((symbol, "Close"))]
wallet_balance = account_state["wallet_balance"]
decision[symbol]["book_sell"] = {
"boundary": current_price * 1.05,
"margin": wallet_balance * 0.08,
}
In automatic ordering and simulation, all prices are based on
Last Price
.Mark Price
is not used in the calculation.
Side Note
Binance has a minimum order amount. As of February 2022, it is $5. In addition, the decimal precision of the quantity that can be ordered for each symbol is fixed. Therefore, in the actual automatic order, the order is sent with the quantity slightly rounded up from the margin
determined by the decision. Because of this rounding, the actual amount being orders can have a significant amount of numerical error. Always check your position status before making an order in the decision script.
Open orders are limited to only one per type. During an actual automatic order, even if multiple open orders of the same type are stacked, all but the most recent one will be lost. This is Kolie's own rules for a convenient decision system. For example, there cannot be more than one open order classified as later_up_buy
at the same time. However, it is possible to have different kinds of commands open simultaneously. An open spell with later_up_buy
and an open spell with later_up_sell
can exist at the same time.
Even with the same margin
, the actual amount value will vary depending on the leverage. For example, putting in a margin of $5 at 4x leverage means you are investing $20 in real money. Since leverage is the concept of borrowing and investing, the amount invested in my assets is less than the actual investment amount by the leverage multiplier.