- Use the proper table definition, including natural key on - (story_id, ztimestamp).
 
- BTW - timestampis a data type, better not use it as a column name.
 
- BTW2: you probably want - story_idto be an integer field in stead of a text field, and since it is a key field you may also want it to be NOT NULL.
 
-- DDL
DROP TABLE story CASCADE;
CREATE TABLE story
        ( id serial not null primary key
        , story_id text NOT NULL
        , ztimestamp timestamp not null
        , zvalue integer not null default 0
        , UNIQUE (story_id, ztimestamp) -- the natural key
        );
\d+ story
EXPLAIN
SELECT * FROM story st
WHERE story_id IN('1','2','3')
AND NOT EXISTS(
        SELECT *
        FROM story nx
        WHERE nx.story_id = st.story_id
        AND nx.ztimestamp > st.ztimestamp
        );
DROP TABLE
CREATE TABLE
                                                              Table "tmp.story"
   Column   |            Type             | Collation | Nullable |              Default              | Storage  | Stats target | Description 
------------+-----------------------------+-----------+----------+-----------------------------------+----------+--------------+-------------
 id         | integer                     |           | not null | nextval('story_id_seq'::regclass) | plain    |              | 
 story_id   | text                        |           | not null |                                   | extended |              | 
 ztimestamp | timestamp without time zone |           | not null |                                   | plain    |              | 
 zvalue     | integer                     |           | not null | 0                                 | plain    |              | 
Indexes:
    "story_pkey" PRIMARY KEY, btree (id)
    "story_story_id_ztimestamp_key" UNIQUE CONSTRAINT, btree (story_id, ztimestamp)
                                                QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
 Nested Loop Anti Join  (cost=1.83..18.97 rows=13 width=48)
   ->  Bitmap Heap Scan on story st  (cost=1.67..10.94 rows=16 width=48)
         Recheck Cond: (story_id = ANY ('{1,2,3}'::text[]))
         ->  Bitmap Index Scan on story_story_id_ztimestamp_key  (cost=0.00..1.67 rows=16 width=0)
               Index Cond: (story_id = ANY ('{1,2,3}'::text[]))
   ->  Index Only Scan using story_story_id_ztimestamp_key on story nx  (cost=0.15..0.95 rows=2 width=40)
         Index Cond: ((story_id = st.story_id) AND (ztimestamp > st.ztimestamp))
(7 rows)