Develop Your System
This section is to discuss the key components in Cyan Spring framework and how to develop your system based on those componentsTutorial on strategy development
We will demonstrate how to develop a single-Instrument strategy based on Cyan Spring strategy framework in this page. For multi-instrument strategies, please refer to Dollar Neutral and Low High strategies implementation included in the software distribution.
Custom Strategy - Version One
Developing an aglorithmic trading strategy is never a trivial task. However, Cyan Spring Strategy Framework has made it a lot easier. In the coming example, we are going to develop a commonly used Single Instrument Strategy called Iceberg. You will need Java programming knowledge for this. Experience with Eclipse will help but not important.
The Requirements
Iceberg strategy is useful when you have a huge order and you do not want to show all the quantity in market. Iceberg strategy will control the the release of quantity to market bit by bit. The following are the business requirements:
- Queue child order(s) at best bid price for buy or best ask price for sell, in display quantity.
- If your child orders in market get partially filled or fully filled, send more quantity to market to maintain the display quantity
- Child orders must stay within Limit Price specified by order if any
Writing The Codes
Assuming you have build the project successfully in Maven and your source code directory is c:\projects\algo, open the custom project, you will find 3 files:CustomStrategy1.java CustomStrategy1PriceAnalyzer.java CustomStrategy1QuantityAnalyzer.java
Open them up to take a look, they are pretty much empty. Next we are going to fill in some codes for CustomStrategy1PriceAnalyzer.java and CustomStrategy1QuantityAnalyzer.java.
For file: CustomStrategy1QuantityAnalyzer.java
public class CustomStrategy1QuantityAnalyzer extends AbstractQuantityAnalyzer { @Override protected QuantityInstruction calculate(SingleInstrumentStrategy strategy) { ParentOrder order = strategy.getParentOrder(); //retrieve display quantity Double disQty = order.get(Double.class, "Dis Qty"); if(null == disQty) strategy.logError("Missing Dis Qty Parameter"); //set passive quantity to display quantity QuantityInstruction qi = new QuantityInstruction(); qi.setPassiveQty(disQty); return qi; } }
For file: CustomStrategy1PriceAnalyzer.java
public class CustomStrategy1PriceAnalyzer extends AbstractPriceAnalyzer { @Override protected PriceInstruction calculate(QuantityInstruction qtyInstruction, SingleInstrumentStrategy strategy) { ParentOrder order = strategy.getParentOrder(); Quote quote = strategy.getQuote(); // setting the price to best bid for buy or best ask for sell double price = order.getSide().isBuy()?quote.getBid():quote.getAsk(); PriceInstruction pi = new PriceInstruction(); // taking the price and quantity we set and send to framework as // a PriceInstruction object pi.add(new PriceAllocation(order.getSymbol(), order.getSide(), price, qtyInstruction.getPassiveQty(), ExchangeOrderType.LIMIT, strategy.getId())); return pi; } }
That's it! This is all you need to do. You don't get to see any order sending/receiving codes or any market data process codes because they are already taken care by the strategy framework.
XML configuration
After finishing writing the java codes, we will need to do some xml editing to tell the strategy framework how to load your strategy. we need to have a [class name].xml to put in /conf directory. In our case, the xml configuration file name is conf/CustomStrategy1.xml. You may load it up in your editor and change it to the following<bean id="customStrategy1" class="com.cyanspring.custom.strategy.CustomStrategy1" scope="prototype"> <property name="strategyName" value="CUSTOM1"/> <property name="executionAnalyzer"> <ref bean="defaultExecutionAnalyzer"/> </property> <property name="executionManager"> <ref bean="defaultExecutionManager"/> </property> <property name="quantityAnalyzer"> <bean class="com.cyanspring.custom.strategy.CustomStrategy1QuantityAnalyzer"></bean> </property> <property name="priceAnalyzer"> <bean class="com.cyanspring.custom.strategy.CustomStrategy1PriceAnalyzer"></bean> </property> <property name="strategyFieldDefs"> <list> <bean class="com.cyanspring.common.business.FieldDef"> <property name="name" value="Dis Qty"/> <property name="type" value="java.lang.Double"/> <property name="input" value="true"/> <property name="amendable" value="true"/> <property name="value" value="0"/> </bean> </list> </property> </bean>Some explanation on the strategyFieldDefs property. It takes a list of FieldDef objects and defines the strategy parameters:
- name - name of the parameter
- type - the type of the parameter
- input - whether the parameter is compulsory on creating a new strategy
- amendable - whether the parameter is amendable
- value - the default value of it
Build/Deploy/Test
After finish coding, you may ranmvn clean install
to build the custom project. When it is successfully built, take cyanspring-custom-1.53.jar from the target directory and copy it into
your server/strategies directory. You should be able to enter a new single-instrument strategy with CUSTOM1 and test it with simulator.
Custom Strategy - Version Two
The vanilla Iceberg strategy that we developed in previous section is a relative passive strategy. It just sits there to wait for things happen. If market doesn't move towards us, it may become difficult to complete the order. In this section, we are going to enhance this strategy and make it a bit more aggressive.
The Requirements
We add the following in addition to version one requirements:
- If display quantity doesnt get any fill during X(definable) seconds, strategy will send aggressive order to hit far touch
- Aggressive order price is the best ask price for buy order or the best bid price for sell order
- Aggressive order quantity is the minimum of display quantity and the best volum of far touch
Adding A Strategy Parameter
The the new feature of our strategy requires a new parameter. Adding a strategy parameter can be something tedious especially where there is a chain of handling across server and clients. The good news is that Cyan Spring strategy framework has already taken care of this. It is only a matter of changing a configuration file.
Please load up conf/CustomStrategy1.xml in your editor and add the new parameter "Agg interval" shown below
<bean id="customStrategy1" class="com.cyanspring.custom.strategy.CustomStrategy1" scope="prototype"> <property name="strategyName" value="CUSTOM1"/> <property name="executionAnalyzer"> <ref bean="defaultExecutionAnalyzer"/> </property> <property name="executionManager"> <ref bean="defaultExecutionManager"/> </property> <property name="quantityAnalyzer"> <bean class="com.cyanspring.custom.strategy.CustomStrategy1QuantityAnalyzer"></bean> </property> <property name="priceAnalyzer"> <bean class="com.cyanspring.custom.strategy.CustomStrategy1PriceAnalyzer"></bean> </property> <property name="strategyFieldDefs"> <list> <bean class="com.cyanspring.common.business.FieldDef"> <property name="name" value="Dis Qty"/> <property name="type" value="java.lang.Double"/> <property name="input" value="true"/> <property name="amendable" value="true"/> <property name="value" value="0"/> </bean> <bean class="com.cyanspring.common.business.FieldDef"> <property name="name" value="Agg interval"/> <property name="type" value="java.lang.Long"/> <property name="input" value="false"/> <property name="amendable" value="true"/> <property name="value" value="15"/> </bean> </list> </property> </bean>
You may do a quick build and deploy on project custom to test it. Open up the order entering dialog, you should find a new parameter called "Agg interval" with default value of 15
Writing The Codes
There are a bit more handling codes for our new requirements but not really much. The strategy container already provides a timer mechanism, we only need to hook our logic in.
For file: CustomStrategy1.java
public class CustomStrategy1 extends SingleInstrumentStrategy { private static final Logger log = LoggerFactory .getLogger(CustomStrategy1.class); @Override public void processAsyncTimerEvent(AsyncTimerEvent event) { Long interval = parentOrder.get(Long.class, "Agg interval"); // only apply this logic when field "Agg interval" is set and not 0 if(null != interval && interval > 0) { // the time when last fill came Date lastExecutionTime = this.getLastExecutionTime(); long pass = Clock.getInstance().now().getTime() - lastExecutionTime.getTime(); if(pass >= interval * 1000) // execute our logic now execute(ExecuteTiming.NOW); } super.processAsyncTimerEvent(event); } }
Some new codes are added for CustomStrategy1PriceAnalyzer.java and CustomStrategy1QuantityAnalyzer.java.
For file: CustomStrategy1QuantityAnalyzer.java
public class CustomStrategy1QuantityAnalyzer extends AbstractQuantityAnalyzer { @Override protected QuantityInstruction calculate(SingleInstrumentStrategy strategy) { ParentOrder order = strategy.getParentOrder(); // retrieve display quantity Double disQty = order.get(Double.class, "Dis Qty"); if (null == disQty) strategy.logError("Missing Dis Qty Parameter"); // set passive quantity to display quantity QuantityInstruction qi = new QuantityInstruction(); qi.setPassiveQty(disQty); // added for version two - work out aggressive quantity Long interval = order.get(Long.class, "Agg interval"); // only apply this logic when field "Agg interval" is set and not 0 if (null != interval && interval > 0) { // the time when last fill came Date lastExecutionTime = strategy.getLastExecutionTime(); long pass = Clock.getInstance().now().getTime() - lastExecutionTime.getTime(); if (pass >= interval * 1000) qi.setAggresiveQty(disQty); } return qi; } }
For file: CustomStrategy1PriceAnalyzer.java
public class CustomStrategy1PriceAnalyzer extends AbstractPriceAnalyzer { @Override protected PriceInstruction calculate(QuantityInstruction qtyInstruction, SingleInstrumentStrategy strategy) { ParentOrder order = strategy.getParentOrder(); Quote quote = strategy.getQuote(); // setting the price to best bid for buy or best ask for sell double price = order.getSide().isBuy() ? quote.getBid() : quote .getAsk(); PriceInstruction pi = new PriceInstruction(order.getSide()); // taking the price and quantity we set and send to framework as // a PriceInstruction object pi.add(new PriceAllocation(order.getSymbol(), order.getSide(), price, qtyInstruction.getPassiveQty(), ExchangeOrderType.LIMIT)); // added for version two - work out aggressive price for aggress quantity if (qtyInstruction.getAggresiveQty() > 0) { price = order.getSide().isBuy() ? quote.getAsk() : quote.getBid(); double qty = order.getSide().isBuy() ? quote.getAskVol() : quote .getBidVol(); // since we dont want to move market much, we only take out one // price level of // far touch qty = Math.min(qtyInstruction.getAggresiveQty(), qty); pi.add(new PriceAllocation(order.getSymbol(), order.getSide(), price, qty, ExchangeOrderType.LIMIT)); } return pi; } }
Build/Deploy/Test
You may rebuild project custom with "mvn clean install" and deploy the jar to test the new functionality.Summary
We hope the above sample programs give you some ideas of how to develop strategies by using Cyan Spring strategy framework. You may get help here if you hit any issues in completing this exercise. The following are the tips for some common problems/confusions:
- Running several strategies together with the same stock that affects each other may cause confusion
- You have a limit price specifed for you order which may prevent your strategy from doing things that you expect it to do
- Since the demo version runs an embedded JMS broker(to save the hassle of running a seperate JMS broker), you will need to start server before starting CSTW.