Is the Sortino Ratio script right?
2 replies
eastpeace
4 years ago #257305
`/*
* Copyright (c) 2017-2018, StrategyQuant – All rights reserved.
*
* Code in this file was made in a good faith that it is correct and does what it should.
* If you found a bug in this code OR you have an improvement suggestion OR you want to include
* your own code snippet into our standard library please contact us at:
* https://roadmap.strategyquant.com
*
* This code can be used only within StrategyQuant products.
* Every owner of valid (free, trial or commercial) license of any StrategyQuant product
* is allowed to freely use, copy, modify or make derivative work of this code without limitations,
* to be used in all StrategyQuant products and share his/her modifications or derivative work
* with the StrategyQuant community.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package SQ.Columns.Databanks;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.strategyquant.lib.L;
import com.strategyquant.lib.SQTime;
import com.strategyquant.lib.SQUtils;
import com.strategyquant.lib.SettingsMap;
import com.strategyquant.tradinglib.DatabankColumn;
import com.strategyquant.tradinglib.Directions;
import com.strategyquant.tradinglib.Order;
import com.strategyquant.tradinglib.OrdersList;
import com.strategyquant.tradinglib.PlTypes;
import com.strategyquant.tradinglib.SQStats;
import com.strategyquant.tradinglib.StatsTypeCombination;
import com.strategyquant.tradinglib.ValueTypes;
import SQ.Functions.StatFunctions;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.ints.Int2DoubleAVLTreeMap;
public class Sortino extends DatabankColumn {
public static final Logger Log = LoggerFactory.getLogger(“Sortino”);
private final long DAY_DURATION = 24 * 60* 60 * 1000;
//————————————————————————
//————————————————————————
//————————————————————————
public Sortino() {
super(L.t(“Sortino Ratio”), DatabankColumn.Decimal2, ValueTypes.Maximize, 0, -1, 1);
this.setTooltip(L.t(“Sortino Ratio (annualized)”));
// restrict Sortino computation only for money PL Type (we’ll not compute separate Sortino for % or pips results, it is as same as for money)
setPLTypeRestrictions(PlTypes.Money);
// restrict Sortino computation only for Both direction, we’ll not compute Sharpe for Long only or Short only results
setDirectionRestrictions(Directions.Both);
}
//————————————————————————
@Override
public double compute(SQStats stats, StatsTypeCombination combination, OrdersList ordersList, SettingsMap settings, SQStats statsLong, SQStats statsShort) throws Exception {
DoubleArrayList dailyReturnsPct = computeDailyReturn(ordersList);
double returnsMean = StatFunctions.computeAverage(dailyReturnsPct);
double returnsStddev = computeLowStdev(returnsMean, dailyReturnsPct,0,-1);
double Sortino = Math.sqrt(252) * SQUtils.safeDivide(returnsMean, returnsStddev);
return round2(Sortino);
}
//————————————————————————
private DoubleArrayList computeDailyReturn(OrdersList ordersList) {
if(ordersList.size() == 0) {
return null;
}
// we take 5% yearly profit as benchmark for computing Sortino Ratio
double benchmark = 0.05/252;
// compute first and last date
long firstDate = Long.MAX_VALUE;
long lastDate = -1;
for(int i=0; i<ordersList.size(); i++) {
Order o = ordersList.get(i);
if(o.CloseTime < firstDate) {
firstDate = o.CloseTime;
}
if(o.CloseTime > lastDate) {
lastDate = o.CloseTime;
}
}
// fix it so that it starts on Monday
firstDate = SQTime.correctDayStart(firstDate);
int firstDateDow = SQTime.getDayOfWeek(firstDate);
// create array of returns for every day that is not saturday/sunday
DoubleArrayList returns = new DoubleArrayList();
int index = 1;
long startDate = firstDate;
while(true) {
int weekIndex = index+firstDateDow-1;
if(weekIndex > 7) {
int weeks = weekIndex / 7;
weekIndex -= weeks*7;
}
if(weekIndex % 6 != 0 && weekIndex % 7 != 0) {
// skip Saturdays and Sundays
returns.add(-benchmark);
}
index++;
startDate += DAY_DURATION;
if(startDate > lastDate) {
break;
}
}
// now go through orders and add PctPL for every order
for(int i=0; i<ordersList.size(); i++) {
Order o = ordersList.get(i);
int dow = SQTime.getDayOfWeek(o.CloseTime);
if(dow == 6 || dow == 7) {
// skip Saturdays and Sundays
continue;
}
long closeTime = SQTime.correctDayStart(o.CloseTime);
index = (int) ((closeTime – firstDate) / DAY_DURATION);
// deduct weekends
if(index > firstDateDow) {
index -= 2;
}
int weeks = index / 7;
index -= weeks*2;
if(index < 0 || index >= returns.size()) {
continue;
}
double currentDayPL = returns.getDouble(index);
returns.set(index, currentDayPL+o.PctPL);
}
return returns;
}
public static double computeLowStdev(double mean, DoubleArrayList values, int from, int to ) {
if(values == null || values.size() == 0) {
return 0;
}
if(to == -1) {
to = values.size();
}
double sum = 0, stdev, pl;
for (int i = from; i < to; i++) {
pl = values.getDouble(i);
sum += Math.pow((Math.min(pl – mean,0)), 2d);
}
stdev = (double) Math.sqrt(sum / ((double) (to – from)));
return (stdev);
}
}
tomas262
4 years ago #257432
Hello, I have checked but this returns 0 for me. I guess there is something wrong with the computeLowStdev function but I am not a Java coder myself. Developers will review the Sortino code so it could be added into following SQ builds
eastpeace
3 years ago #258033
It has result. Maybe you need restart SQ and do retest.
But I just copied the Sharpe ratio and am not sure if it is correct. Especially I feel confused about the statistics of daily return.
Viewing 2 replies - 1 through 2 (of 2 total)