Hi,
Unfortunately, your two requirements are somewhat contradictory. By requiring an 'exact' number of trades over a given period, the trades are no longer 'random'. Likewise with the holding periods.
What you are asking for is a way to randomly *distribute* a fixed number of trades, as opposed to randomly *enter* a trade at a fixed likelihood. The former will not vary the number of trades, the latter will.
Randomly entering a trade at a specified likelihood is easy in AFL. But, it is only a likelihood, not a guaranteed number of trades. Over infinite runs it will average the desired number of trades, though any given run might have greater or fewer trades.
To get what you are after would be far more involved, and might look something like this:
1. Let X be the exact number of desired trades.
2. Randomly select a trade period Y, without replacement, from groupings of periods.
3. For each trade, randomly select a contiguous block of Y bar indicies, without replacement, from the range of bars.
4. Buy when the current bar index equals the start of one of the contiguous ranges selected in #3 above.
5. Sell at the last bar of the contiguous range.
I've taken a first crack at it below, assuming end of day trading with a Sell trade delay of 1 (i.e. buy on monday open, sell on tuesday open counts as single bar period even though AmiBroker shows it as 2 bars in report).
The example is written to be run as a backtest, with 'allow same bar exit' set to false in AA settings window. No promises about absolute correctness or efficiency. Just a good starting point. At first blush, it appears to work. Open Trace tab in Log Window to see how trades are allocated and filled.
If nothing else, it's a good example of the various looping mechanisms in AmiBroker.
//
// Assume end of day trading.
//
SetTradeDelays(0, 1, 0, 0);
BuyPrice = Open;
SellPrice = Open;
SetPositionSize(2,
spsPercentOfEquity);
if (Status("action") == actionBacktest) {
_TRACE(" ");
_TRACE("Begin output " + Name());
Buy = 0;
Sell =
0;
//
// Arrange trades into buckets.
//
totalBuckets = 3;
bucket1Trades = 3; bucket1Period = 2;
bucket2Trades = 5; bucket2Period = 4;
bucket3Trades = 2; bucket3Period = 1;
// Calculate total trades based on contents of all buckets.
// Less error prone than hard coding totalTrades and trying to
// remember to make bucket counts match.
totalTrades = 0;
for (bucket = 1; bucket <= totalBuckets; bucket++) {
totalTrades += VarGet("bucket" + bucket + "Trades");
}
// Randomly assign holding periods to trades, without replacement.
// It may take some time for the periods to finally get distributed
// since we rely on random number generation to eventually select
// every trade at least once.
for (bucket = 1; bucket <= totalBuckets; bucket++) {
tradeCount = VarGet("bucket" + bucket + "Trades");
tradePeriod = Varget("bucket" + bucket + "Period");
for (count = 1; count <= tradeCount; count++) {
do {
trade = ceil(mtRandom() * totalTrades) ; // Use ceil for 1 based index.
} while (!IsNull(VarGet("Trade" + trade))); // Loop 'till unassigned trade found.
VarSet("Trade" + trade, tradePeriod) ; // Assign period to trade.
_TRACE("Trade " + trade +
" assigned period of " + tradePeriod) ;
}
}
_TRACE(" ");
inRange = Status("barinrange");
dates = DateTime();
// Randomly assign trades to bars. We must ensure that no two trades overlap
// with each other. It may take some time for the trades to finally get
// distributed since we rely on random number
generation to eventually select
// neighboring empty bars.
for (trade = 1; trade <= totalTrades; trade++) {
period = VarGet("Trade" + trade); // Get randomly assigned period.
stillSearching = true;
while (stillSearching) {
do {
bar = floor(mtRandom() * BarCount); // Use floor for 0 based index.
} while (Buy[bar] || Sell[bar] || !inRange[bar] ); // Loop 'till unassigned bar found.
// Verify neighboring bars also empty for entire period, or end of bars,
// whichever comes first. This code assumes that a period of 1 means a
// single bar (i.e. enter/exit same bar).
neighbors = min(period - 1, BarCount - 1 - bar);
for (neighbor = 1; neighbor <= neighbors; neighbor++) {
if (Buy[bar + neighbor]) {
break; // Overlapping trade! Need to start over.
}
}
if (neighbor > neighbors) {
// We either found enough empty bars to satisfy the life of the trade,
// else ran out of bars and trade remains open. Either way, we're done.
// Commit neighboring bars as taken and quit searching.
Buy[bar] = 1;
for (neighbor =
1; neighbor <= neighbors; neighbor++) {
Buy[bar + neighbor] = 1;
}
if (neighbor == period) {
Sell[bar + period - 1] = 1;
_TRACE("Trade " + trade + " entered on " + DateTimeToStr(dates[bar]) + ", bar " + bar + ", exited on " + DateTimeToStr(dates[bar + period - 1]) + ", bar " + (bar + period - 1));
} else
{
_TRACE("Open Trade " + trade + " entered on " + DateTimeToStr(dates[bar]) + ", bar " + bar);
}
stillSearching = false;
}
}
}
_TRACE("End output
" + Name());
}
Mike
--- In amibroker@xxxxxxxxx ps.com, B S <bs2167@xxx> wrote:
>
> Hi-
>
> I am trying to put together a script which will generate random trades and adhere to the following rules:
>
> 1. It must exactly generate a predetermined # of trades per symbol in the time period being tested
>
> 2. Of the trades generated, it must adhere to a predetermined subset of # of days in each in trade (e.g. if the designated total # of trades that should be generated per symbol in the period = 10 as per step 1, i'd like to be able to constrain that 3 of the 10 trades are held for 2 days, 5 of the 10 are held for 4 days, and 2 of the 10 are held for 1 day)
>
> To address the first point, my initial pass at this was to generate entry signals when the PercentRank of the current bar's
random value (as compared to all the random values in the array) was less than (# of desired trades) / (# of bars in periods). This was close but does not consistently deliver the exact # of desired trades. Without that working, I haven't attempted to get part 2 right yet.
>
> Would appreciate it if anyone has suggestions on how to go about accomplishing either part of this. Thanks in advance.
>
> -B
>