Positions/exposure by symbol for each bar

I would need to export positions/exposure for each symbol for each bar from Amibroker (in some .afl writing to csv file). The resulting export should look something like:

index	AMD	CERN	COST	DELL	GPS	INTC	MMM	cash
2004-01-09	6961.92	21017.07875	7282.266152	21264.55188	7091.08002	21259.33389	21316.12961	-6192.360298
2004-01-12	18198.58	18071.25	17675.8364	10804.31924	10685.41187	17872.47748	10882.0264	-3329.289887
2004-01-13	12060.86	11942.24625	12838.47745	16078.9038	16272.139	12465.39251	12579.13576	4708.039735
2004-01-14	13102.4	15534.28125	14447.42264	15414.4508	15666.44019	14884.06962	13454.54262	-2749.47003
2004-01-15	15518.4	14547.05	14164.03968	14407.48813	14926.12262	14422.38586	13929.15905	-2462.919316
2004-01-16	15670.6	14817.6875	14164.03968	14359.58256	13890.35178	14251.92581	14507.59497	-715.717096

How can this be done?
Does there perhaps already exist some afl code somewhere doing similar thing?

I can see that getting "cash" is straightforward since it is part of ~~~EQUITY (Low). But there is also Backtester object property: double Cash - available funds (cash) in your portfolio (does this hold cash for each bar?)

For the positions/exposure per symbol per bar the only thing that comes to mind is mid level custom backtester as per the post here: MarketPosition and alternate exit condition - #4 by Tomasz

SetCustomBacktestProc( "" );

if ( Status( "action" ) == actionPortfolio )
{
    bo = GetBacktesterObject();    //  Get backtester object
    bo.PreProcess();    //  Do pre-processing (always required)

    for ( i = 0; i < BarCount; i++ )  //  Loop through all bars
    {
        bo.ProcessTradeSignals( i );  //  Process trades at bar (always required)

        for ( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() )
        {
            // openpos variable now holds Trade object - you can query it

        }
    }    //  End of for loop over bars

    bo.PostProcess();    //  Do post-processing (always required)
}

Thanks.

I have "solved" the above in the following way:

RoundLotSize = 1;

InitialEQ = 100000;
MaxPositions = 1;
PercentEquityPositionSize = 100;

SetBacktestMode(backtestRegular);

SetTradeDelays(1, 1, 1, 1);
SetOption("allowsamebarexit", False);
SetOption("AllowPositionShrinking", True);
SetOption("initialequity", InitialEQ);
SetOption("MaxOpenPositions", MaxPositions);
//SetOption("CommissionMode", 0); 
SetOption("MinShares", 1);
SetPositionSize(PercentEquityPositionSize, spsPercentOfEquity); COMPOUNDING
SetOption("AccountMargin", 100); 
SetOption("HoldMinBars", 1);

SetFormulaName("TestEquityAndCash");

Buy = Sell = Short = Cover = 0;
BuyPrice = SellPrice = ShortPrice = CoverPrice = O;

//ThisIsLastBar = BarIndex() == LastValue( BarIndex() );

// Divergence
bull1 = C > MA(C, 200);
cum1 = Ref(RSI(2), -1) + RSI(2);

// Rules
Buy =  cum1 < 10;// and bull1 AND Open > 5 AND MA(V, 100) > 250000;
Sell = cum1 > 50; // OR ThisIsLastbar;
 
BuyPrice = Open;
SellPrice = Open;

SetCustomBacktestProc( "" );

if ( Status( "action" ) == actionPortfolio ) {
	resultsFolder = "C:\\tmp\\";
	positionsCSVFileName = "Positions.csv";
	fh = fopen(resultsFolder + positionsCSVFileName, "w");
	
	if (fh) {
		dt = DateTime();
		bo = GetBacktesterObject();    //  Get backtester object
		bo.PreProcess();    //  Do pre-processing (always required)
		
		for ( i = 0; i < BarCount; i++ ) { //  Loop through all bars
			bo.ProcessTradeSignals( i );  //  Process trades at bar (always required)
			
			fputs(NumToStr(dt[i], formatDateTime) + "," + "CASHPOSITION" + "," + StrFormat("%1.5f", bo.Cash) + "\n", fh);
			fputs(NumToStr(dt[i], formatDateTime) + "," + "EQUITY" + "," + StrFormat("%1.5f", bo.Equity) + "\n", fh);
					
			for ( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() ) {
				// openpos variable now holds Trade object - you can query it
				if (IsTrue(openpos.IsOpen)) {
					multiplier = 1;
					if (IsTrue(openpos.IsLong)) {
						multiplier = 1;
					} else {
						multiplier = -1;
					}
					
					fputs(
						NumToStr(dt[i], formatDateTime) + "," + 
						openpos.Symbol + "," + 
						StrFormat("%1.5f", multiplier*openpos.GetPositionValue()) + 
						"\n"
						, fh);
				}
			}
		}    //  End of for loop over bars

		bo.PostProcess();    //  Do post-processing (always required)
		
		fclose(fh);
    }
}

And then read Positions.csv in Python and transform it like this:

dfPositions = pd.read_csv(os.path.join("C:\\tmp\\", "Positions.csv"))
dfPositions["Date"] = pd.to_datetime(dfPositions["Date"], format="%d/%m/%Y").dt.tz_localize('UTC')#(None)  # Convert the "Date" column to tz-aware datetime format and extract the date
dfPositions.set_index(dfPositions["Date"], inplace=True)

dfPositionsWide = dfPositions.pivot(index='Date', columns='PositionName', values='Position')
dfPositionsWide = dfPositionsWide.fillna(0)
dfPositionsWide.drop("EQUITY", axis=1, inplace=True)
dfPositionsWide.rename(columns={"CASHPOSITION": "cash"}, inplace=True)

I hope it is correct.