' . "\n\n" . '

Market Story (Plain-English)

' . "\n" . '

4-6 sentences explaining what is happening, why it matters, and how traders may be positioned. Include brief 90-day context.

' . "\n\n" . '

Technical Summary

' . "\n" . '

One compact paragraph (2-4 sentences) summarizing 4H trend, momentum, volatility, and structure.

' . "\n\n" . '

Key Indicators

' . "\n" . ' ' . "\n\n" . '

Support & Resistance

' . "\n" . ' ' . "\n\n" . '

Final Trading Signal

' . "\n" . '

Recommendation: Buy / Hold / Sell

' . "\n" . '

Confidence: HIGH / MEDIUM / LOW

' . "\n" . '

Entry Price: $[value]

' . "\n" . '

Target Price (1st): $[value]

' . "\n" . '

Stop-Loss: $[value]

' . "\n" . '

Risk/Reward Ratio: 1:[value]

' . "\n" . '

Reasoning: 4-5 sentences: confluence + invalidation + main risk + 90-day alignment.

' . "\n\n" . '

JSON Data Export

' . "\n" . '
' . "\n"
            . '  {insert ONLY valid JSON - no trailing commas - containing:' . "\n"
            . '   timeframe metadata,' . "\n"
            . '   indicator outputs (including MACD line/signal/histogram),' . "\n"
            . '   support/resistance levels,' . "\n"
            . '   long-term summary values,' . "\n"
            . '   and final signal.' . "\n"
            . '   Do NOT include the full raw OHLC array.}' . "\n"
            . '  
' . "\n" . '' . "\n\n" . '====================================================' . "\n" . 'STYLE GUIDELINES' . "\n" . '====================================================' . "\n" . '- No Markdown.' . "\n" . '- No code blocks.' . "\n" . '- No CSS beyond the image tag.' . "\n" . '- Do not invent extra fields.' . "\n" . '- All numeric values must be exact decimals.' . "\n\n" . '====================================================' . "\n" . 'GOAL' . "\n" . '====================================================' . "\n" . 'Produce a professional-grade, investor-ready technical analysis in clean HTML with a' . "\n" . 'justified Buy/Hold/Sell signal based on 4H candles, informed - but not overridden - by' . "\n" . '90-day trend context, with both plain-English interpretation and advanced precision.' . "\n\n" . 'Cryptocurrency: ' . $safe_coin_id . "\n\n" . 'DATA:' . "\n" . $json_data; return $prompt_text; } /** * Get the system prompt for all AI providers. * * @return string System prompt text. */ public static function get_system_prompt() { return 'You are a professional cryptocurrency technical analyst specializing in swing trading signals.'; } /** * Send prompt to OpenAI. * * @param string $prompt_text The full user prompt. * @return string|WP_Error Response text or WP_Error. */ private static function send_to_openai( $prompt_text ) { $api_key = get_option( 'adamca_ai_api_key', '' ); $model_name = get_option( 'adamca_ai_model', 'gpt-4o' ); // GPT-5 models use the Responses API. if ( 0 === strpos( $model_name, 'gpt-5' ) ) { $combined_prompt = self::get_system_prompt() . "\n\n" . $prompt_text; return self::send_to_openai_responses( $combined_prompt, $api_key, $model_name ); } $request_url = 'https://api.openai.com/v1/chat/completions'; $request_body = wp_json_encode( array( 'model' => $model_name, 'messages' => array( array( 'role' => 'system', 'content' => self::get_system_prompt() ), array( 'role' => 'user', 'content' => $prompt_text ), ), 'max_tokens' => 8096, ) ); $response = wp_remote_post( $request_url, array( 'timeout' => 140, 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ), 'body' => $request_body, ) ); if ( is_wp_error( $response ) ) { return $response; } $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $status_code ) { return new WP_Error( 'adamca_openai_error', sprintf( /* translators: %d: HTTP status code */ __( 'OpenAI returned HTTP %d.', 'adams-crypto-analysis' ), $status_code ) ); } $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $response_body['choices'][0]['message']['content'] ) ) { return new WP_Error( 'adamca_openai_empty', __( 'OpenAI returned an empty response.', 'adams-crypto-analysis' ) ); } return $response_body['choices'][0]['message']['content']; } /** * Send prompt to OpenAI Responses API (GPT-5 models). * * @param string $prompt_text The full user prompt. * @param string $api_key OpenAI API key. * @param string $model_name Model name (e.g. gpt-5, gpt-5-mini). * @return string|WP_Error Response text or WP_Error. */ private static function send_to_openai_responses( $prompt_text, $api_key, $model_name ) { $request_url = 'https://api.openai.com/v1/responses'; $request_body = wp_json_encode( array( 'model' => $model_name, 'input' => array( array( 'role' => 'user', 'content' => array( array( 'type' => 'input_text', 'text' => $prompt_text, ), ), ), ), 'reasoning' => array( 'effort' => 'low', ), 'text' => array( 'verbosity' => 'high', ), 'max_output_tokens' => 9096, ) ); self::log_openai_debug( 'Responses request', array( 'endpoint' => $request_url, 'model' => $model_name, 'payload_chars' => mb_strlen( $request_body ), 'payload_bytes' => strlen( $request_body ), 'payload' => $request_body, ) ); $response = wp_remote_post( $request_url, array( 'timeout' => 120, 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ), 'body' => $request_body, ) ); if ( is_wp_error( $response ) ) { self::log_openai_debug( 'Responses transport error', array( 'error' => $response->get_error_message(), ) ); return $response; } $status_code = wp_remote_retrieve_response_code( $response ); $raw_body = wp_remote_retrieve_body( $response ); self::log_openai_debug( 'Responses raw response', array( 'status_code' => $status_code, 'body' => $raw_body, ) ); if ( 200 !== $status_code ) { return new WP_Error( 'adamca_openai_error', sprintf( /* translators: %d: HTTP status code */ __( 'OpenAI returned HTTP %d.', 'adams-crypto-analysis' ), $status_code ) ); } $response_body = json_decode( $raw_body, true ); if ( ! is_array( $response_body ) ) { $json_error = function_exists( 'json_last_error_msg' ) ? json_last_error_msg() : 'Unknown JSON decode error'; self::log_openai_debug( 'Responses JSON decode error', array( 'json_error' => $json_error, 'raw_body' => $raw_body, ) ); return new WP_Error( 'adamca_openai_parse_error', __( 'OpenAI returned an unreadable response.', 'adams-crypto-analysis' ) ); } self::log_openai_debug( 'Responses parsed JSON', array( 'json_decode_ok' => true, 'has_output_text' => isset( $response_body['output_text'] ), 'output_text_type' => isset( $response_body['output_text'] ) ? gettype( $response_body['output_text'] ) : 'absent', 'has_output' => isset( $response_body['output'] ), 'output_count' => isset( $response_body['output'] ) && is_array( $response_body['output'] ) ? count( $response_body['output'] ) : 0, 'top_level_keys' => is_array( $response_body ) ? array_keys( $response_body ) : array(), ) ); $response_text = self::extract_openai_responses_text( $response_body ); self::log_openai_debug( 'Responses extraction result', array( 'parsed_text_length' => mb_strlen( $response_text ), 'parsed_text_empty' => '' === $response_text, ) ); if ( '' === $response_text ) { self::log_openai_debug( 'Responses full response body (empty result)', array( 'response_body' => $response_body, ) ); return new WP_Error( 'adamca_openai_empty', __( 'OpenAI returned an empty response.', 'adams-crypto-analysis' ) ); } return $response_text; } /** * Extract assistant text from OpenAI Responses API payload. * * Handles both the top-level `output_text` convenience field and the nested * `output[].content[].text` structure that OpenAI may return. * * @param array $response_body Decoded JSON response body. * @return string */ private static function extract_openai_responses_text( $response_body ) { if ( ! is_array( $response_body ) ) { return ''; } // 1. Prefer the top-level output_text convenience field if present and non-empty. if ( ! empty( $response_body['output_text'] ) ) { if ( is_array( $response_body['output_text'] ) ) { $output_text_parts = array(); foreach ( $response_body['output_text'] as $output_text_item ) { $normalized = self::extract_openai_text_value( $output_text_item ); if ( '' !== $normalized ) { $output_text_parts[] = $normalized; } } $joined = trim( implode( "\n", $output_text_parts ) ); if ( '' !== $joined ) { return $joined; } } if ( is_string( $response_body['output_text'] ) ) { $trimmed = trim( $response_body['output_text'] ); if ( '' !== $trimmed ) { return $trimmed; } } } // 2. Fall back to iterating output[] -> content[] -> text. if ( empty( $response_body['output'] ) || ! is_array( $response_body['output'] ) ) { return ''; } $text_parts = array(); foreach ( $response_body['output'] as $output_item ) { if ( ! is_array( $output_item ) ) { continue; } // Some output items may carry a text field directly (e.g. type=output_text at item level). if ( isset( $output_item['type'] ) && 'output_text' === $output_item['type'] && isset( $output_item['text'] ) ) { $normalized_text = self::extract_openai_text_value( $output_item['text'] ); if ( '' !== $normalized_text ) { $text_parts[] = $normalized_text; } continue; } if ( empty( $output_item['content'] ) || ! is_array( $output_item['content'] ) ) { continue; } foreach ( $output_item['content'] as $content_item ) { if ( ! is_array( $content_item ) ) { continue; } // Accept content blocks with type "output_text" or any block that has a text field. $is_text_type = isset( $content_item['type'] ) && in_array( $content_item['type'], array( 'output_text', 'text' ), true ); $has_text = isset( $content_item['text'] ); if ( $is_text_type || $has_text ) { if ( $has_text ) { $normalized_text = self::extract_openai_text_value( $content_item['text'] ); if ( '' !== $normalized_text ) { $text_parts[] = $normalized_text; } } } } } return trim( implode( "\n", $text_parts ) ); } /** * Normalize OpenAI Responses text payload variants to a plain string. * * @param mixed $value Raw text value from Responses JSON. * @return string */ private static function extract_openai_text_value( $value ) { if ( is_string( $value ) ) { return trim( $value ); } if ( is_array( $value ) ) { if ( isset( $value['value'] ) && is_string( $value['value'] ) ) { return trim( $value['value'] ); } if ( isset( $value['text'] ) && is_string( $value['text'] ) ) { return trim( $value['text'] ); } $parts = array(); foreach ( $value as $sub_value ) { $normalized = self::extract_openai_text_value( $sub_value ); if ( '' !== $normalized ) { $parts[] = $normalized; } } return trim( implode( "\n", $parts ) ); } return ''; } /** * Log OpenAI-only debug details when explicitly enabled. * * @param string $label Log label. * @param array $context Log context payload. * @return void */ private static function log_openai_debug( $label, $context ) { // Safe debug mode is disabled by default because prompts can include large data payloads. // Enable as needed: add_filter( 'adamca_openai_safe_debug_mode', '__return_true' ); $enabled = (bool) apply_filters( 'adamca_openai_safe_debug_mode', false ); if ( ! $enabled ) { return; } // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( 'ADAMCA OpenAI Debug [' . $label . ']: ' . wp_json_encode( $context ) ); } /** * Send prompt to xAI / Grok. * * @param string $prompt_text The full user prompt. * @return string|WP_Error Response text or WP_Error. */ private static function send_to_grok_xai( $prompt_text ) { $api_key = get_option( 'adamca_ai_api_key', '' ); $model_name = get_option( 'adamca_ai_model', 'grok-3' ); $request_url = 'https://api.x.ai/v1/chat/completions'; $body_array = array( 'model' => $model_name, 'messages' => array( array( 'role' => 'system', 'content' => self::get_system_prompt() ), array( 'role' => 'user', 'content' => $prompt_text ), ), 'max_tokens' => 4096, 'temperature' => 0.3, 'stream' => false, ); $request_body = wp_json_encode( $body_array ); $response = wp_remote_post( $request_url, array( 'timeout' => 120, 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ), 'body' => $request_body, ) ); if ( is_wp_error( $response ) ) { return $response; } $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $status_code ) { return new WP_Error( 'adamca_xai_error', sprintf( /* translators: %d: HTTP status code */ __( 'xAI returned HTTP %d.', 'adams-crypto-analysis' ), $status_code ) ); } $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $response_body['choices'][0]['message']['content'] ) ) { return new WP_Error( 'adamca_xai_empty', __( 'xAI returned an empty response.', 'adams-crypto-analysis' ) ); } return $response_body['choices'][0]['message']['content']; } /** * Send prompt to Anthropic / Claude. * * @param string $prompt_text The full user prompt. * @return string|WP_Error Response text or WP_Error. */ private static function send_to_anthropic( $prompt_text ) { $api_key = get_option( 'adamca_ai_api_key', '' ); $model_name = get_option( 'adamca_ai_model', 'claude-opus-4-5' ); $request_url = 'https://api.anthropic.com/v1/messages'; $request_body = wp_json_encode( array( 'model' => $model_name, 'max_tokens' => 4096, 'system' => self::get_system_prompt(), 'messages' => array( array( 'role' => 'user', 'content' => $prompt_text ), ), ) ); $response = wp_remote_post( $request_url, array( 'timeout' => 120, 'headers' => array( 'Content-Type' => 'application/json', 'x-api-key' => $api_key, 'anthropic-version' => '2023-06-01', ), 'body' => $request_body, ) ); if ( is_wp_error( $response ) ) { return $response; } $status_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $status_code ) { return new WP_Error( 'adamca_anthropic_error', sprintf( /* translators: %d: HTTP status code */ __( 'Anthropic returned HTTP %d.', 'adams-crypto-analysis' ), $status_code ) ); } $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $response_body['content'][0]['text'] ) ) { return new WP_Error( 'adamca_anthropic_empty', __( 'Anthropic returned an empty response.', 'adams-crypto-analysis' ) ); } return $response_body['content'][0]['text']; } /** * Strip accidental markdown fencing from AI response and inject signal image. * * @param string $raw_response Raw response text from AI. * @return string Cleaned HTML. */ public static function clean_response( $raw_response ) { $html = preg_replace( '/^```html\s*|^```\s*|```\s*$/m', '', trim( $raw_response ) ); // Remove any AI-generated tags (we inject our own). $html = preg_replace( '/]*>/i', '', $html ); // Detect recommendation and inject the correct signal image. $signal = self::detect_recommendation( $html ); if ( $signal ) { $images_url = ADAMS_CRYPTO_ANALYSIS_URL . 'assets/images/'; $img_tag = '' . esc_attr( $signal )
                        . ''; // Insert after
opening tag. $html = preg_replace( '/(]*>)/i', '$1' . "\n " . $img_tag, $html, 1 ); } return $html; } /** * Detect BUY/HOLD/SELL recommendation from the AI-generated HTML. * * @param string $html The analysis HTML. * @return string|false 'BUY', 'HOLD', or 'SELL', or false if not found. */ private static function detect_recommendation( $html ) { // Primary: look for Recommendation: pattern. if ( preg_match( '/\s*Recommendation\s*:?\s*<\/strong>\s*(BUY|HOLD|SELL)/i', $html, $matches ) ) { return strtoupper( $matches[1] ); } // Fallback: look for "Recommendation:" as plain text. if ( preg_match( '/Recommendation\s*:\s*(BUY|HOLD|SELL)/i', $html, $matches ) ) { return strtoupper( $matches[1] ); } return false; } }