Ordem de Mercado Múltiplo
Aqui está um trecho de Ordens Múltiplas no Mercado.
Ele enviará vários pedidos ao mesmo tempo com um número mágico diferente.
Na captura, você pode selecionar o nb (número) de ordem e um número mágico.
Por exemplo, se você selecionar 3 como número de ordens e 1000 como número mágico, criará 3 ordens de mercado com o seguinte número mágico: 1000, 1001 e depois 1002...
(você pode criar uma grande quantidade de pedidos conforme sua necessidade)
Então você pode fechar parcialmente algumas das ordens do mercado em uma meta de lucro e depois fechar outras ordens em diferentes metas de lucro ou stop loss.
package SQ.Blocks.Order.Open;
import java.util.ArrayList;
import com.strategyquant.datalib.TradingException;
import com.strategyquant.datalib.UpdateEventTypes;
import com.strategyquant.lib.SQTime;
import com.strategyquant.lib.SQUtils;
import com.strategyquant.lib.SettingsMap;
import com.strategyquant.tradinglib.ATM;
import com.strategyquant.tradinglib.ATMExit;
import com.strategyquant.tradinglib.BlockDefinitionException;
import com.strategyquant.tradinglib.BuildingBlock;
import com.strategyquant.tradinglib.CategoryOrder;
import com.strategyquant.tradinglib.Editor;
import com.strategyquant.tradinglib.Editors;
import com.strategyquant.tradinglib.ExitMethod;
import com.strategyquant.tradinglib.ExitTypes;
import com.strategyquant.tradinglib.Help;
import com.strategyquant.tradinglib.IActionEventListener;
import com.strategyquant.tradinglib.IFormula;
import com.strategyquant.tradinglib.ILiveOrder;
import com.strategyquant.tradinglib.Order;
import com.strategyquant.tradinglib.OrderCloseTypes;
import com.strategyquant.tradinglib.OrderTypes;
import com.strategyquant.tradinglib.Parameter;
import com.strategyquant.tradinglib.Required;
import com.strategyquant.tradinglib.ReturnTypes;
import com.strategyquant.tradinglib.SortOrder;
import com.strategyquant.tradinglib.StrategyBase;
import com.strategyquant.tradinglib.atm.exits.None;
import com.strategyquant.tradinglib.atm.exits.TrailingStop;
import com.strategyquant.tradinglib.simulator.Engines;
import SQ.ExitMethods.StopLoss;
import SQ.Functions.OrderFunctions;
import SQ.Internal.ActionBlock;
import SQ.Internal.MMFormulaBlock;
// Modified in France by Emmanuel Evrard for the StrategyQuantX Community :)
@BuildingBlock(name="(MMKT) Enter Multiple Order at market", display="EnterMultipleOrderAtMarket", returnType=ReturnTypes.Order)
@Help("Opens Multiple order at current market price")
@SortOrder(400)
@CategoryOrder(100)
public class EnterAtMarketMultiOrder extends ActionBlock
{
private static final int MaxDistanceFromMarketHash = "MaxDistanceFromMarket.MaxDistanceFromMarket".hashCode();
private static final int MaxDistanceFromMarketPctHash = "MaxDistanceFromMarket.MaxDistancePct".hashCode();
// Basic parameters
@Parameter(defaultValue="Any", category="Basic", showIfDefault=false)
@Editor(type=Editors.SelectionSymbolsWithAny)
public String Symbol;
@Parameter(defaultValue="1", category="Basic")
@Editor(type=Editors.Selection, values="Long=1,Short=-1")
public int Direction;
@Parameter(category="Basic")
@Editor(type=Editors.Formula, formulaName="Size")
@Required
public IFormula Size;
// Other parameters
@Parameter(defaultValue="MagicNumber", category="Order identification", showIfDefault=false)
@Help("Magic number is used to identify the first trade, it should be unique for every trade you open.")
@Editor(type=Editors.SelectionVariables)
public int MagicNumber;
@Parameter(defaultValue="Number Of Order", category="Order identification", showIfDefault=false)
@Help("Number Of Order is used to set the quantity of order, Their MagicNumbers are successives to the above MagicNumber")
@Editor(type=Editors.SelectionVariables)
public int NbOrder;
@Parameter(defaultValue="", category="Order identification", showIfDefault=false)
public String Comment;
@Parameter(defaultValue="false", category="Order identification", showIfDefault=false)
@Help("If set to true, it will allow to place multiple trades with the same Magic Number")
public boolean AllowDuplicateTrades;
public ExitMethod[] ExitMethods;
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
@Override
public void OnAction() throws TradingException
{
if((!AllowDuplicateTrades || !engineSupportsDuplicateTrades()) && checkLiveOrderExists(0, true) != null)
{
// market is not flat and duplicate trades are not allowed
return;
}
// open trade
byte orderType = (Direction > 0 ? OrderTypes.Buy : OrderTypes.Sell);
double sl = computeSL(orderType, (Direction > 0 ? Strategy.MarketData.Chart(Symbol).Ask() : Strategy.MarketData.Chart(Symbol).Bid()));
double size = computeSize(orderType, 0, sl);
ATM atm = Strategy.getATM();
if(atm != null && atm.isApplicable(Strategy, size, sl, orderType))
{
double pt = computePT(orderType, (Direction > 0 ? Strategy.MarketData.Chart(Symbol).Ask() : Strategy.MarketData.Chart(Symbol).Bid()));
int MagicNumber1 = MagicNumber;
for(int i = 0; i < NbOrder; i++)
{
MagicNumber = MagicNumber1 + i ;
openATMOrder(atm, -1, size, sl, pt, orderType, 0);
}
}
else
{
int MagicNumber1 = MagicNumber;
for(int i = 0; i < NbOrder; i++)
{
MagicNumber = MagicNumber1 + i ;
openNormalOrder(-1, size, sl, orderType, 0);
}
}
}
//------------------------------------------------------------------------
protected void openATMOrder(ATM atm, double openPrice, double fullSize, double sl, double pt, byte orderType, int barsValid) throws TradingException
{
ExitMethod slExit = getStopLossExit();
double sizeSoFar = 0;
if(Strategy.getEngine() == Engines.MetaTrader4 || Strategy.getEngine() == Engines.MetaTrader5Hedged) {
for(int i=0; i<atm.getExitsCount(); i++) {
ATMExit atmExit = tryCloneATMExit(atm.getExit(i));
boolean isLastExit = (i == atm.getExitsCount()-1);
double size = atmExit.computeSize(fullSize, sizeSoFar, isLastExit);
if(size > 0) {
ILiveOrder order = Strategy.Trader.Open(orderType, Symbol, openPrice)
.setSize(size)
.setMagicNumber(MagicNumber)
.setComment(Comment)
.Send();
sizeSoFar += order.getSize();
if(order.isSuccessful()) {
handleBarsValid(order, barsValid);
// add standard SL
if(slExit != null && !order.isClosedOrder()) {
slExit.setForOrder(order, Strategy);
}
// add ATM exit
atmExit.setForOrder(order, Strategy, sl, pt);
}
}
}
}
else {
ILiveOrder order = Strategy.Trader.Open(orderType, Symbol, openPrice)
.setSize(fullSize)
.setMagicNumber(MagicNumber)
.setComment(Comment)
.Send();
if(order.isSuccessful()) {
handleBarsValid(order, barsValid);
// add standard SL
if(slExit != null && !order.isClosedOrder()) {
slExit.setForOrder(order, Strategy);
}
for(byte i=0; i<atm.getExitsCount(); i++) {
ATMExit atmExit = tryCloneATMExit(atm.getExit(i));
boolean isLastExit = (i == atm.getExitsCount() - 1);
double size = atmExit.computeSize(fullSize, sizeSoFar, isLastExit);
if(size > 0) {
sizeSoFar += Strategy.getEngine() == Engines.MetaTrader5Netted ? openNewATMNettingOrder(order, atmExit, size, sl, pt, i) : openNewTSATMOrder(order, atmExit, size, sl, pt, i);
}
}
}
}
}
//------------------------------------------------------------------------
private double openNewTSATMOrder(ILiveOrder mainOrder, ATMExit atmExit, double size, double sl, double pt, byte exitIndex) throws TradingException {
mainOrder.registerEvent(UpdateEventTypes.BarOpen, new IActionEventListener() {
private ILiveOrder exitOrder = null;
private double actualSL = -1, actualPT = -1;
private double lastTS = -1;
private boolean filled = false;
@Override
public void OnActionEvent(StrategyBase strategy) throws TradingException {
if(mainOrder.isClosedOrder()) {
if(exitOrder != null) {
exitOrder.Close(OrderCloseTypes.Deleted);
exitOrder = null;
}
return;
}
if(!mainOrder.isMarketOrder()) return;
//SL and PT must be calculated when order is filled. Otherwise it may use old indicator values (TradeStation fills on next bar)
actualSL = computeSL(mainOrder.getOrderType(), mainOrder.getOpenPrice());
actualPT = computePT(mainOrder.getOrderType(), mainOrder.getOpenPrice());
if(atmExit.exitLevel instanceof None) {
None noneExit = (None) atmExit.exitLevel;
if(noneExit.checkExitAfterBars(mainOrder)) {
noneExit.deactivate();
Strategy.Trader.Open(mainOrder.isLong() ? OrderTypes.Sell : OrderTypes.Buy, mainOrder.getSymbol(), 0)
.setSize(size)
.setMagicNumber(MagicNumber)
.setComment(Comment)
.setExitIndex(exitIndex)
.Send();
}
}
else if(atmExit.exitLevel instanceof TrailingStop) {
if(filled) return;
double newTrailingPrice = SQUtils.fixPrice(Strategy.getInstrumentInfo().tickStep, atmExit.exitLevel.getNettingPrice(Strategy, mainOrder, actualSL, actualPT));
if(mainOrder.isLong()) {
if(newTrailingPrice > mainOrder.getOpenPrice() && newTrailingPrice > actualSL && newTrailingPrice > lastTS) {
//move trailing exit closer to current price
lastTS = newTrailingPrice;
}
}
else {
if(newTrailingPrice < mainOrder.getOpenPrice() && newTrailingPrice < actualSL && (newTrailingPrice < lastTS || lastTS < 0)) {
//move trailing exit closer to current price
lastTS = newTrailingPrice;
}
}
if(exitOrder != null) {
if(exitWasFilled(exitOrder)) { //Trailing stop has been hit
filled = true;
return;
}
else {
exitOrder.Close(OrderCloseTypes.Replaced);
}
}
if(lastTS > 0) {
exitOrder = Strategy.Trader.Open(Direction > 0 ? OrderTypes.SellStop : OrderTypes.BuyStop, Symbol, lastTS)
.setSize(size)
.setMagicNumber(MagicNumber)
.setComment(Comment)
.setExitIndex(exitIndex)
.Send();
exitOrder.registerEvent(UpdateEventTypes.OrderFilled, new IActionEventListener() {
@Override
public void OnActionEvent(StrategyBase Strategy) throws TradingException {
if(exitWasFilled(exitOrder)) {
filled = true;
}
}
});
}
}
else {
if(filled) return;
double exitOpenPrice = SQUtils.fixPrice(Strategy.getInstrumentInfo().tickStep, atmExit.exitLevel.getNettingPrice(Strategy, mainOrder, actualSL, actualPT));
if(exitOpenPrice <= 0) {
return;
}
if(exitOrder != null) {
if(exitWasFilled(exitOrder)) return;
else {
exitOrder.Close(OrderCloseTypes.Deleted);
exitOrder = null;
}
}
exitOrder = Strategy.Trader.Open(Direction > 0 ? OrderTypes.SellLimit : OrderTypes.BuyLimit, Symbol, exitOpenPrice)
.setSize(size)
.setMagicNumber(MagicNumber)
.setComment(Comment)
.setExitIndex(exitIndex)
.Send();
if(exitOrder.isSuccessful()) {
exitOrder.registerEvent(UpdateEventTypes.BarOpen, new IActionEventListener() {
@Override
public void OnActionEvent(StrategyBase strategy) throws TradingException {
boolean mainOrderExists = false;
for(int i=Strategy.Trader.getOpenOrdersCount(false) - 1; i >= 0; i--) {
ILiveOrder order = Strategy.Trader.getOpenOrder(i, false);
if(order.getOrderId() == mainOrder.getOrderId()) {
mainOrderExists = true;
}
}
if(!mainOrderExists) {
exitOrder.Close(OrderCloseTypes.Deleted);
}
}
});
exitOrder.registerEvent(UpdateEventTypes.OrderFilled, new IActionEventListener() {
@Override
public void OnActionEvent(StrategyBase Strategy) throws TradingException {
if(exitWasFilled(exitOrder)) {
filled = true;
}
}
});
}
}
}
});
return size;
}
//------------------------------------------------------------------------
private double openNewATMNettingOrder(ILiveOrder mainOrder, ATMExit atmExit, double size, double sl, double pt, byte exitIndex) throws TradingException {
// for None attach a BarOpen event listener and close after number of bars set
if(atmExit.exitLevel instanceof None) {
mainOrder.registerEvent(UpdateEventTypes.BarOpen, new IActionEventListener() {
@Override
public void OnActionEvent(StrategyBase strategy) throws TradingException {
if(mainOrder.isClosedOrder() || !mainOrder.isMarketOrder()) return;
None noneExit = (None) atmExit.exitLevel;
if(noneExit.checkExitAfterBars(mainOrder)) {
noneExit.deactivate();
ILiveOrder exitOrder = Strategy.Trader.Open(mainOrder.isLong() ? OrderTypes.Sell : OrderTypes.Buy, mainOrder.getSymbol(), 0)
.setSize(size)
.setMagicNumber(MagicNumber)
.setComment(Comment)
.setExitIndex(exitIndex)
.Send();
}
}
});
return size;
}
else if(atmExit.exitLevel instanceof TrailingStop) {
mainOrder.registerEvent(UpdateEventTypes.BarOpen, new IActionEventListener() {
private ILiveOrder exitOrder = null;
private double lastTS = -1;
private boolean filled = false;
@Override
public void OnActionEvent(StrategyBase strategy) throws TradingException {
if(filled) return;
if(mainOrder.isClosedOrder()) {
if(exitOrder != null) {
exitOrder.Close(OrderCloseTypes.Deleted);
exitOrder = null;
}
return;
}
if(mainOrder.isMarketOrder()) {
double newTrailingPrice = SQUtils.fixPrice(Strategy.getInstrumentInfo().tickStep, atmExit.exitLevel.getNettingPrice(Strategy, mainOrder, mainOrder.getSL(), mainOrder.getPT()));
double prevTS = lastTS;
if(mainOrder.isLong()) {
if(newTrailingPrice > mainOrder.getOpenPrice() && newTrailingPrice > mainOrder.getSL() && newTrailingPrice > lastTS) {
//move trailing exit closer to current price
lastTS = newTrailingPrice;
}
}
else {
if(newTrailingPrice < mainOrder.getOpenPrice() && newTrailingPrice < mainOrder.getSL() && (newTrailingPrice < lastTS || lastTS < 0)) {
//move trailing exit closer to current price
lastTS = newTrailingPrice;
}
}
if(prevTS != lastTS && lastTS > 0) {
if(exitOrder != null) {
exitOrder.Close(OrderCloseTypes.Replaced);
}
exitOrder = Strategy.Trader.Open(Direction > 0 ? OrderTypes.SellStop : OrderTypes.BuyStop, Symbol, lastTS)
.setSize(size)
.setMagicNumber(MagicNumber)
.setComment(Comment)
.setExitIndex(exitIndex)
.Send();
exitOrder.registerEvent(UpdateEventTypes.OrderFilled, new IActionEventListener() {
@Override
public void OnActionEvent(StrategyBase Strategy) throws TradingException {
if(exitWasFilled(exitOrder)) {
filled = true;
}
}
});
}
}
}
});
return size;
}
// for other types create limit orders
double exitOpenPrice = SQUtils.fixPrice(Strategy.getInstrumentInfo().tickStep, atmExit.exitLevel.getNettingPrice(Strategy, mainOrder, sl, pt));
if(exitOpenPrice <= 0) {
return 0;
}
ILiveOrder exitOrder = Strategy.Trader.Open(Direction > 0 ? OrderTypes.SellLimit : OrderTypes.BuyLimit, Symbol, exitOpenPrice)
.setSize(size)
.setMagicNumber(MagicNumber)
.setComment(Comment)
.setExitIndex(exitIndex)
.Send();
if(exitOrder.isSuccessful()) {
exitOrder.registerEvent(UpdateEventTypes.BarOpen, new IActionEventListener() {
@Override
public void OnActionEvent(StrategyBase strategy) throws TradingException {
boolean mainOrderExists = false;
for(int i=Strategy.Trader.getOpenOrdersCount(false) - 1; i >= 0; i--) {
ILiveOrder order = Strategy.Trader.getOpenOrder(i, false);
if(order.getOrderId() == mainOrder.getOrderId()) {
mainOrderExists = true;
}
}
if(!mainOrderExists) {
exitOrder.Close(OrderCloseTypes.Deleted);
}
}
});
return size;
}
else return 0;
}
//------------------------------------------------------------------------
private boolean exitWasFilled(ILiveOrder exitOrder) {
return exitOrder.isMarketOrder() && exitOrder.getCloseTime() > 0;
}
//------------------------------------------------------------------------
protected void openNormalOrder(double openPrice, double size, double sl, byte orderType, int barsValid) throws TradingException {
ILiveOrder order = Strategy.Trader.Open(orderType, Symbol, openPrice)
.setSize(size)
.setMagicNumber(MagicNumber)
.setComment(Comment)
.Send();
if(order.isSuccessful()) {
handleBarsValid(order, barsValid);
for(ExitMethod exitMethod : ExitMethods) {
if(!order.isClosedOrder()) {
if(AllowDuplicateTrades) {
//we have to clone the exit method, otherwise it makes problems when strategy has duplicate trades enabled (problem example: Trailing Stop is set only for the first order)
try {
ExitMethod exitMethodCloned = (ExitMethod) exitMethod.clone(true, Strategy);
exitMethodCloned.setForOrder(order, Strategy);
}
catch (BlockDefinitionException e) {
Log.error("Cannot clone exit method '" + exitMethod.getClass().getName() + "' for order #" + order.getOrderId(), e);
}
}
else {
exitMethod.setForOrder(order, Strategy);
}
}
}
}
}
//------------------------------------------------------------------------
protected void handleBarsValid(ILiveOrder order, int barsValid) throws TradingException {
if(order.isClosedOrder() || order.isMarketOrder()) return;
// order is placed, now handle order validity
if(barsValid != 0) {
order.registerEvent(UpdateEventTypes.BarOpen, new IActionEventListener() {
@Override
public void OnActionEvent(StrategyBase strategy) throws TradingException {
checkBarsValid(order, barsValid);
}
});
}
}
//------------------------------------------------------------------------
protected void checkBarsValid(ILiveOrder order, int barsValid) throws TradingException {
if(order.isClosedOrder()) return;
if(order.isPendingOrder() && order.getBarsInTrade() >= barsValid) {
order.Close(OrderCloseTypes.Expired);
}
}
//------------------------------------------------------------------------
protected double computeSL(byte orderType, double orderPrice) throws TradingException {
ExitMethod slExit = getStopLossExit();
if(slExit == null) {
// no SL
return Order.NOT_DEFINED;
}
return SQUtils.fixPrice(Strategy.getInstrumentInfo().tickStep, slExit.computeValue(orderType, Strategy, Symbol, orderPrice));
}
//------------------------------------------------------------------------
protected double computePT(byte orderType, double orderPrice) throws TradingException {
ExitMethod ptExit = getProfitTargetExit();
if(ptExit == null) {
// no PT
return Order.NOT_DEFINED;
}
double tickStep = Strategy.getInstrumentInfo().tickStep;
return SQUtils.fixPrice(tickStep, ptExit.computeValue(orderType, Strategy, Symbol, orderPrice));
}
//------------------------------------------------------------------------
private ExitMethod getStopLossExit() {
ExitMethod slExit = null;
for(ExitMethod exitMethod : ExitMethods) {
if(exitMethod.getExitType() == ExitTypes.StopLoss) {
slExit = exitMethod;
break;
}
}
return slExit;
}
//------------------------------------------------------------------------
private ExitMethod getProfitTargetExit() {
ExitMethod ptExit = null;
for(ExitMethod exitMethod : ExitMethods) {
if(exitMethod.getExitType() == ExitTypes.ProfitTarget) {
ptExit = exitMethod;
break;
}
}
return ptExit;
}
//------------------------------------------------------------------------
protected double computeSize(byte orderType, double price, double sl) throws TradingException {
MMFormulaBlock sizeFormula = (MMFormulaBlock) Size;
return sizeFormula.computeSize(Strategy, Symbol, orderType, price, sl);
}
//------------------------------------------------------------------------
protected ILiveOrder checkLiveOrderExists(int direction, boolean includeClosingOrders) {
int count = Strategy.Trader.getOpenOrdersCount(includeClosingOrders) - 1;
for(int i=count; i >= 0; i--) {
ILiveOrder order = Strategy.Trader.getOpenOrder(i, includeClosingOrders);
if(OrderFunctions.identify(order, Strategy, Symbol, direction, MagicNumber, Comment) && order.isMarketOrder()) {
return order;
}
}
return null;
}
//------------------------------------------------------------------------
/**
* this method is called only in Tradestation engine, to handle exits (SL, PT, etc.) as same as they are handled in TS.
* @throws TradingException
*/
public void OnApplyExits() throws TradingException {
ArrayList<ILiveOrder> orders = getOpenOrders(Direction);
ATM atm = Strategy.getATM();
boolean slPlaced = false;
if(orders != null) {
for(int i=0; i<orders.size(); i++) {
ILiveOrder order = orders.get(i);
boolean atmUsed = atm != null && atm.isApplicable(Strategy, order.getSize(), order.getSL(), order.getOrderType());
for(ExitMethod exitMethod : ExitMethods) {
if(!atmUsed || exitMethod instanceof StopLoss) {
if(exitMethod.setExit(order, Strategy)) {
slPlaced = true;
}
}
}
if(!slPlaced && order.getSL() != Order.NOT_DEFINED) {
// we have to set SL from order - handling if there is no SL, only for Trailing stop or Move2BE
int direction = order.isLong() ? -1 : 1;
byte orderType = (direction > 0 ? OrderTypes.BuyToCoverStop : OrderTypes.SellToCoverStop);
ILiveOrder slOrder = Strategy.Trader.Open(orderType, order.getSymbol(), order.getSL())
.setComment("SL")
.setMagicNumber(order.getMagicNumber())
.Send();
}
}
}
}
//------------------------------------------------------------------------
private ArrayList<ILiveOrder> getOpenOrders(int direction) {
ArrayList<ILiveOrder> orders = null;
for(int i=0; i<Strategy.Trader.getOpenOrdersCount(false); i++) {
ILiveOrder order = Strategy.Trader.getOpenOrder(i, false);
if(order.isPendingOrder()) {
continue;
}
if(OrderFunctions.identify(order, Strategy, Symbol, direction, MagicNumber, Comment) && order.isMarketOrder()) {
if(orders == null) {
orders = new ArrayList<ILiveOrder>();
}
orders.add(order);
}
}
return orders;
}
//------------------------------------------------------------------------
protected boolean engineSupportsDuplicateTrades() {
return Strategy.Trader.supportsDuplicateTrades();
}
//------------------------------------------------------------------------
private ATMExit tryCloneATMExit(ATMExit exit) throws TradingException {
ATMExit clone = exit.clone();
if(clone == null) {
throw new TradingException("Unable to create ATMExit object");
}
return clone;
}
//------------------------------------------------------------------------
protected boolean checkOpenPriceWithinRange(double openPrice) {
try {
SettingsMap settings = Strategy.getSettings();
if(settings.containsKey(MaxDistanceFromMarketHash) && settings.containsKey(MaxDistanceFromMarketPctHash) && ((boolean) settings.get(MaxDistanceFromMarketHash))) {
double maxPctDistance = SQUtils.round2((double) settings.get(MaxDistanceFromMarketPctHash));
double currentPrice = Direction > 0 ? Strategy.MarketData.Chart(Symbol).Ask() : Strategy.MarketData.Chart(Symbol).Bid();
double distancePct = SQUtils.round2(Math.abs(currentPrice - openPrice) / currentPrice * 100);
if(distancePct > maxPctDistance) {
Log.debug("Order skipped - too far from market. Open price: {}, Market price: {}, Max distance: {}%", openPrice, currentPrice, maxPctDistance);
return false;
}
}
}
catch(Throwable t) {
Log.error("Error while checking open price max distance", t);
}
return true;
}
}
Bom trabalho!
Obrigado Bentra!
Olá,
Você poderia me explicar como posso adicionar esse código à estratégia existente?
Isso pode ser feito para ordem de limite, por exemplo, enteratlimitmultiorder