Struttura dati per la corrispondenza del modello

6

Diciamo che hai un file di input con molte voci come queste:

date, ticker, open, high, low, close, <and some other values>

E si desidera eseguire una routine di corrispondenza del modello sulle voci (righe) in quel file, usando ad esempio un motivo a candela. (Vedi, Doji) E quel modello può apparire in qualsiasi intervallo di tempo uniforme (lasciare t = 1s, 5s, 10s, 1d, 7d, 2w, 2y, e così via ...).

Supponiamo che una routine di abbinamento di pattern possa prendere un numero arbitrario di righe per eseguire un'analisi e contenere un numero arbitrario di sotto-schemi. In altre parole, alcuni pattern potrebbero richiedere 4 voci per operare su altri, potrebbero richiedere più o meno.

Supponiamo anche che la routine (possa) successivamente debba trovare e classificare gli estremi (massimi e minimi locali e globali come pure i punti di flesso) per il ticker su un intervallo chiuso, ad esempio, si potrebbe dire che una funzione cubica ( x ^ 3) ha il extrema sull'intervallo [-1, 1]. (Vedi link)

Dato che non conosco le dimensioni del file di input alla lettura? Quale sarebbe la scelta più naturale in termini di struttura dei dati? Che dire di un'interfaccia che è conforme a un oggetto Ticker contenente una riga di dati a una raccolta di Ticker in modo che un motivo arbitrario possa essere applicato ai dati. Ad esempio, supponiamo di avere un pattern che richiede n elementi per formare una corrispondenza, ho bisogno di applicare il pattern a quegli elementi e quindi analizzare ciascuno di quei blocchi.

Qual è la prima cosa che ti viene in mente?

Ho scelto una lista circolare circolare doppiamente collegata che ha i seguenti metodi:

push_front()
push_back()
pop_front()
pop_back()
[] //overloaded, can be used with negative parameters

Ma quella struttura di dati sembra molto maldestra, dal momento che stanno succedendo così tante cose, devo fare una copia profonda della struttura dei dati prima di eseguire un'analisi su di essa.

Quindi, non so se ho fatto la mia domanda in modo chiaro - ma i punti principali sono:

  1. Che tipo di strutture dati devono essere considerate quando si analizzano i punti dati sequenziali per conformarsi a un modello che NON richiede accesso casuale?
  2. Che tipo di strutture dati dovrebbero essere considerate quando si classificano gli estremi di un insieme di punti dati?

Aggiornamento

Ecco il ciclo principale dell'analizzatore per il mio programma (in alcuni pseudocodi che è un poliglotta di poche lingue)

data = new circular_linkedList()
// in my case, data is a circular linked list of elements.
foreach(element in dataFeed) {
    data.push_front(element)
}

step_value = Pattern.numberRequiredElementsForMatch()
buffer = new circular_linkedList()
// at this point, you'll have a circular linked list
// where pop_back() will return the oldest entry and 
// entry, and pop_front() will return the newest

// initialize the buffer
for(value in range(step_value()) {
    buffer.push_back(data.pop_back())
    // fill a buffer with the number of
    // values required for a match. 
    // so now buffer will contain
    // [elementk, ... , elementk+n]
    // where buffer[0] < ... < buffer[n]
}
do {
    last = buffer[len(buffer)]
    ...
    first = buffer[0]

    Pattern.match(first, ... , last)
    buffer.pop_front() // remove the first element
    buffer.push_back(data.pop_back()) //add another element to the back


} while (!data.isEmpty())

...

Ad ogni modo, è un'idea di base di quello che sto succedendo. Il problema è che, ora che lo sto facendo, devo ripetere il ciclo per applicare un altro pattern. (le dimensioni del buffer differiscono) Sembra proprio inefficiente farlo. Inoltre, cosa succede quando non ci sono più dati nel buffer e non è equamente divisibile per il numero di elementi necessari a una partita?

    
posta alvonellos 01.11.2012 - 18:20
fonte

2 risposte

7

Vettore anziché Elenco collegato

Qualunque cosa tu faccia, non usare una lista doppiamente collegata per questo. Ha un sacco di allocazione e spazio in testa. I dati vengono letti e aggiunti sequenzialmente, non vi è alcun accesso casuale o rimozione casuale di elementi, quindi una struttura di dati di tipo vettoriale sarebbe molto più adatta per memorizzare i dati. Ma anche questo è eccessivo per i dati grezzi, come vedrai di seguito.

I dati fluiscono attraverso gli ascoltatori, quindi scompaiono

Per fare la corrispondenza di modelli lineari non è necessario memorizzare i dati, e dovrai solo attraversarli una volta. L'idea è di avere diversi giocatori che ascoltano i loro modelli mentre i dati si susseguono. Questi memorizzano solo i dati necessari per rilevare un modello. Tutti gli oggetti che non possono più far parte di un modello saranno dimenticati.

Descriverò un modo per ottenerlo. Devo avvertirti che l'attività che vuoi eseguire non è banale da fare in modo efficiente. A giudicare dalla tua proposta di utilizzare un elenco collegato, potrebbe essere necessario del tempo per comprendere i principi coinvolti.

Registra continuamente i Matcher che ascoltano i dati

Iniziamo aggiungendo alcune entità per ascoltare i tuoi pattern nei dati. Registra una fabbrica di abbinamenti per ogni combinazione che vuoi riconoscere. In genere ogni modello si trova nella propria classe di corrispondenza, parametrizzata dalla risoluzione che sta cercando.

Ora inizia a leggere i dati e invia ogni articolo a ogni factory di corrispondenza durante la lettura. Quindi fai in modo che la fabbrica istanzia / riutilizzi un matcher per ogni posto che potrebbe essere il punto di partenza di un pattern. Ad esempio, un matcher con una risoluzione di 7 giorni può essere istanziato ogni volta che arriva il primo punto dati per la nuova settimana.

I Matcher aggiornano lo stato interno finché non rifiutano / accettano un pattern

Gli elementi ticker vengono anche inviati a ogni matcher attivo. Ogni matcher dovrebbe tracciare il proprio stato di matching interno. Ad esempio, un matcher con una risoluzione di 7 giorni può accumulare valori di ticker per calcolare la media di 7 giorni. Dopo ogni passaggio di 7 giorni, memorizza la media nella posizione successiva di un array, iniziando un nuovo accumulo medio per gli elementi di ticker successivi in entrata. Questo continua finché non ha visto abbastanza settimane per rifiutare o confermare la presenza del pattern. Per avere qualche idea su come farlo, guarda in "Macchine a stati finiti".

Guadagni di efficienza eliminando i calcoli duplicati

Naturalmente, se ci sono più dispositivi di corrispondenza che richiedono i dati su una risoluzione di 7 giorni, non è efficiente farli calcolare da soli. Puoi costruire una gerarchia di fiammiferi in modo che i modelli intermedi debbano essere calcolati una sola volta. Cerca nei buffer dell'anello le idee su come mantenere le medie mobili (o altre funzioni aggregate)

Correlati: generatori di parser

I cosiddetti "Generatori di parser" fanno automaticamente una cosa simile per le grammatiche formali. I parser generati impiegano una macchina a stati finiti per rilevare centinaia di modelli con lo stesso sforzo che occorrerebbe per riconoscerne solo uno e in un solo passaggio dei dati di origine. Immagino che tali strumenti possano esistere anche per dati di serie temporali continue, oppure potresti trasformare le loro idee per applicarle al tuo problema.

Spero che questo aiuti!

    
risposta data 08.11.2012 - 19:19
fonte
2

Poiché hai detto che stai leggendo i dati da un file, la mia prima scelta per la struttura dati sottostante sarebbe un array. È la forma di archiviazione più compatta, che presenta vantaggi con la cache e gli array funzionano molto bene quando le dimensioni dei dati sono fisse e conosciute in anticipo. Non essere buttati fuori non "aver bisogno" dell'accesso casuale. Le strutture dati ad accesso casuale possono essere facilmente sostituite con strutture dati sequenziali, anche se il contrario non è vero.

Vorrei anche raccomandare un qualche tipo di oggetto View nell'array, che fornisce un'interfaccia più adatta alle tue esigenze. Può gestire internamente i dettagli del punto di partenza del pattern, l'intervallo di tempo, ecc., In modo che le funzioni di corrispondenza dei pattern possano avere un'interfaccia molto semplice, qualcosa del tipo:

bool pattern(View &data) {
     return data[1].high > data[0].high;
}

La tua funzione di corrispondenza del modello non interessa se data[0] e data[1] sono distanziati di un secondo o di un anno. Non importa se data[0] è la prima riga del tuo file o il milionesimo. L'oggetto View astrae questi dettagli. Basta istanziare diversi oggetti View come necessario, ma tutti condividono lo stesso array sottostante.

Se gli extrema sono usati come parte della corrispondenza del pattern, puoi calcolarli una volta e memorizzarli nell'oggetto View , e usarli in questo modo:

bool pattern(View &data) {
     return (data.max() - data.min()) > 1.0;
}

In sintesi, l'idea è di separare il più possibile la struttura dei dati dalle funzioni di corrispondenza. Vuoi che quelle funzioni leggano il più vicino possibile a come un esperto di dominio le esprimerebbe in inglese. Nota Posso facilmente modificare l'oggetto View , se devo abbinare i pattern in streaming su un socket invece che da un file. La "carne" del pattern matching non ha bisogno di cambiare.

    
risposta data 07.11.2012 - 18:54
fonte

Leggi altre domande sui tag