feat: initial polymarket copy trade tool
- Watcher: polls Data API for trader activity - Executor: buy/sell with market orders + retry logic - Position manager: JSON-based state tracking - Auto-sell: limit orders at profit target - Redeemer: check & redeem winning positions on-chain - Config: env-based settings with validation - DRY_RUN mode for safe testing
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
const COLORS = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
white: '\x1b[37m',
|
||||
};
|
||||
|
||||
function timestamp() {
|
||||
return new Date().toISOString().replace('T', ' ').substring(0, 19);
|
||||
}
|
||||
|
||||
function formatMsg(level, color, emoji, ...args) {
|
||||
const ts = timestamp();
|
||||
const prefix = `${COLORS.dim}[${ts}]${COLORS.reset} ${color}${emoji} ${level}${COLORS.reset}`;
|
||||
console.log(prefix, ...args);
|
||||
}
|
||||
|
||||
const logger = {
|
||||
info: (...args) => formatMsg('INFO', COLORS.blue, 'ℹ️ ', ...args),
|
||||
success: (...args) => formatMsg('SUCCESS', COLORS.green, '✅', ...args),
|
||||
warn: (...args) => formatMsg('WARN', COLORS.yellow, '⚠️ ', ...args),
|
||||
error: (...args) => formatMsg('ERROR', COLORS.red, '❌', ...args),
|
||||
trade: (...args) => formatMsg('TRADE', COLORS.magenta, '📊', ...args),
|
||||
watch: (...args) => formatMsg('WATCH', COLORS.cyan, '👀', ...args),
|
||||
money: (...args) => formatMsg('MONEY', COLORS.green, '💰', ...args),
|
||||
};
|
||||
|
||||
export default logger;
|
||||
@@ -0,0 +1,48 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const DATA_DIR = path.join(__dirname, '..', '..', 'data');
|
||||
|
||||
// Ensure data directory exists
|
||||
if (!fs.existsSync(DATA_DIR)) {
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Read JSON state file
|
||||
* @param {string} filename - File name (e.g., "positions.json")
|
||||
* @param {*} defaultValue - Default value if file doesn't exist
|
||||
* @returns {*} Parsed JSON data
|
||||
*/
|
||||
export function readState(filename, defaultValue = {}) {
|
||||
const filePath = path.join(DATA_DIR, filename);
|
||||
try {
|
||||
if (fs.existsSync(filePath)) {
|
||||
const data = fs.readFileSync(filePath, 'utf-8');
|
||||
return JSON.parse(data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error reading state file ${filename}:`, err.message);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write JSON state file (atomic write via temp file)
|
||||
* @param {string} filename - File name (e.g., "positions.json")
|
||||
* @param {*} data - Data to write
|
||||
*/
|
||||
export function writeState(filename, data) {
|
||||
const filePath = path.join(DATA_DIR, filename);
|
||||
const tempPath = filePath + '.tmp';
|
||||
try {
|
||||
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
fs.renameSync(tempPath, filePath);
|
||||
} catch (err) {
|
||||
console.error(`Error writing state file ${filename}:`, err.message);
|
||||
// Clean up temp file if rename failed
|
||||
try { fs.unlinkSync(tempPath); } catch (_) { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user