As we have discussed here, cleaning up traders inventories does not prevent handle generator overflow, but it can keep trader inventories cleaner and make it easier for players to find the stuff traders actually sell.

So here is the Osiris code I use for that.

Prerequisites:
- create a unique root template that will never be used anywhere else. we need this as a 'signal token'
(I created one from a diamond and named it 'XC_SIGNALTOKEN_Internal_A'. It does not matter what it is, we will never see it.)
- place a hidden container somewhere in your game world and make it global so that Osiris can use it
(again, it does not matter what container it is, but it must be a container.)

Note: this code does not require any change to _Trade.txt

The code, I hope I added enough comments

INIT section

Code
// set up a dialog for our new trader ;-)
DB_Dialogs( CHARACTER_XC_CYS_Blackhole_Trader, "CYS_BlackHoleTrader" );

// level difference (explanation see below)
XCBlackholeDB_LevelDifference( 1 );
// time difference: (explanation see below)
XCBlackholeDB_TimeDifference( 12 );

// traders that are in the 'black hole club' (i.e. that have 'black hole trading' enabled)
XCBlackholeDB_Traders( CHARACTER_XC_CYS_Blackhole_Trader );

XCBlackholeDB__Ignore__Templates( "LOOT_Gold_Big_A_1c3c9c74-34a1-4685-989e-410dc080be6f"			);
XCBlackholeDB__Ignore__Templates( "LOOT_Gold_Coin_A_f99231ed-4e41-4d7e-8a3f-4175e1b1ded5"			);
XCBlackholeDB__Ignore__Templates( "XC_SIGNALTOKEN_Internal_A_09bb7492-ed2b-4ccb-b610-f9835f20b649"	);

// level difference:
// after the player (or companion) involved gained that many levels since the level when the items were sold,
// the sold items will be removed
// Must be between 1 and 3, everything else would defeat the purpose of a 'black hole' trader.
// (... well almost, not entirely)

// time difference:
// items sold to a trader will be removed after that many ingame hours
// 1 ingame hour equals 5 minutes of our real time

// Both level and time checks are only done on trade start, never on levelup or
// after the specified time has passed, because there is no need to do that


KB section

Code
// Before we start trading, we clean up what needs to be cleaned up before the trade window opens.
// We need an event for this.
// We can use the DialogStarted() event, or the setting of DB_DialogNPCs database, which might already
// contain an involved blackhole trader.
// But that would add more rule calls than necessary, for all non-blackhole NPCs when we are only interested in those.
//
// What always happens in PROC StartTrade() before opening the trade window is the setup of the database
// TradeRunning( _Player, _Trader, 1 ), and it is only ever set and reset in _Trade.txt, perfect for our
// purpose, since it already contains the trader and the party member involved in the trade.
IF
	TradeRunning( _Player, _Trader, 1 )
	AND
	XCBlackholeDB_Traders( _Trader )
	AND
	_Player.DB_IsPlayer()
	AND
	CharacterGetLevel( _Player, _CharLevel )
	AND
	Time( _, _, _InGameHour )
THEN
	XCBlackholeProc__RemoveSoldItems( _Trader, _CharLevel, _InGameHour );
	XCBlackholeDB__ItemRegisterEnabled( _Trader, _CharLevel, _InGameHour );

// stop registration after trade ends, subsequent trades should start registration again
// because they run through the process where TradeRunning() is set.
// And hopefully this event is only ever thrown after completely closing the trade window, because
// otherwise subsequent trades of the same trade session would no longer be registered.
// (Since there is an explicit HappyWithDeal() event, I suppose that this is the case.)
IF
	TradeEnds( _, _Trader )
	AND
	XCBlackholeDB__ItemRegisterEnabled( _Trader, _CharLevel, _InGameHour )
THEN
	NOT XCBlackholeDB__ItemRegisterEnabled( _Trader, _CharLevel, _InGameHour );


// Remove sold items after the configured number of ingame hours
PROC
	XCBlackholeProc__RemoveSoldItems( (CHARACTER)_Trader, (INTEGER)_CharLevel, (INTEGER)_InGameHour )
	AND
	XCBlackholeDB_TimeDifference( _TimeDiff )
	AND
	_TimeDiff > 0
	AND
	IntegerSubtract( _IngameHour, _TimeDiff, _MaxInGameHour )
	AND
	XCBlackholeDB__SoldItemHandles( _Trader, _RegisteredCharLevel, _RegisteredInGameHour, _ItemHandle, _Amount )
	AND
	_RegisteredInGameHour <= _MaxInGameHour
THEN
	NOT XCBlackholeDB__SoldItemHandles( _Trader, _RegisteredCharLevel, _RegisteredInGameHour, _ItemHandle, _Amount );
	// Move the stored amount to the black hole.
	// Using ItemHandleDelete() here would never work here, because the _ItemHandle we registered is not the handle
	// of a real item but the handle of the entity occupying one inventory slot.
	// We also cannot move the handle to a container and then delete it, because it would still be the handle of
	// a possibly remaining stack in the trader's inventory.
	// So what we need is to move the registered amount of items away somewhere else and remove it there.
	// The 'black hole' does exactly that, delete whatever is added to it.
	// Basically, ItemHandleToContainer() takes a specified amount of a stack behind an 'item handle' and move it to
	// wherever specified. This is the reason, why the call has an _Amount parameter in the first place !
	// The move target again adds the moved target to an existing stack or creates a new stack and assigns it a new
	// 'item handle'. (Well, not a stack, but an 'entitiy-that-occupies-one-inventory-slot, as said above.)
	ItemHandleToContainer( _ItemHandle, ITEM_XC_STORAGE_BLACK_HOLE_SUN, _Amount );

// Remove sold items after the configured number of player level-ups
PROC
	XCBlackholeProc__RemoveSoldItems( (CHARACTER)_Trader, (INTEGER)_CharLevel, (INTEGER)_InGameHour )
	AND
	XCBlackholeDB_LevelDifference( _ConfiguredDiff )			// get the configured 'age difference'
	AND
	IntegerMax( _ConfiguredDiff, 1, _MinDiff )					// must be at least 1
	AND
	IntegerMin( _MinDiff, 3, _Diff )							// may not be more than 3
	AND
	IntegerSubtract( _CharLevel, _Diff, _MaxCharLevelToRemove )	// subtract it from passed char level
	AND
	XCBlackholeDB__SoldItemHandles( _Trader, _RegisteredCharLevel, _RegisteredInGameHour, _ItemHandle, _Amount )
	AND
	_RegisteredCharLevel <= _MaxCharLevelToRemove				// selects all items of this or lower level
THEN
	NOT XCBlackholeDB__SoldItemHandles( _Trader, _RegisteredCharLevel, _RegisteredInGameHour, _ItemHandle, _Amount );
	// same as above ;-)
	ItemHandleToContainer( _ItemHandle, ITEM_XC_STORAGE_BLACK_HOLE_SUN, _Amount );

// After trade treasure has been generated, we add a signal token to the trader as a signal for the ...AddedTo...
// handler indicating that all items added to the trader so far were generated by treasure generation and all
// the following items added to the trader will be ones from the actual trade with the player.
//
// We need to find an event that safely happens AFTER treasure generation and before the trade window opens.
// (the latter condition is actually not necessary, because no items are yet transferred on ActivateTrade())
// ProcGenerateTradeTreasure( (CHARACTER)_Player, (CHARACTER)_Trader ) cannot be safely be used for that because
// that would make us dependent of the order or PROC executions and thus dependent on file names.
//
// The LastTradeItemGeneration() DB is only set in 3 places in _Trade.txt, twice as a reset with _TH = 0
// and once with _TH != 0 directly at the end of the ProcGenerateTradeTreasure()
// So we use that one.
//
// Since we also need to set up the disable registration fact and this must only be set somewhere within the whole
// trade generation process and before any Added() event handler can execute, which is the case for the
// ProcGenerateTradeTreasure() procedure and since the setting of the LastTradeItemGeneration() fact happens
// within the procedure context, we can use the setting of this fact to set the temporary disabled fact here as well
// and do not need to create another call to the procedure itself.
IF
	LastTradeItemGeneration( _Trader, _TH )
	AND
	_TH != 0
	AND
	XCBlackholeDB_Traders( _Trader )
THEN
	XCBlackholeDB__ItemRegisterTempDisabled( _Trader );
	ItemTemplateAddToCharacter( "XC_SIGNALTOKEN_Internal_A_09bb7492-ed2b-4ccb-b610-f9835f20b649", _Trader, 1 );

// Handle registration must be done by a procedure. We need to registrate every single occurence of a template
// added to a character and this requires keeping an _Amount.
// So we need to look if we already have the handle registered and in that case, increase the amount, otherwise
// we add a new entry.
//
// The reason for this is, that the _ItemHandle is NOT the handle of an item but the handle of one
// 'object-that-occupies-one-inventory-slot', which can be a whole stack.
// That is the reason why we need the 'black hole' in the first place: we need to use a call that actually lets us
// specify an amount of items that should be removed from the trader.
// ItemHandleDelete() does not have any further parameters and would remove a complete stack, no matter if it was
// treasure-generation-added or trade-added.
// Which is no problem for item types the trader never sells but a problem for stuff the trader sells.
// (Skillbooks, scrolls and lockpicks are examples for items traders sell and players might sell to them.)
// But we want the traders 'original' inventory to stay intact.
PROC
	XCBlackholeProcHelper__RegisterHandleForTrader( (CHARACTER)_Trader, (INTEGER)_CharLevel, (INTEGER)_ItemHandle, (INTEGER)_InGameHour )
	AND
	XCBlackholeDB__SoldItemHandles( _Trader, _CharLevel, _InGameHour, _ItemHandle, _Amount )
	AND
	IntegerSum( _Amount, 1, _NewAmount )
THEN
	NOT XCBlackholeDB__SoldItemHandles( _Trader, _CharLevel, _InGameHour, _ItemHandle, _Amount );
	XCBlackholeDB__SoldItemHandles( _Trader, _CharLevel, _InGameHour, _ItemHandle, _NewAmount );

PROC
	XCBlackholeProcHelper__RegisterHandleForTrader( (CHARACTER)_Trader, (INTEGER)_CharLevel, (INTEGER)_ItemHandle, (INTEGER)_InGameHour )
	AND
	NOT XCBlackholeDB__SoldItemHandles( _Trader, _CharLevel, _InGameHour, _ItemHandle, _ )
THEN
	XCBlackholeDB__SoldItemHandles( _Trader, _CharLevel, _InGameHour, _ItemHandle, 1 );


// Register an added item (or 'slot-entity' as mentioned above several times ;-)
// If registering is enabled and not temporarily disabled, it must be a sold item
IF
	ItemTemplateAddedToCharacter( _ItemTemplate, _ItemHandle, _Trader )
	AND
	XCBlackholeDB_Traders( _Trader )
	AND
	NOT XCBlackholeDB__Ignore__Templates( _ItemTemplate )
	AND
	XCBlackholeDB__ItemRegisterEnabled( _Trader, _CharLevel, _InGameHour )
	AND
	NOT XCBlackholeDB__ItemRegisterTempDisabled( _Trader )
THEN
	XCBlackholeProcHelper__RegisterHandleForTrader( _Trader, _CharLevel, _ItemHandle, _InGameHour );

// If new treasure was generated, the signal token was added afterwards, so the event it created was after trade
// treasure generation. We can remove the signal token and remove the disable flag.
// It seems safe here to use ItemHandleDelete() because the tokens are not stackable and we don't care about anything else.
// We only need to be very careful if we ever added another token of the same template to this character.
// But probably the delete() call would just silently fail but the logic would still kick in and that is all
// that is interesting.
// (The logic might be executed at the wrong time though, so it's still not a good idea to ever only use one
// type of token for a character.)
IF
	ItemTemplateAddedToCharacter( "XC_SIGNALTOKEN_Internal_A_09bb7492-ed2b-4ccb-b610-f9835f20b649", _ItemHandle, _Trader )
	AND
	XCBlackholeDB_Traders( _Trader )
THEN
	ItemHandleDelete( _ItemHandle );
	NOT XCBlackholeDB__ItemRegisterTempDisabled( _Trader );


// This is the 'Black Hole', a container which destroys everything it receives immediately.
// Can be used to remove items. Simply put them in here.
// There is no hook to intervene and stop the destruction, so be careful.
IF
	ItemTemplateAddedToContainer( _, _ItemHandle, ITEM_XC_STORAGE_BLACK_HOLE_SUN )
THEN
	ItemHandleDelete( _ItemHandle );